From 08c4c2180a78bc2f67141e0bb0943757346bb77b Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 17 七月 2026 14:58:02 +0800
Subject: [PATCH] 银川 1.新增生产计划页面 2.销售订单流程生产流程修改 3.客户管理公私海逻辑修改
---
src/views/mes/pro/mps/data.ts | 299 +++++++++++++
src/views/erp/purchase/order/index.vue | 7
src/views/erp/sale/order/modules/item-form.vue | 22
src/views/mes/pro/mps/index.vue | 321 +++++++++++++
src/api/crm/customer/index.ts | 4
src/views/erp/sale/order/data.ts | 13
src/views/erp/purchase/order/data.ts | 2
src/views/mes/pro/mps/modules/form.vue | 175 +++++++
src/api/erp/sale/order/index.ts | 3
src/components/crm-customer-select.vue | 166 ++++--
src/views/erp/sale/order/index.vue | 4
src/components/crm-customer-select-dialog.vue | 236 ++++++++++
src/api/mes/pro/mps/index.ts | 107 ++++
13 files changed, 1,302 insertions(+), 57 deletions(-)
diff --git a/src/api/crm/customer/index.ts b/src/api/crm/customer/index.ts
index 038625f..4c17fde 100644
--- a/src/api/crm/customer/index.ts
+++ b/src/api/crm/customer/index.ts
@@ -110,7 +110,9 @@
/** 棰嗗彇鍏捣瀹㈡埛 */
export function receiveCustomer(ids: number[]) {
- return requestClient.put('/crm/customer/receive', { ids: ids.join(',') });
+ return requestClient.put('/crm/customer/receive', null, {
+ params: { ids: ids.join(',') },
+ });
}
/** 鍒嗛厤鍏捣缁欏搴旇礋璐d汉 */
diff --git a/src/api/erp/sale/order/index.ts b/src/api/erp/sale/order/index.ts
index 2d36e54..5126cf7 100644
--- a/src/api/erp/sale/order/index.ts
+++ b/src/api/erp/sale/order/index.ts
@@ -25,6 +25,7 @@
depositPrice?: number; // 瀹氶噾閲戦锛屽崟浣嶏細鍏�
contractId?: number; // 鍏宠仈 CRM 鍚堝悓缂栧彿
contractNo?: string; // CRM 鍚堝悓鍗曞彿
+ needProduction?: number; // 鏄惁闇�瑕佺敓浜э細0-涓嶉渶瑕侊紝1-闇�瑕�
items?: SaleOrderItem[]; // 閿�鍞鍗曚骇鍝佹槑缁嗗垪琛�
}
@@ -47,6 +48,8 @@
totalTaxPrice?: number; // 鍚◣鎬讳环锛屽崟浣嶏細鍏�
remark?: string; // 澶囨敞
stockCount?: number; // 搴撳瓨鏁伴噺锛堟樉绀哄瓧娈碉級
+ needProduction?: number; // 鏄惁闇�瑕佺敓浜э細0-涓嶉渶瑕侊紝1-闇�瑕�
+ mpsId?: number; // MPS缂栧彿
}
}
diff --git a/src/api/mes/pro/mps/index.ts b/src/api/mes/pro/mps/index.ts
new file mode 100644
index 0000000..c8b4b8e
--- /dev/null
+++ b/src/api/mes/pro/mps/index.ts
@@ -0,0 +1,107 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesProMpsApi {
+ /** MPS 涓荤敓浜ц鍒� */
+ export interface Mps {
+ id?: number; // MPS 缂栧彿
+ code?: string; // MPS 缂栫爜
+ status?: number; // 鐘舵�侊紙0=鑽夌 1=宸蹭笅鍙� 2=宸插畬鎴� 3=宸插彇娑堬級
+ saleOrderId?: number; // 閿�鍞鍗旾D
+ saleOrderNo?: string; // 閿�鍞鍗曞彿
+ saleOrderItemId?: number; // 閿�鍞鍗曟槑缁咺D
+ productId?: number; // 浜у搧ID
+ productCode?: string; // 浜у搧缂栫爜
+ productName?: string; // 浜у搧鍚嶇О
+ productSpecification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 璁″垝鐢熶骇鏁伴噺
+ quantityProduced?: number; // 宸茬敓浜ф暟閲�
+ clientId?: number; // 瀹㈡埛ID
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ requestDate?: number; // 闇�姹傛棩鏈�
+ issueDate?: number; // 涓嬪彂鏃ユ湡
+ finishDate?: number; // 瀹屾垚鏃ユ湡
+ workOrderId?: number; // 鐢熶骇宸ュ崟ID
+ workOrderCode?: string; // 鐢熶骇宸ュ崟缂栫爜
+ mergeFlag?: number; // 鏄惁鍚堝苟涓嬪彂锛�0-鍚︼紝1-鏄級
+ mergeBatchNo?: string; // 鍚堝苟鎵规鍙�
+ remark?: string; // 澶囨敞
+ createTime?: number; // 鍒涘缓鏃堕棿
+ }
+
+ /** MPS 鍒嗛〉鏌ヨ鍙傛暟 */
+ export interface PageParams extends PageParam {
+ code?: string;
+ saleOrderNo?: string;
+ productId?: number;
+ clientId?: number;
+ status?: number;
+ requestDate?: number[];
+ }
+
+ /** MPS 鍒涘缓璇锋眰 */
+ export interface CreateMpsReq {
+ code?: string;
+ saleOrderId?: number;
+ saleOrderNo?: string;
+ saleOrderItemId?: number;
+ productId?: number;
+ quantity?: number;
+ unitMeasureId?: number;
+ clientId?: number;
+ requestDate?: string;
+ remark?: string;
+ }
+}
+
+/** 鏌ヨ MPS 鍒嗛〉 */
+export function getMpsPage(params: MesProMpsApi.PageParams) {
+ return requestClient.get<PageResult<MesProMpsApi.Mps>>('/mes/pro-mps/page', {
+ params,
+ });
+}
+
+/** 鏌ヨ MPS 璇︽儏 */
+export function getMps(id: number) {
+ return requestClient.get<MesProMpsApi.Mps>(`/mes/pro-mps/get?id=${id}`);
+}
+
+/** 鍒涘缓 MPS */
+export function createMps(data: MesProMpsApi.CreateMpsReq) {
+ return requestClient.post('/mes/pro-mps/create', data);
+}
+
+/** 鏇存柊 MPS */
+export function updateMps(data: MesProMpsApi.CreateMpsReq & { id: number }) {
+ return requestClient.put('/mes/pro-mps/update', data);
+}
+
+/** 鍒犻櫎 MPS */
+export function deleteMps(ids: number[]) {
+ return requestClient.delete('/mes/pro-mps/delete', {
+ params: { ids: ids.join(',') },
+ });
+}
+
+/** 瀵煎嚭 MPS Excel */
+export function exportMps(params: any) {
+ return requestClient.download('/mes/pro-mps/export-excel', { params });
+}
+
+/** 涓嬪彂 MPS */
+export function issueMps(id: number) {
+ return requestClient.post(`/mes/pro-mps/issue?id=${id}`);
+}
+
+/** 鍚堝苟涓嬪彂 MPS */
+export function mergeIssueMps(ids: number[]) {
+ return requestClient.post('/mes/pro-mps/merge-issue', ids);
+}
+
+/** 鍙栨秷 MPS */
+export function cancelMps(id: number) {
+ return requestClient.post(`/mes/pro-mps/cancel?id=${id}`);
+}
diff --git a/src/components/crm-customer-select-dialog.vue b/src/components/crm-customer-select-dialog.vue
new file mode 100644
index 0000000..a514f9c
--- /dev/null
+++ b/src/components/crm-customer-select-dialog.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { CrmCustomerApi } from '#/api/crm/customer';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal, Tabs } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getCustomerPage } from '#/api/crm/customer';
+
+defineOptions({ name: 'CrmCustomerSelectDialog' });
+
+const emit = defineEmits<{
+ selected: [rows: CrmCustomerApi.Customer[]];
+}>();
+
+const open = ref(false);
+const multiple = ref(true);
+const selectedRows = ref<CrmCustomerApi.Customer[]>([]);
+const preSelectedIds = ref<number[]>([]);
+const sceneType = ref('1');
+
+function getTableRows() {
+ return gridApi.grid.getTableData().fullData as CrmCustomerApi.Customer[];
+}
+
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, CrmCustomerApi.Customer>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as CrmCustomerApi.Customer[];
+ records.forEach((row) => {
+ const rowId = row.id;
+ if (rowId) {
+ selectedMap.set(rowId, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+function handleCheckboxSelectChange() {
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+function handleRadioChange(row: CrmCustomerApi.Customer) {
+ selectedRows.value = [row];
+}
+
+async function toggleMultipleRow(row: CrmCustomerApi.Customer) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+async function handleCellDblclick({
+ row,
+}: {
+ row: CrmCustomerApi.Customer;
+}) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = getTableRows();
+ for (const row of rows) {
+ if (
+ row.id === undefined ||
+ row.id === null ||
+ !preSelectedIds.value.includes(row.id)
+ ) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+function handleSceneTypeChange(key: number | string) {
+ sceneType.value = key.toString();
+ gridApi.query();
+}
+
+const gridColumns = [
+ { field: 'name', title: '瀹㈡埛鍚嶇О', minWidth: 180 },
+ { field: 'mobile', title: '鎵嬫満鍙�', width: 140 },
+ { field: 'telephone', title: '鐢佃瘽', width: 140 },
+ { field: 'areaName', title: '鎵�鍦ㄥ湴', width: 160 },
+ { field: 'detailAddress', title: '璇︾粏鍦板潃', minWidth: 200 },
+];
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: [
+ {
+ fieldName: 'name',
+ label: '瀹㈡埛鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ鎴峰悕绉�',
+ },
+ },
+ ],
+ },
+ gridOptions: {
+ columns: [{ type: 'radio', width: 50 }, ...gridColumns],
+ height: 460,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getCustomerPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ sceneType: sceneType.value,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<CrmCustomerApi.Customer>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({
+ row,
+ }: {
+ row: CrmCustomerApi.Customer;
+ }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+async function resetQueryState() {
+ sceneType.value = '1';
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? true;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: multiple.value
+ ? [{ type: 'checkbox', width: 50 }, ...gridColumns]
+ : [{ type: 'radio', width: 50 }, ...gridColumns],
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+async function closeModal() {
+ open.value = false;
+ await resetQueryState();
+}
+
+function handleConfirm() {
+ const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+ if (rows.length === 0) {
+ message.warning(
+ multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�',
+ );
+ return;
+ }
+ emit('selected', multiple.value ? rows : [rows[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="瀹㈡埛閫夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @ok="handleConfirm"
+ @cancel="closeModal"
+ >
+ <Tabs class="mb-2" @change="handleSceneTypeChange">
+ <Tabs.TabPane key="1" tab="鎴戣礋璐g殑" />
+ <Tabs.TabPane key="2" tab="鎴戝弬涓庣殑" />
+ <Tabs.TabPane key="3" tab="涓嬪睘璐熻矗鐨�" />
+ </Tabs>
+ <Grid table-title="瀹㈡埛鍒楄〃" />
+ </Modal>
+</template>
diff --git a/src/components/crm-customer-select.vue b/src/components/crm-customer-select.vue
index 8f982bc..64dcc12 100644
--- a/src/components/crm-customer-select.vue
+++ b/src/components/crm-customer-select.vue
@@ -1,75 +1,133 @@
<script lang="ts" setup>
import type { CrmCustomerApi } from '#/api/crm/customer';
-import { computed, ref, watch } from 'vue';
+import { computed, ref, useAttrs, watch } from 'vue';
-import { Select } from 'ant-design-vue';
+import { IconifyIcon } from '@vben/icons';
-import { getCustomerSimpleList } from '#/api/crm/customer';
+import { Input, Tooltip } from 'ant-design-vue';
-interface Props {
- value?: number;
- disabled?: boolean;
-}
+import { getCustomer } from '#/api/crm/customer';
-const props = withDefaults(defineProps<Props>(), {
- value: undefined,
- disabled: false,
-});
+import CrmCustomerSelectDialog from './crm-customer-select-dialog.vue';
-const emit = defineEmits(['update:value', 'change']);
+defineOptions({ name: 'CrmCustomerSelect', inheritAttrs: false });
-const customerList = ref<CrmCustomerApi.Customer[]>([]);
-const loading = ref(false);
-
-const options = computed(() => {
- return customerList.value.map((item) => ({
- label: item.name,
- value: item.id,
- }));
-});
-
-const selectedValue = ref(props.value);
-
-watch(
- () => props.value,
- (val) => {
- selectedValue.value = val;
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ placeholder?: string;
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨瀹㈡埛',
},
);
+const emit = defineEmits<{
+ change: [item: CrmCustomerApi.Customer | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof CrmCustomerSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<CrmCustomerApi.Customer>();
-watch(selectedValue, (val) => {
- emit('update:value', val);
- const customer = customerList.value.find((item) => item.id === val);
- emit('change', customer);
-});
+const displayLabel = computed(() => selectedItem.value?.name ?? '');
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
-async function loadCustomers() {
- if (customerList.value.length > 0) return;
- loading.value = true;
- try {
- customerList.value = await getCustomerSimpleList();
- } finally {
- loading.value = false;
+async function resolveCustomerById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
}
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getCustomer(id);
}
-function filterOption(input: string, option: any) {
- return option.label.toLowerCase().includes(input.toLowerCase());
+watch(
+ () => props.modelValue,
+ (value) => {
+ resolveCustomerById(value);
+ },
+ { immediate: true },
+);
+
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
}
-loadCustomers();
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+function handleSelected(rows: CrmCustomerApi.Customer[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
</script>
<template>
- <Select
- v-model:value="selectedValue"
- :options="options"
- :loading="loading"
- :disabled="disabled"
- placeholder="璇烽�夋嫨瀹㈡埛"
- show-search
- :filter-option="filterOption"
- allow-clear
- />
-</template>
\ No newline at end of file
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+ <div>鎵嬫満锛歿{ selectedItem.mobile || '-' }}</div>
+ <div>鍦板尯锛歿{ selectedItem.areaName || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <CrmCustomerSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/erp/purchase/order/data.ts b/src/views/erp/purchase/order/data.ts
index c97f2c2..cf47ffd 100644
--- a/src/views/erp/purchase/order/data.ts
+++ b/src/views/erp/purchase/order/data.ts
@@ -431,6 +431,7 @@
field: 'status',
title: '鐘舵��',
minWidth: 120,
+ fixed: 'right',
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.ERP_AUDIT_STATUS },
@@ -440,6 +441,7 @@
field: 'inStatus',
title: '鍏ュ簱鐘舵��',
minWidth: 100,
+ fixed: 'right',
slots: { default: 'inStatus' },
},
{
diff --git a/src/views/erp/purchase/order/index.vue b/src/views/erp/purchase/order/index.vue
index 1d76f59..cc275a3 100644
--- a/src/views/erp/purchase/order/index.vue
+++ b/src/views/erp/purchase/order/index.vue
@@ -7,7 +7,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
-import { message } from 'ant-design-vue';
+import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -183,6 +183,11 @@
]"
/>
</template>
+ <template #inStatus="{ row }">
+ <Tag v-if="row.inStatus === 0" color="default">鏈叆搴�</Tag>
+ <Tag v-else-if="row.inStatus === 1" color="warning">閮ㄥ垎鍏ュ簱</Tag>
+ <Tag v-else-if="row.inStatus === 2" color="success">鍏ㄩ儴鍏ュ簱</Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 4428be2..d23fc18 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -221,6 +221,12 @@
minWidth: 80,
},
{
+ field: 'needProduction',
+ title: '闇�瑕佺敓浜�',
+ minWidth: 100,
+ slots: { default: 'needProduction' },
+ },
+ {
field: 'remark',
title: '澶囨敞',
minWidth: 150,
@@ -482,6 +488,13 @@
slots: { default: 'status' },
},
{
+ field: 'needProduction',
+ title: '闇�瑕佺敓浜�',
+ minWidth: 100,
+ fixed: 'right',
+ slots: { default: 'needProduction' },
+ },
+ {
title: '鎿嶄綔',
width: 260,
fixed: 'right',
diff --git a/src/views/erp/sale/order/index.vue b/src/views/erp/sale/order/index.vue
index 99cf346..fa775a4 100644
--- a/src/views/erp/sale/order/index.vue
+++ b/src/views/erp/sale/order/index.vue
@@ -248,6 +248,10 @@
<Tag v-if="row.status === 20" color="success">宸插鏍�</Tag>
<Tag v-if="row.status === 30" color="error">宸蹭綔搴�</Tag>
</template>
+ <template #needProduction="{ row }">
+ <Tag v-if="row.needProduction === 1" color="success">鏄�</Tag>
+ <Tag v-else color="default">鍚�</Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/erp/sale/order/modules/item-form.vue b/src/views/erp/sale/order/modules/item-form.vue
index f84d5ac..f877805 100644
--- a/src/views/erp/sale/order/modules/item-form.vue
+++ b/src/views/erp/sale/order/modules/item-form.vue
@@ -10,7 +10,7 @@
erpPriceMultiply,
} from '@vben/utils';
-import { Input, InputNumber, Select } from 'ant-design-vue';
+import { Input, InputNumber, Select, Tag } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getItemPage } from '#/api/mdm/item';
@@ -134,6 +134,7 @@
taxPrice: undefined,
totalPrice: undefined,
remark: undefined,
+ needProduction: 1, // 榛樿闇�瑕佺敓浜�
};
tableData.value.push(newRow);
// 閫氱煡鐖剁粍浠舵洿鏂�
@@ -190,6 +191,10 @@
erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
row.totalPrice = row.totalProductPrice + row.taxPrice;
}
+ // 榛樿闇�瑕佺敓浜�
+ if (row.needProduction === undefined) {
+ row.needProduction = 1;
+ }
}
/** 琛ㄥ崟鏍¢獙 */
@@ -240,6 +245,21 @@
/>
<span v-else>{{ row.productName || '-' }}</span>
</template>
+ <template #needProduction="{ row }">
+ <Select
+ v-if="!disabled"
+ v-model:value="row.needProduction"
+ :options="[
+ { label: '鏄�', value: 1 },
+ { label: '鍚�', value: 0 },
+ ]"
+ class="w-full"
+ placeholder="璇烽�夋嫨"
+ @change="handleRowChange(row)"
+ />
+ <Tag v-else-if="row.needProduction === 1" color="success">鏄�</Tag>
+ <Tag v-else color="default">鍚�</Tag>
+ </template>
<template #count="{ row }">
<InputNumber
v-if="!disabled"
diff --git a/src/views/mes/pro/mps/data.ts b/src/views/mes/pro/mps/data.ts
new file mode 100644
index 0000000..7e0a1a9
--- /dev/null
+++ b/src/views/mes/pro/mps/data.ts
@@ -0,0 +1,299 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProMpsApi } from '#/api/mes/pro/mps';
+
+import { markRaw } from 'vue';
+
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import CrmCustomerSelect from '#/components/crm-customer-select.vue';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'edit';
+
+/** MPS 鐘舵�佹灇涓� */
+export const MpsStatusEnum = {
+ DRAFT: 0, // 鑽夌
+ ISSUED: 1, // 宸蹭笅鍙�
+ FINISHED: 2, // 宸插畬鎴�
+ CANCELLED: 3, // 宸插彇娑�
+};
+
+/** 鏂板/缂栬緫琛ㄥ崟 */
+export function useFormSchema(formType: FormType): VbenFormSchema[] {
+ const isCreate = formType === 'create';
+ const isEdit = formType === 'edit';
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: 'MPS缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '绯荤粺鑷姩鐢熸垚',
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'saleOrderId',
+ label: '閿�鍞鍗旾D',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ攢鍞鍗旾D锛堝彲閫夛級',
+ disabled: !isCreate && !isEdit,
+ },
+ },
+ {
+ fieldName: 'saleOrderNo',
+ label: '閿�鍞鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曞彿锛堝彲閫夛級',
+ disabled: !isCreate && !isEdit,
+ },
+ },
+ {
+ fieldName: 'saleOrderItemId',
+ label: '閿�鍞鍗曟槑缁咺D',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曟槑缁咺D锛堝彲閫夛級',
+ disabled: !isCreate && !isEdit,
+ },
+ },
+ {
+ fieldName: 'productId',
+ label: '浜у搧',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧',
+ disabled: !isCreate && !isEdit,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '璁″垝鐢熶骇鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇疯緭鍏ヨ鍒掔敓浜ф暟閲�',
+ min: 1,
+ precision: 2,
+ disabled: !isCreate && !isEdit,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(CrmCustomerSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ disabled: !isCreate && !isEdit,
+ },
+ },
+ {
+ fieldName: 'requestDate',
+ label: '闇�姹傛棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇烽�夋嫨闇�姹傛棩鏈�',
+ format: 'YYYY-MM-DD',
+ valueFormat: 'x',
+ disabled: !isCreate && !isEdit,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ disabled: !isCreate && !isEdit,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: 'MPS缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏PS缂栫爜',
+ },
+ },
+ {
+ fieldName: 'saleOrderNo',
+ label: '閿�鍞鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'productId',
+ label: '浜у搧',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(CrmCustomerSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: [
+ { label: '鑽夌', value: 0 },
+ { label: '宸蹭笅鍙�', value: 1 },
+ { label: '宸插畬鎴�', value: 2 },
+ { label: '宸插彇娑�', value: 3 },
+ ],
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ {
+ fieldName: 'requestDate',
+ label: '闇�姹傛棩鏈�',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesProMpsApi.Mps>['columns'] {
+ return [
+ {
+ type: 'checkbox',
+ width: 50,
+ fixed: 'left',
+ },
+ {
+ field: 'code',
+ title: 'MPS缂栫爜',
+ fixed: 'left',
+ width: 180,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 100,
+ slots: { default: 'status' },
+ },
+ {
+ field: 'saleOrderNo',
+ title: '閿�鍞鍗曞彿',
+ width: 180,
+ },
+ {
+ field: 'productCode',
+ title: '浜у搧缂栫爜',
+ width: 120,
+ },
+ {
+ field: 'productName',
+ title: '浜у搧鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'productSpecification',
+ title: '瑙勬牸鍨嬪彿',
+ width: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '璁″垝鏁伴噺',
+ width: 100,
+ formatter: 'formatAmount3',
+ },
+ {
+ field: 'quantityProduced',
+ title: '宸茬敓浜ф暟閲�',
+ width: 100,
+ formatter: 'formatAmount3',
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ width: 120,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'requestDate',
+ title: '闇�姹傛棩鏈�',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'issueDate',
+ title: '涓嬪彂鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'workOrderCode',
+ title: '鐢熶骇宸ュ崟缂栫爜',
+ width: 180,
+ slots: { default: 'workOrderCode' },
+ },
+ {
+ field: 'mergeFlag',
+ title: '鍚堝苟涓嬪彂',
+ width: 100,
+ slots: { default: 'mergeFlag' },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 280,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/mes/pro/mps/index.vue b/src/views/mes/pro/mps/index.vue
new file mode 100644
index 0000000..c0dcdbf
--- /dev/null
+++ b/src/views/mes/pro/mps/index.vue
@@ -0,0 +1,321 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProMpsApi } from '#/api/mes/pro/mps';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
+
+import { Button, message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelMps,
+ deleteMps,
+ exportMps,
+ getMpsPage,
+ issueMps,
+ mergeIssueMps,
+} from '#/api/mes/pro/mps';
+import { $t } from '#/locales';
+
+import { MpsStatusEnum, useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+/** MPS 鐢熶骇涓昏鍒掑垪琛� */
+defineOptions({ name: 'MesProMps' });
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const checkedIds = ref<number[]>([]);
+const checkedRows = ref<MesProMpsApi.Mps[]>([]);
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+ checkedIds.value = [];
+ checkedRows.value = [];
+}
+
+/** 鏂板 MPS */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportMps(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鐢熶骇涓昏鍒�.xls', source: data });
+}
+
+/** 鏌ョ湅璇︽儏 */
+function handleDetail(row: MesProMpsApi.Mps) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫 */
+function handleEdit(row: MesProMpsApi.Mps) {
+ formModalApi.setData({ formType: 'edit', id: row.id }).open();
+}
+
+/** 鍒犻櫎 */
+async function handleDelete(ids: number[]) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting'),
+ duration: 0,
+ });
+ try {
+ await deleteMps(ids);
+ message.success($t('ui.actionMessage.deleteSuccess'));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓嬪彂 MPS */
+async function handleIssue(row: MesProMpsApi.Mps) {
+ const hideLoading = message.loading({
+ content: '涓嬪彂涓�...',
+ duration: 0,
+ });
+ try {
+ await issueMps(row.id!);
+ message.success('涓嬪彂鎴愬姛');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍚堝苟涓嬪彂 */
+async function handleMergeIssue() {
+ if (checkedIds.value.length < 2) {
+ message.warning('璇疯嚦灏戦�夋嫨2鏉¤褰�');
+ return;
+ }
+ // 鏍¢獙锛氱浉鍚屼骇鍝併�佽崏绋跨姸鎬�
+ const firstRow = checkedRows.value[0];
+ const isSameProduct = checkedRows.value.every(
+ (row) => row.productId === firstRow.productId,
+ );
+ if (!isSameProduct) {
+ message.error('鍙兘閫夋嫨鐩稿悓浜у搧鐨凪PS杩涜鍚堝苟涓嬪彂');
+ return;
+ }
+ const isAllDraft = checkedRows.value.every(
+ (row) => row.status === MpsStatusEnum.DRAFT,
+ );
+ if (!isAllDraft) {
+ message.error('鍙兘閫夋嫨鑽夌鐘舵�佺殑MPS杩涜鍚堝苟涓嬪彂');
+ return;
+ }
+ const hideLoading = message.loading({
+ content: '鍚堝苟涓嬪彂涓�...',
+ duration: 0,
+ });
+ try {
+ await mergeIssueMps(checkedIds.value);
+ message.success('鍚堝苟涓嬪彂鎴愬姛');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷 MPS */
+async function handleCancel(row: MesProMpsApi.Mps) {
+ const hideLoading = message.loading({
+ content: '鍙栨秷涓�...',
+ duration: 0,
+ });
+ try {
+ await cancelMps(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 璺宠浆鍒扮敓浜у伐鍗� */
+function handleGoToWorkOrder(row: MesProMpsApi.Mps) {
+ if (row.workOrderId) {
+ router.push({
+ path: '/mes/pro/workorder',
+ query: { id: row.workOrderId },
+ });
+ }
+}
+
+/** 琛岄�夋嫨鍙樺寲 */
+function handleRowCheckboxChange({
+ records,
+}: {
+ records: MesProMpsApi.Mps[];
+}) {
+ checkedIds.value = records.map((item) => item.id!);
+ checkedRows.value = records;
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getMpsPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesProMpsApi.Mps>,
+ gridEvents: {
+ checkboxAll: handleRowCheckboxChange,
+ checkboxChange: handleRowCheckboxChange,
+ },
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="鐢熶骇涓昏鍒�(MPS)鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['MPS']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:pro-mps:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:pro-mps:export'],
+ onClick: handleExport,
+ },
+ {
+ label: '鍚堝苟涓嬪彂',
+ type: 'primary',
+ disabled: isEmpty(checkedIds) || checkedIds.length < 2,
+ icon: ACTION_ICON.AUDIT,
+ auth: ['mes:pro-mps:merge-issue'],
+ popConfirm: {
+ title: '纭鍚堝苟涓嬪彂閫変腑鐨凪PS鍚楋紵',
+ confirm: handleMergeIssue,
+ },
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #status="{ row }">
+ <Tag v-if="row.status === 0" color="blue">鑽夌</Tag>
+ <Tag v-else-if="row.status === 1" color="success">宸蹭笅鍙�</Tag>
+ <Tag v-else-if="row.status === 2" color="purple">宸插畬鎴�</Tag>
+ <Tag v-else-if="row.status === 3" color="red">宸插彇娑�</Tag>
+ </template>
+ <template #workOrderCode="{ row }">
+ <Button
+ v-if="row.workOrderCode"
+ type="link"
+ @click="handleGoToWorkOrder(row)"
+ >
+ {{ row.workOrderCode }}
+ </Button>
+ <span v-else>-</span>
+ </template>
+ <template #mergeFlag="{ row }">
+ <Tag v-if="row.mergeFlag === 1" color="success">鏄�</Tag>
+ <Tag v-else color="default">鍚�</Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.detail'),
+ type: 'link',
+ icon: ACTION_ICON.VIEW,
+ auth: ['mes:pro-mps:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:pro-mps:update'],
+ ifShow: () => row.status === MpsStatusEnum.DRAFT,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '涓嬪彂',
+ type: 'link',
+ icon: ACTION_ICON.AUDIT,
+ auth: ['mes:pro-mps:issue'],
+ ifShow: () => row.status === MpsStatusEnum.DRAFT,
+ popConfirm: {
+ title: `纭涓嬪彂${row.code}鍚楋紵`,
+ confirm: handleIssue.bind(null, row),
+ },
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ icon: 'ant-design:stop-outlined',
+ auth: ['mes:pro-mps:cancel'],
+ ifShow: () => row.status === MpsStatusEnum.ISSUED,
+ popConfirm: {
+ title: `纭鍙栨秷${row.code}鍚楋紵`,
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:pro-mps:delete'],
+ ifShow: () => row.status === MpsStatusEnum.DRAFT,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, [row.id!]),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/mes/pro/mps/modules/form.vue b/src/views/mes/pro/mps/modules/form.vue
new file mode 100644
index 0000000..4cc8308
--- /dev/null
+++ b/src/views/mes/pro/mps/modules/form.vue
@@ -0,0 +1,175 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesProMpsApi } from '#/api/mes/pro/mps';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { Descriptions, message, Tag } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createMps, getMps, updateMps } from '#/api/mes/pro/mps';
+import { $t } from '#/locales';
+
+import { MpsStatusEnum, useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesProMpsApi.Mps>();
+const formType = ref<FormType>('detail');
+
+const isEditable = computed(() => ['create', 'edit'].includes(formType.value));
+
+const getTitle = computed(() => {
+ switch (formType.value) {
+ case 'create':
+ return '鏂板MPS';
+ case 'edit':
+ return '缂栬緫MPS';
+ default:
+ return 'MPS璇︽儏';
+ }
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 閲嶆柊鎸傝浇 schema 骞舵寜琛ㄥ崟鎬佹帶鍒跺簳閮ㄧ‘璁ゆ寜閽� */
+function applySchema() {
+ formApi.setState({ schema: useFormSchema(formType.value) });
+ modalApi.setState({ showConfirmButton: isEditable.value });
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const data = (await formApi.getValues()) as MesProMpsApi.CreateMpsReq;
+ if (formData.value?.id) {
+ await updateMps({ ...data, id: formData.value.id });
+ } else {
+ await createMps(data);
+ }
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ applySchema();
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getMps(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 璇︽儏妯″紡棰濆灞曠ず鍙淇℃伅 -->
+ <div v-if="formType === 'detail' && formData" class="mx-4">
+ <Descriptions bordered :column="2">
+ <Descriptions.Item label="MPS缂栫爜">
+ {{ formData.code }}
+ </Descriptions.Item>
+ <Descriptions.Item label="鐘舵��">
+ <Tag v-if="formData.status === 0" color="blue">鑽夌</Tag>
+ <Tag v-else-if="formData.status === 1" color="success">宸蹭笅鍙�</Tag>
+ <Tag v-else-if="formData.status === 2" color="purple">宸插畬鎴�</Tag>
+ <Tag v-else-if="formData.status === 3" color="red">宸插彇娑�</Tag>
+ </Descriptions.Item>
+ <Descriptions.Item label="閿�鍞鍗曞彿">
+ {{ formData.saleOrderNo }}
+ </Descriptions.Item>
+ <Descriptions.Item label="閿�鍞鍗曟槑缁咺D">
+ {{ formData.saleOrderItemId }}
+ </Descriptions.Item>
+ <Descriptions.Item label="浜у搧缂栫爜">
+ {{ formData.productCode }}
+ </Descriptions.Item>
+ <Descriptions.Item label="浜у搧鍚嶇О">
+ {{ formData.productName }}
+ </Descriptions.Item>
+ <Descriptions.Item label="瑙勬牸鍨嬪彿">
+ {{ formData.productSpecification }}
+ </Descriptions.Item>
+ <Descriptions.Item label="鍗曚綅">
+ {{ formData.unitMeasureName }}
+ </Descriptions.Item>
+ <Descriptions.Item label="璁″垝鐢熶骇鏁伴噺">
+ {{ formData.quantity }}
+ </Descriptions.Item>
+ <Descriptions.Item label="宸茬敓浜ф暟閲�">
+ {{ formData.quantityProduced }}
+ </Descriptions.Item>
+ <Descriptions.Item label="瀹㈡埛缂栫爜">
+ {{ formData.clientCode }}
+ </Descriptions.Item>
+ <Descriptions.Item label="瀹㈡埛鍚嶇О">
+ {{ formData.clientName }}
+ </Descriptions.Item>
+ <Descriptions.Item label="闇�姹傛棩鏈�">
+ {{ formData.requestDate ? new Date(formData.requestDate).toLocaleDateString() : '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="涓嬪彂鏃ユ湡">
+ {{ formData.issueDate ? new Date(formData.issueDate).toLocaleDateString() : '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="瀹屾垚鏃ユ湡">
+ {{ formData.finishDate ? new Date(formData.finishDate).toLocaleDateString() : '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="鐢熶骇宸ュ崟缂栫爜">
+ {{ formData.workOrderCode || '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="鏄惁鍚堝苟涓嬪彂">
+ <Tag v-if="formData.mergeFlag === 1" color="success">鏄�</Tag>
+ <Tag v-else color="default">鍚�</Tag>
+ </Descriptions.Item>
+ <Descriptions.Item label="鍚堝苟鎵规鍙�">
+ {{ formData.mergeBatchNo || '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="澶囨敞" :span="2">
+ {{ formData.remark || '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="鍒涘缓鏃堕棿" :span="2">
+ {{ formData.createTime ? new Date(formData.createTime).toLocaleString() : '-' }}
+ </Descriptions.Item>
+ </Descriptions>
+ </div>
+ </Modal>
+</template>
--
Gitblit v1.9.3