From a3057955f855b8a266c017df00645845e7471410 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 07 八月 2026 18:01:59 +0800
Subject: [PATCH] 银川 1.不合格品处理页面开发联调 2.招投标管理 - 可配置版前端联调方案
---
src/views/wls/stocktaking/plan/data.ts | 4
src/views/mes/qc/ncr/modules/form.vue | 81 +++
src/views/mes/qc/iqc/modules/form.vue | 16
src/views/mes/qc/ipqc/data.ts | 1
src/views/mes/qc/iqc/data.ts | 21
src/api/srm/tender-config/index.ts | 16
src/views/srm/tender/detail.vue | 120 ++++
src/views/mes/qc/oqc/modules/form.vue | 16
src/views/srm/tender/config/index.vue | 65 ++
src/api/mes/qc/ncr/index.ts | 90 +++
src/views/srm/tender/data.ts | 37 +
src/views/srm/tender/index.vue | 107 +++
src/views/srm/tender/modules/form.vue | 10
src/api/srm/tender/index.ts | 9
src/views/mes/qc/ncr/index.vue | 339 +++++++++++++
src/views/mes/qc/ipqc/modules/form.vue | 16
src/views/mes/qc/ncr/data.ts | 447 +++++++++++++++++
src/views/srm/enums.ts | 29 +
src/packages/constants/src/biz-mes-enum.ts | 6
src/views/wls/stocktaking/plan/components/condition-value-input.vue | 30
src/views/erp/sale/order/data.ts | 4
src/views/mes/qc/oqc/data.ts | 1
src/views/mes/qc/rqc/modules/form.vue | 16
src/views/erp/sale/order/modules/form.vue | 2
src/packages/constants/src/dict-enum.ts | 2
src/views/mes/qc/rqc/data.ts | 1
26 files changed, 1,438 insertions(+), 48 deletions(-)
diff --git a/src/api/mes/qc/ncr/index.ts b/src/api/mes/qc/ncr/index.ts
new file mode 100644
index 0000000..060d8d3
--- /dev/null
+++ b/src/api/mes/qc/ncr/index.ts
@@ -0,0 +1,90 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesQcNcrApi {
+ /** MES 涓嶅悎鏍煎搧澶勭悊鍗� */
+ export interface Ncr {
+ id?: number;
+ code?: string;
+ name?: string;
+ qcType?: number;
+ qcId?: number;
+ qcCode?: string;
+ itemId?: number;
+ itemCode?: string;
+ itemName?: string;
+ itemSpecification?: string;
+ defectLevel?: number;
+ unqualifiedQuantity?: number;
+ defectDescription?: string;
+ disposition?: number;
+ responsibleDeptId?: number;
+ responsibleDeptName?: string;
+ reviewerUserId?: number;
+ reviewerNickname?: string;
+ reviewDate?: string;
+ reviewOpinion?: string;
+ handlerUserId?: number;
+ handlerNickname?: string;
+ handleDate?: string;
+ handleResult?: string;
+ rootCause?: string;
+ correctiveAction?: string;
+ preventiveAction?: string;
+ closeUserId?: number;
+ closeUserNickname?: string;
+ closeDate?: string;
+ closeOpinion?: string;
+ status?: number;
+ remark?: string;
+ createTime?: string;
+ }
+}
+
+/** 鏌ヨ涓嶅悎鏍煎搧澶勭悊鍗曞垎椤� */
+export function getNcrPage(params: PageParam) {
+ return requestClient.get<PageResult<MesQcNcrApi.Ncr>>('/mes/qc/ncr/page', {
+ params,
+ });
+}
+
+/** 鏌ヨ涓嶅悎鏍煎搧澶勭悊鍗曡鎯� */
+export function getNcr(id: number) {
+ return requestClient.get<MesQcNcrApi.Ncr>(`/mes/qc/ncr/get?id=${id}`);
+}
+
+/** 鏂板涓嶅悎鏍煎搧澶勭悊鍗� */
+export function createNcr(data: MesQcNcrApi.Ncr) {
+ return requestClient.post('/mes/qc/ncr/create', data);
+}
+
+/** 淇敼涓嶅悎鏍煎搧澶勭悊鍗� */
+export function updateNcr(data: MesQcNcrApi.Ncr) {
+ return requestClient.put('/mes/qc/ncr/update', data);
+}
+
+/** 鍒犻櫎涓嶅悎鏍煎搧澶勭悊鍗� */
+export function deleteNcr(id: number) {
+ return requestClient.delete(`/mes/qc/ncr/delete?id=${id}`);
+}
+
+/** 璇勫涓嶅悎鏍煎搧澶勭悊鍗� */
+export function reviewNcr(data: { defectLevel?: number; disposition: number; id: number; responsibleDeptId?: number; reviewOpinion?: string }) {
+ return requestClient.put('/mes/qc/ncr/review', data);
+}
+
+/** 澶勭疆涓嶅悎鏍煎搧澶勭悊鍗� */
+export function disposeNcr(data: { handleResult?: string; handlerUserId?: number; id: number }) {
+ return requestClient.put('/mes/qc/ncr/dispose', data);
+}
+
+/** 鍏抽棴涓嶅悎鏍煎搧澶勭悊鍗� */
+export function closeNcr(data: { closeOpinion?: string; id: number }) {
+ return requestClient.put('/mes/qc/ncr/close', data);
+}
+
+/** 瀵煎嚭涓嶅悎鏍煎搧澶勭悊鍗� */
+export function exportNcr(params: any) {
+ return requestClient.download('/mes/qc/ncr/export-excel', { params });
+}
diff --git a/src/api/srm/tender-config/index.ts b/src/api/srm/tender-config/index.ts
new file mode 100644
index 0000000..976938d
--- /dev/null
+++ b/src/api/srm/tender-config/index.ts
@@ -0,0 +1,16 @@
+import { requestClient } from '#/api/request';
+
+export namespace SrmTenderConfigApi {
+ export interface TenderConfigVO {
+ id?: number;
+ defaultBiddingMode?: string;
+ }
+}
+
+export function getTenderConfig() {
+ return requestClient.get<SrmTenderConfigApi.TenderConfigVO>('/srm/tender-config/get');
+}
+
+export function saveTenderConfig(data: SrmTenderConfigApi.TenderConfigVO) {
+ return requestClient.put('/srm/tender-config/save', data);
+}
diff --git a/src/api/srm/tender/index.ts b/src/api/srm/tender/index.ts
index 5ac7ad8..a44c5b9 100644
--- a/src/api/srm/tender/index.ts
+++ b/src/api/srm/tender/index.ts
@@ -21,6 +21,7 @@
purchaseRequestId?: number;
awardId?: number;
purchaseOrderNo?: string;
+ biddingMode?: string;
createTime?: string;
}
@@ -149,6 +150,14 @@
return requestClient.put(`/srm/tender-project/start-evaluation?id=${id}`);
}
+export function startPriceComparing(id: number) {
+ return requestClient.put(`/srm/tender-project/start-price-comparing?id=${id}`);
+}
+
+export function confirmTenderProject(data: { id: number; bidId: number }) {
+ return requestClient.put('/srm/tender-project/confirm', data);
+}
+
// ========== 鐗╂枡瀛愯〃 ==========
export function getMaterialList(tenderProjectId: number) {
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index 0a1b051..1ac585c 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -484,6 +484,12 @@
RQC: 4, // 閫�璐ф楠�
} as const;
+/** MES 璐ㄦ妫�娴嬬粨鏋滄灇涓� */
+export const MesQcCheckResultEnum = {
+ PASS: 1, // 鏍¢獙閫氳繃
+ FAIL: 2, // 鏍¢獙涓嶉�氳繃
+} as const;
+
/** MES 璐ㄦ鍗曠姸鎬佹灇涓� */
export const MesQcStatusEnum = {
DRAFT: MesOrderStatusConstants.DRAFT,
diff --git a/src/packages/constants/src/dict-enum.ts b/src/packages/constants/src/dict-enum.ts
index e899ad2..ab5df28 100644
--- a/src/packages/constants/src/dict-enum.ts
+++ b/src/packages/constants/src/dict-enum.ts
@@ -286,6 +286,8 @@
MES_WM_TRANSFER_TYPE: 'mes_wm_transfer_type', // MES 杞Щ鍗曠被鍨�
MES_PD_PARAM_TYPE: 'mes_pd_param_type', // MES 宸ヨ壓鍙傛暟绫诲瀷
MES_PD_TEMPLATE_TYPE: 'mes_pd_template_type', // MES 宸ヨ壓妯℃澘绫诲瀷
+ MES_NCR_STATUS: 'mes_ncr_status', // MES 涓嶅悎鏍煎搧澶勭悊鍗曠姸鎬�
+ MES_NCR_DISPOSITION: 'mes_ncr_disposition', // MES 涓嶅悎鏍煎搧澶勭疆鏂瑰紡
} as const;
/** ========== WMS - 浠撳偍绠$悊妯″潡 ========== */
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 7886d14..9597b94 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -216,6 +216,7 @@
title: '搴撳瓨',
minWidth: 80,
formatter: 'formatAmount3',
+ visible: false,
},
{
field: 'productUnitName',
@@ -226,11 +227,13 @@
field: 'productUnitName2',
title: '杈呭崟浣�1',
minWidth: 80,
+ visible: false,
},
{
field: 'productUnitName3',
title: '杈呭崟浣�2',
minWidth: 80,
+ visible: false,
},
{
field: 'needProduction',
@@ -243,6 +246,7 @@
title: '澶囨敞',
minWidth: 150,
slots: { default: 'remark' },
+ visible: false,
},
{
field: 'count',
diff --git a/src/views/erp/sale/order/modules/form.vue b/src/views/erp/sale/order/modules/form.vue
index 5dac3de..638f3ae 100644
--- a/src/views/erp/sale/order/modules/form.vue
+++ b/src/views/erp/sale/order/modules/form.vue
@@ -154,7 +154,7 @@
<template>
<Modal
:title="getTitle"
- class="w-3/4"
+ class="w-5/6"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">
diff --git a/src/views/mes/qc/ipqc/data.ts b/src/views/mes/qc/ipqc/data.ts
index eeeff54..58f1e88 100644
--- a/src/views/mes/qc/ipqc/data.ts
+++ b/src/views/mes/qc/ipqc/data.ts
@@ -250,6 +250,7 @@
precision: 2,
},
rules: 'required',
+ help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
},
{
fieldName: 'laborScrapQuantity',
diff --git a/src/views/mes/qc/ipqc/modules/form.vue b/src/views/mes/qc/ipqc/modules/form.vue
index af80f2c..0969fc7 100644
--- a/src/views/mes/qc/ipqc/modules/form.vue
+++ b/src/views/mes/qc/ipqc/modules/form.vue
@@ -6,7 +6,7 @@
import { computed, ref } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
@@ -69,6 +69,13 @@
return false;
}
const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return false;
+ }
if (formData.value?.id) {
await updateIpqc({ ...data, id: formData.value.id });
formData.value = { ...formData.value, ...data };
@@ -108,6 +115,13 @@
const current = JSON.stringify(await formApi.getValues());
if (current !== originalSnapshot.value) {
const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return;
+ }
await updateIpqc({ ...data, id });
formData.value = { ...formData.value, ...data };
originalSnapshot.value = current;
diff --git a/src/views/mes/qc/iqc/data.ts b/src/views/mes/qc/iqc/data.ts
index da23bdb..40cb523 100644
--- a/src/views/mes/qc/iqc/data.ts
+++ b/src/views/mes/qc/iqc/data.ts
@@ -11,10 +11,11 @@
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
import { MdmItemSelect } from '#/views/basicData/mdm/components';
-import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+
/** 琛ㄥ崟绫诲瀷 */
export type FormType = 'create' | 'detail' | 'update';
@@ -136,16 +137,23 @@
{
fieldName: 'vendorId',
label: '渚涘簲鍟�',
- component: markRaw(MdVendorSelect),
+ component: 'ApiSelect',
componentProps: {
+ allowClear: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ valueField: 'id',
},
- rules: 'selectRequired',
dependencies: {
triggerFields: ['sourceDocId'],
componentProps: (values) => ({
disabled: !!values.sourceDocId,
+ allowClear: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ valueField: 'id',
}),
},
},
@@ -200,6 +208,7 @@
placeholder: '璇疯緭鍏ヤ笉鍚堟牸鍝佹暟閲�',
},
rules: 'required',
+ help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
},
{
fieldName: 'receiveDate',
@@ -274,9 +283,13 @@
{
fieldName: 'vendorId',
label: '渚涘簲鍟�',
- component: markRaw(MdVendorSelect),
+ component: 'ApiSelect',
componentProps: {
+ allowClear: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ valueField: 'id',
},
},
{
diff --git a/src/views/mes/qc/iqc/modules/form.vue b/src/views/mes/qc/iqc/modules/form.vue
index 2f0bd83..45c1c35 100644
--- a/src/views/mes/qc/iqc/modules/form.vue
+++ b/src/views/mes/qc/iqc/modules/form.vue
@@ -6,7 +6,7 @@
import { computed, ref } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
@@ -69,6 +69,13 @@
return false;
}
const data = (await formApi.getValues()) as MesQcIqcApi.Iqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return false;
+ }
if (formData.value?.id) {
await updateIqc({ ...data, id: formData.value.id });
formData.value = { ...formData.value, ...data };
@@ -108,6 +115,13 @@
const current = JSON.stringify(await formApi.getValues());
if (current !== originalSnapshot.value) {
const data = (await formApi.getValues()) as MesQcIqcApi.Iqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return;
+ }
await updateIqc({ ...data, id });
formData.value = { ...formData.value, ...data };
originalSnapshot.value = current;
diff --git a/src/views/mes/qc/ncr/data.ts b/src/views/mes/qc/ncr/data.ts
new file mode 100644
index 0000000..f6bdbfb
--- /dev/null
+++ b/src/views/mes/qc/ncr/data.ts
@@ -0,0 +1,447 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesQcNcrApi } from '#/api/mes/qc/ncr';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+
+import { Button } from 'ant-design-vue';
+
+import { getSimpleUserList } from '#/api/system/user';
+import { DeptTreeSelect } from '#/views/system/dept/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+
+/** NCR 鐘舵�佹灇涓� */
+export const NCR_STATUS = {
+ PENDING_REVIEW: 0,
+ REVIEWED: 1,
+ DISPOSED: 2,
+ CLOSED: 3,
+} as const;
+
+export const NCR_STATUS_OPTIONS = [
+ { label: '寰呰瘎瀹�', value: 0 },
+ { label: '宸茶瘎瀹�', value: 1 },
+ { label: '宸插缃�', value: 2 },
+ { label: '宸插叧闂�', value: 3 },
+];
+
+/** 妫�楠岀被鍨嬫灇涓� */
+export const QC_TYPE_OPTIONS = [
+ { label: 'IQC', value: 1 },
+ { label: 'IPQC', value: 2 },
+ { label: 'OQC', value: 3 },
+ { label: 'RQC', value: 4 },
+];
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'qcId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: 'NCR鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏CR鍗曞彿',
+ },
+ rules: 'required',
+ suffix: () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const now = new Date();
+ const pad = (n: number) => String(n).padStart(2, '0');
+ const code = `NCR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鑷姩鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: 'NCR鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏CR鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'qcType',
+ label: '妫�楠岀被鍨�',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: QC_TYPE_OPTIONS,
+ placeholder: '璇烽�夋嫨妫�楠岀被鍨�',
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '涓嶅悎鏍肩墿鏂�',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨涓嶅悎鏍肩墿鏂�',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'defectLevel',
+ label: '缂洪櫡绛夌骇',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
+ placeholder: '璇烽�夋嫨缂洪櫡绛夌骇',
+ },
+ },
+ {
+ fieldName: 'unqualifiedQuantity',
+ label: '涓嶅悎鏍兼暟閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ笉鍚堟牸鏁伴噺',
+ precision: 2,
+ },
+ },
+ {
+ fieldName: 'defectDescription',
+ label: '涓嶅悎鏍兼弿杩�',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ笉鍚堟牸鎻忚堪',
+ rows: 3,
+ },
+ },
+ {
+ fieldName: 'rootCause',
+ label: '鏍规湰鍘熷洜',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ牴鏈師鍥�',
+ rows: 2,
+ },
+ },
+ {
+ fieldName: 'correctiveAction',
+ label: '绾犳鎺柦',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ籂姝f帾鏂�',
+ rows: 2,
+ },
+ },
+ {
+ fieldName: 'preventiveAction',
+ label: '棰勯槻鎺柦',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ闃叉帾鏂�',
+ rows: 2,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 2,
+ },
+ },
+ ];
+}
+
+/** 璇勫琛ㄥ崟 */
+export function useReviewFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'disposition',
+ label: '澶勭疆鏂瑰紡',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_NCR_DISPOSITION, 'number'),
+ placeholder: '璇烽�夋嫨澶勭疆鏂瑰紡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'defectLevel',
+ label: '缂洪櫡绛夌骇',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
+ placeholder: '璇烽�夋嫨缂洪櫡绛夌骇',
+ },
+ },
+ {
+ fieldName: 'responsibleDeptId',
+ label: '璐d换閮ㄩ棬',
+ component: markRaw(DeptTreeSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨璐d换閮ㄩ棬',
+ },
+ },
+ {
+ fieldName: 'reviewOpinion',
+ label: '璇勫鎰忚',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ瘎瀹℃剰瑙�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 澶勭疆琛ㄥ崟 */
+export function useDisposeFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'handlerUserId',
+ label: '澶勭悊浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨澶勭悊浜�',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'handleResult',
+ label: '澶勭悊缁撴灉璇存槑',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ鐞嗙粨鏋滆鏄�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍏抽棴琛ㄥ崟 */
+export function useCloseFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'closeOpinion',
+ label: '鍏抽棴鎰忚',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叧闂剰瑙�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: 'NCR鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏CR鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'qcType',
+ label: '妫�楠岀被鍨�',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: QC_TYPE_OPTIONS,
+ placeholder: '璇烽�夋嫨妫�楠岀被鍨�',
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '涓嶅悎鏍肩墿鏂�',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ },
+ {
+ fieldName: 'defectLevel',
+ label: '缂洪櫡绛夌骇',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
+ placeholder: '璇烽�夋嫨缂洪櫡绛夌骇',
+ },
+ },
+ {
+ fieldName: 'disposition',
+ label: '澶勭疆鏂瑰紡',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_NCR_DISPOSITION, 'number'),
+ placeholder: '璇烽�夋嫨澶勭疆鏂瑰紡',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: NCR_STATUS_OPTIONS,
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ {
+ fieldName: 'createTime',
+ label: '鍒涘缓鏃堕棿',
+ component: 'DatePicker',
+ componentProps: {
+ allowClear: true,
+ class: '!w-full',
+ placeholder: '璇烽�夋嫨鍒涘缓鏃堕棿',
+ showTime: true,
+ type: 'date',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesQcNcrApi.Ncr>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: 'NCR鍗曞彿',
+ width: 200,
+ },
+ {
+ field: 'qcCode',
+ title: '鏉ユ簮妫�楠屽崟缂栧彿',
+ width: 180,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'itemSpecification',
+ title: '瑙勬牸鍨嬪彿',
+ width: 130,
+ },
+ {
+ field: 'qcType',
+ title: '妫�楠岀被鍨�',
+ width: 100,
+ slots: { default: 'qcType' },
+ },
+ {
+ field: 'defectLevel',
+ title: '缂洪櫡绛夌骇',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_DEFECT_LEVEL },
+ },
+ },
+ {
+ field: 'unqualifiedQuantity',
+ title: '涓嶅悎鏍兼暟閲�',
+ width: 110,
+ },
+ {
+ field: 'disposition',
+ title: '澶勭疆鏂瑰紡',
+ width: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_NCR_DISPOSITION },
+ },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_NCR_STATUS },
+ },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 220,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/mes/qc/ncr/index.vue b/src/views/mes/qc/ncr/index.vue
new file mode 100644
index 0000000..6204415
--- /dev/null
+++ b/src/views/mes/qc/ncr/index.vue
@@ -0,0 +1,339 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesQcNcrApi } from '#/api/mes/qc/ncr';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { useVbenForm } from '#/adapter/form';
+import {
+ closeNcr,
+ deleteNcr,
+ disposeNcr,
+ exportNcr,
+ getNcrPage,
+ reviewNcr,
+} from '#/api/mes/qc/ncr';
+import { $t } from '#/locales';
+
+import {
+ NCR_STATUS,
+ QC_TYPE_OPTIONS,
+ useCloseFormSchema,
+ useDisposeFormSchema,
+ useGridColumns,
+ useGridFormSchema,
+ useReviewFormSchema,
+} from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'NcrIndex' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 璇勫寮圭獥 */
+const [ReviewForm, reviewFormApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ formItemClass: 'col-span-2',
+ labelWidth: 120,
+ },
+ layout: 'horizontal',
+ schema: useReviewFormSchema(),
+ showDefaultActions: false,
+});
+const [ReviewModal, reviewModalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await reviewFormApi.validate();
+ if (!valid) return;
+ reviewModalApi.lock();
+ try {
+ await reviewNcr(
+ (await reviewFormApi.getValues()) as {
+ defectLevel?: number;
+ disposition: number;
+ id: number;
+ responsibleDeptId?: number;
+ reviewOpinion?: string;
+ },
+ );
+ await reviewModalApi.close();
+ message.success('璇勫鎴愬姛');
+ handleRefresh();
+ } finally {
+ reviewModalApi.unlock();
+ }
+ },
+ onOpenChange(isOpen: boolean) {
+ if (!isOpen) return;
+ const data = reviewModalApi.getData<MesQcNcrApi.Ncr>();
+ reviewFormApi.setValues({ id: data?.id });
+ },
+});
+
+/** 澶勭疆寮圭獥 */
+const [DisposeForm, disposeFormApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ formItemClass: 'col-span-2',
+ labelWidth: 120,
+ },
+ layout: 'horizontal',
+ schema: useDisposeFormSchema(),
+ showDefaultActions: false,
+});
+const [DisposeModal, disposeModalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await disposeFormApi.validate();
+ if (!valid) return;
+ disposeModalApi.lock();
+ try {
+ await disposeNcr(
+ (await disposeFormApi.getValues()) as {
+ handleResult?: string;
+ handlerUserId?: number;
+ id: number;
+ },
+ );
+ await disposeModalApi.close();
+ message.success('澶勭疆鎴愬姛');
+ handleRefresh();
+ } finally {
+ disposeModalApi.unlock();
+ }
+ },
+ onOpenChange(isOpen: boolean) {
+ if (!isOpen) return;
+ const data = disposeModalApi.getData<MesQcNcrApi.Ncr>();
+ disposeFormApi.setValues({ id: data?.id });
+ },
+});
+
+/** 鍏抽棴寮圭獥 */
+const [CloseForm, closeFormApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ formItemClass: 'col-span-2',
+ labelWidth: 120,
+ },
+ layout: 'horizontal',
+ schema: useCloseFormSchema(),
+ showDefaultActions: false,
+});
+const [CloseModal, closeModalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await closeFormApi.validate();
+ if (!valid) return;
+ closeModalApi.lock();
+ try {
+ await closeNcr(
+ (await closeFormApi.getValues()) as {
+ closeOpinion?: string;
+ id: number;
+ },
+ );
+ await closeModalApi.close();
+ message.success('鍏抽棴鎴愬姛');
+ handleRefresh();
+ } finally {
+ closeModalApi.unlock();
+ }
+ },
+ onOpenChange(isOpen: boolean) {
+ if (!isOpen) return;
+ const data = closeModalApi.getData<MesQcNcrApi.Ncr>();
+ closeFormApi.setValues({ id: data?.id });
+ },
+});
+
+function getQcTypeLabel(type?: number): string {
+ return QC_TYPE_OPTIONS.find((o) => o.value === type)?.label || '';
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓 */
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+/** 缂栬緫 */
+function handleEdit(row: MesQcNcrApi.Ncr) {
+ formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎 */
+async function handleDelete(row: MesQcNcrApi.Ncr) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteNcr(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 璇勫 */
+function handleReview(row: MesQcNcrApi.Ncr) {
+ reviewModalApi.setData(row).open();
+}
+
+/** 澶勭疆 */
+function handleDispose(row: MesQcNcrApi.Ncr) {
+ disposeModalApi.setData(row).open();
+}
+
+/** 鍏抽棴 */
+function handleClose(row: MesQcNcrApi.Ncr) {
+ closeModalApi.setData(row).open();
+}
+
+/** 瀵煎嚭 */
+async function handleExport() {
+ const data = await exportNcr(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '涓嶅悎鏍煎搧澶勭悊鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getNcrPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesQcNcrApi.Ncr>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <ReviewModal title="璇勫涓嶅悎鏍煎搧澶勭悊鍗�" class="w-2/5">
+ <ReviewForm class="mx-4" />
+ </ReviewModal>
+ <DisposeModal title="澶勭疆涓嶅悎鏍煎搧澶勭悊鍗�" class="w-2/5">
+ <DisposeForm class="mx-4" />
+ </DisposeModal>
+ <CloseModal title="鍏抽棴涓嶅悎鏍煎搧澶勭悊鍗�" class="w-2/5">
+ <CloseForm class="mx-4" />
+ </CloseModal>
+
+ <Grid table-title="涓嶅悎鏍煎搧澶勭悊">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['涓嶅悎鏍煎搧澶勭悊鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:qc-ncr:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:qc-ncr:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #qcType="{ row }">
+ <Tag v-if="row.qcType !== undefined && row.qcType !== null">
+ {{ getQcTypeLabel(row.qcType) }}
+ </Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ v-if="row.status === NCR_STATUS.PENDING_REVIEW"
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:qc-ncr:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:qc-ncr:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '璇勫',
+ type: 'link',
+ icon: 'lucide:clipboard-check',
+ auth: ['mes:qc-ncr:review'],
+ onClick: handleReview.bind(null, row),
+ },
+ ]"
+ />
+ <TableAction
+ v-else-if="row.status === NCR_STATUS.REVIEWED"
+ :actions="[
+ {
+ label: '澶勭疆',
+ type: 'link',
+ icon: 'lucide:play',
+ auth: ['mes:qc-ncr:dispose'],
+ onClick: handleDispose.bind(null, row),
+ },
+ ]"
+ />
+ <TableAction
+ v-else-if="row.status === NCR_STATUS.DISPOSED"
+ :actions="[
+ {
+ label: '鍏抽棴',
+ type: 'link',
+ icon: 'lucide:check-circle',
+ auth: ['mes:qc-ncr:close'],
+ onClick: handleClose.bind(null, row),
+ },
+ ]"
+ />
+ <span v-else class="text-gray-400">--</span>
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/mes/qc/ncr/modules/form.vue b/src/views/mes/qc/ncr/modules/form.vue
new file mode 100644
index 0000000..a4a6cdc
--- /dev/null
+++ b/src/views/mes/qc/ncr/modules/form.vue
@@ -0,0 +1,81 @@
+<script lang="ts" setup>
+import type { MesQcNcrApi } from '#/api/mes/qc/ncr';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createNcr, getNcr, updateNcr } from '#/api/mes/qc/ncr';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'NcrForm' });
+
+const emit = defineEmits(['success']);
+const formData = ref<MesQcNcrApi.Ncr>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['涓嶅悎鏍煎搧澶勭悊鍗�'])
+ : $t('ui.actionTitle.create', ['涓嶅悎鏍煎搧澶勭悊鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-2',
+ labelWidth: 120,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as MesQcNcrApi.Ncr;
+ try {
+ await (formData.value?.id ? updateNcr(data) : createNcr(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;
+ }
+ formApi.setState({ schema: useFormSchema(formApi) });
+ const data = modalApi.getData<MesQcNcrApi.Ncr>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getNcr(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/qc/oqc/data.ts b/src/views/mes/qc/oqc/data.ts
index 62ef238..0183a35 100644
--- a/src/views/mes/qc/oqc/data.ts
+++ b/src/views/mes/qc/oqc/data.ts
@@ -233,6 +233,7 @@
placeholder: '璇疯緭鍏ヤ笉鍚堟牸鍝佹暟閲�',
},
rules: 'required',
+ help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
},
{
fieldName: 'inspectorUserId',
diff --git a/src/views/mes/qc/oqc/modules/form.vue b/src/views/mes/qc/oqc/modules/form.vue
index b66de48..e9e7258 100644
--- a/src/views/mes/qc/oqc/modules/form.vue
+++ b/src/views/mes/qc/oqc/modules/form.vue
@@ -6,7 +6,7 @@
import { computed, ref } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
@@ -69,6 +69,13 @@
return false;
}
const data = (await formApi.getValues()) as MesQcOqcApi.Oqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return false;
+ }
if (formData.value?.id) {
await updateOqc({ ...data, id: formData.value.id });
formData.value = { ...formData.value, ...data };
@@ -107,6 +114,13 @@
const current = JSON.stringify(await formApi.getValues());
if (current !== originalSnapshot.value) {
const data = (await formApi.getValues()) as MesQcOqcApi.Oqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return;
+ }
await updateOqc({ ...data, id });
formData.value = { ...formData.value, ...data };
originalSnapshot.value = current;
diff --git a/src/views/mes/qc/rqc/data.ts b/src/views/mes/qc/rqc/data.ts
index 4ea4739..92d5e1e 100644
--- a/src/views/mes/qc/rqc/data.ts
+++ b/src/views/mes/qc/rqc/data.ts
@@ -210,6 +210,7 @@
precision: 2,
},
rules: 'required',
+ help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
},
{
fieldName: 'inspectDate',
diff --git a/src/views/mes/qc/rqc/modules/form.vue b/src/views/mes/qc/rqc/modules/form.vue
index 96ffd82..b931684 100644
--- a/src/views/mes/qc/rqc/modules/form.vue
+++ b/src/views/mes/qc/rqc/modules/form.vue
@@ -6,7 +6,7 @@
import { computed, ref } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
@@ -69,6 +69,13 @@
return false;
}
const data = (await formApi.getValues()) as MesQcRqcApi.Rqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return false;
+ }
if (formData.value?.id) {
await updateRqc({ ...data, id: formData.value.id });
formData.value = { ...formData.value, ...data };
@@ -107,6 +114,13 @@
const current = JSON.stringify(await formApi.getValues());
if (current !== originalSnapshot.value) {
const data = (await formApi.getValues()) as MesQcRqcApi.Rqc;
+ if (
+ (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+ data.checkResult === MesQcCheckResultEnum.FAIL
+ ) {
+ message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+ return;
+ }
await updateRqc({ ...data, id });
formData.value = { ...formData.value, ...data };
originalSnapshot.value = current;
diff --git a/src/views/srm/enums.ts b/src/views/srm/enums.ts
index 4d014f1..6077634 100644
--- a/src/views/srm/enums.ts
+++ b/src/views/srm/enums.ts
@@ -172,7 +172,7 @@
labels: { 1: '鍏紑鎷涙爣', 2: '閭�璇锋嫑鏍�', 3: '绔炰簤鎬ц皥鍒�' },
};
-/** 鎷涙爣椤圭洰鐘舵�� 鈥� 瀵归綈 TenderStatusEnum (0=鑽夌 1=鍙戝竷涓� 2=鎶曟爣涓� 3=璇勬爣涓� 4=宸插畾鏍� 5=宸插叧闂�) */
+/** 鎷涙爣椤圭洰鐘舵�� 鈥� 瀵归綈 TenderStatusEnum (0=鑽夌 1=鍙戝竷涓� 2=鎶曟爣涓� 3=璇勬爣涓� 4=宸插畾鏍� 5=宸插叧闂� 10=姣斾环涓� 20=宸茬‘璁�) */
export const TENDER_STATUS = {
DRAFT: 0,
PUBLISHED: 1,
@@ -180,6 +180,8 @@
EVALUATING: 3,
AWARDED: 4,
CLOSED: 5,
+ PRICE_COMPARING: 10,
+ CONFIRMED: 20,
} as const;
export const TENDER_STATUS_OPTIONS: EnumOption[] = [
@@ -189,6 +191,8 @@
{ label: '璇勬爣涓�', value: 3 },
{ label: '宸插畾鏍�', value: 4 },
{ label: '宸插叧闂�', value: 5 },
+ { label: '姣斾环涓�', value: 10 },
+ { label: '宸茬‘璁�', value: 20 },
];
export const TENDER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
@@ -199,8 +203,10 @@
{ value: 3, color: 'orange' },
{ value: 4, color: 'green' },
{ value: 5, color: 'red' },
+ { value: 10, color: 'purple' },
+ { value: 20, color: 'green' },
],
- labels: { 0: '鑽夌', 1: '鍙戝竷涓�', 2: '鎶曟爣涓�', 3: '璇勬爣涓�', 4: '宸插畾鏍�', 5: '宸插叧闂�' },
+ labels: { 0: '鑽夌', 1: '鍙戝竷涓�', 2: '鎶曟爣涓�', 3: '璇勬爣涓�', 4: '宸插畾鏍�', 5: '宸插叧闂�', 10: '姣斾环涓�', 20: '宸茬‘璁�' },
};
/** 鎷涙爣鐘舵�� Ant Design Tag 棰滆壊鏄犲皠 */
@@ -211,6 +217,25 @@
[TENDER_STATUS.EVALUATING]: 'orange',
[TENDER_STATUS.AWARDED]: 'green',
[TENDER_STATUS.CLOSED]: 'red',
+ [TENDER_STATUS.PRICE_COMPARING]: 'purple',
+ [TENDER_STATUS.CONFIRMED]: 'green',
+};
+
+// ========== 鎷涙姇鏍囨ā寮� 鈥� FULL=瀹屾暣鐗� SIMPLE=绠�鏄撶増 ==========
+
+export const BIDDING_MODE = {
+ FULL: 'FULL',
+ SIMPLE: 'SIMPLE',
+} as const;
+
+export const BIDDING_MODE_OPTIONS: EnumOption[] = [
+ { label: '瀹屾暣鐗�', value: 'FULL' },
+ { label: '绠�鏄撶増', value: 'SIMPLE' },
+];
+
+export const BIDDING_MODE_MAP: Record<string, string> = {
+ FULL: '瀹屾暣鐗�',
+ SIMPLE: '绠�鏄撶増',
};
// ========== 鎶曟爣 鈥� 瀵归綈 BidStatusEnum (0=鎶ュ悕 10=宸叉姇鏍� 20=鎾ゆ爣 30=涓爣 40=鏈腑鏍�) ==========
diff --git a/src/views/srm/tender/config/index.vue b/src/views/srm/tender/config/index.vue
new file mode 100644
index 0000000..8ece764
--- /dev/null
+++ b/src/views/srm/tender/config/index.vue
@@ -0,0 +1,65 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { Page } from '@vben/common-ui';
+
+import { message, Radio, Button, Card, Spin } from 'ant-design-vue';
+
+import { getTenderConfig, saveTenderConfig } from '#/api/srm/tender-config';
+import { BIDDING_MODE, BIDDING_MODE_OPTIONS } from '#/views/srm/enums';
+
+defineOptions({ name: 'SrmTenderConfig' });
+
+const loading = ref(false);
+const saving = ref(false);
+const defaultBiddingMode = ref<string>(BIDDING_MODE.FULL);
+
+async function loadConfig() {
+ loading.value = true;
+ try {
+ const res = await getTenderConfig();
+ if (res?.defaultBiddingMode) {
+ defaultBiddingMode.value = res.defaultBiddingMode;
+ }
+ } finally {
+ loading.value = false;
+ }
+}
+
+async function handleSave() {
+ saving.value = true;
+ try {
+ await saveTenderConfig({ defaultBiddingMode: defaultBiddingMode.value });
+ message.success('淇濆瓨鎴愬姛');
+ await loadConfig();
+ } finally {
+ saving.value = false;
+ }
+}
+
+loadConfig();
+</script>
+
+<template>
+ <Page>
+ <Spin :spinning="loading">
+ <Card title="鎷涙姇鏍囬粯璁ゆā寮忛厤缃�" class="max-w-lg">
+ <div class="mb-4 text-gray-500 text-sm">
+ 璁剧疆鏂板缓鎷涙爣椤圭洰鏃剁殑榛樿鎷涙姇鏍囨ā寮忥紝鐢ㄦ埛鍦ㄥ垱寤洪」鐩椂浠嶅彲鎵嬪姩鏇存敼銆�
+ </div>
+ <div class="flex items-center gap-4">
+ <span class="font-medium">榛樿妯″紡锛�</span>
+ <Radio.Group
+ v-model:value="defaultBiddingMode"
+ :options="BIDDING_MODE_OPTIONS"
+ />
+ </div>
+ <div class="mt-6">
+ <Button type="primary" :loading="saving" @click="handleSave">
+ 淇濆瓨
+ </Button>
+ </div>
+ </Card>
+ </Spin>
+ </Page>
+</template>
diff --git a/src/views/srm/tender/data.ts b/src/views/srm/tender/data.ts
index c06e4dd..a3b1005 100644
--- a/src/views/srm/tender/data.ts
+++ b/src/views/srm/tender/data.ts
@@ -2,6 +2,8 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import {
+ BIDDING_MODE_MAP,
+ BIDDING_MODE_OPTIONS,
TENDER_STATUS_CELLTAG,
TENDER_STATUS_OPTIONS,
TENDER_TYPE_CELLTAG,
@@ -146,6 +148,25 @@
},
},
{
+ fieldName: 'biddingMode',
+ label: '鎷涙姇鏍囨ā寮�',
+ component: 'Select',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鎷涙姇鏍囨ā寮�',
+ options: BIDDING_MODE_OPTIONS,
+ },
+ dependencies: {
+ triggerFields: ['_partial', 'id'],
+ show: (values) => !values._partial,
+ componentProps: (values) => ({
+ disabled: !!values.id,
+ placeholder: '璇烽�夋嫨鎷涙姇鏍囨ā寮�',
+ options: BIDDING_MODE_OPTIONS,
+ }),
+ },
+ },
+ {
fieldName: 'tenderDesc',
label: '鎷涙爣璇存槑',
component: 'Textarea',
@@ -197,6 +218,16 @@
options: TENDER_STATUS_OPTIONS,
},
},
+ {
+ fieldName: 'biddingMode',
+ label: '鎷涙姇鏍囨ā寮�',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鎷涙姇鏍囨ā寮�',
+ allowClear: true,
+ options: BIDDING_MODE_OPTIONS,
+ },
+ },
];
}
@@ -217,6 +248,12 @@
minWidth: 100,
cellRender: { name: 'CellTag', props: TENDER_STATUS_CELLTAG },
},
+ {
+ field: 'biddingMode',
+ title: '鎷涙爣妯″紡',
+ minWidth: 100,
+ formatter: ({ cellValue }) => BIDDING_MODE_MAP[cellValue] || cellValue,
+ },
{ field: 'budgetAmount', title: '棰勭畻閲戦', minWidth: 120 },
{ field: 'purchaseOrg', title: '閲囪喘缁勭粐', minWidth: 100 },
{ field: 'startTime', title: '寮�濮嬫椂闂�', minWidth: 160 },
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index 4dfbeda..7b2f1de 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -17,6 +17,7 @@
createBidOpen, getBidOpen,
getEvaluationList, createBidEvaluation, updateBidEvaluation, calculateRanking,
createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder,
+ confirmTenderProject,
} from '#/api/srm/tender';
import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getItemSimpleList } from '#/api/mdm/item';
@@ -31,6 +32,7 @@
AWARD_STATUS,
AWARD_STATUS_MAP,
getLabel,
+ BIDDING_MODE,
} from '../enums';
defineOptions({ name: 'SrmTenderDetail' });
@@ -318,6 +320,36 @@
loadProject();
}
+// ========== 绠�鏄撴ā寮忥細纭涓爣 ==========
+const confirmBidId = ref<number>();
+const confirmModalVisible = ref(false);
+
+async function handleConfirmBidAsWinner(bidId: number) {
+ await confirmTenderProject({ id: projectId, bidId });
+ message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+ loadProject();
+ loadBids();
+ loadAward();
+}
+
+function openSimpleConfirmModal() {
+ confirmBidId.value = undefined;
+ confirmModalVisible.value = true;
+}
+
+async function handleSimpleConfirm() {
+ if (!confirmBidId.value) {
+ message.warning('璇烽�夋嫨涓爣鐨勬姇鏍囪褰�');
+ return;
+ }
+ await confirmTenderProject({ id: projectId, bidId: confirmBidId.value });
+ message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+ confirmModalVisible.value = false;
+ loadProject();
+ loadBids();
+ loadAward();
+}
+
// ========== 鍒濆鍖� ==========
loadProject();
loadMaterials();
@@ -344,23 +376,37 @@
const canApproveAward = computed(() => award.value?.awardStatus === AWARD_STATUS.PENDING_APPROVAL);
const canGeneratePO = computed(() => award.value?.awardStatus === AWARD_STATUS.AWARDED);
-const stepItems = [
- { title: '鑽夌', status: TENDER_STATUS.DRAFT },
- { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
- { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
- { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
- { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
-];
+// 绠�鏄撴ā寮�
+const isSimpleMode = computed(() => project.value?.biddingMode === BIDDING_MODE.SIMPLE);
+const isPriceComparing = computed(() => project.value?.tenderStatus === TENDER_STATUS.PRICE_COMPARING);
+
+const stepItems = computed(() => {
+ if (isSimpleMode.value) {
+ return [
+ { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+ { title: '姣斾环涓�', status: TENDER_STATUS.PRICE_COMPARING },
+ { title: '宸茬‘璁�', status: TENDER_STATUS.CONFIRMED },
+ ];
+ }
+ return [
+ { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+ { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
+ { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
+ { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
+ { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
+ ];
+});
const currentStep = computed(() => {
const s = project.value?.tenderStatus ?? 0;
- if (s === TENDER_STATUS.CLOSED) return 4;
- const idx = stepItems.findIndex((item) => item.status >= s);
+ const items = stepItems.value;
+ if (s === TENDER_STATUS.CLOSED) return items.length - 1;
+ const idx = items.findIndex((item) => item.status >= s);
return idx === -1 ? 0 : idx;
});
const stepStatus = computed(() => {
const s = project.value?.tenderStatus;
if (s === TENDER_STATUS.CLOSED) return 'error';
- if (s === TENDER_STATUS.AWARDED) return 'finish';
+ if (s === TENDER_STATUS.AWARDED || s === TENDER_STATUS.CONFIRMED) return 'finish';
return 'process';
});
@@ -395,7 +441,7 @@
{ title: '鎶曟爣閲戦', dataIndex: 'bidTotalAmount', width: 120 },
{ title: '鎶曟爣鏃堕棿', dataIndex: 'bidTime', width: 160 },
{ title: '鐘舵��', dataIndex: 'bidStatus', width: 100, customRender: ({ text }: any) => BID_STATUS_MAP[text] || text },
- { title: '鎿嶄綔', key: 'actions', width: 180 },
+ { title: '鎿嶄綔', key: 'actions', width: 240 },
];
const quoteColumns = [
@@ -438,7 +484,16 @@
<span class="text-gray-400 text-xs">{{ project.tenderNo }}</span>
<a-tag :color="statusColor">{{ statusLabel }}</a-tag>
<a-tag color="blue">{{ typeLabel }}</a-tag>
+ <a-tag v-if="isSimpleMode" color="purple">绠�鏄撶増</a-tag>
<div class="flex-1" />
+ <a-button
+ v-if="isPriceComparing"
+ type="primary"
+ size="small"
+ @click="openSimpleConfirmModal"
+ >
+ 纭涓爣
+ </a-button>
<a-button size="small" @click="loadProject(); loadMaterials(); loadBids(); loadEvaluations(); loadAward();">
<template #icon><IconifyIcon icon="ant-design:reload-outlined" /></template>
鍒锋柊
@@ -539,7 +594,7 @@
</template>
<div class="flex items-center justify-between mb-3">
<span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ bidCount }}</b> 涓姇鏍�</span>
- <a-button v-if="isPublishedOrBidding" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true">
+ <a-button v-if="isPublishedOrBidding || isDraft || isPriceComparing" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
鏂板鎶曟爣
</a-button>
@@ -557,7 +612,10 @@
<template v-if="column.key === 'actions'">
<a-space>
<a-button size="small" type="link" @click="loadQuotes(record.id); activeTab = 'quote'"><IconifyIcon icon="ant-design:dollar-outlined" />鎶ヤ环</a-button>
- <a-popconfirm v-if="isPublishedOrBidding && record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
+ <a-popconfirm v-if="isPriceComparing && record.bidStatus !== BID_STATUS.WITHDRAWN" title="纭閫夋嫨璇ユ姇鏍囦负涓爣锛熷皢鑷姩鐢熸垚閲囪喘璁㈠崟銆�" ok-text="纭涓爣" cancel-text="鍙栨秷" @confirm="handleConfirmBidAsWinner(record.id)">
+ <a-button size="small" type="link" style="color: #52c41a;"><IconifyIcon icon="ant-design:check-circle-outlined" />纭涓爣</a-button>
+ </a-popconfirm>
+ <a-popconfirm v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
<a-button size="small" type="link" danger><IconifyIcon icon="ant-design:close-circle-outlined" />鎾ゆ爣</a-button>
</a-popconfirm>
</a-space>
@@ -580,7 +638,7 @@
<a-button size="small" type="link" class="ml-auto" @click="activeTab = 'bid'">鍒囨崲鎶曟爣</a-button>
</div>
<div class="text-right mb-3">
- <a-button v-if="isPublishedOrBidding && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
+ <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
<template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
鏂板鎶ヤ环
</a-button>
@@ -600,7 +658,7 @@
{{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 涓湀` : '-' }}
</template>
<template v-if="column.key === 'actions'">
- <a-button v-if="isPublishedOrBidding && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+ <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
</template>
</template>
</a-table>
@@ -612,7 +670,7 @@
</a-tab-pane>
<!-- Tab4: 寮�鏍� -->
- <a-tab-pane key="bidOpen">
+ <a-tab-pane v-if="!isSimpleMode" key="bidOpen">
<template #tab>
<a-space :size="4"><IconifyIcon icon="ant-design:unlock-outlined" />寮�鏍�</a-space>
</template>
@@ -639,7 +697,7 @@
</a-tab-pane>
<!-- Tab5: 璇勬爣 -->
- <a-tab-pane key="evaluation">
+ <a-tab-pane v-if="!isSimpleMode" key="evaluation">
<template #tab>
<a-space :size="4"><IconifyIcon icon="ant-design:trophy-outlined" />璇勬爣</a-space>
</template>
@@ -685,7 +743,7 @@
</a-tab-pane>
<!-- Tab6: 瀹氭爣 -->
- <a-tab-pane key="award">
+ <a-tab-pane v-if="!isSimpleMode" key="award">
<template #tab>
<a-space :size="4"><IconifyIcon icon="ant-design:check-circle-outlined" />瀹氭爣</a-space>
</template>
@@ -886,6 +944,32 @@
</a-form>
</a-modal>
+ <!-- 绠�鏄撴ā寮忥細纭涓爣寮圭獥 -->
+ <a-modal v-model:open="confirmModalVisible" title="纭涓爣" :width="460" @ok="handleSimpleConfirm">
+ <a-form layout="vertical" class="mt-4">
+ <a-form-item label="涓爣鎶曟爣璁板綍" required>
+ <a-select
+ v-model:value="confirmBidId"
+ show-search
+ placeholder="閫夋嫨涓爣鐨勬姇鏍囪褰�"
+ :filter-option="filterOption"
+ >
+ <a-select-option
+ v-for="b in bidList.filter((i) => i.bidStatus !== BID_STATUS.WITHDRAWN)"
+ :key="b.id"
+ :value="b.id"
+ :label="`${b.supplierName} - ${b.bidNo}`"
+ >
+ {{ b.supplierName }} - {{ b.bidNo }} (楼{{ b.bidTotalAmount?.toLocaleString?.() ?? b.bidTotalAmount }})
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ <div class="text-gray-400 text-xs">
+ 纭鍚庡皢鑷姩鐢熸垚閲囪喘璁㈠崟锛岃鎿嶄綔涓嶅彲鎾ら攢銆�
+ </div>
+ </a-form>
+ </a-modal>
+
<!-- 瀹氭爣琛ㄥ崟 -->
<a-modal v-model:open="awardFormVisible" title="鍒涘缓瀹氭爣" :width="500" @ok="handleCreateAward">
<a-form layout="vertical" class="mt-4">
diff --git a/src/views/srm/tender/index.vue b/src/views/srm/tender/index.vue
index 7e09d1a..44f6ed7 100644
--- a/src/views/srm/tender/index.vue
+++ b/src/views/srm/tender/index.vue
@@ -4,24 +4,28 @@
import { Page, useVbenModal } from '@vben/common-ui';
-import { message } from 'ant-design-vue';
+import { message, Select } from 'ant-design-vue';
+import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
closeTender,
+ confirmTenderProject,
deleteTenderProject,
generatePurchaseOrder,
+ getBidListByProject,
getTenderProjectPage,
publishTender,
startBidding,
startEvaluation,
+ startPriceComparing,
} from '#/api/srm/tender';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import TenderForm from './modules/form.vue';
-import { TENDER_STATUS } from '../enums';
+import { BIDDING_MODE, TENDER_STATUS } from '../enums';
defineOptions({ name: 'SrmTender' });
@@ -88,6 +92,64 @@
handleRefresh();
}
+async function handleStartPriceComparing(row: SrmTenderApi.TenderProjectVO) {
+ await startPriceComparing(row.id!);
+ message.success('宸插紑濮嬫瘮浠�');
+ handleRefresh();
+}
+
+// 纭涓爣鐩稿叧
+const confirmTarget = ref<SrmTenderApi.TenderProjectVO | null>(null);
+const confirmBidId = ref<number>();
+const confirmBidOptions = ref<{ label: string; value: number }[]>([]);
+const confirmLoading = ref(false);
+
+async function openConfirmModal(row: SrmTenderApi.TenderProjectVO) {
+ confirmTarget.value = row;
+ confirmBidId.value = undefined;
+ confirmLoading.value = true;
+ try {
+ const bids = await getBidListByProject(row.id!);
+ confirmBidOptions.value = (bids || []).map((b: any) => ({
+ label: `${b.supplierName} - ${b.bidNo} (楼${b.bidTotalAmount ?? 0})`,
+ value: b.id!,
+ }));
+ } finally {
+ confirmLoading.value = false;
+ }
+ confirmModalApi.open();
+}
+
+async function handleConfirmBid() {
+ if (!confirmTarget.value || !confirmBidId.value) {
+ message.warning('璇烽�夋嫨涓爣渚涘簲鍟嗙殑鎶曟爣璁板綍');
+ return;
+ }
+ confirmLoading.value = true;
+ try {
+ await confirmTenderProject({
+ id: confirmTarget.value.id!,
+ bidId: confirmBidId.value,
+ });
+ message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+ confirmModalApi.close();
+ handleRefresh();
+ } finally {
+ confirmLoading.value = false;
+ }
+}
+
+const [ConfirmModal, confirmModalApi] = useVbenModal({
+ title: '纭涓爣',
+ onConfirm: handleConfirmBid,
+ onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ confirmTarget.value = null;
+ confirmBidId.value = undefined;
+ }
+ },
+});
+
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: TenderForm,
destroyOnClose: true,
@@ -121,6 +183,18 @@
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
+ <ConfirmModal>
+ <div class="flex items-center gap-2">
+ <span class="font-medium whitespace-nowrap">涓爣鎶曟爣璁板綍锛�</span>
+ <Select
+ v-model:value="confirmBidId"
+ :options="confirmBidOptions"
+ :loading="confirmLoading"
+ placeholder="璇烽�夋嫨涓爣鐨勬姇鏍囪褰�"
+ class="flex-1"
+ />
+ </div>
+ </ConfirmModal>
<Grid table-title="鎷涙爣椤圭洰鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -148,7 +222,7 @@
label: '鍙戝竷',
type: 'link',
auth: ['srm:tender-project:update'],
- ifShow: row.tenderStatus === TENDER_STATUS.DRAFT,
+ ifShow: row.tenderStatus === TENDER_STATUS.DRAFT && row.biddingMode !== BIDDING_MODE.SIMPLE,
popConfirm: {
title: '纭瑕佸彂甯冭椤圭洰鍚楋紵鍙戝竷鍚庢棤娉曚慨鏀广��',
confirm: handlePublish.bind(null, row),
@@ -167,7 +241,7 @@
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['srm:tender-project:update'],
- ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING,
+ ifShow: (row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING) && row.biddingMode !== BIDDING_MODE.SIMPLE,
onClick: handleEdit.bind(null, row),
},
{
@@ -186,7 +260,7 @@
label: '寮�濮嬫姇鏍�',
type: 'link',
auth: ['srm:tender-project:update'],
- ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED,
+ ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED && row.biddingMode !== BIDDING_MODE.SIMPLE,
popConfirm: {
title: '纭瑕佸紑濮嬫姇鏍囧悧锛�',
confirm: handleStartBidding.bind(null, row),
@@ -197,7 +271,7 @@
type: 'link',
danger: true,
auth: ['srm:tender-project:update'],
- ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING,
+ ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING || row.tenderStatus === TENDER_STATUS.PRICE_COMPARING,
popConfirm: {
title: '纭瑕佸叧闂椤圭洰鍚楋紵',
confirm: handleClose.bind(null, row),
@@ -207,7 +281,7 @@
label: '寮�濮嬭瘎鏍�',
type: 'link',
auth: ['srm:tender-project:update'],
- ifShow: row.tenderStatus === TENDER_STATUS.BIDDING,
+ ifShow: row.tenderStatus === TENDER_STATUS.BIDDING && row.biddingMode !== BIDDING_MODE.SIMPLE,
popConfirm: {
title: '纭瑕佸紑濮嬭瘎鏍囧悧锛�',
confirm: handleStartEvaluation.bind(null, row),
@@ -217,12 +291,29 @@
label: '鐢熸垚閲囪喘璁㈠崟',
type: 'link',
auth: ['srm:bid-award:update'],
- ifShow: !!row.awardId && !row.purchaseOrderNo,
+ ifShow: !!row.awardId && !row.purchaseOrderNo && row.biddingMode !== BIDDING_MODE.SIMPLE,
popConfirm: {
title: '纭瑕佺敓鎴愰噰璐鍗曞悧锛�',
confirm: handleGeneratePurchaseOrder.bind(null, row),
},
},
+ {
+ label: '寮�濮嬫瘮浠�',
+ type: 'link',
+ auth: ['srm:tender-project:update'],
+ ifShow: row.biddingMode === BIDDING_MODE.SIMPLE && row.tenderStatus === TENDER_STATUS.DRAFT,
+ popConfirm: {
+ title: '纭瑕佸紑濮嬫瘮浠峰悧锛�',
+ confirm: handleStartPriceComparing.bind(null, row),
+ },
+ },
+ {
+ label: '纭涓爣',
+ type: 'link',
+ auth: ['srm:tender-project:update'],
+ ifShow: row.biddingMode === BIDDING_MODE.SIMPLE && row.tenderStatus === TENDER_STATUS.PRICE_COMPARING,
+ onClick: openConfirmModal.bind(null, row),
+ },
]"
/>
</template>
diff --git a/src/views/srm/tender/modules/form.vue b/src/views/srm/tender/modules/form.vue
index 5812d05..bcdc8d2 100644
--- a/src/views/srm/tender/modules/form.vue
+++ b/src/views/srm/tender/modules/form.vue
@@ -14,6 +14,7 @@
updateTenderProject,
updateTenderProjectPartial,
} from '#/api/srm/tender';
+import { getTenderConfig } from '#/api/srm/tender-config';
import { $t } from '#/locales';
import { TENDER_STATUS } from '../../enums';
@@ -77,6 +78,15 @@
}
const data = modalApi.getData<SrmTenderApi.TenderProjectVO>();
if (!data || !data.id) {
+ // 鏂板缓妯″紡锛氬姞杞介厤缃幏鍙栭粯璁ゆ嫑鎶曟爣妯″紡
+ try {
+ const config = await getTenderConfig();
+ if (config?.defaultBiddingMode) {
+ await formApi.setFieldValue('biddingMode', config.defaultBiddingMode);
+ }
+ } catch {
+ // 閰嶇疆鍔犺浇澶辫触锛屼娇鐢ㄩ粯璁ゅ��
+ }
return;
}
modalApi.lock();
diff --git a/src/views/wls/stocktaking/plan/components/condition-value-input.vue b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
index d38c9de..1ad1dfc 100644
--- a/src/views/wls/stocktaking/plan/components/condition-value-input.vue
+++ b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { NumberDictDataType } from '@vben/hooks';
-import { ref, watch } from 'vue';
+import { computed, ref, watch } from 'vue';
import { DICT_TYPE, WlsStockTakingParamTypeEnum } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -24,16 +24,19 @@
defineProps<{
modelValue?: number;
type?: number;
+ value?: number;
valueCode?: string;
}>(),
{
modelValue: undefined,
type: undefined,
+ value: undefined,
valueCode: undefined,
},
);
const emit = defineEmits<{
'update:modelValue': [value?: number];
+ 'update:value': [value?: number];
valueChange: [
payload: { valueCode?: string; valueId?: number; valueName?: string },
];
@@ -44,6 +47,9 @@
'number',
) as NumberDictDataType[];
+/** 鍏煎 VbenForm 榛樿 v-model prop ('value') 鍜� Vue 3 鏍囧噯 ('modelValue') */
+const effectiveValue = computed(() => props.value ?? props.modelValue);
+
const locationWarehouseId = ref<number>(); // 搴撳尯閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
const areaWarehouseId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
const areaLocationId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫夊簱鍖哄悗浼犵粰搴撲綅閫夋嫨鍣�
@@ -53,6 +59,7 @@
/** 閫氱敤閫夋嫨鍣ㄥ彉鍖栵細鍥炲~鏉′欢鍊肩紪鐮併�佸悕绉� */
function handleSelectorChange(item?: any) {
emit('update:modelValue', item?.id);
+ emit('update:value', item?.id);
emit('valueChange', {
valueId: item?.id,
valueCode: item?.code || '',
@@ -63,6 +70,7 @@
/** 鎵规閫夋嫨鍣ㄥ彉鍖� */
function handleBatchChange(batch?: any) {
emit('update:modelValue', batch?.id);
+ emit('update:value', batch?.id);
emit('valueChange', {
valueId: batch?.id,
valueCode: batch?.code || '',
@@ -75,6 +83,7 @@
qualityStatusValue.value = value;
const selected = qualityStatusOptions.find((item) => item.value === value);
emit('update:modelValue', undefined);
+ emit('update:value', undefined);
emit('valueChange', {
valueId: undefined,
valueCode: value === null ? '' : String(value),
@@ -85,6 +94,7 @@
/** 搴撳尯浠撳簱閫夋嫨鍥炶皟锛氭竻绌哄簱鍖� */
function handleLocationWarehouseChange() {
emit('update:modelValue', undefined);
+ emit('update:value', undefined);
emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
}
@@ -92,12 +102,14 @@
function handleAreaWarehouseChange() {
areaLocationId.value = undefined;
emit('update:modelValue', undefined);
+ emit('update:value', undefined);
emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
}
/** 搴撲綅搴撳尯閫夋嫨鍥炶皟锛氭竻绌哄簱浣� */
function handleAreaLocationChange() {
emit('update:modelValue', undefined);
+ emit('update:value', undefined);
emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
}
@@ -109,14 +121,14 @@
: undefined;
return;
}
- if (!props.modelValue) {
+ if (!effectiveValue.value) {
return;
}
if (props.type === WlsStockTakingParamTypeEnum.LOCATION) {
- const location = await getWarehouseLocation(props.modelValue);
+ const location = await getWarehouseLocation(effectiveValue.value);
locationWarehouseId.value = location?.warehouseId;
} else if (props.type === WlsStockTakingParamTypeEnum.AREA) {
- const area = await getWarehouseArea(props.modelValue);
+ const area = await getWarehouseArea(effectiveValue.value);
areaWarehouseId.value = area?.warehouseId;
areaLocationId.value = area?.locationId;
}
@@ -139,7 +151,7 @@
<template>
<WmWarehouseSelect
v-if="type === WlsStockTakingParamTypeEnum.WAREHOUSE"
- :model-value="modelValue"
+ :model-value="effectiveValue"
@change="handleSelectorChange"
/>
<div
@@ -153,7 +165,7 @@
/>
<WmWarehouseLocationSelect
v-if="locationWarehouseId"
- :model-value="modelValue"
+ :model-value="effectiveValue"
:warehouse-id="locationWarehouseId"
placeholder="璇烽�夋嫨搴撳尯"
@change="handleSelectorChange"
@@ -177,7 +189,7 @@
/>
<WmWarehouseAreaSelect
v-if="areaLocationId"
- :model-value="modelValue"
+ :model-value="effectiveValue"
:location-id="areaLocationId"
placeholder="璇烽�夋嫨搴撲綅"
@change="handleSelectorChange"
@@ -185,12 +197,12 @@
</div>
<MdItemSelect
v-else-if="type === WlsStockTakingParamTypeEnum.ITEM"
- :model-value="modelValue"
+ :model-value="effectiveValue"
@change="handleSelectorChange"
/>
<WmBatchSelect
v-else-if="type === WlsStockTakingParamTypeEnum.BATCH"
- :model-value="modelValue"
+ :model-value="effectiveValue"
@change="handleBatchChange"
/>
<Select
diff --git a/src/views/wls/stocktaking/plan/data.ts b/src/views/wls/stocktaking/plan/data.ts
index a8b2d91..263e3fa 100644
--- a/src/views/wls/stocktaking/plan/data.ts
+++ b/src/views/wls/stocktaking/plan/data.ts
@@ -326,8 +326,8 @@
component: markRaw(StockTakingPlanConditionValueInput),
// 鏉′欢鍊兼帶浠跺唴閮ㄦ寜鏉′欢绫诲瀷鍒囨崲閫夋嫨鍣紝浠呴�夋嫨绫诲瀷鍚庡睍绀�
dependencies: {
- triggerFields: ['type'],
- if: (values) => values.type !== null,
+ triggerFields: ['type', 'valueCode'],
+ show: (values) => values.type != null,
componentProps: (values) => ({
type: values.type,
valueCode: values.valueCode,
--
Gitblit v1.9.3