diff --git a/src/views/oroqen/heritage-project/OroqenHeritageProjectList.vue b/src/views/oroqen/heritage-project/OroqenHeritageProjectList.vue
index e30bc67..6ce28a0 100644
--- a/src/views/oroqen/heritage-project/OroqenHeritageProjectList.vue
+++ b/src/views/oroqen/heritage-project/OroqenHeritageProjectList.vue
@@ -67,7 +67,7 @@
title: 'oroqen_heritage_project',
api: list,
columns,
- canResize: true,
+ canResize: false,
formConfig: {
//labelWidth: 120,
schemas: searchFormSchema,
diff --git a/src/views/oroqen/product/OroqenProduct.api.ts b/src/views/oroqen/product/OroqenProduct.api.ts
new file mode 100644
index 0000000..8d1950b
--- /dev/null
+++ b/src/views/oroqen/product/OroqenProduct.api.ts
@@ -0,0 +1,104 @@
+import { defHttp } from '/@/utils/http/axios';
+import { useMessage } from '/@/hooks/web/useMessage';
+
+const { createConfirm } = useMessage();
+
+enum Api {
+ list = '/oroqen/product/list',
+ save = '/oroqen/product/add',
+ edit = '/oroqen/product/edit',
+ deleteOne = '/oroqen/product/delete',
+ deleteBatch = '/oroqen/product/deleteBatch',
+ importExcel = '/oroqen/product/importExcel',
+ exportXls = '/oroqen/product/exportXls',
+ queryById = '/oroqen/product/queryById',
+ listByCategory = '/oroqen/product/listByCategory',
+ featured = '/oroqen/product/featured',
+ hot = '/oroqen/product/hot',
+ checkStock = '/oroqen/product/checkStock',
+}
+
+/**
+ * 导出api
+ */
+export const getExportUrl = Api.exportXls;
+
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+
+/**
+ * 列表接口
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除单个
+ */
+export const deleteOne = (params, handleSuccess) => {
+ return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
+ handleSuccess();
+ });
+};
+
+/**
+ * 批量删除
+ */
+export const batchDelete = (params, handleSuccess) => {
+ createConfirm({
+ iconType: 'warning',
+ title: '确认删除',
+ content: '是否删除选中数据',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: () => {
+ return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+ handleSuccess();
+ });
+ }
+ });
+};
+
+/**
+ * 保存或者更新
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+ const url = isUpdate ? Api.edit : Api.save;
+ return defHttp.post({ url: url, params }, { isTransformResponse: false });
+};
+
+/**
+ * 根据ID查询
+ */
+export const queryById = (params) => {
+ return defHttp.get({ url: Api.queryById, params });
+};
+
+/**
+ * 根据分类ID查询产品列表
+ */
+export const listByCategory = (params) => {
+ return defHttp.get({ url: Api.listByCategory, params });
+};
+
+/**
+ * 查询推荐产品
+ */
+export const getFeaturedProducts = (params) => {
+ return defHttp.get({ url: Api.featured, params });
+};
+
+/**
+ * 查询热销产品
+ */
+export const getHotProducts = (params) => {
+ return defHttp.get({ url: Api.hot, params });
+};
+
+/**
+ * 检查库存
+ */
+export const checkStock = (params) => {
+ return defHttp.get({ url: Api.checkStock, params });
+};
\ No newline at end of file
diff --git a/src/views/oroqen/product/OroqenProduct.data.ts b/src/views/oroqen/product/OroqenProduct.data.ts
new file mode 100644
index 0000000..451726c
--- /dev/null
+++ b/src/views/oroqen/product/OroqenProduct.data.ts
@@ -0,0 +1,494 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { h } from 'vue';
+import { Image, Tag } from 'ant-design-vue';
+import { render } from '/@/utils/common/renderUtils';
+import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
+
+//列表数据
+export const columns: BasicColumn[] = [
+ {
+ title: '主图',
+ align: 'center',
+ dataIndex: 'mainImage',
+ width: 80,
+ customRender: ({ text, record }) => {
+ if (!text) return '-';
+ const imageUrl = getFileAccessHttpUrl(text);
+
+ return h(Image, {
+ src: imageUrl,
+ alt: record?.productName || '产品主图',
+ width: 50,
+ height: 50,
+ style: { objectFit: 'cover', borderRadius: '4px' },
+ preview: true,
+ fallback: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3Ik1RnG4W+FgYxN'
+ });
+ }
+ },
+ {
+ title: '产品名称',
+ dataIndex: 'productName',
+ width: 150,
+ align: 'left',
+ ellipsis: true,
+ fixed: 'left',
+ },
+ {
+ title: '产品编码',
+ dataIndex: 'productCode',
+ width: 120,
+ align: 'center',
+ },
+ {
+ title: '产品分类',
+ dataIndex: 'categoryId',
+ width: 120,
+ align: 'center',
+ // TODO: 这里可以关联分类表显示分类名称
+ },
+ {
+ title: '价格',
+ dataIndex: 'price',
+ width: 100,
+ align: 'center',
+ customRender: ({ text }) => {
+ return text ? `¥${text}` : '-';
+ },
+ },
+ {
+ title: '库存数量',
+ dataIndex: 'stock',
+ width: 100,
+ align: 'center',
+ customRender: ({ text }) => {
+ const color = text > 10 ? 'green' : text > 0 ? 'orange' : 'red';
+ return h(Tag, { color }, text || 0);
+ },
+ },
+ {
+ title: '销量',
+ dataIndex: 'salesCount',
+ width: 80,
+ align: 'center',
+ },
+ {
+ title: '产品状态',
+ dataIndex: 'status',
+ width: 100,
+ align: 'center',
+ customRender: ({ text }) => {
+ const statusMap = {
+ 1: { text: '上架', color: 'green' },
+ 0: { text: '下架', color: 'red' },
+ };
+ const status = statusMap[text] || { text: '未知', color: 'default' };
+ return render.renderTag(status.text, status.color);
+ },
+ },
+ {
+ title: '是否推荐',
+ dataIndex: 'isFeatured',
+ width: 100,
+ align: 'center',
+ customRender: ({ text }) => {
+ return text === 1 ? render.renderTag('推荐', 'blue') : render.renderTag('普通', 'default');
+ },
+ },
+ {
+ title: '是否热销',
+ dataIndex: 'isHot',
+ width: 100,
+ align: 'center',
+ customRender: ({ text }) => {
+ return text === 1 ? render.renderTag('热销', 'orange') : render.renderTag('普通', 'default');
+ },
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createTime',
+ width: 150,
+ align: 'center',
+ sorter: true,
+ },
+];
+
+//查询数据
+export const searchFormSchema: FormSchema[] = [
+ {
+ label: '产品名称',
+ field: 'productName',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入产品名称',
+ },
+ colProps: { span: 6 },
+ },
+ {
+ label: '产品编码',
+ field: 'productCode',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入产品编码',
+ },
+ colProps: { span: 6 },
+ },
+ {
+ label: '产品状态',
+ field: 'status',
+ component: 'Select',
+ componentProps: {
+ placeholder: '请选择产品状态',
+ options: [
+ { label: '上架', value: 1 },
+ { label: '下架', value: 0 },
+ ],
+ },
+ colProps: { span: 6 },
+ },
+ {
+ label: '是否推荐',
+ field: 'isFeatured',
+ component: 'Select',
+ componentProps: {
+ placeholder: '请选择是否推荐',
+ options: [
+ { label: '推荐', value: 1 },
+ { label: '普通', value: 0 },
+ ],
+ },
+ colProps: { span: 6 },
+ },
+ {
+ label: '是否热销',
+ field: 'isHot',
+ component: 'Select',
+ componentProps: {
+ placeholder: '请选择是否热销',
+ options: [
+ { label: '热销', value: 1 },
+ { label: '普通', value: 0 },
+ ],
+ },
+ colProps: { span: 6 },
+ },
+];
+
+//表单数据
+export const formSchema: FormSchema[] = [
+ {
+ label: '',
+ field: 'id',
+ component: 'Input',
+ show: false,
+ },
+ {
+ label: '产品名称',
+ field: 'productName',
+ component: 'Input',
+ required: true,
+ componentProps: {
+ placeholder: '请输入产品名称',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '产品编码',
+ field: 'productCode',
+ component: 'Input',
+ required: true,
+ componentProps: {
+ placeholder: '请输入产品编码',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '分类ID',
+ field: 'categoryId',
+ component: 'Input',
+ required: true,
+ componentProps: {
+ placeholder: '请输入分类ID',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '价格',
+ field: 'price',
+ component: 'InputNumber',
+ componentProps: {
+ min: 0,
+ precision: 2,
+ addonBefore: '¥',
+ placeholder: '请输入价格',
+ },
+ required: true,
+ colProps: { span: 12 },
+ },
+ {
+ label: '库存数量',
+ field: 'stock',
+ component: 'InputNumber',
+ componentProps: {
+ min: 0,
+ placeholder: '请输入库存数量',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '销量',
+ field: 'salesCount',
+ component: 'InputNumber',
+ componentProps: {
+ min: 0,
+ placeholder: '请输入销量',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '排序',
+ field: 'sortOrder',
+ component: 'InputNumber',
+ componentProps: {
+ min: 0,
+ placeholder: '请输入排序值',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '产品状态',
+ field: 'status',
+ component: 'Select',
+ componentProps: {
+ placeholder: '请选择产品状态',
+ options: [
+ { label: '上架', value: 1 },
+ { label: '下架', value: 0 },
+ ],
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '是否推荐',
+ field: 'isFeatured',
+ component: 'Select',
+ componentProps: {
+ placeholder: '请选择是否推荐',
+ options: [
+ { label: '推荐', value: 1 },
+ { label: '普通', value: 0 },
+ ],
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '是否热销',
+ field: 'isHot',
+ component: 'Select',
+ componentProps: {
+ placeholder: '请选择是否热销',
+ options: [
+ { label: '热销', value: 1 },
+ { label: '普通', value: 0 },
+ ],
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '材质',
+ field: 'material',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入材质',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '规格参数',
+ field: 'specifications',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入规格参数',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '重量(克)',
+ field: 'weight',
+ component: 'InputNumber',
+ componentProps: {
+ min: 0,
+ placeholder: '请输入重量',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '尺寸',
+ field: 'dimensions',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入尺寸',
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '标签',
+ field: 'tags',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入标签,多个标签用逗号分隔',
+ },
+ colProps: { span: 24 },
+ },
+ {
+ label: '产品描述',
+ field: 'description',
+ component: 'InputTextArea',
+ componentProps: {
+ rows: 4,
+ placeholder: '请输入产品描述',
+ },
+ colProps: { span: 24 },
+ },
+ {
+ label: '文化故事',
+ field: 'culturalStory',
+ component: 'InputTextArea',
+ componentProps: {
+ rows: 3,
+ placeholder: '请输入文化故事',
+ },
+ colProps: { span: 24 },
+ },
+ {
+ label: '工匠信息',
+ field: 'craftsmanInfo',
+ component: 'InputTextArea',
+ componentProps: {
+ rows: 3,
+ placeholder: '请输入工匠信息',
+ },
+ colProps: { span: 24 },
+ },
+ {
+ label: '主图',
+ field: 'mainImage',
+ component: 'JUpload',
+ componentProps: {
+ fileType: 'image',
+ maxCount: 1,
+ maxSize: 5,
+ bizPath: 'product/main',
+ customRequest: (options) => {
+ // 验证文件类型和大小
+ const { file } = options;
+ const isImage = file.type.startsWith('image/');
+ if (!isImage) {
+ console.error('只能上传图片文件!');
+ return;
+ }
+ const isLt5M = file.size / 1024 / 1024 < 5;
+ if (!isLt5M) {
+ console.error('图片大小不能超过5MB!');
+ return;
+ }
+
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file);
+
+ // 直接设置文件的url为本地预览URL
+ file.url = localUrl;
+
+ // 模拟上传成功响应,但不返回local:前缀,避免被解析
+ setTimeout(() => {
+ options.onSuccess({
+ success: true,
+ message: file.name, // 仅使用文件名,不使用local:前缀
+ }, file);
+ }, 100);
+ },
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '产品图片',
+ field: 'images',
+ component: 'JUpload',
+ componentProps: {
+ fileType: 'image',
+ maxCount: 8,
+ maxSize: 5,
+ bizPath: 'product/images',
+ customRequest: (options) => {
+ // 验证文件类型和大小
+ const { file } = options;
+ const isImage = file.type.startsWith('image/');
+ if (!isImage) {
+ console.error('只能上传图片文件!');
+ return;
+ }
+ const isLt5M = file.size / 1024 / 1024 < 5;
+ if (!isLt5M) {
+ console.error('图片大小不能超过5MB!');
+ return;
+ }
+
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file);
+
+ // 直接设置文件的url为本地预览URL
+ file.url = localUrl;
+
+ // 模拟上传成功响应,但不返回local:前缀,避免被解析
+ setTimeout(() => {
+ options.onSuccess({
+ success: true,
+ message: file.name, // 仅使用文件名,不使用local:前缀
+ }, file);
+ }, 100);
+ },
+ },
+ colProps: { span: 12 },
+ },
+ {
+ label: '产品视频',
+ field: 'productVideo',
+ component: 'JUpload',
+ componentProps: {
+ fileType: 'video',
+ maxCount: 1,
+ maxSize: 50,
+ bizPath: 'product/video',
+ customRequest: (options) => {
+ // 验证文件类型和大小
+ const { file } = options;
+ const isVideo = file.type.startsWith('video/');
+ if (!isVideo) {
+ console.error('只能上传视频文件!');
+ return;
+ }
+ const isLt50M = file.size / 1024 / 1024 < 50;
+ if (!isLt50M) {
+ console.error('视频大小不能超过50MB!');
+ return;
+ }
+
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file);
+
+ // 直接设置文件的url为本地预览URL
+ file.url = localUrl;
+
+ // 模拟上传成功响应,但不返回local:前缀,避免被解析
+ setTimeout(() => {
+ options.onSuccess({
+ success: true,
+ message: file.name, // 仅使用文件名,不使用local:前缀
+ }, file);
+ }, 100);
+ },
+ },
+ colProps: { span: 24 },
+ },
+];
\ No newline at end of file
diff --git a/src/views/oroqen/product/OroqenProductList.vue b/src/views/oroqen/product/OroqenProductList.vue
new file mode 100644
index 0000000..4efdd26
--- /dev/null
+++ b/src/views/oroqen/product/OroqenProductList.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+ 新增
+ 导出
+ 导入
+
+
+
+
+
+ 删除
+
+
+
+ 批量操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/views/oroqen/product/components/OroqenProductForm.vue b/src/views/oroqen/product/components/OroqenProductForm.vue
new file mode 100644
index 0000000..a2c2674
--- /dev/null
+++ b/src/views/oroqen/product/components/OroqenProductForm.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/views/oroqen/product/components/OroqenProductModal.vue b/src/views/oroqen/product/components/OroqenProductModal.vue
new file mode 100644
index 0000000..03a29e8
--- /dev/null
+++ b/src/views/oroqen/product/components/OroqenProductModal.vue
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
\ No newline at end of file