From 0127fd119fe2a349fbfee92b375a5680e10d3275 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期四, 27 八月 2026 14:30:33 +0800
Subject: [PATCH] feat(erp): 销售订单审批接入协同办公前端支持
---
src/api/erp/sale/order/index.ts | 16 ++
src/views/erp/sale/order/index.vue | 61 +++++++----
src/views/erp/sale/order/modules/item-form.vue | 4
src/views/bpm/processInstance/detail/index.vue | 12 ++
src/views/erp/sale/order/detail/index.vue | 154 ++++++++++++++++++++++++++++++
src/views/erp/sale/order/data.ts | 4
6 files changed, 221 insertions(+), 30 deletions(-)
diff --git a/src/api/erp/sale/order/index.ts b/src/api/erp/sale/order/index.ts
index 1e6366c..3961aca 100644
--- a/src/api/erp/sale/order/index.ts
+++ b/src/api/erp/sale/order/index.ts
@@ -16,6 +16,7 @@
totalPrice: number; // 鍚堣閲戦锛屽崟浣嶏細鍏�
status: number; // 鐘舵��
remark: string; // 澶囨敞
+ processInstanceId?: string; // BPM 娴佺▼瀹炰緥缂栧彿锛堟彁浜ゅ崗鍚屽姙鍏鎵瑰悗鐢熸垚锛�
outCount: number; // 閿�鍞嚭搴撴暟閲�
outStatus?: number; // 鍑哄簱鐘舵�侊細0-鏈嚭搴擄紝1-閮ㄥ垎鍑哄簱锛�2-鍏ㄩ儴鍑哄簱
attachmentList?: { id: number; name?: string; url?: string }[]; // 闄勪欢鍒楄〃
@@ -96,13 +97,20 @@
return requestClient.put('/erp/sale-order/update', data);
}
-/** 鏇存柊閿�鍞鍗曠殑鐘舵�� */
-export function updateSaleOrderStatus(id: number, status: number) {
- return requestClient.put('/erp/sale-order/update-status', null, {
- params: { id, status },
+/** 鎻愪氦閿�鍞鍗曞鎵癸紙鍗忓悓鍔炲叕锛� */
+export function submitSaleOrder(id: number, processDefinitionKey: string) {
+ return requestClient.post('/erp/sale-order/submit', null, {
+ params: { id, processDefinitionKey },
});
}
+/** 鑾峰彇閿�鍞鍗曞鎵规祦绋嬪畾涔夊垪琛� */
+export function getSaleOrderApproveProcessList() {
+ return requestClient.get<
+ { id: string; key: string; name: string }[]
+ >('/erp/sale-order/approve-process-list');
+}
+
/** 鍒犻櫎閿�鍞鍗� */
export function deleteSaleOrder(ids: number[]) {
return requestClient.delete('/erp/sale-order/delete', {
diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue
index f4d8288..4ac3c9e 100644
--- a/src/views/bpm/processInstance/detail/index.vue
+++ b/src/views/bpm/processInstance/detail/index.vue
@@ -4,6 +4,8 @@
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
+import { useRoute } from 'vue-router';
+
import { Page, useVbenModal } from '@vben/common-ui';
import {
BpmFieldPermissionType,
@@ -41,11 +43,16 @@
defineOptions({ name: 'BpmProcessInstanceDetail' });
+const route = useRoute();
+
const props = defineProps<{
activityId?: string; // 娴佺▼娲诲姩缂栧彿锛岀敤浜庢妱閫佹煡鐪�
id: string; // 娴佺▼瀹炰緥鐨勭紪鍙�
taskId?: string; // 浠诲姟缂栧彿
}>();
+
+/** 鍙鏌ョ湅妯″紡锛氫粠涓氬姟妯″潡锛堝閿�鍞鍗曞鎵硅繘搴︼級璺宠浆鏃堕殣钘忓鎵规搷浣滄寜閽紝浠呮煡鐪� */
+const viewOnly = route.query.viewOnly === '1';
const processInstanceLoading = ref(false); // 娴佺▼瀹炰緥鐨勫姞杞戒腑
const processInstance = ref<BpmProcessInstanceApi.ProcessInstance>(); // 娴佺▼瀹炰緥
@@ -362,7 +369,7 @@
</div>
<template #actions>
- <div class="px-4">
+ <div v-if="!viewOnly" class="px-4">
<ProcessInstanceOperationButton
ref="operationButtonRef"
:process-instance="processInstance"
@@ -374,6 +381,9 @@
@success="refresh"
/>
</div>
+ <div v-else class="px-4 py-2 text-sm text-muted-foreground">
+ <span>鍙鏌ョ湅妯″紡锛氬鎵规搷浣滆鍦ㄣ�屽崗鍚屽姙鍏� 鈫� 寰呭姙浠诲姟銆嶄腑澶勭悊</span>
+ </div>
</template>
</Card>
<!-- 鎵撳嵃瀵硅瘽妗� -->
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index b61597e..ffec496 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -378,8 +378,10 @@
component: 'Select',
componentProps: {
options: [
- { label: '鏈鏍�', value: 10 },
+ { label: '鏈彁浜�', value: 0 },
+ { label: '瀹℃壒涓�', value: 15 },
{ label: '宸插鏍�', value: 20 },
+ { label: '宸查┏鍥�', value: 35 },
{ label: '宸蹭綔搴�', value: 30 },
],
placeholder: '璇烽�夋嫨鐘舵��',
diff --git a/src/views/erp/sale/order/detail/index.vue b/src/views/erp/sale/order/detail/index.vue
new file mode 100644
index 0000000..673f4a4
--- /dev/null
+++ b/src/views/erp/sale/order/detail/index.vue
@@ -0,0 +1,154 @@
+<script lang="ts" setup>
+import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, onMounted, ref } from 'vue';
+
+import { ContentWrap } from '@vben/common-ui';
+import { DICT_TYPE } from '@vben/constants';
+import {
+ erpCountInputFormatter,
+ erpPriceInputFormatter,
+ formatDateTime,
+} from '@vben/utils';
+
+import { Spin, Tag } from 'ant-design-vue';
+
+import { getSaleOrder } from '#/api/erp/sale/order';
+import { DictTag } from '#/components/dict-tag';
+import { useDescription } from '#/components/description';
+
+import ItemForm from '../modules/item-form.vue';
+
+defineOptions({ name: 'ErpSaleOrderDetail' });
+
+const props = defineProps<{ id: string }>();
+
+const loading = ref(false);
+const formData = ref<ErpSaleOrderApi.SaleOrder>();
+
+/** 鏂囨湰瀛楁绌哄�煎崰浣� */
+function textRender(val: unknown) {
+ return val === 0 || val ? String(val) : '-';
+}
+
+/** 鍑哄簱鐘舵�佹覆鏌� */
+function renderOutStatus(val?: number) {
+ const map: Record<number, { text: string; color: string }> = {
+ 0: { text: '鏈嚭搴�', color: 'default' },
+ 1: { text: '宸查鐣�', color: 'processing' },
+ 2: { text: '閮ㄥ垎鍑哄簱', color: 'warning' },
+ 3: { text: '鍏ㄩ儴鍑哄簱', color: 'success' },
+ };
+ const item = map[val ?? 0]!;
+ return h(Tag, { color: item.color }, () => item.text);
+}
+
+/** 鐢熶骇鐘舵�佹覆鏌� */
+function renderProductionStatus(val?: number) {
+ if (formData.value?.needProduction !== 1) {
+ return h(Tag, { color: 'default' }, () => '鏃犻渶鐢熶骇');
+ }
+ const map: Record<number, { text: string; color: string }> = {
+ 0: { text: '鏈畬鎴�', color: 'warning' },
+ 1: { text: '宸插畬鎴�', color: 'success' },
+ };
+ const item = map[val ?? 0]!;
+ return h(Tag, { color: item.color }, () => item.text);
+}
+
+const [Descriptions] = useDescription({
+ bordered: true,
+ column: 2,
+ schema: [
+ { label: '璁㈠崟鍗曞彿', field: 'no', render: textRender },
+ { label: '瀹㈡埛鍚嶇О', field: 'customerName', render: textRender },
+ { label: '鍏宠仈鍚堝悓', field: 'contractNo', render: textRender },
+ {
+ label: '璁㈠崟鏃堕棿',
+ field: 'orderTime',
+ render: (val) => formatDateTime(val) as string,
+ },
+ {
+ label: '鍚堣鏁伴噺',
+ field: 'totalCount',
+ render: (val) => erpCountInputFormatter(val) || '-',
+ },
+ {
+ label: '鍚堣閲戦',
+ field: 'totalPrice',
+ render: (val) => erpPriceInputFormatter(val) || '-',
+ },
+ {
+ label: '浼樻儬閲戦',
+ field: 'discountPrice',
+ render: (val) => erpPriceInputFormatter(val) || '-',
+ },
+ {
+ label: '瀹氶噾閲戦',
+ field: 'depositPrice',
+ render: (val) => erpPriceInputFormatter(val) || '-',
+ },
+ {
+ label: '瀹℃壒鐘舵��',
+ field: 'status',
+ render: (val) =>
+ h(DictTag, { type: DICT_TYPE.ERP_AUDIT_STATUS, value: val }),
+ },
+ {
+ label: '鍑哄簱鐘舵��',
+ field: 'outStatus',
+ render: (val) => renderOutStatus(val as number),
+ },
+ {
+ label: '鐢熶骇鐘舵��',
+ field: 'productionStatus',
+ render: (val) => renderProductionStatus(val as number),
+ },
+ { label: '鍒涘缓浜�', field: 'creatorName', render: textRender },
+ {
+ label: '鍒涘缓鏃堕棿',
+ field: 'createTime',
+ render: (val) => formatDateTime(val) as string,
+ },
+ { label: '澶囨敞', field: 'remark', render: textRender },
+ ] as DescriptionItemSchema[],
+});
+
+onMounted(async () => {
+ try {
+ loading.value = true;
+ formData.value = await getSaleOrder(Number(props.id));
+ } finally {
+ loading.value = false;
+ }
+});
+</script>
+
+<template>
+ <ContentWrap class="m-2">
+ <Spin :spinning="loading" tip="鍔犺浇涓�...">
+ <div class="space-y-4">
+ <!-- 鍩烘湰淇℃伅 -->
+ <div class="rounded-lg border border-border bg-background p-4">
+ <div class="mb-4 flex items-center gap-2">
+ <span class="inline-block h-4 w-1 rounded bg-primary" />
+ <span class="text-base font-semibold">鍩烘湰淇℃伅</span>
+ </div>
+ <Descriptions :data="formData" />
+ </div>
+ <!-- 閿�鍞槑缁� -->
+ <div
+ v-if="formData?.items?.length"
+ class="rounded-lg border border-border bg-background p-4"
+ >
+ <div class="mb-4 flex items-center gap-2">
+ <span class="inline-block h-4 w-1 rounded bg-primary" />
+ <span class="text-base font-semibold">閿�鍞槑缁�</span>
+ </div>
+ <ItemForm :items="formData.items" :disabled="true" />
+ </div>
+ </div>
+ </Spin>
+ </ContentWrap>
+</template>
diff --git a/src/views/erp/sale/order/index.vue b/src/views/erp/sale/order/index.vue
index 6124ec7..925be2a 100644
--- a/src/views/erp/sale/order/index.vue
+++ b/src/views/erp/sale/order/index.vue
@@ -16,8 +16,9 @@
deleteSaleOrder,
enableSaleOrder,
exportSaleOrder,
+ getSaleOrderApproveProcessList,
getSaleOrderPage,
- updateSaleOrderStatus,
+ submitSaleOrder,
} from '#/api/erp/sale/order';
import { generateFromSaleOrder } from '#/api/mes/wm/salesnotice';
import { $t } from '#/locales';
@@ -79,21 +80,34 @@
}
}
-/** 瀹℃壒/鍙嶅鎵规搷浣� */
-async function handleUpdateStatus(
- row: ErpSaleOrderApi.SaleOrder,
- status: number,
-) {
+/** 鎻愪氦閿�鍞鍗曞鎵癸紙鍗忓悓鍔炲叕锛� */
+async function handleSubmit(row: ErpSaleOrderApi.SaleOrder) {
+ // 鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔�
+ const processList = await getSaleOrderApproveProcessList();
+ if (isEmpty(processList)) {
+ message.error('閿�鍞鍗曞鎵规祦绋嬫湭閰嶇疆锛岃鍏堝湪銆屽崗鍚屽姙鍏啋娴佺▼绠$悊銆嶄腑鍒涘缓骞跺彂甯冮攢鍞鍗曞鎵规祦绋�');
+ return;
+ }
const hideLoading = message.loading({
- content: `纭畾${status === 20 ? '瀹℃壒' : '鍙嶅鎵�'}璇ヨ鍗曞悧锛焋,
+ content: '姝e湪鎻愪氦瀹℃壒...',
duration: 0,
});
try {
- await updateSaleOrderStatus(row.id!, status);
- message.success(`${status === 20 ? '瀹℃壒' : '鍙嶅鎵�'}鎴愬姛`);
+ await submitSaleOrder(row.id!, processList[0].key);
+ message.success('鎻愪氦瀹℃壒鎴愬姛锛岃鍒板崗鍚屽姙鍏腑澶勭悊');
handleRefresh();
} finally {
hideLoading();
+ }
+}
+
+/** 鏌ョ湅瀹℃壒杩涘害锛堣烦杞崗鍚屽姙鍏祦绋嬭鎯咃紝鍙妯″紡锛屼笉鏄剧ず瀹℃壒鎿嶄綔锛� */
+function handleViewApproval(row: ErpSaleOrderApi.SaleOrder) {
+ if (row.processInstanceId) {
+ router.push({
+ name: 'BpmProcessInstanceDetail',
+ query: { id: row.processInstanceId, viewOnly: '1' },
+ });
}
}
@@ -278,8 +292,10 @@
<span v-else>-</span>
</template>
<template #status="{ row }">
- <Tag v-if="row.status === 10" color="warning">鏈鏍�</Tag>
+ <Tag v-if="row.status === 0" color="default">鏈彁浜�</Tag>
+ <Tag v-if="row.status === 15" color="processing">瀹℃壒涓�</Tag>
<Tag v-if="row.status === 20" color="success">宸插鏍�</Tag>
+ <Tag v-if="row.status === 35" color="warning">宸查┏鍥�</Tag>
<Tag v-if="row.status === 30" color="error">宸蹭綔搴�</Tag>
</template>
<template #needProduction="{ row }">
@@ -323,30 +339,27 @@
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['erp:sale-order:update'],
- ifShow: () => row.status === 10,
+ ifShow: () => row.status === 0 || row.status === 35,
onClick: handleEdit.bind(null, row),
},
{
- label: '瀹℃壒',
+ label: '鎻愪氦瀹℃壒',
type: 'link',
icon: ACTION_ICON.AUDIT,
auth: ['erp:sale-order:update-status'],
- ifShow: () => row.status === 10,
+ ifShow: () => row.status === 0 || row.status === 35,
popConfirm: {
- title: `纭瀹℃壒${row.no}鍚楋紵`,
- confirm: handleUpdateStatus.bind(null, row, 20),
+ title: `纭鎻愪氦${row.no}鐨勫鎵瑰悧锛熸彁浜ゅ悗瀹℃壒灏嗗湪鍗忓悓鍔炲叕涓畬鎴恅,
+ confirm: handleSubmit.bind(null, row),
},
},
{
- label: '鍙嶅鎵�',
+ label: '瀹℃壒杩涘害',
type: 'link',
- icon: ACTION_ICON.AUDIT,
- auth: ['erp:sale-order:update-status'],
- ifShow: () => row.status === 20,
- popConfirm: {
- title: `纭鍙嶅鎵�${row.no}鍚楋紵`,
- confirm: handleUpdateStatus.bind(null, row, 10),
- },
+ icon: 'lucide:file-clock',
+ auth: ['erp:sale-order:query'],
+ ifShow: () => row.status === 15 && !!row.processInstanceId,
+ onClick: handleViewApproval.bind(null, row),
},
{
label: '浣滃簾',
@@ -384,7 +397,7 @@
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:sale-order:delete'],
- ifShow: () => row.status === 10,
+ ifShow: () => row.status === 0 || row.status === 35,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),
confirm: handleDelete.bind(null, [row.id!]),
diff --git a/src/views/erp/sale/order/modules/item-form.vue b/src/views/erp/sale/order/modules/item-form.vue
index ffd3207..eea4f3c 100644
--- a/src/views/erp/sale/order/modules/item-form.vue
+++ b/src/views/erp/sale/order/modules/item-form.vue
@@ -63,6 +63,10 @@
minHeight: 250,
autoResize: true,
border: true,
+ // 鏄庣粏鍒楄緝澶氾紝鍚敤妯悜婊氬姩锛岄伩鍏嶅垪琚帇缂╁鑷存樉绀轰笉瀹屾暣锛堝崗鍚屽姙鍏鎵硅鎯呯瓑绐勫鍣ㄥ満鏅級
+ scroll: {
+ x: 'auto',
+ },
rowConfig: {
keyField: 'seq',
isHover: true,
--
Gitblit v1.9.3