From 49c9eff61882f1f7d519397ccfdd90cdd8735cb8 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 04 八月 2026 13:12:21 +0800
Subject: [PATCH] feat(aftersales): 售后工单支持附件上传和显示
---
src/views/aftersales/ticket/data.ts | 12 +++
src/api/mes/pro/route/process/index.ts | 2
src/views/mes/pro/workorder/modules/process-form.vue | 3
.claude/settings.local.json | 6 +
src/views/mes/process-design/route/modules/process-list.vue | 4 -
src/views/mes/pro/workorder/data.ts | 11 --
src/api/mes/pro/workorder/process/index.ts | 8 +
src/views/aftersales/ticket/modules/form.vue | 8 ++
src/views/ai/knowledge/document/form/modules/split-step.vue | 3
src/api/aftersales/ticket.ts | 4 +
src/views/mes/pro/workorder/modules/process-list.vue | 5
src/views/aftersales/ticket/detail.vue | 15 +++
src/views/mes/md/item/components/select.vue | 103 +++++++++++++++++--------
src/views/mes/process-design/route/data.ts | 8 +-
14 files changed, 133 insertions(+), 59 deletions(-)
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 3d8e4b9..b82122e 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -22,5 +22,9 @@
"Bash(node lib/script/postinstall.js)",
"Bash(mvn compile *)"
]
- }
+ },
+ "enabledMcpjsonServers": [
+ "idea"
+ ],
+ "enableAllProjectMcpServers": true
}
diff --git a/src/api/aftersales/ticket.ts b/src/api/aftersales/ticket.ts
index be94b90..36120d0 100644
--- a/src/api/aftersales/ticket.ts
+++ b/src/api/aftersales/ticket.ts
@@ -51,6 +51,10 @@
createTime?: string;
updater?: string;
updateTime?: string;
+ /** 闄勪欢 blobId 鍒楄〃锛堜繚瀛樻椂浼犲叆锛� */
+ blobIds?: number[];
+ /** 闄勪欢鍒楄〃锛堣鎯呮椂杩斿洖锛� */
+ attachmentList?: { id: number; name?: string; url?: string }[];
items?: TicketItem[];
}
}
diff --git a/src/api/mes/pro/route/process/index.ts b/src/api/mes/pro/route/process/index.ts
index 3e5c2f8..c207284 100644
--- a/src/api/mes/pro/route/process/index.ts
+++ b/src/api/mes/pro/route/process/index.ts
@@ -30,6 +30,8 @@
outputItemId?: number;
outputItemCode?: string;
outputItemName?: string;
+ outputItemIds?: number[];
+ outputItemNames?: string;
remark?: string;
bomItems?: ProcessBomItem[];
createTime?: Date;
diff --git a/src/api/mes/pro/workorder/process/index.ts b/src/api/mes/pro/workorder/process/index.ts
index 4444ca0..cc3630b 100644
--- a/src/api/mes/pro/workorder/process/index.ts
+++ b/src/api/mes/pro/workorder/process/index.ts
@@ -29,9 +29,11 @@
keyFlag?: boolean; // 鏄惁鍏抽敭宸ュ簭
checkFlag?: boolean; // 鏄惁璐ㄦ宸ュ簭
backflushFlag?: boolean; // 鏄惁鍊掑啿
- outputItemId?: number; // 浜у嚭浜у搧鐗╂枡缂栧彿
- outputItemCode?: string; // 浜у嚭浜у搧鐗╂枡缂栫爜
- outputItemName?: string; // 浜у嚭浜у搧鐗╂枡鍚嶇О
+ outputItemId?: number; // 浜у嚭浜у搧鐗╂枡缂栧彿锛堢涓�涓紝鍚戝悗鍏煎锛�
+ outputItemCode?: string; // 浜у嚭浜у搧鐗╂枡缂栫爜锛堢涓�涓級
+ outputItemName?: string; // 浜у嚭浜у搧鐗╂枡鍚嶇О锛堢涓�涓級
+ outputItemIds?: number[]; // 浜у嚭浜у搧鐗╂枡缂栧彿鍒楄〃
+ outputItemNames?: string; // 浜у嚭浜у搧鐗╂枡鍚嶇О锛堥�楀彿鎷兼帴锛�
bomItems?: ProcessBomItem[]; // 宸ュ簭鎶曟枡BOM鏄庣粏
remark?: string; // 澶囨敞
}
diff --git a/src/views/aftersales/ticket/data.ts b/src/views/aftersales/ticket/data.ts
index 226e4a6..15c83cc 100644
--- a/src/views/aftersales/ticket/data.ts
+++ b/src/views/aftersales/ticket/data.ts
@@ -182,6 +182,18 @@
rows: 3,
},
},
+ {
+ fieldName: 'blobIds',
+ label: '闄勪欢',
+ component: 'FileUpload',
+ componentProps: {
+ valueKey: 'id',
+ maxNumber: 10,
+ multiple: true,
+ disabled: readonly,
+ },
+ formItemClass: 'col-span-3',
+ },
];
}
diff --git a/src/views/aftersales/ticket/detail.vue b/src/views/aftersales/ticket/detail.vue
index 09eb0b4..b9a6d7e 100644
--- a/src/views/aftersales/ticket/detail.vue
+++ b/src/views/aftersales/ticket/detail.vue
@@ -241,6 +241,21 @@
</div>
</Card>
+ <!-- 闄勪欢 -->
+ <Card v-if="ticket?.attachmentList?.length" title="闄勪欢" class="mb-4">
+ <div class="flex flex-wrap gap-3">
+ <a
+ v-for="file in ticket.attachmentList"
+ :key="file.id"
+ :href="file.url"
+ target="_blank"
+ class="text-blue-500 hover:underline"
+ >
+ {{ file.name || '闄勪欢-' + file.id }}
+ </a>
+ </div>
+ </Card>
+
<!-- 闂鍒ゅ畾琛ㄥ崟 -->
<Card v-if="showClassifyInput" title="闂鍒ゅ畾" class="mb-4">
<div class="flex flex-wrap items-center gap-3">
diff --git a/src/views/aftersales/ticket/modules/form.vue b/src/views/aftersales/ticket/modules/form.vue
index 3a4ba1c..bdb3fab 100644
--- a/src/views/aftersales/ticket/modules/form.vue
+++ b/src/views/aftersales/ticket/modules/form.vue
@@ -145,6 +145,14 @@
items.value = formData.value?.items || [];
await refreshItemsGrid();
await formApi.setValues(formData.value as Record<string, unknown>);
+ // 鍥炴樉闄勪欢
+ if (formData.value?.attachmentList?.length) {
+ const blobIds = formData.value.attachmentList.map((item) => ({
+ uid: String(item.id), name: item.name || '', url: item.url || '',
+ status: 'done', id: item.id,
+ }));
+ await formApi.setFieldValue('blobIds', blobIds);
+ }
if (formData.value?.saleOrderId) {
const order = await getSaleOrder(formData.value.saleOrderId);
orderItems.value = (order as Record<string, unknown>).items as ErpSaleOrderApi.SaleOrderItem[] || [];
diff --git a/src/views/ai/knowledge/document/form/modules/split-step.vue b/src/views/ai/knowledge/document/form/modules/split-step.vue
index b03cc47..a96285d 100644
--- a/src/views/ai/knowledge/document/form/modules/split-step.vue
+++ b/src/views/ai/knowledge/document/form/modules/split-step.vue
@@ -57,8 +57,9 @@
splitLoading.value = true;
try {
// 璋冪敤鍚庣鍒嗘鎺ュ彛锛岃幏鍙栨枃妗g殑鍒嗘鍐呭銆佸瓧绗︽暟鍜� Token 鏁�
+ const url = typeof file.url === 'string' ? file.url : file.url?.url;
file.segments = await splitContent(
- file.url,
+ url,
modelData.value.segmentMaxTokens,
);
} catch (error) {
diff --git a/src/views/mes/md/item/components/select.vue b/src/views/mes/md/item/components/select.vue
index d157aff..6a45bdb 100644
--- a/src/views/mes/md/item/components/select.vue
+++ b/src/views/mes/md/item/components/select.vue
@@ -17,7 +17,8 @@
defineProps<{
allowClear?: boolean;
disabled?: boolean;
- modelValue?: number;
+ modelValue?: number | number[];
+ multiple?: boolean;
placeholder?: string;
productIds?: number[];
}>(),
@@ -25,53 +26,70 @@
allowClear: true,
disabled: false,
modelValue: undefined,
+ multiple: false,
placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
productIds: () => [],
},
);
const emit = defineEmits<{
- change: [item: MesMdItemApi.Item | undefined];
- 'update:modelValue': [value: number | undefined];
+ change: [items: MesMdItemApi.Item | MesMdItemApi.Item[] | undefined];
+ 'update:modelValue': [value: number | number[] | undefined];
}>();
-const attrs = useAttrs(); // 閫忎紶灞炴��
-const dialogRef = ref<InstanceType<typeof MdItemSelectDialog>>(); // 鐗╂枡閫夋嫨寮圭獥
-const hovering = ref(false); // 鏄惁鎮仠
-const selectedItem = ref<MesMdItemApi.Item>(); // 褰撳墠閫変腑鐗╂枡
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof MdItemSelectDialog>>();
+const hovering = ref(false);
+const selectedItems = ref<MesMdItemApi.Item[]>([]);
-const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 閫夋嫨鍣ㄥ睍绀哄悕绉�
+const displayLabel = computed(() => {
+ if (selectedItems.value.length === 0) return '';
+ return selectedItems.value.map(item => item.name ?? '').join('銆�');
+});
const showClear = computed(
() =>
props.allowClear &&
!props.disabled &&
hovering.value &&
- props.modelValue !== null,
+ (props.multiple
+ ? Array.isArray(props.modelValue) && props.modelValue.length > 0
+ : props.modelValue != null),
);
/** 鏍规嵁鐗╂枡缂栧彿鍥炴樉閫夋嫨鍣� */
-async function resolveItemById(id: number | undefined) {
- if (!id) {
- selectedItem.value = undefined;
+async function resolveItemsById(values: number | number[] | undefined) {
+ if (values == null || (Array.isArray(values) && values.length === 0)) {
+ selectedItems.value = [];
return;
}
- if (selectedItem.value?.id === id) {
+ const ids = Array.isArray(values) ? values : [values];
+ // 宸插叏閮ㄥ洖鏄惧垯璺宠繃
+ if (
+ ids.length === selectedItems.value.length &&
+ ids.every((id, i) => selectedItems.value[i]?.id === id)
+ ) {
return;
}
- selectedItem.value = await getItem(id);
+ const items = await Promise.all(ids.map(id => getItem(id)));
+ selectedItems.value = items.filter(Boolean) as MesMdItemApi.Item[];
}
watch(
() => props.modelValue,
(value) => {
- resolveItemById(value);
+ resolveItemsById(value);
},
{ immediate: true },
);
/** 娓呯┖宸查�夌墿鏂� */
function clearSelected() {
- selectedItem.value = undefined;
- emit('update:modelValue', undefined);
- emit('change', undefined);
+ selectedItems.value = [];
+ if (props.multiple) {
+ emit('update:modelValue', []);
+ emit('change', []);
+ } else {
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+ }
}
/** 鎵撳紑鐗╂枡閫夋嫨寮圭獥 */
@@ -85,24 +103,33 @@
clearSelected();
return;
}
- const selectedIds = (
- props.modelValue === null ? [] : [props.modelValue]
- ) as number[];
+ const selectedIds: number[] = props.multiple
+ ? (props.modelValue as number[] | undefined) ?? []
+ : props.modelValue != null
+ ? [props.modelValue as number]
+ : [];
dialogRef.value?.open(selectedIds, {
- multiple: false,
+ multiple: props.multiple,
productIds: props.productIds,
});
}
/** 鍥炲~閫変腑鐨勭墿鏂� */
function handleSelected(rows: MesMdItemApi.Item[]) {
- const item = rows[0];
- if (!item) {
- return;
+ if (props.multiple) {
+ selectedItems.value = rows;
+ const ids = rows.map(r => r.id).filter(Boolean) as number[];
+ emit('update:modelValue', ids);
+ emit('change', rows);
+ } else {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItems.value = [item];
+ emit('update:modelValue', item.id);
+ emit('change', item);
}
- selectedItem.value = item;
- emit('update:modelValue', item.id);
- emit('change', item);
}
</script>
@@ -115,13 +142,21 @@
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
- <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItems.length > 0 ? undefined : false">
<template #title>
- <div v-if="selectedItem" class="leading-6">
- <div>缂栫爜锛歿{ selectedItem.code || '-' }}</div>
- <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
- <div>瑙勬牸锛歿{ selectedItem.specification || '-' }}</div>
- <div>鍗曚綅锛歿{ selectedItem.unitMeasureName || '-' }}</div>
+ <div v-if="selectedItems.length === 1" class="leading-6">
+ <div>缂栫爜锛歿{ selectedItems[0].code || '-' }}</div>
+ <div>鍚嶇О锛歿{ selectedItems[0].name || '-' }}</div>
+ <div>瑙勬牸锛歿{ selectedItems[0].specification || '-' }}</div>
+ <div>鍗曚綅锛歿{ selectedItems[0].unitMeasureName || '-' }}</div>
+ </div>
+ <div v-else-if="selectedItems.length > 1" class="leading-6">
+ <div v-for="item in selectedItems" :key="item.id" class="mb-2 border-b border-gray-100 pb-1 last:border-b-0 last:pb-0">
+ <div>缂栫爜锛歿{ item.code || '-' }}</div>
+ <div>鍚嶇О锛歿{ item.name || '-' }}</div>
+ <div>瑙勬牸锛歿{ item.specification || '-' }}</div>
+ <div>鍗曚綅锛歿{ item.unitMeasureName || '-' }}</div>
+ </div>
</div>
</template>
<Input
diff --git a/src/views/mes/pro/workorder/data.ts b/src/views/mes/pro/workorder/data.ts
index d01a104..af895bd 100644
--- a/src/views/mes/pro/workorder/data.ts
+++ b/src/views/mes/pro/workorder/data.ts
@@ -686,14 +686,9 @@
slots: { default: 'backflushFlag' },
},
{
- field: 'outputItemCode',
- title: '浜у嚭鐗╂枡缂栫爜',
- width: 120,
- },
- {
- field: 'outputItemName',
- title: '浜у嚭鐗╂枡鍚嶇О',
- width: 120,
+ field: 'outputItemNames',
+ title: '浜у嚭鐗╂枡',
+ width: 160,
},
{
field: 'remark',
diff --git a/src/views/mes/pro/workorder/modules/process-form.vue b/src/views/mes/pro/workorder/modules/process-form.vue
index 7cfb93c..bdbdcaa 100644
--- a/src/views/mes/pro/workorder/modules/process-form.vue
+++ b/src/views/mes/pro/workorder/modules/process-form.vue
@@ -170,10 +170,11 @@
defaultValue: true,
},
{
- fieldName: 'outputItemId',
+ fieldName: 'outputItemIds',
label: '浜у嚭鐗╂枡',
component: MdItemSelect,
componentProps: {
+ multiple: true,
placeholder: '璇烽�夋嫨浜у嚭鐗╂枡',
},
},
diff --git a/src/views/mes/pro/workorder/modules/process-list.vue b/src/views/mes/pro/workorder/modules/process-list.vue
index fd2a7e7..283424d 100644
--- a/src/views/mes/pro/workorder/modules/process-list.vue
+++ b/src/views/mes/pro/workorder/modules/process-list.vue
@@ -156,9 +156,8 @@
keyFlag: item.keyFlag,
checkFlag: item.checkFlag,
backflushFlag: item.backflushFlag,
- outputItemId: item.outputItemId,
- outputItemCode: item.outputItemCode,
- outputItemName: item.outputItemName,
+ outputItemIds: item.outputItemIds,
+ outputItemNames: item.outputItemNames,
remark: item.remark,
bomItems: item.bomItems,
}));
diff --git a/src/views/mes/process-design/route/data.ts b/src/views/mes/process-design/route/data.ts
index 3c76624..941a2be 100644
--- a/src/views/mes/process-design/route/data.ts
+++ b/src/views/mes/process-design/route/data.ts
@@ -248,10 +248,11 @@
rules: z.boolean().default(true),
},
{
- fieldName: "outputItemId",
+ fieldName: "outputItemIds",
label: "浜у嚭浜у搧",
component: markRaw(MdItemSelect),
componentProps: {
+ multiple: true,
placeholder: "鐣欑┖鍒欓粯璁や负宸ュ崟鎴愬搧",
},
formItemClass: "col-span-2",
@@ -336,10 +337,9 @@
},
},
{
- field: "outputItemName",
+ field: "outputItemNames",
title: "浜у嚭浜у搧",
- width: 140,
- slots: { default: "outputItemName" },
+ width: 160,
},
{ field: "prepareTime", title: "鍑嗗鏃堕棿(鍒�)", width: 110 },
{ field: "waitTime", title: "绛夊緟鏃堕棿(鍒�)", width: 110 },
diff --git a/src/views/mes/process-design/route/modules/process-list.vue b/src/views/mes/process-design/route/modules/process-list.vue
index e6a99c1..192cb5c 100644
--- a/src/views/mes/process-design/route/modules/process-list.vue
+++ b/src/views/mes/process-design/route/modules/process-list.vue
@@ -110,10 +110,6 @@
<span>{{ row.colorCode }}</span>
</div>
</template>
- <template #outputItemName="{ row }">
- <span v-if="row.outputItemName">{{ row.outputItemName }}</span>
- <span v-else class="text-gray-400">-</span>
- </template>
<template #actions="{ row }">
<TableAction
:actions="[
--
Gitblit v1.9.3