From ed3afac4679174a2d21863c7b2cad91c2e69f3c2 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Sun, 4 Jan 2026 16:39:08 +0800 Subject: [PATCH 01/31] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=A1=A8=E6=A0=BC=E4=B8=AD=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E5=88=97=E7=9A=84=E5=B1=9E=E6=80=A7=E5=90=8D=E4=BB=8ETenantId?= =?UTF-8?q?=E6=94=B9=E4=B8=BAtenantName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index b84acdd..573afa3 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -95,7 +95,7 @@ - + + + + + diff --git a/src/views/digitalHuman/avatar/index.vue b/src/views/digitalHuman/avatar/index.vue new file mode 100644 index 0000000..ffea72a --- /dev/null +++ b/src/views/digitalHuman/avatar/index.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/src/views/digitalHuman/videoAssets/index.vue b/src/views/digitalHuman/videoAssets/index.vue new file mode 100644 index 0000000..1b05a8a --- /dev/null +++ b/src/views/digitalHuman/videoAssets/index.vue @@ -0,0 +1,483 @@ + + + + + From 8e60b009a438a5d3c9331922fd687390caaed626 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Fri, 9 Jan 2026 15:25:01 +0800 Subject: [PATCH 09/31] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E4=BA=BA=E5=BD=A2=E8=B1=A1=E5=8D=A1=E7=89=87=E7=9A=84=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=E5=9B=BE=E7=89=87=E6=BA=90=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=A0=B7=E5=BC=8F,=E5=B0=86=E9=9A=8F?= =?UTF-8?q?=E6=9C=BA=E5=9B=BE=E7=89=87=E6=9B=BF=E6=8D=A2=E4=B8=BAicons8?= =?UTF-8?q?=E7=9A=843D=E5=9B=BE=E6=A0=87,=E5=90=8C=E6=97=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E5=8D=A1=E7=89=87=E9=AB=98=E5=BA=A6=E5=92=8C=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=B1=95=E7=A4=BA=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/digitalHuman/avatar/index.vue | 36 ++++++++++++++++--------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/views/digitalHuman/avatar/index.vue b/src/views/digitalHuman/avatar/index.vue index ffea72a..bed8b23 100644 --- a/src/views/digitalHuman/avatar/index.vue +++ b/src/views/digitalHuman/avatar/index.vue @@ -103,7 +103,7 @@ const mockData: AvatarItem[] = [ { id: 1, name: '商务男性形象', - avatar: 'https://picsum.photos/300/400?random=1', + avatar: 'https://img.icons8.com/3d-fluency/512/businessman.png', description: '专业商务风格的男性数字人形象,适合企业宣传', type: '真人形象', status: 1, @@ -112,7 +112,7 @@ const mockData: AvatarItem[] = [ { id: 2, name: '甜美女性形象', - avatar: 'https://picsum.photos/300/400?random=2', + avatar: 'https://img.icons8.com/3d-fluency/512/businesswoman.png', description: '甜美可爱的女性数字人形象,适合直播带货', type: '真人形象', status: 1, @@ -121,7 +121,7 @@ const mockData: AvatarItem[] = [ { id: 3, name: '卡通男孩形象', - avatar: 'https://picsum.photos/300/400?random=3', + avatar: 'https://img.icons8.com/3d-fluency/512/boy.png', description: '活泼可爱的卡通男孩形象,适合儿童教育', type: '卡通形象', status: 1, @@ -130,7 +130,7 @@ const mockData: AvatarItem[] = [ { id: 4, name: '知性女性形象', - avatar: 'https://picsum.photos/300/400?random=4', + avatar: 'https://img.icons8.com/3d-fluency/512/teacher.png', description: '知性优雅的女性数字人形象,适合知识讲解', type: '真人形象', status: 0, @@ -139,7 +139,7 @@ const mockData: AvatarItem[] = [ { id: 5, name: '科技机器人形象', - avatar: 'https://picsum.photos/300/400?random=5', + avatar: 'https://img.icons8.com/3d-fluency/512/robot-2.png', description: '未来科技风格的机器人形象,适合科技产品', type: '3D形象', status: 1, @@ -147,10 +147,10 @@ const mockData: AvatarItem[] = [ }, { id: 6, - name: '古风女性形象', - avatar: 'https://picsum.photos/300/400?random=6', - description: '古典优雅的古风女性形象,适合文化传播', - type: '真人形象', + name: '客服助手形象', + avatar: 'https://img.icons8.com/3d-fluency/512/customer-support.png', + description: '专业友好的客服助手形象,适合在线客服场景', + type: '3D形象', status: 1, createdAt: '2024-01-10 08:30:00', }, @@ -250,13 +250,23 @@ onMounted(() => { .avatar-image { position: relative; width: 100%; - height: 200px; + height: 220px; overflow: hidden; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; img { - width: 100%; - height: 100%; - object-fit: cover; + width: 160px; + height: 160px; + object-fit: contain; + filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2)); + transition: transform 0.3s ease; + } + + &:hover img { + transform: scale(1.1); } .avatar-overlay { From a06d4955d474c9ec53a262e66ff33edb4f504c84 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Mon, 12 Jan 2026 15:49:47 +0800 Subject: [PATCH 10/31] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E8=87=AA=E5=AE=9A=E4=B9=89=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E7=9A=84=E5=AD=97=E5=85=B8=E7=B1=BB=E5=9E=8B=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E9=80=BB=E8=BE=91,=E6=96=B0=E5=A2=9EdictType=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E7=B2=BE=E7=A1=AE=E5=8C=B9=E9=85=8D=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E6=95=B0=E6=8D=AE,=E9=81=BF=E5=85=8D=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E5=90=8D=E7=A7=B0=E4=BF=AE=E6=94=B9=E5=90=8E=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=E5=8C=B9=E9=85=8D=E5=A4=B1=E8=B4=A5,?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E5=9C=A8=E7=BC=96=E8=BE=91=E6=97=B6=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=90=8C=E6=AD=A5=E6=9C=80=E6=96=B0=E7=9A=84=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/component/editCategory.vue | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/views/assets/category/component/editCategory.vue b/src/views/assets/category/component/editCategory.vue index 6f009b1..8c09c00 100644 --- a/src/views/assets/category/component/editCategory.vue +++ b/src/views/assets/category/component/editCategory.vue @@ -84,7 +84,7 @@ :max-collapse-tags="2" > { // 获取字典类型数据 const fetchDictTypeOptions = () => { dictLoading.value = true; - getDicts('assets') + return getDicts('assets') .then((res: any) => { const list = res.data?.list ?? []; // 提取所有字典类型信息 @@ -231,11 +233,18 @@ const fetchDictTypeOptions = () => { }); }; -// 根据字典类型获取对应的字典值 -const getDictValuesByType = (dictKey: string) => { - if (!dictKey) return []; - // 根据字典类型名称找到对应的字典数据 - const dictItem = dictValueOptions.value.find((item: any) => item.info?.name === dictKey); +// 根据字典类型获取对应的字典值(优先使用 dictType 匹配) +const getDictValuesByType = (dictName: string, dictType?: string) => { + if (!dictName && !dictType) return []; + // 优先使用 dictType 匹配,这样即使字典名称修改也能正确匹配 + let dictItem; + if (dictType) { + dictItem = dictValueOptions.value.find((item: any) => item.info?.type === dictType); + } + // 如果 dictType 没匹配到,再用 name 匹配(兼容旧数据) + if (!dictItem && dictName) { + dictItem = dictValueOptions.value.find((item: any) => item.info?.name === dictName); + } return dictItem?.values ?? []; }; @@ -246,13 +255,19 @@ const isDictType = (type?: string) => type === 'select' || type === 'multi_selec const onDictKeyChange = (attr: CustomAttr) => { // 清空已选的字典值 attr.options = []; + // 根据选择的字典名称,找到对应的 dictType 并保存 + const selectedDict = dictTypeOptions.value.find((item) => item.name === attr.name); + attr.dictType = selectedDict?.type || ''; }; // 判断字典选项是否应被禁用 const isDictOptionDisabled = (dictName: string, currentAttr: CustomAttr) => { if (!dictName) return false; - // 检查该字典名称是否已被其他属性使用 - return ruleForm.attrs.some((attr) => attr !== currentAttr && isDictType(attr.type) && attr.name === dictName); + // 找到该字典对应的 type + const dictInfo = dictTypeOptions.value.find((item) => item.name === dictName); + const dictType = dictInfo?.type || ''; + // 检查该字典是否已被其他属性使用(使用 dictType 判断) + return ruleForm.attrs.some((attr) => attr !== currentAttr && isDictType(attr.type) && (attr.dictType === dictType || (!attr.dictType && attr.name === dictName))); }; // 添加自定义属性 @@ -335,9 +350,19 @@ const openDialog = (row?: CategoryRow | string, edit?: boolean) => { } return { ...attr, options: options || [] }; }); - // 如果有单选/多选属性,预加载字典类型数据 + // 如果有单选/多选属性,预加载字典类型数据并更新字典名称 if (ruleForm.attrs.some((attr: CustomAttr) => attr.type === 'select' || attr.type === 'multi_select')) { - fetchDictTypeOptions(); + fetchDictTypeOptions().then(() => { + // 根据 dictType 更新字典名称(字典名称可能已被修改) + ruleForm.attrs.forEach((attr: CustomAttr) => { + if (isDictType(attr.type) && attr.dictType) { + const dictInfo = dictTypeOptions.value.find((item) => item.type === attr.dictType); + if (dictInfo) { + attr.name = dictInfo.name; + } + } + }); + }); } }); } else if (row && typeof row === 'string') { @@ -368,7 +393,7 @@ const formatDictOptions = (attr: CustomAttr) => { value: opt.value ?? opt.key ?? '', })); } - const dictValues = getDictValuesByType(attr.name || ''); + const dictValues = getDictValuesByType(attr.name || '', attr.dictType); return options.map((optValue: string) => { const dictItem = dictValues.find((d: any) => d.key === optValue); return { @@ -396,6 +421,7 @@ const onSubmit = () => { return { ...base, name: attr.name || '', + dictType: attr.dictType || '', options: formatDictOptions(attr), }; } From 586e0d51dd14160d041b0e7db74646bc162f13ce Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Mon, 12 Jan 2026 15:59:05 +0800 Subject: [PATCH 11/31] =?UTF-8?q?=E5=9C=A8=E8=B5=84=E4=BA=A7=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=92=8CSKU=E5=AF=B9=E8=AF=9D=E6=A1=86=E4=B8=AD?= =?UTF-8?q?=E6=96=B0=E5=A2=9EdictType=E5=AD=97=E6=AE=B5=E6=94=AF=E6=8C=81,?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E8=87=AA=E5=AE=9A=E4=B9=89=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E7=9A=84=E5=AD=97=E5=85=B8=E7=B1=BB=E5=9E=8B=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=9C=A8=E8=AF=B7=E6=B1=82=E4=BD=93=E4=B8=AD=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E5=92=8C=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/assets/asset/component/editAsset.vue | 2 ++ src/views/assets/asset/component/skuDialog.vue | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/views/assets/asset/component/editAsset.vue b/src/views/assets/asset/component/editAsset.vue index 1a8487f..21bd4c6 100644 --- a/src/views/assets/asset/component/editAsset.vue +++ b/src/views/assets/asset/component/editAsset.vue @@ -469,6 +469,7 @@ interface CategoryAttr { type: string; options?: { label: string; value: string }[]; required?: boolean; + dictType?: string; } interface KeyValuePair { @@ -1158,6 +1159,7 @@ const buildRequestBody = async (): Promise => { name: attr.name, type: attr.type, value: value, + ...(attr.dictType ? { dictType: attr.dictType } : {}), }; // 只有单选和多选类型才传递 options,且只传递选中的值对应的选项 diff --git a/src/views/assets/asset/component/skuDialog.vue b/src/views/assets/asset/component/skuDialog.vue index 46fb3ad..0182187 100644 --- a/src/views/assets/asset/component/skuDialog.vue +++ b/src/views/assets/asset/component/skuDialog.vue @@ -149,6 +149,7 @@ interface SpecValueItem { interface AssetSpecAttr { name: string; options?: string[]; + dictType?: string; } const dialogVisible = ref(false); @@ -241,6 +242,7 @@ const fetchAssetSpecAttrs = () => { .map((item: any) => ({ name: item.name, options: item.options?.map((opt: any) => opt.label || opt.value) || [], + dictType: item.dictType || '', })); } else { assetSpecAttrs.value = []; From ffaf455fe804df14f58e03ce7b8b01bce03cbf96 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Mon, 12 Jan 2026 17:56:47 +0800 Subject: [PATCH 12/31] =?UTF-8?q?=E4=BC=98=E5=8C=96SKU=E8=A7=84=E6=A0=BC?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84,?= =?UTF-8?q?=E5=B0=86=E5=AF=B9=E8=B1=A1=E6=A0=BC=E5=BC=8F=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=A0=BC=E5=BC=8F=E4=BB=A5=E5=8C=B9=E9=85=8D?= =?UTF-8?q?metadata=E8=A7=84=E8=8C=83,=E5=90=8C=E6=97=B6=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=97=A7=E6=A0=BC=E5=BC=8F=E6=95=B0=E6=8D=AE=E7=9A=84=E5=9B=9E?= =?UTF-8?q?=E6=98=BE=E5=92=8C=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/asset/component/skuDialog.vue | 71 +++++++++++++------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/views/assets/asset/component/skuDialog.vue b/src/views/assets/asset/component/skuDialog.vue index 0182187..f4e3c47 100644 --- a/src/views/assets/asset/component/skuDialog.vue +++ b/src/views/assets/asset/component/skuDialog.vue @@ -328,16 +328,33 @@ const onEditSku = async (row: any) => { skuImagePreview.value = formatImageUrl(data.imageUrl); } - // 处理规格属性 - if (data.specValues && Object.keys(data.specValues).length > 0) { - specValuesList.value = Object.entries(data.specValues).map(([key, value]) => ({ - key, - value: String(value), - })); - // 回显到 specValuesMap - Object.entries(data.specValues).forEach(([key, value]) => { - specValuesMap[key] = String(value); - }); + // 处理规格属性(支持新格式数组和旧格式对象) + if (data.specValues) { + if (Array.isArray(data.specValues)) { + // 新格式:与 metadata 相同的数组格式 + data.specValues.forEach((item: any) => { + if (item.name) { + // value 可能是数组或字符串 + const val = Array.isArray(item.value) ? item.value[0] : item.value; + specValuesMap[item.name] = String(val || ''); + } + }); + specValuesList.value = data.specValues.map((item: any) => ({ + key: item.name, + value: String(Array.isArray(item.value) ? item.value[0] : item.value || ''), + })); + } else if (typeof data.specValues === 'object' && Object.keys(data.specValues).length > 0) { + // 旧格式:对象格式 { key: value } + specValuesList.value = Object.entries(data.specValues).map(([key, value]) => ({ + key, + value: String(value), + })); + Object.entries(data.specValues).forEach(([key, value]) => { + specValuesMap[key] = String(value); + }); + } else { + specValuesList.value = [{ key: '', value: '' }]; + } } else { specValuesList.value = [{ key: '', value: '' }]; } @@ -471,18 +488,26 @@ const onSubmitSku = async () => { submitLoading.value = true; - // 构建规格属性对象(优先使用 specValuesMap) - const specValues: Record = {}; - // 从 specValuesMap 获取(直接展示的属性) - Object.entries(specValuesMap).forEach(([key, value]) => { - if (key && value) { - specValues[key] = value; - } - }); - // 兼容旧的 specValuesList - specValuesList.value.forEach((item) => { - if (item.key.trim() && !specValues[item.key.trim()]) { - specValues[item.key.trim()] = item.value.trim(); + // 构建规格属性数组(与 metadata 格式相同) + const specValues: any[] = []; + assetSpecAttrs.value.forEach((attr) => { + const selectedValue = specValuesMap[attr.name]; + if (selectedValue) { + // 构建与 metadata 相同的数据结构 + const specItem: any = { + name: attr.name, + type: 'multi_select', + value: [selectedValue], + }; + // 添加 dictType + if (attr.dictType) { + specItem.dictType = attr.dictType; + } + // 添加 options(只包含选中的值) + if (attr.options && attr.options.length > 0) { + specItem.options = [{ label: selectedValue, value: selectedValue }]; + } + specValues.push(specItem); } }); @@ -491,7 +516,7 @@ const onSubmitSku = async () => { assetName: assetName.value, skuName: skuForm.skuName, imageUrl: skuForm.imageUrl || undefined, - specValues: Object.keys(specValues).length > 0 ? specValues : undefined, + specValues: specValues.length > 0 ? specValues : undefined, price: Math.round(skuForm.price * 100), unlimitedStock: skuForm.unlimitedStock, stock: skuForm.stock, From eec9a72a1d5a58e0379d793da3de63125b949fde Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Tue, 13 Jan 2026 10:24:08 +0800 Subject: [PATCH 13/31] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E9=97=B4=E8=B7=9D=E6=A0=B7=E5=BC=8F=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8DSKU=E8=A7=84=E6=A0=BC=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E6=98=BE=E7=A4=BA,=E5=B0=86=E5=9B=BE=E6=A0=87=E9=97=B4?= =?UTF-8?q?=E8=B7=9D=E8=A7=84=E5=88=99=E8=B0=83=E6=95=B4=E4=B8=BA=E4=BB=85?= =?UTF-8?q?=E5=AF=B9=E9=9D=9E=E5=9C=86=E5=BD=A2=E6=8C=89=E9=92=AE=E7=94=9F?= =?UTF-8?q?=E6=95=88,=E5=90=8C=E6=97=B6=E4=BF=AE=E5=A4=8D=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E4=BA=BA=E5=BD=A2=E8=B1=A1=E5=A4=B4=E5=83=8F=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=B0=BA=E5=AF=B8=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/theme/element.scss | 15 +++++++++++ .../assets/asset/component/skuDialog.vue | 25 +++++++++++++------ src/views/digitalHuman/avatar/index.vue | 12 ++++----- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/theme/element.scss b/src/theme/element.scss index 17c36d4..4945469 100644 --- a/src/theme/element.scss +++ b/src/theme/element.scss @@ -9,11 +9,26 @@ .el-button--default i.iconfont, .el-button--default i.fa { font-size: 14px !important; +} +// 非圆形按钮的图标右边距(圆形按钮不需要) +.el-button:not(.is-circle) i.el-icon + span, +.el-button:not(.is-circle) i.iconfont, +.el-button:not(.is-circle) i.fa, +.el-button--default:not(.is-circle) i.iconfont, +.el-button--default:not(.is-circle) i.fa { + margin-left: 5px; +} +.el-button:not(.is-circle) > i.el-icon:first-child:not(:last-child), +.el-button:not(.is-circle) > i.iconfont:first-child:not(:last-child), +.el-button:not(.is-circle) > i.fa:first-child:not(:last-child) { margin-right: 5px; } .el-button--small i.iconfont, .el-button--small i.fa { font-size: 12px !important; +} +.el-button--small:not(.is-circle) > i.iconfont:first-child:not(:last-child), +.el-button--small:not(.is-circle) > i.fa:first-child:not(:last-child) { margin-right: 5px; } diff --git a/src/views/assets/asset/component/skuDialog.vue b/src/views/assets/asset/component/skuDialog.vue index f4e3c47..88b0a6a 100644 --- a/src/views/assets/asset/component/skuDialog.vue +++ b/src/views/assets/asset/component/skuDialog.vue @@ -23,7 +23,12 @@ + + + + + + + + + + + @@ -170,7 +214,7 @@ import { ref, reactive } from 'vue'; import { ElMessage, type FormInstance, type FormRules } from 'element-plus'; import { ElMessageBox } from 'element-plus'; -import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions } from '/@/api/assets/asset'; +import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions, getStockFormFields, stockOperation } from '/@/api/assets/asset'; import { createFormDiff } from '/@/utils/diffUtils'; import type { UploadRequestOptions, UploadUserFile } from 'element-plus'; @@ -185,6 +229,18 @@ interface AssetSpecAttr { dictType?: string; } +// 库存表单字段接口 +interface StockFormField { + name: string; + label: string; + type: string; + required?: boolean; + min?: number; + max?: number; + maxLength?: number; + default?: string | number; +} + const dialogVisible = ref(false); const loading = ref(false); const submitLoading = ref(false); @@ -193,6 +249,16 @@ const skuFormVisible = ref(false); const isEditSku = ref(false); const editSkuId = ref(''); +// 库存弹窗相关 +const stockFormVisible = ref(false); +const stockFormLoading = ref(false); +const stockSubmitLoading = ref(false); +const stockFormFields = ref([]); +const stockFormRef = ref(); +const stockForm = reactive>({}); +const currentSkuId = ref(''); +const currentSkuName = ref(''); + const assetId = ref(''); const assetName = ref(''); const assetType = ref(''); @@ -479,9 +545,82 @@ const onDeleteSku = (row: any) => { }; // 生成库存 -const onGenerateStock = (row: any) => { - // TODO: 实现生成库存功能 - ElMessage.info(`生成库存功能待实现,SKU: ${row.skuName}`); +const onGenerateStock = async (row: any) => { + currentSkuId.value = row.id; + currentSkuName.value = row.skuName; + stockFormVisible.value = true; + stockFormLoading.value = true; + + // 重置表单 + Object.keys(stockForm).forEach((key) => delete stockForm[key]); + + try { + const res = await getStockFormFields(row.id); + stockFormFields.value = res.data.fields || []; + + // 设置默认值 + stockFormFields.value.forEach((field) => { + if (field.default !== undefined) { + stockForm[field.name] = field.default; + } else if (field.type === 'number') { + stockForm[field.name] = field.min || 0; + } else { + stockForm[field.name] = ''; + } + }); + } catch (error) { + console.error('获取库存表单字段失败:', error); + ElMessage.error('获取库存表单字段失败'); + stockFormVisible.value = false; + } finally { + stockFormLoading.value = false; + } +}; + +// 提交库存操作 +const onSubmitStock = async () => { + const form = stockFormRef.value; + if (!form) return; + + form.validate(async (valid: boolean) => { + if (valid) { + stockSubmitLoading.value = true; + try { + await stockOperation({ + assetSkuId: currentSkuId.value, + ...stockForm, + }); + ElMessage.success('库存生成成功'); + stockFormVisible.value = false; + getSkuList(); + } catch (error) { + console.error('库存操作失败:', error); + } finally { + stockSubmitLoading.value = false; + } + } + }); +}; + +// 生成库存表单验证规则 +const getStockFormRules = () => { + const rules: Record = {}; + stockFormFields.value.forEach((field) => { + const fieldRules: any[] = []; + if (field.required) { + fieldRules.push({ required: true, message: `${field.label}不能为空`, trigger: 'blur' }); + } + if (field.type === 'number' && field.min !== undefined) { + fieldRules.push({ type: 'number', min: field.min, message: `${field.label}最小值为${field.min}`, trigger: 'blur' }); + } + if (field.maxLength) { + fieldRules.push({ max: field.maxLength, message: `${field.label}最大长度为${field.maxLength}`, trigger: 'blur' }); + } + if (fieldRules.length > 0) { + rules[field.name] = fieldRules; + } + }); + return rules; }; // 重置 SKU 表单 From f98e1f8c44ffe8f69e724bfdd36db02ac567a62a Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Wed, 14 Jan 2026 17:07:38 +0800 Subject: [PATCH 19/31] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=9F=E6=88=B7ID?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=AF=94=E8=BE=83=E9=97=AE=E9=A2=98,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/assets/asset/component/editAsset.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/assets/asset/component/editAsset.vue b/src/views/assets/asset/component/editAsset.vue index ff4e32a..e82e222 100644 --- a/src/views/assets/asset/component/editAsset.vue +++ b/src/views/assets/asset/component/editAsset.vue @@ -70,7 +70,7 @@ - + 明细模式 @@ -580,6 +580,8 @@ const assetFormDiff = createFormDiff>(); // 获取租户ID const tenantId = ref(Session.get('userInfo')?.tenantId || ''); +console.log(tenantId.value,'租户id'); + const formatImageUrl = (url?: string) => { if (!url) return ''; @@ -1126,7 +1128,7 @@ const buildRequestBody = async (): Promise => { body.unlimitedStock = ruleForm.unlimitedStock; // 库存存储模式(仅租户ID为1时提交) - if (tenantId.value === '1') { + if (tenantId.value == 1) { body.stockMode = ruleForm.stockMode; } From 139b7e003e53e485acdf17174f2749d536288acf Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Wed, 14 Jan 2026 17:36:36 +0800 Subject: [PATCH 20/31] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E7=94=9F=E6=88=90=E8=A1=A8=E5=8D=95=E7=9A=84=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=A4=84=E7=90=86=E5=92=8C=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/asset/component/skuDialog.vue | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/views/assets/asset/component/skuDialog.vue b/src/views/assets/asset/component/skuDialog.vue index 964744a..394c28a 100644 --- a/src/views/assets/asset/component/skuDialog.vue +++ b/src/views/assets/asset/component/skuDialog.vue @@ -178,6 +178,7 @@ v-model="stockForm[field.name]" :min="field.min" :max="field.max" + :controls="!field.maxLength" controls-position="right" style="width: 200px" /> @@ -558,10 +559,15 @@ const onGenerateStock = async (row: any) => { const res = await getStockFormFields(row.id); stockFormFields.value = res.data.fields || []; - // 设置默认值 + // 设置默认值,根据字段类型转换 stockFormFields.value.forEach((field) => { if (field.default !== undefined) { - stockForm[field.name] = field.default; + // 如果是数字类型,确保默认值也是数字 + if (field.type === 'number') { + stockForm[field.name] = Number(field.default); + } else { + stockForm[field.name] = field.default; + } } else if (field.type === 'number') { stockForm[field.name] = field.min || 0; } else { @@ -586,10 +592,20 @@ const onSubmitStock = async () => { if (valid) { stockSubmitLoading.value = true; try { - await stockOperation({ + // 根据字段类型转换数据 + const submitData: Record = { assetSkuId: currentSkuId.value, - ...stockForm, + }; + stockFormFields.value.forEach((field) => { + const value = stockForm[field.name]; + if (field.type === 'number' && value !== undefined && value !== '') { + submitData[field.name] = Number(value); + } else if (value !== undefined && value !== '') { + submitData[field.name] = value; + } }); + + await stockOperation(submitData as any); ElMessage.success('库存生成成功'); stockFormVisible.value = false; getSkuList(); @@ -610,10 +626,34 @@ const getStockFormRules = () => { if (field.required) { fieldRules.push({ required: true, message: `${field.label}不能为空`, trigger: 'blur' }); } - if (field.type === 'number' && field.min !== undefined) { - fieldRules.push({ type: 'number', min: field.min, message: `${field.label}最小值为${field.min}`, trigger: 'blur' }); - } - if (field.maxLength) { + if (field.type === 'number') { + // 数字类型的最小值和最大值验证 + if (field.min !== undefined) { + fieldRules.push({ type: 'number', min: field.min, message: `${field.label}最小值为${field.min}`, trigger: 'blur' }); + } + if (field.max !== undefined) { + fieldRules.push({ type: 'number', max: field.max, message: `${field.label}最大值为${field.max}`, trigger: 'blur' }); + } + // 数字位数验证 + if (field.maxLength) { + fieldRules.push({ + validator: (_rule: any, value: any, callback: any) => { + if (value !== undefined && value !== null && value !== '') { + const strValue = String(value); + if (strValue.length > field.maxLength!) { + callback(new Error(`${field.label}不能超过${field.maxLength}位`)); + } else { + callback(); + } + } else { + callback(); + } + }, + trigger: 'blur', + }); + } + } else if (field.maxLength) { + // 字符串类型的最大长度验证 fieldRules.push({ max: field.maxLength, message: `${field.label}最大长度为${field.maxLength}`, trigger: 'blur' }); } if (fieldRules.length > 0) { From e77bb4005b9323c8071c821dbdf474cfcc79f38a Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Thu, 15 Jan 2026 15:56:21 +0800 Subject: [PATCH 21/31] =?UTF-8?q?=E5=9C=A8=E8=AF=B7=E6=B1=82=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=E4=B8=AD=E6=96=B0=E5=A2=9E429?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=A0=81=E5=A4=84=E7=90=86,=E5=BD=93?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E8=BF=87=E4=BA=8E=E9=A2=91=E7=B9=81=E6=97=B6?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF=E5=B9=B6?= =?UTF-8?q?=E8=A7=A6=E5=8F=91token=E8=BF=87=E6=9C=9F=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/request.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/request.ts b/src/utils/request.ts index 97e20dc..cad63b3 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -199,6 +199,10 @@ const responseErrorHandler = (error: any) => { case 404: showErrorMessage(responseMessage || '请求的资源不存在'); break; + case 429: + showErrorMessage(responseMessage || '请求过于频繁,请稍后再试'); + handleTokenExpired(); + break; case 500: showErrorMessage(responseMessage || '服务器内部错误'); break; From 404f0b719d59cd01de58a5543b1d41e0906dc88b Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Thu, 15 Jan 2026 16:43:02 +0800 Subject: [PATCH 22/31] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E5=8E=9F=E5=A7=8B=E6=95=B0=E6=8D=AE=E4=BF=9D=E5=AD=98=E6=97=B6?= =?UTF-8?q?=E6=9C=BA=E5=92=8C=E7=BB=93=E6=9E=84,=E5=9C=A8=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E7=BC=96=E8=BE=91=E4=B8=AD=E5=B0=86=E5=8E=9F=E5=A7=8B?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=BF=9D=E5=AD=98=E7=A7=BB=E8=87=B3=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E5=B1=9E=E6=80=A7=E5=8A=A0=E8=BD=BD=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=90=8E,=E5=9C=A8=E5=88=86=E7=B1=BB=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E4=B8=AD=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8buildSubmitData?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=8E=9F=E5=A7=8B=E6=95=B0=E6=8D=AE=E4=BB=A5?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E4=B8=8E=E6=8F=90=E4=BA=A4=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=B8=80=E8=87=B4,=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E5=9B=A0=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=B7=AE=E5=BC=82?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E6=9C=80=E5=B0=8F=E5=8C=96=E4=BC=A0?= =?UTF-8?q?=E5=8F=82=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/asset/component/editAsset.vue | 14 +++- .../category/component/editCategory.vue | 74 ++++++++++--------- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/views/assets/asset/component/editAsset.vue b/src/views/assets/asset/component/editAsset.vue index e82e222..92b57af 100644 --- a/src/views/assets/asset/component/editAsset.vue +++ b/src/views/assets/asset/component/editAsset.vue @@ -1050,11 +1050,19 @@ const openDialog = (row?: any, edit?: boolean) => { }) .catch(() => { categoryAttrs.value = []; + }) + .finally(() => { + // 分类属性加载完成后,保存原始数据用于最小化传参 + buildRequestBody().then((originalBody) => { + assetFormDiff.saveOriginal(JSON.parse(JSON.stringify(originalBody))); + }); }); + } else { + // 没有分类属性,直接保存原始数据 + buildRequestBody().then((originalBody) => { + assetFormDiff.saveOriginal(JSON.parse(JSON.stringify(originalBody))); + }); } - - // 保存原始数据用于最小化传参 - assetFormDiff.saveOriginal(JSON.parse(JSON.stringify(ruleForm))); }) .finally(() => { formLoading.value = false; diff --git a/src/views/assets/category/component/editCategory.vue b/src/views/assets/category/component/editCategory.vue index 7fb5ea5..b29e901 100644 --- a/src/views/assets/category/component/editCategory.vue +++ b/src/views/assets/category/component/editCategory.vue @@ -365,12 +365,12 @@ const openDialog = (row?: CategoryRow | string, edit?: boolean) => { } } }); - // 保存原始数据用于最小化传参 - categoryFormDiff.saveOriginal(JSON.parse(JSON.stringify(ruleForm))); + // 保存原始数据用于最小化传参(使用与提交相同的结构) + categoryFormDiff.saveOriginal(JSON.parse(JSON.stringify(buildSubmitData()))); }); } else { - // 保存原始数据用于最小化传参 - categoryFormDiff.saveOriginal(JSON.parse(JSON.stringify(ruleForm))); + // 保存原始数据用于最小化传参(使用与提交相同的结构) + categoryFormDiff.saveOriginal(JSON.parse(JSON.stringify(buildSubmitData()))); } }); } else if (row && typeof row === 'string') { @@ -411,46 +411,55 @@ const formatDictOptions = (attr: CustomAttr) => { }); }; +// 构建提交数据(用于保存原始数据和提交) +const buildSubmitData = () => { + // 处理 attrs:统一清理脏数据 + const processedAttrs = ruleForm.attrs.map((attr) => { + const base = { + type: attr.type, + required: attr.required ?? false, + multiple: attr.type === 'multi_select', + sort: attr.sort ?? 0, + }; + + if (isDictType(attr.type)) { + return { + ...base, + name: attr.name || '', + dictType: attr.dictType || '', + options: formatDictOptions(attr), + }; + } + + return { + ...base, + name: (attr.name || '').trim(), + options: [], + }; + }); + + return { + ...ruleForm, + attrs: processedAttrs, + }; +}; + // 提交 const onSubmit = () => { formRef.value.validate((valid: boolean) => { if (valid) { submitLoading.value = true; - // 处理 attrs:统一清理脏数据 - const processedAttrs = ruleForm.attrs.map((attr) => { - const base = { - type: attr.type, - required: attr.required ?? false, - multiple: attr.type === 'multi_select', - sort: attr.sort ?? 0, - }; - - if (isDictType(attr.type)) { - return { - ...base, - name: attr.name || '', - dictType: attr.dictType || '', - options: formatDictOptions(attr), - }; - } - - return { - ...base, - name: (attr.name || '').trim(), - options: [], - }; - }); + const submitData = buildSubmitData(); if (isEdit.value) { // 编辑模式:通过 _originalData 让拦截器自动处理最小化传参 const originalData = categoryFormDiff.getOriginal(); - const submitData = { - ...ruleForm, - attrs: processedAttrs, + const requestData = { + ...submitData, _originalData: originalData, }; - updateCategory(submitData) + updateCategory(requestData) .then(() => { ElMessage.success('修改成功'); closeDialog(); @@ -461,7 +470,6 @@ const onSubmit = () => { }); } else { // 新增模式:传递所有字段 - const submitData = { ...ruleForm, attrs: processedAttrs }; addCategory(submitData) .then(() => { ElMessage.success('添加成功'); From 30da5e3c292d383c404f5bd7d0258019ff3a0544 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Fri, 16 Jan 2026 14:19:24 +0800 Subject: [PATCH 23/31] =?UTF-8?q?=E5=9C=A8=E8=B5=84=E4=BA=A7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E3=80=81SKU=E7=AE=A1=E7=90=86=E5=92=8C=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E7=AE=A1=E7=90=86=E4=B8=AD=E6=96=B0=E5=A2=9E=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97=E6=9F=A5=E7=9C=8B=E5=8A=9F=E8=83=BD?= =?UTF-8?q?,=E6=94=AF=E6=8C=81=E6=9F=A5=E7=9C=8B=E5=90=84=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E7=9A=84=E6=93=8D=E4=BD=9C=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95,=E5=90=8C=E6=97=B6=E6=96=B0=E5=A2=9E=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97API=E6=8E=A5=E5=8F=A3=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=8C=85=E5=90=AB=E6=9F=A5=E8=AF=A2=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=92=8C=E6=97=A5=E5=BF=97=E4=BF=A1=E6=81=AF=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/assets/asset/index.ts | 29 ++++ .../assets/asset/component/skuDialog.vue | 11 +- src/views/assets/asset/index.vue | 11 +- src/views/assets/category/index.vue | 11 +- .../assets/component/operationLogDialog.vue | 149 ++++++++++++++++++ 5 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/views/assets/component/operationLogDialog.vue diff --git a/src/api/assets/asset/index.ts b/src/api/assets/asset/index.ts index 0529548..8fc2218 100644 --- a/src/api/assets/asset/index.ts +++ b/src/api/assets/asset/index.ts @@ -191,3 +191,32 @@ export function stockOperation(data: StockOperationParams) { data, }); } + +// 操作日志查询参数 +export interface LogQueryParams { + collection_id: string; + pageNum?: number; + pageSize?: number; +} + +// 操作日志信息 +export interface OperationLogInfo { + id: string; + service_name: string; + collection: string; + collection_id: string[]; + operation: string; + creator: string; + createdAt: string; + data: { FieldName: string; FieldValue: any }[] | null; + ip_address: string; +} + +// 查询操作日志 +export function listLogs(params: LogQueryParams) { + return newService({ + url: '/assets/log/listLogs', + method: 'get', + params, + }); +} diff --git a/src/views/assets/asset/component/skuDialog.vue b/src/views/assets/asset/component/skuDialog.vue index 394c28a..2bac64c 100644 --- a/src/views/assets/asset/component/skuDialog.vue +++ b/src/views/assets/asset/component/skuDialog.vue @@ -78,10 +78,11 @@ - + @@ -209,6 +210,7 @@ + + + From e090b0a468abf539f189c809737b02be4efa13b1 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Sat, 17 Jan 2026 15:22:52 +0800 Subject: [PATCH 24/31] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=AF=B9=E8=AF=9D=E6=A1=86=E4=B8=AD=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E5=86=85=E5=AE=B9=E7=9A=84=E6=98=BE=E7=A4=BA=E6=95=88?= =?UTF-8?q?=E6=9E=9C,=E4=B8=BA=E9=95=BF=E6=96=87=E6=9C=AC=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=88=AA=E6=96=AD=E5=92=8C=E6=82=AC=E6=B5=AE=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E5=8A=9F=E8=83=BD,=E5=90=8C=E6=97=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=86=85=E5=AE=B9=E5=8C=BA=E5=9F=9F=E7=9A=84=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E5=92=8C=E6=8D=A2=E8=A1=8C=E5=A4=84=E7=90=86=E4=BB=A5?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/component/operationLogDialog.vue | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/views/assets/component/operationLogDialog.vue b/src/views/assets/component/operationLogDialog.vue index 0ec98c0..cd0f356 100644 --- a/src/views/assets/component/operationLogDialog.vue +++ b/src/views/assets/component/operationLogDialog.vue @@ -10,10 +10,13 @@ @@ -19,10 +25,12 @@ import setIntroduction from '/@/utils/setIconfont'; import LockScreen from '/@/layout/lockScreen/index.vue'; import Setings from '/@/layout/navBars/breadcrumb/setings.vue'; import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue'; +import AssetSubscribeDialog from '/@/components/assetSubscribe/index.vue'; +import { assetSubscribeState } from '/@/utils/assetSubscribe'; export default defineComponent({ name: 'app', - components: { LockScreen, Setings, CloseFull }, + components: { LockScreen, Setings, CloseFull, AssetSubscribeDialog }, setup() { const { proxy } = getCurrentInstance(); const setingsRef = ref(); @@ -89,6 +97,7 @@ export default defineComponent({ themeConfig, setingsRef, getGlobalComponentSize, + assetSubscribeState, ...toRefs(state), }; }, diff --git a/src/api/assets/asset/index.ts b/src/api/assets/asset/index.ts index 8fc2218..f36e3f9 100644 --- a/src/api/assets/asset/index.ts +++ b/src/api/assets/asset/index.ts @@ -119,6 +119,15 @@ export function listAssetSkus(params: SkuQueryParams) { }); } +// 根据assetId获取资产和SKU信息(用于套餐开通弹窗) +export function getAssetAndSku(params: { assetId: string }) { + return newService({ + url: '/assets/asset/getAssetAndSku', + method: 'get', + params, + }); +} + // 创建 SKU export function createAssetSku(data: CreateSkuParams) { return newService({ @@ -220,3 +229,18 @@ export function listLogs(params: LogQueryParams) { params, }); } + +// 订阅/开通资产服务参数 +export interface SubscribeAssetParams { + skuId: string; + assetId?: string; +} + +// 订阅/开通资产服务 +export function subscribeAsset(data: SubscribeAssetParams) { + return newService({ + url: '/assets/asset/subscribe', + method: 'post', + data, + }); +} diff --git a/src/components/assetSubscribe/index.vue b/src/components/assetSubscribe/index.vue new file mode 100644 index 0000000..c4ce99d --- /dev/null +++ b/src/components/assetSubscribe/index.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/src/utils/assetSubscribe.ts b/src/utils/assetSubscribe.ts new file mode 100644 index 0000000..1df0289 --- /dev/null +++ b/src/utils/assetSubscribe.ts @@ -0,0 +1,83 @@ +import { ref } from 'vue'; + +// 路由路径与 assetId 的映射关系 +const ROUTE_ASSET_MAP: Record = { + // CID广告业务(聚合广告) + '/cidService': { assetId: '696f423705e496ba4ccbe665', serviceName: '聚合广告' }, + + // AI客服业务 + '/customerService': { assetId: '696f421205e496ba4ccbe662', serviceName: 'AI客服' }, + + // 聚合电商业务(资产管理) + '/assets': { assetId: '696b4acd1be1c8b76c4b4c15', serviceName: '资产管理' }, + + // 订单 + // '/order': { assetId: '696b4acd1be1c8b76c4b4c15', serviceName: '资产管理' }, + + // AI数字人 + // '/digitalHuman': { assetId: '696f421205e496ba4ccbe662', serviceName: 'AI客服' }, +}; + +// 当前弹窗状态(响应式,供组件使用) +export const assetSubscribeState = ref({ + visible: false, + assetId: '', + serviceName: '', +}); + +/** + * 根据路由路径获取对应的 assetId 和服务名称 + */ +export function getAssetInfoByRoute(routePath: string): { assetId: string; serviceName: string } | null { + // 精确匹配 + if (ROUTE_ASSET_MAP[routePath]) { + return ROUTE_ASSET_MAP[routePath]; + } + + // 前缀匹配 + for (const [prefix, info] of Object.entries(ROUTE_ASSET_MAP)) { + if (routePath.startsWith(prefix)) { + return info; + } + } + + return null; +} + +/** + * 显示服务开通弹窗 + */ +export function showAssetSubscribeDialog(assetId: string, serviceName: string) { + console.log('[showAssetSubscribeDialog] 显示弹窗:', { assetId, serviceName }); + console.log('[showAssetSubscribeDialog] 修改前状态:', JSON.stringify(assetSubscribeState.value)); + assetSubscribeState.value.visible = true; + assetSubscribeState.value.assetId = assetId; + assetSubscribeState.value.serviceName = serviceName; + console.log('[showAssetSubscribeDialog] 修改后状态:', JSON.stringify(assetSubscribeState.value)); +} + +/** + * 关闭服务开通弹窗 + */ +export function closeAssetSubscribeDialog() { + assetSubscribeState.value.visible = false; +} + +/** + * 处理 403 错误码(模块未开通) + */ +export function handleModuleNotEnabled(routePath: string): boolean { + console.log('[模块未开通] 当前路由路径:', routePath); + const assetInfo = getAssetInfoByRoute(routePath); + console.log('[模块未开通] 匹配到的资产信息:', assetInfo); + + if (assetInfo) { + showAssetSubscribeDialog(assetInfo.assetId, assetInfo.serviceName); + return true; + } + + // 如果没有匹配到路由,尝试使用默认的资产管理 + console.warn('[模块未开通] 未匹配到路由,使用默认资产管理'); + showAssetSubscribeDialog('696b4acd1be1c8b76c4b4c15', '资产管理'); + return true; +} diff --git a/src/utils/request.ts b/src/utils/request.ts index cad63b3..5ca7d7a 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -3,6 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'; import { Session } from '/@/utils/storage'; import qs from 'qs'; import { getChangedFields } from '/@/utils/diffUtils'; +import { handleModuleNotEnabled } from '/@/utils/assetSubscribe'; // 标记是否正在处理 token 过期,避免重复弹窗 let isHandlingTokenExpired = false; @@ -156,8 +157,20 @@ const responseInterceptor = (response: AxiosResponse) => { return Promise.reject(new Error('登录状态已过期')); } - // 业务逻辑错误处理 - if (code !== undefined && code !== 0 && code !== 200) { + // 处理模块未开通错误 (403) + // 跳过资产SKU查询接口,避免弹窗内部请求触发循环 + const requestUrl = response.config.url || ''; + if (code === 402 && !requestUrl.includes('/assets/asset/sku/')) { + // 获取当前路由路径 + const currentPath = window.location.hash.replace('#', '') || window.location.pathname; + console.log('[request.ts] 检测到403错误,当前路径:', currentPath); + handleModuleNotEnabled(currentPath); + // 直接返回,不再显示错误消息 + return Promise.reject(new Error('模块未开通')); + } + + // 业务逻辑错误处理(排除403,因为上面已处理) + if (code !== undefined && code !== 0 && code !== 200 && code !== 403) { const errorMsg = message || `请求失败(${code})`; showErrorMessage(errorMsg); return Promise.reject(new Error(errorMsg)); @@ -189,10 +202,21 @@ const responseErrorHandler = (error: any) => { const responseMessage = error.response.data?.message; // 处理 HTTP 错误状态 + const requestUrl = error.response.config?.url || ''; switch (httpStatus) { case 401: handleTokenExpired(); break; + case 402: + // 模块未开通处理,跳过SKU相关接口避免循环 + if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) { + const currentPath = window.location.hash.replace('#', '') || window.location.pathname; + console.log('[responseErrorHandler] 检测到HTTP 402错误,当前路径:', currentPath); + handleModuleNotEnabled(currentPath); + return Promise.reject(new Error('模块未开通')); + } + showErrorMessage(responseMessage || '服务未开通'); + break; case 403: showErrorMessage(responseMessage || '没有权限访问该资源'); break; From af17d81422477e04cafc0be830781ad464746f88 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Wed, 21 Jan 2026 14:02:42 +0800 Subject: [PATCH 27/31] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E5=8A=9F=E8=83=BD,=E5=B0=86=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E6=A8=A1=E5=BC=8F=E6=94=B9=E4=B8=BA=E5=A4=96=E9=83=A8?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E8=B7=B3=E8=BD=AC=E6=A8=A1=E5=BC=8F,?= =?UTF-8?q?=E7=A7=BB=E9=99=A4AssetSubscribeDialog=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=8F=8A=E5=85=B6=E5=9C=A8App.vue=E4=B8=AD=E7=9A=84=E5=BC=95?= =?UTF-8?q?=E7=94=A8,=E4=BF=AE=E6=94=B9assetSubscribe=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB=E5=B0=86showAssetSubscribeDialog=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=BAredirectToSubscribePage=E6=96=B9?= =?UTF-8?q?=E6=B3=95,=E5=BD=93=E6=A3=80=E6=B5=8B=E5=88=B0402=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=A0=81=E6=97=B6=E7=9B=B4=E6=8E=A5=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E5=88=B0=E5=A4=96=E9=83=A8=E5=BC=80=E9=80=9A=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=B9=B6=E6=90=BA=E5=B8=A6=E8=B5=84=E4=BA=A7ID=E5=92=8C?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=9C=B0=E5=9D=80=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 1 + public/web/subscribe.html | 580 ++++++++++++++++++++++++ src/App.vue | 11 +- src/components/assetSubscribe/index.vue | 277 ----------- src/utils/assetSubscribe.ts | 46 +- 5 files changed, 596 insertions(+), 319 deletions(-) create mode 100644 public/web/subscribe.html delete mode 100644 src/components/assetSubscribe/index.vue diff --git a/.env.development b/.env.development index 5fb1b59..4add404 100644 --- a/.env.development +++ b/.env.development @@ -6,3 +6,4 @@ VITE_API_URL = 'http://192.168.3.200:8808/' # VITE_API_URL = 'http://192.168.3.11:8808/' + diff --git a/public/web/subscribe.html b/public/web/subscribe.html new file mode 100644 index 0000000..3037141 --- /dev/null +++ b/public/web/subscribe.html @@ -0,0 +1,580 @@ + + + + + + 服务开通 - 智能营销服务平台 + + + + + + + + + + +
+ + + +
+
+

正在加载套餐信息...

+
+ + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/App.vue b/src/App.vue index 61073d9..c06763c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,12 +4,6 @@ - - @@ -25,12 +19,10 @@ import setIntroduction from '/@/utils/setIconfont'; import LockScreen from '/@/layout/lockScreen/index.vue'; import Setings from '/@/layout/navBars/breadcrumb/setings.vue'; import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue'; -import AssetSubscribeDialog from '/@/components/assetSubscribe/index.vue'; -import { assetSubscribeState } from '/@/utils/assetSubscribe'; export default defineComponent({ name: 'app', - components: { LockScreen, Setings, CloseFull, AssetSubscribeDialog }, + components: { LockScreen, Setings, CloseFull }, setup() { const { proxy } = getCurrentInstance(); const setingsRef = ref(); @@ -97,7 +89,6 @@ export default defineComponent({ themeConfig, setingsRef, getGlobalComponentSize, - assetSubscribeState, ...toRefs(state), }; }, diff --git a/src/components/assetSubscribe/index.vue b/src/components/assetSubscribe/index.vue deleted file mode 100644 index c4ce99d..0000000 --- a/src/components/assetSubscribe/index.vue +++ /dev/null @@ -1,277 +0,0 @@ - - - - - diff --git a/src/utils/assetSubscribe.ts b/src/utils/assetSubscribe.ts index 1df0289..27fead5 100644 --- a/src/utils/assetSubscribe.ts +++ b/src/utils/assetSubscribe.ts @@ -1,4 +1,5 @@ -import { ref } from 'vue'; +// 开通页面地址(public/web/subscribe.html) +const SUBSCRIBE_PAGE_URL = '/web/subscribe.html'; // 路由路径与 assetId 的映射关系 const ROUTE_ASSET_MAP: Record = { @@ -10,21 +11,8 @@ const ROUTE_ASSET_MAP: Record // 聚合电商业务(资产管理) '/assets': { assetId: '696b4acd1be1c8b76c4b4c15', serviceName: '资产管理' }, - - // 订单 - // '/order': { assetId: '696b4acd1be1c8b76c4b4c15', serviceName: '资产管理' }, - - // AI数字人 - // '/digitalHuman': { assetId: '696f421205e496ba4ccbe662', serviceName: 'AI客服' }, }; -// 当前弹窗状态(响应式,供组件使用) -export const assetSubscribeState = ref({ - visible: false, - assetId: '', - serviceName: '', -}); - /** * 根据路由路径获取对应的 assetId 和服务名称 */ @@ -45,26 +33,20 @@ export function getAssetInfoByRoute(routePath: string): { assetId: string; servi } /** - * 显示服务开通弹窗 + * 跳转到外部开通页面 + * @param assetId 资产ID */ -export function showAssetSubscribeDialog(assetId: string, serviceName: string) { - console.log('[showAssetSubscribeDialog] 显示弹窗:', { assetId, serviceName }); - console.log('[showAssetSubscribeDialog] 修改前状态:', JSON.stringify(assetSubscribeState.value)); - assetSubscribeState.value.visible = true; - assetSubscribeState.value.assetId = assetId; - assetSubscribeState.value.serviceName = serviceName; - console.log('[showAssetSubscribeDialog] 修改后状态:', JSON.stringify(assetSubscribeState.value)); +export function redirectToSubscribePage(assetId: string) { + // 当前页面地址作为返回地址 + const returnUrl = encodeURIComponent(window.location.href); + // 构建跳转URL + const url = `${SUBSCRIBE_PAGE_URL}?assetId=${assetId}&returnUrl=${returnUrl}`; + console.log('[redirectToSubscribePage] 跳转到开通页面:', url); + window.location.href = url; } /** - * 关闭服务开通弹窗 - */ -export function closeAssetSubscribeDialog() { - assetSubscribeState.value.visible = false; -} - -/** - * 处理 403 错误码(模块未开通) + * 处理 402 错误码(模块未开通) */ export function handleModuleNotEnabled(routePath: string): boolean { console.log('[模块未开通] 当前路由路径:', routePath); @@ -72,12 +54,12 @@ export function handleModuleNotEnabled(routePath: string): boolean { console.log('[模块未开通] 匹配到的资产信息:', assetInfo); if (assetInfo) { - showAssetSubscribeDialog(assetInfo.assetId, assetInfo.serviceName); + redirectToSubscribePage(assetInfo.assetId); return true; } // 如果没有匹配到路由,尝试使用默认的资产管理 console.warn('[模块未开通] 未匹配到路由,使用默认资产管理'); - showAssetSubscribeDialog('696b4acd1be1c8b76c4b4c15', '资产管理'); + redirectToSubscribePage('696b4acd1be1c8b76c4b4c15'); return true; } From b9795c2cc43e0cd161a4c403aa4aee954ec4994a Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Wed, 21 Jan 2026 16:27:54 +0800 Subject: [PATCH 28/31] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E9=A1=B5=E9=9D=A2,=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=B1=BB=E5=9E=8B=E9=80=89=E6=8B=A9=E5=8A=9F?= =?UTF-8?q?=E8=83=BD,=E6=94=AF=E6=8C=81=E5=9C=A8=E5=BC=80=E9=80=9A?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=97=B6=E9=80=89=E6=8B=A9=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=B1=BB=E5=9E=8B,=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BC=80=E9=80=9A=E6=88=90=E5=8A=9F=E5=90=8E?= =?UTF-8?q?=E7=9A=84=E8=B7=B3=E8=BD=AC=E9=80=BB=E8=BE=91,=E5=A2=9E?= =?UTF-8?q?=E5=8A=A05=E7=A7=92=E5=86=85=E9=98=B2=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=A7=A6=E5=8F=91402=E7=8A=B6=E6=80=81=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E4=BF=9D=E6=8A=A4=E6=9C=BA=E5=88=B6,=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E5=BC=80=E9=80=9A=E5=AE=8C=E6=88=90=E5=90=8E=E7=AB=8B=E5=8D=B3?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=8F=88=E8=A7=A6=E5=8F=91=E6=9C=AA=E5=BC=80?= =?UTF-8?q?=E9=80=9A=E6=8F=90=E7=A4=BA=E7=9A=84=E5=BE=AA=E7=8E=AF=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/web/subscribe.html | 202 ++++++++++++++++++++++++++++++++++---- src/utils/request.ts | 9 ++ 2 files changed, 190 insertions(+), 21 deletions(-) diff --git a/public/web/subscribe.html b/public/web/subscribe.html index 3037141..a584e41 100644 --- a/public/web/subscribe.html +++ b/public/web/subscribe.html @@ -86,6 +86,64 @@ margin: 0; } + .section-title { + font-size: 0.875rem; + font-weight: 600; + color: #475569; + margin-bottom: 12px; + } + + .type-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + margin-bottom: 24px; + } + + @media (max-width: 600px) { + .type-grid { + grid-template-columns: 1fr; + } + } + + .type-card { + background: #fff; + border: 2px solid #e2e8f0; + border-radius: 10px; + padding: 14px 16px; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; + position: relative; + } + + .type-card:hover { + border-color: #10b981; + background: #f0fdf4; + } + + .type-card.selected { + border-color: #10b981; + background: #ecfdf5; + } + + .type-card.selected::after { + content: ''; + position: absolute; + top: 8px; + right: 8px; + width: 18px; + height: 18px; + background: #10b981 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E") center/12px no-repeat; + border-radius: 50%; + } + + .type-card .type-name { + font-size: 0.95rem; + font-weight: 500; + color: #334155; + } + .sku-grid { display: grid; grid-template-columns: repeat(2, 1fr); @@ -292,7 +350,7 @@