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