From 0558f330f7e3781b91d00ed4a12e60d9fa469d87 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 17 九月 2026 13:11:12 +0800
Subject: [PATCH] feat(mes): 增加质检指标层级结构与多批次录入功能
---
src/views/mes/qc/components/qc-report-print.vue | 325 ++++++++++++++
src/api/mes/qc/template/indicator/index.ts | 2
src/views/mes/qc/template/data.ts | 43 +
src/views/mes/qc/iqc/index.vue | 19
src/api/mes/qc/template/index.ts | 1
src/views/mes/qc/ipqc/data.ts | 51 ++
src/packages/constants/src/biz-mes-enum.ts | 6
src/views/mes/qc/iqc/data.ts | 51 ++
src/views/mes/qc/indicator/data.ts | 95 ++++
src/api/mes/qc/ipqc/index.ts | 7
src/views/mes/qc/indicatorresult/components/form.vue | 393 ++++++++++++++---
src/views/mes/qc/oqc/data.ts | 51 ++
src/api/mes/qc/rqc/index.ts | 7
src/views/mes/qc/ipqc/index.vue | 20
src/views/mes/qc/oqc/index.vue | 20
src/views/mes/qc/rqc/index.vue | 20
src/views/mes/qc/template/modules/indicator-list.vue | 24 +
src/api/mes/qc/iqc/index.ts | 7
src/api/mes/qc/oqc/index.ts | 7
src/api/mes/qc/indicatorresult/index.ts | 85 +++
src/views/mes/qc/rqc/data.ts | 51 ++
src/api/mes/qc/indicator/index.ts | 13
22 files changed, 1,214 insertions(+), 84 deletions(-)
diff --git a/src/api/mes/qc/indicator/index.ts b/src/api/mes/qc/indicator/index.ts
index 163f11d..3a0fb74 100644
--- a/src/api/mes/qc/indicator/index.ts
+++ b/src/api/mes/qc/indicator/index.ts
@@ -8,11 +8,17 @@
id?: number; // 缂栧彿
code?: string; // 妫�娴嬮」缂栫爜
name?: string; // 妫�娴嬮」鍚嶇О
+ parentId?: number; // 鐖舵寚鏍囩紪鍙凤紙0=椤剁骇锛�
+ itemType?: number; // 鏉$洰绫诲瀷锛�1=褰曞叆椤癸紝2=鍒嗙粍椤�
+ formulaText?: string; // 鍏紡灞曠ず鏂囨湰锛堟墦鍗扮敤锛屼笉鍙備笌璁$畻锛�
+ unitText?: string; // 鍗曚綅鏂囨湰锛堝 kg/m鲁銆�/%锛�
+ sortOrder?: number; // 鍚岀骇鎺掑簭鍙�
type?: number; // 妫�娴嬮」绫诲瀷
tool?: string; // 妫�娴嬪伐鍏�
resultType?: number; // 缁撴灉鍊肩被鍨�
resultSpecification?: string; // 缁撴灉鍊煎睘鎬�
remark?: string; // 澶囨敞
+ parentName?: string; // 鐖舵寚鏍囧悕绉帮紙鍒楄〃/璇︽儏鍥炲~锛�
createTime?: Date; // 鍒涘缓鏃堕棿
}
}
@@ -47,6 +53,13 @@
return requestClient.delete(`/mes/qc/indicator/delete?id=${id}`);
}
+/** 鏌ヨ璐ㄦ鎸囨爣鍏ㄩ噺鍒楄〃锛堝惈鍒嗙粍灞傜骇锛屼緵鏍戣〃/涓嬫媺浣跨敤锛� */
+export function getIndicatorList() {
+ return requestClient.get<MesQcIndicatorApi.Indicator[]>(
+ '/mes/qc/indicator/list',
+ );
+}
+
/** 瀵煎嚭璐ㄦ鎸囨爣 */
export function exportIndicator(params: any) {
return requestClient.download('/mes/qc/indicator/export-excel', { params });
diff --git a/src/api/mes/qc/indicatorresult/index.ts b/src/api/mes/qc/indicatorresult/index.ts
index dfa8a7f..6cc37f8 100644
--- a/src/api/mes/qc/indicatorresult/index.ts
+++ b/src/api/mes/qc/indicatorresult/index.ts
@@ -3,18 +3,35 @@
import { requestClient } from '#/api/request';
export namespace MesQcIndicatorResultApi {
- /** MES 妫�楠岀粨鏋滄槑缁� */
+ /** 骞宠娴嬮噺鎵规鏄庣粏锛堝悓涓�鎸囨爣鐨勪竴娆℃祴閲忥級 */
+ export interface Measure {
+ detailId?: number; // 鏄庣粏缂栧彿
+ resultId?: number; // 鍏宠仈妫�楠岀粨鏋� ID
+ measureNo?: number; // 娴嬮噺鎵规鍙凤紙浠� 1 寮�濮嬶級
+ value?: string; // 妫�娴嬪��
+ remark?: string; // 澶囨敞
+ }
+
+ /** MES 妫�楠岀粨鏋滄槑缁嗭紙鏍戝舰锛氬垎缁勯」涓嬫寕瀛愰」锛涘钩琛屾祴閲忔斁鍏� measures锛� */
export interface IndicatorResultDetail {
- id?: number; // 缂栧彿
+ id?: number; // 缂栧彿锛堢涓�鎵规槑缁� ID锛屽吋瀹规棫瀛楁锛�
resultId?: number; // 鍏宠仈妫�楠岀粨鏋� ID
indicatorId?: number; // 妫�娴嬫寚鏍� ID
- value?: string; // 妫�娴嬪�硷紙缁熶竴瀛樹负瀛楃涓诧級
+ value?: string; // 妫�娴嬪�硷紙绗竴鎵癸紝鍏煎鏃у瓧娈碉級
valueNumber?: number; // UI 鏁板�肩粦瀹氾紙鎻愪氦鍓嶈浆瀛楃涓诧級
+ measureNo?: number; // 娴嬮噺鎵规鍙凤紙鎻愪氦鐢紝榛樿 1锛�
remark?: string; // 澶囨敞
// 鍏宠仈鏌ヨ瀛楁锛堟潵鑷� indicator锛�
indicatorName?: string; // 妫�娴嬫寚鏍囧悕绉�
valueType?: number; // 璐ㄦ鍊肩被鍨�
valueSpecification?: string; // 鍊煎睘鎬�
+ parentId?: number; // 鐖舵寚鏍囩紪鍙凤紙0=椤剁骇锛�
+ itemType?: number; // 鏉$洰绫诲瀷锛�1=褰曞叆椤癸紝2=鍒嗙粍椤�
+ formulaText?: string; // 鍏紡灞曠ず鏂囨湰
+ unitText?: string; // 鍗曚綅鏂囨湰
+ sortOrder?: number; // 鍚岀骇鎺掑簭鍙�
+ measures?: Measure[]; // 骞宠娴嬮噺鎵规鍒楄〃锛堟寜 measureNo 鍗囧簭锛�
+ children?: IndicatorResultDetail[]; // 瀛愰」鍒楄〃锛堝垎缁勯」涓嬫寕锛�
}
/** MES 妫�楠岀粨鏋� */
@@ -71,3 +88,65 @@
export function deleteIndicatorResult(id: number) {
return requestClient.delete(`/mes/qc/indicator-result/delete?id=${id}`);
}
+
+// ==================== 妫�楠屾姤鍛婂崟锛堟墦鍗版暟鎹級 ====================
+
+export namespace MesQcReportApi {
+ /** 鎶ュ憡娴嬮噺鍊� */
+ export interface ReportMeasure {
+ measureNo?: number; // 娴嬮噺鎵规鍙�
+ value?: string; // 妫�娴嬪��
+ remark?: string; // 澶囨敞
+ }
+
+ /** 鎶ュ憡妫�楠岄」锛堟爲褰級 */
+ export interface ReportItem {
+ indicatorId?: number;
+ indicatorCode?: string;
+ indicatorName?: string;
+ parentId?: number;
+ itemType?: number; // 1=褰曞叆椤癸紝2=鍒嗙粍椤�
+ formulaText?: string;
+ unitText?: string;
+ sortOrder?: number;
+ checkMethod?: string; // 妫�娴嬫柟娉�
+ standardValue?: string; // 鏍囧噯鍊�
+ thresholdMin?: string; // 闃堝�间笅闄�
+ thresholdMax?: string; // 闃堝�间笂闄�
+ measures?: ReportMeasure[];
+ children?: ReportItem[];
+ }
+
+ /** 妫�楠屾姤鍛婂崟 */
+ export interface QcReport {
+ qcType?: number;
+ docCode?: string;
+ docName?: string;
+ itemCode?: string;
+ itemName?: string;
+ itemSpecification?: string;
+ batchNo?: string;
+ samplingLocation?: string;
+ certificateNo?: string;
+ executeStandard?: string;
+ samplingTime?: Date;
+ batchQuantity?: number;
+ checkResult?: number;
+ inspectorUserId?: number;
+ inspectorName?: string;
+ auditUserId?: number;
+ auditName?: string;
+ approveUserId?: number;
+ approveName?: string;
+ remark?: string;
+ measureNos?: number[]; // 鍒楀ご锛氭祴閲忔壒娆″彿鍒楄〃
+ items?: ReportItem[];
+ }
+}
+
+/** 鏌ヨ妫�楠屾姤鍛婂崟鏁版嵁锛堟墦鍗扮敤锛� */
+export function getQcReport(qcId: number, qcType: number, resultId?: number) {
+ return requestClient.get<MesQcReportApi.QcReport>('/mes/qc/indicator-result/report', {
+ params: { qcId, qcType, resultId },
+ });
+}
diff --git a/src/api/mes/qc/ipqc/index.ts b/src/api/mes/qc/ipqc/index.ts
index 72c0e49..e25a440 100644
--- a/src/api/mes/qc/ipqc/index.ts
+++ b/src/api/mes/qc/ipqc/index.ts
@@ -43,6 +43,13 @@
inspectDate?: number; // 妫�娴嬫棩鏈�
inspectorUserId?: number; // 妫�娴嬩汉鍛樼敤鎴� ID
inspectorNickname?: string; // 妫�娴嬩汉鍛樻樀绉帮紙鍏宠仈鏌ヨ锛�
+ auditUserId?: number; // 澶嶆牳浜� ID
+ auditNickname?: string; // 澶嶆牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ approveUserId?: number; // 瀹℃牳浜� ID
+ approveNickname?: string; // 瀹℃牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ samplingLocation?: string; // 閲囨牱鍦扮偣
+ certificateNo?: string; // 璇佷功缂栧彿/鍚堟牸璇佸彿
+ executeStandard?: string; // 鎵ц鏍囧噯
status?: number; // 鐘舵��
remark?: string; // 澶囨敞
}
diff --git a/src/api/mes/qc/iqc/index.ts b/src/api/mes/qc/iqc/index.ts
index cdb04cb..94b2c3d 100644
--- a/src/api/mes/qc/iqc/index.ts
+++ b/src/api/mes/qc/iqc/index.ts
@@ -37,6 +37,13 @@
inspector?: string; // 妫�娴嬩汉鍛橈紙鏄电О锛�
inspectorUserId?: number; // 妫�娴嬩汉鍛� ID
inspectorNickname?: string; // 妫�娴嬩汉鍛樻樀绉帮紙鍏宠仈鏌ヨ锛�
+ auditUserId?: number; // 澶嶆牳浜� ID
+ auditNickname?: string; // 澶嶆牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ approveUserId?: number; // 瀹℃牳浜� ID
+ approveNickname?: string; // 瀹℃牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ samplingLocation?: string; // 閲囨牱鍦扮偣
+ certificateNo?: string; // 璇佷功缂栧彿/鍚堟牸璇佸彿
+ executeStandard?: string; // 鎵ц鏍囧噯
status?: number; // 鐘舵��
remark?: string; // 澶囨敞
}
diff --git a/src/api/mes/qc/oqc/index.ts b/src/api/mes/qc/oqc/index.ts
index 793f6db..28bfe5b 100644
--- a/src/api/mes/qc/oqc/index.ts
+++ b/src/api/mes/qc/oqc/index.ts
@@ -38,6 +38,13 @@
inspectDate?: number; // 妫�娴嬫棩鏈�
inspectorUserId?: number; // 妫�娴嬩汉鍛樼敤鎴� ID
inspectorNickname?: string; // 妫�娴嬩汉鍛樻樀绉帮紙鍏宠仈鏌ヨ锛�
+ auditUserId?: number; // 澶嶆牳浜� ID
+ auditNickname?: string; // 澶嶆牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ approveUserId?: number; // 瀹℃牳浜� ID
+ approveNickname?: string; // 瀹℃牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ samplingLocation?: string; // 閲囨牱鍦扮偣
+ certificateNo?: string; // 璇佷功缂栧彿/鍚堟牸璇佸彿
+ executeStandard?: string; // 鎵ц鏍囧噯
status?: number; // 鐘舵��
remark?: string; // 澶囨敞
}
diff --git a/src/api/mes/qc/rqc/index.ts b/src/api/mes/qc/rqc/index.ts
index 60b54da..207c610 100644
--- a/src/api/mes/qc/rqc/index.ts
+++ b/src/api/mes/qc/rqc/index.ts
@@ -27,6 +27,13 @@
inspectDate?: number; // 妫�娴嬫棩鏈�
inspectorUserId?: number; // 妫�娴嬩汉鍛樼敤鎴� ID
inspectorNickname?: string; // 妫�娴嬩汉鍛樻樀绉帮紙鍏宠仈鏌ヨ锛�
+ auditUserId?: number; // 澶嶆牳浜� ID
+ auditNickname?: string; // 澶嶆牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ approveUserId?: number; // 瀹℃牳浜� ID
+ approveNickname?: string; // 瀹℃牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+ samplingLocation?: string; // 閲囨牱鍦扮偣
+ certificateNo?: string; // 璇佷功缂栧彿/鍚堟牸璇佸彿
+ executeStandard?: string; // 鎵ц鏍囧噯
status?: number; // 鐘舵��
remark?: string; // 澶囨敞
// 缂洪櫡缁熻
diff --git a/src/api/mes/qc/template/index.ts b/src/api/mes/qc/template/index.ts
index 7688644..06c8cd8 100644
--- a/src/api/mes/qc/template/index.ts
+++ b/src/api/mes/qc/template/index.ts
@@ -10,6 +10,7 @@
name?: string; // 鏂规鍚嶇О
types?: number[]; // 妫�娴嬬绫�
status?: number; // 鐘舵��
+ defaultExecuteStandard?: string; // 榛樿鎵ц鏍囧噯锛堝缓鍗曟椂甯﹀叆涓昏〃锛�
remark?: string; // 澶囨敞
createTime?: Date; // 鍒涘缓鏃堕棿
}
diff --git a/src/api/mes/qc/template/indicator/index.ts b/src/api/mes/qc/template/indicator/index.ts
index 519c86d..ff69124 100644
--- a/src/api/mes/qc/template/indicator/index.ts
+++ b/src/api/mes/qc/template/indicator/index.ts
@@ -14,6 +14,8 @@
thresholdMax?: number; // 璇樊涓婇檺
thresholdMin?: number; // 璇樊涓嬮檺
docUrl?: string; // 璇存槑鍥綰RL
+ sortOrder?: number; // 鎺掑簭鍙凤紙鏂规鍐呭睍绀洪『搴忥級
+ judgeFlag?: boolean; // 鏄惁鍙備笌鍒ゅ畾鎻愮ず锛堝垎缁�/鍙傛暟琛�=false锛�
remark?: string; // 澶囨敞
indicatorCode?: string; // 妫�娴嬮」缂栫爜锛圝OIN锛�
indicatorName?: string; // 妫�娴嬮」鍚嶇О锛圝OIN锛�
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index c84f3fc..3ad002f 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -476,6 +476,12 @@
FILE: 5,
} as const;
+/** MES 璐ㄦ鎸囨爣鏉$洰绫诲瀷鏋氫妇锛�1=褰曞叆椤癸紝2=鍒嗙粍椤癸紙瀵归綈鍚庣 MesQcIndicatorItemTypeEnum锛� */
+export const MesQcIndicatorItemType = {
+ RECORD: 1,
+ GROUP: 2,
+} as const;
+
/** MES 涓氬姟绫诲瀷甯搁噺 */
export const MesBizTypeEnum = {
// WM 浠撳簱妯″潡 [100, 200)
diff --git a/src/views/mes/qc/components/qc-report-print.vue b/src/views/mes/qc/components/qc-report-print.vue
new file mode 100644
index 0000000..a3cfb4a
--- /dev/null
+++ b/src/views/mes/qc/components/qc-report-print.vue
@@ -0,0 +1,325 @@
+<script lang="ts" setup>
+import type { MesQcReportApi } from '#/api/mes/qc/indicatorresult';
+
+import { computed, nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictLabel } from '@vben/hooks';
+import { useUserStore } from '@vben/stores';
+import { formatDateTime } from '@vben/utils';
+
+import { getQcReport } from '#/api/mes/qc/indicatorresult';
+
+/**
+ * MES 妫�楠屾姤鍛婂崟鎵撳嵃缁勪欢锛堝寲宸ユ楠岃褰曠増寮忥細琛ㄥご + 妫�楠岄」鐭╅樀 + 骞宠娴嬮噺鍒� + 绛惧瓧鍖猴級
+ *
+ * 鎵撳嵃鏍峰紡鍙傝�� erp/sale/order/modules/print.vue锛圱eleport + body class + @media print锛夈��
+ */
+defineOptions({ name: 'MesQcReportPrint' });
+
+const userStore = useUserStore();
+
+/** 鎵佸钩鍖栧悗鐨勬姤鍛婅 */
+interface PrintRow {
+ kind: 'group' | 'record';
+ indicatorId?: number;
+ name?: string;
+ unit?: string;
+ method?: string; // 鍒嗙粍=鍏紡鏂囨湰锛屽綍鍏ラ」=妫�娴嬫柟娉�
+ standard?: string; // 鏍囧噯鍊硷紙鍚槇鍊煎尯闂达級
+ values: Record<number, string>; // measureNo -> 妫�娴嬪��
+ remarks: Record<number, string>; // measureNo -> 澶囨敞
+}
+
+const report = ref<MesQcReportApi.QcReport>({});
+const measureNos = ref<number[]>([1]);
+const printTime = ref('');
+
+/** 鏍囧噯鍊煎睍绀猴細鏍囧噯鍊� + 闃堝�煎尯闂� */
+function formatStandard(item: MesQcReportApi.ReportItem) {
+ const parts: string[] = [];
+ if (item.standardValue != null) {
+ parts.push(String(item.standardValue));
+ }
+ if (item.thresholdMin != null || item.thresholdMax != null) {
+ parts.push(`${item.thresholdMin ?? ''}~${item.thresholdMax ?? ''}`);
+ }
+ return parts.join(' ');
+}
+
+/** 灏嗘楠岄」鏍戞墎骞冲寲涓烘墦鍗拌锛堝垎缁勮 + 鍏跺瓙褰曞叆琛岋紱椤剁骇褰曞叆琛岀嫭绔嬫垚琛岋級 */
+const printRows = computed<PrintRow[]>(() => {
+ const rows: PrintRow[] = [];
+ const pushRecord = (item: MesQcReportApi.ReportItem, method: string) => {
+ const values: Record<number, string> = {};
+ const remarks: Record<number, string> = {};
+ (item.measures ?? []).forEach((m) => {
+ if (m.measureNo != null) {
+ values[m.measureNo] = m.value ?? '';
+ remarks[m.measureNo] = m.remark ?? '';
+ }
+ });
+ rows.push({
+ kind: 'record',
+ indicatorId: item.indicatorId,
+ name: item.indicatorName,
+ unit: item.unitText,
+ method,
+ standard: formatStandard(item),
+ values,
+ remarks,
+ });
+ };
+ const walk = (items: MesQcReportApi.ReportItem[] = []) => {
+ items.forEach((item) => {
+ if (item.itemType === 2) {
+ rows.push({
+ kind: 'group',
+ indicatorId: item.indicatorId,
+ name: item.indicatorName,
+ method: item.formulaText,
+ values: {},
+ remarks: {},
+ });
+ walk(item.children ?? []);
+ } else {
+ pushRecord(item, item.checkMethod ?? '');
+ }
+ });
+ };
+ walk(report.value.items ?? []);
+ return rows;
+});
+
+/** 妫�娴嬬粨鏋滄枃妗堬紙瀛楀吀 mes_qc_check_result锛� */
+const checkResultText = computed(() => {
+ if (report.value.checkResult == null) {
+ return '-';
+ }
+ return (
+ getDictLabel(DICT_TYPE.MES_QC_CHECK_RESULT, report.value.checkResult) || '-'
+ );
+});
+
+function waitForPaint() {
+ return new Promise<void>((resolve) => {
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
+ });
+}
+
+function removePrintMode() {
+ document.body.classList.remove('mes-qc-report-printing');
+}
+
+/** 鎵撳嵃锛氬姞杞芥姤鍛婃暟鎹悗鍒囧埌鎵撳嵃妯″紡璋冪敤娴忚鍣ㄦ墦鍗� */
+async function print(qcId: number, qcType: number, resultId?: number) {
+ const data = await getQcReport(qcId, qcType, resultId);
+ report.value = data;
+ measureNos.value = data.measureNos?.length ? data.measureNos : [1];
+ printTime.value = formatDateTime(new Date());
+ await nextTick();
+ await waitForPaint();
+ document.body.classList.add('mes-qc-report-printing');
+ window.addEventListener('afterprint', removePrintMode, { once: true });
+ window.print();
+}
+
+defineExpose({ print });
+</script>
+
+<template>
+ <Teleport to="body">
+ <div
+ id="mesQcReportPrint"
+ class="mes-qc-report-print pointer-events-none fixed left-0 top-0 z-[-1] w-full bg-white text-[#303133] opacity-0"
+ >
+ <h2 class="m-0 mb-1 text-center text-[1.6em] font-bold leading-[1.2]">
+ {{ report.docName || '妫�楠屾姤鍛�' }}
+ </h2>
+ <div class="mb-2 text-center text-sm">
+ {{ report.itemName || '' }}
+ <span v-if="report.itemSpecification">锛弡{ report.itemSpecification }}</span>
+ <span v-if="report.executeStandard">銆�鎵ц鏍囧噯锛歿{ report.executeStandard }}</span>
+ </div>
+
+ <!-- 琛ㄥご淇℃伅鏍� -->
+ <div
+ class="mb-3 grid grid-cols-3 gap-x-6 gap-y-1 border border-solid border-[#dcdfe6] p-3 text-sm leading-[1.6]"
+ >
+ <div>妫�楠屽崟鍙凤細{{ report.docCode || '-' }}</div>
+ <div>鐗╂枡缂栫爜锛歿{ report.itemCode || '-' }}</div>
+ <div>鎵规鍙凤細{{ report.batchNo || '-' }}</div>
+ <div>閲囨牱鍦扮偣锛歿{ report.samplingLocation || '-' }}</div>
+ <div>璇佷功缂栧彿锛歿{ report.certificateNo || '-' }}</div>
+ <div>鎵归噺锛歿{ report.batchQuantity ?? '-' }}</div>
+ <div>
+ 閲囨牱/妫�楠屾椂闂达細{{ formatDateTime(report.samplingTime) || '-' }}
+ </div>
+ <div>妫�娴嬬粨璁猴細{{ checkResultText }}</div>
+ <div>鎵撳嵃鏃堕棿锛歿{ printTime || '-' }}</div>
+ </div>
+
+ <!-- 妫�楠岄」鐭╅樀 -->
+ <table class="w-full border-collapse text-[13px] leading-[1.5]">
+ <thead>
+ <tr>
+ <th class="cell bg">妫�楠岄」鐩�</th>
+ <th class="cell w-16 bg">鍗曚綅</th>
+ <th class="cell bg">鏂规硶/鍏紡</th>
+ <th class="cell w-24 bg">鏍囧噯鍊�</th>
+ <th v-for="no in measureNos" :key="`h-${no}`" class="cell w-20 bg">
+ 娴嬮噺{{ no }}
+ </th>
+ <th class="cell bg">澶囨敞</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="(row, i) in printRows" :key="row.indicatorId ?? i">
+ <template v-if="row.kind === 'group'">
+ <td class="cell group-name" colspan="3">{{ row.name }}</td>
+ <td class="cell group-formula" :colspan="measureNos.length + 2">
+ {{ row.method || '' }}
+ </td>
+ </template>
+ <template v-else>
+ <td class="cell record-name">{{ row.name }}</td>
+ <td class="cell text-center">{{ row.unit || '-' }}</td>
+ <td class="cell">{{ row.method || '-' }}</td>
+ <td class="cell text-center">{{ row.standard || '-' }}</td>
+ <td
+ v-for="no in measureNos"
+ :key="`v-${row.indicatorId}-${no}`"
+ class="cell text-center"
+ >
+ {{ row.values[no] || '' }}
+ </td>
+ <td class="cell">
+ <template v-if="measureNos.length === 1">
+ {{ row.remarks[measureNos[0]!] || '' }}
+ </template>
+ <template v-else>
+ <div v-for="no in measureNos" :key="`r-${row.indicatorId}-${no}`">
+ {{ no }}:{{ row.remarks[no] || '' }}
+ </div>
+ </template>
+ </td>
+ </template>
+ </tr>
+ <tr v-if="printRows.length === 0">
+ <td class="cell text-center" :colspan="6 + measureNos.length">
+ 鏆傛棤妫�楠岄」
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- 澶囨敞 + 绛惧瓧鍖� -->
+ <div class="mt-3 border border-solid border-[#dcdfe6] p-3 text-sm leading-[1.6]">
+ 澶囨敞锛歿{ report.remark || '-' }}
+ </div>
+ <div
+ class="mt-4 grid grid-cols-3 gap-x-10 gap-y-4 border border-dashed border-[#dcdfe6] p-4 text-sm leading-[1.8]"
+ >
+ <div>
+ 鍒嗘瀽鑰咃細{{ report.inspectorName || '-' }}
+ </div>
+ <div>
+ 澶嶆牳浜猴細{{ report.auditName || '-' }}
+ </div>
+ <div>
+ 瀹℃牳浜猴細{{ report.approveName || '-' }}
+ </div>
+ <div>
+ 绛惧瓧锛�<span class="sig"></span>
+ </div>
+ <div>
+ 绛惧瓧锛�<span class="sig"></span>
+ </div>
+ <div>
+ 绛惧瓧锛�<span class="sig"></span>
+ </div>
+ </div>
+
+ <div class="mt-4 flex justify-between text-xs text-[#909399]">
+ <div>鎵撳嵃浜哄憳锛歿{ userStore.userInfo?.nickname || '-' }}</div>
+ <div>鎵撳嵃鏃堕棿锛歿{ printTime || '-' }}</div>
+ </div>
+ </div>
+ </Teleport>
+</template>
+
+<style scoped>
+@page {
+ margin: 8mm 10mm;
+}
+
+.mes-qc-report-print {
+ .cell {
+ padding: 4px 6px;
+ text-align: left;
+ border: 1px solid #dcdfe6;
+ }
+
+ .cell.bg {
+ font-weight: bold;
+ background: #f5f7fa;
+ text-align: center;
+ }
+
+ .cell.text-center {
+ text-align: center;
+ }
+
+ .group-name {
+ font-weight: bold;
+ background: #fafafa;
+ }
+
+ .group-formula {
+ font-style: italic;
+ color: #555;
+ background: #fafafa;
+ }
+
+ .record-name {
+ padding-left: 20px;
+ }
+
+ .sig {
+ display: inline-block;
+ width: 90px;
+ border-bottom: 1px solid #303133;
+ }
+}
+
+@media print {
+ :global(body.mes-qc-report-printing) {
+ padding: 0 !important;
+ margin: 0 !important;
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+
+ :global(body.mes-qc-report-printing *) {
+ visibility: hidden !important;
+ }
+
+ :global(body.mes-qc-report-printing .mes-qc-report-print),
+ :global(body.mes-qc-report-printing .mes-qc-report-print *) {
+ visibility: visible !important;
+ }
+
+ :global(body.mes-qc-report-printing .mes-qc-report-print) {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: auto;
+ box-sizing: border-box;
+ width: 100%;
+ padding: 0 !important;
+ margin: 0 !important;
+ pointer-events: auto;
+ opacity: 1;
+ }
+}
+</style>
diff --git a/src/views/mes/qc/indicator/data.ts b/src/views/mes/qc/indicator/data.ts
index a20cc84..32c409e 100644
--- a/src/views/mes/qc/indicator/data.ts
+++ b/src/views/mes/qc/indicator/data.ts
@@ -7,12 +7,14 @@
import {
DICT_TYPE,
MesAutoCodeRuleCode,
+ MesQcIndicatorItemType,
MesQcResultValueType,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button } from 'ant-design-vue';
+import { getIndicatorList } from '#/api/mes/qc/indicator';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { QcIndicatorResultSpecificationInput } from './components';
@@ -63,6 +65,42 @@
rules: 'required',
},
{
+ fieldName: 'itemType',
+ label: '鏉$洰绫诲瀷',
+ component: 'RadioGroup',
+ defaultValue: MesQcIndicatorItemType.RECORD,
+ componentProps: {
+ options: [
+ { label: '褰曞叆椤�', value: MesQcIndicatorItemType.RECORD },
+ { label: '鍒嗙粍椤�', value: MesQcIndicatorItemType.GROUP },
+ ],
+ optionType: 'button',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'parentId',
+ label: '鎵�灞炲垎缁�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: async () => {
+ const list = await getIndicatorList();
+ // 浠呭彲閫夋嫨鍒嗙粍椤逛綔涓虹埗绾э紝涓斿垎缁勯」鏈韩涓嶅彲鍐嶆寕鐖剁骇锛堜粎涓ょ骇锛�
+ return list
+ .filter((i) => i.itemType === MesQcIndicatorItemType.GROUP)
+ .map((i) => ({ label: i.name, value: i.id }));
+ },
+ placeholder: '涓嶉�夊垯涓洪《绾ч」',
+ },
+ dependencies: {
+ triggerFields: ['itemType'],
+ // 鍒嗙粍椤瑰繀椤讳负椤剁骇锛岄殣钘忓苟娓呯┖涓婄骇
+ show: (values) => values.itemType !== MesQcIndicatorItemType.GROUP,
+ if: (values) => values.itemType !== MesQcIndicatorItemType.GROUP,
+ },
+ },
+ {
fieldName: 'type',
label: '妫�娴嬮」绫诲瀷',
component: 'Select',
@@ -74,12 +112,45 @@
rules: 'required',
},
{
+ fieldName: 'formulaText',
+ label: '鍏紡鏂囨湰',
+ component: 'Input',
+ componentProps: {
+ maxlength: 500,
+ placeholder: '濡� w3 = c 脳 V 脳 0.06005 / m 脳 100%锛堜粎灞曠ず锛屼笉鍙備笌璁$畻锛�',
+ },
+ dependencies: {
+ triggerFields: ['itemType'],
+ if: (values) => values.itemType === MesQcIndicatorItemType.GROUP,
+ },
+ },
+ {
fieldName: 'tool',
label: '妫�娴嬪伐鍏�',
component: 'Input',
componentProps: {
maxlength: 100,
placeholder: '璇疯緭鍏ユ娴嬪伐鍏�',
+ },
+ },
+ {
+ fieldName: 'unitText',
+ label: '鍗曚綅鏂囨湰',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '濡� %銆乬銆佹棤锛屼粎灞曠ず涓嶅弬涓庤绠�',
+ },
+ },
+ {
+ fieldName: 'sortOrder',
+ label: '鎺掑簭鍙�',
+ component: 'InputNumber',
+ defaultValue: 0,
+ componentProps: {
+ min: 0,
+ precision: 0,
+ placeholder: '鍚岀骇鎺掑簭锛屾暟瀛楀皬鐨勫湪鍓�',
},
},
{
@@ -95,6 +166,11 @@
placeholder: '璇烽�夋嫨缁撴灉鍊肩被鍨�',
},
rules: 'required',
+ dependencies: {
+ triggerFields: ['itemType'],
+ // 鍒嗙粍椤逛笉褰曞叆缁撴灉鍊硷紝闅愯棌骞惰烦杩囨牎楠�
+ if: (values) => values.itemType !== MesQcIndicatorItemType.GROUP,
+ },
},
{
fieldName: 'resultSpecification',
@@ -183,6 +259,19 @@
minWidth: 160,
},
{
+ field: 'itemType',
+ title: '鏉$洰绫诲瀷',
+ width: 90,
+ formatter: ({ cellValue }) =>
+ cellValue === MesQcIndicatorItemType.GROUP ? '鍒嗙粍椤�' : '褰曞叆椤�',
+ },
+ {
+ field: 'parentName',
+ title: '鎵�灞炲垎缁�',
+ width: 120,
+ formatter: ({ cellValue }) => cellValue || '-',
+ },
+ {
field: 'type',
title: '妫�娴嬮」绫诲瀷',
width: 120,
@@ -194,6 +283,12 @@
width: 140,
},
{
+ field: 'unitText',
+ title: '鍗曚綅',
+ width: 80,
+ formatter: ({ cellValue }) => cellValue || '-',
+ },
+ {
field: 'resultType',
title: '缁撴灉鍊肩被鍨�',
width: 120,
diff --git a/src/views/mes/qc/indicatorresult/components/form.vue b/src/views/mes/qc/indicatorresult/components/form.vue
index c6481b2..0dbd5a5 100644
--- a/src/views/mes/qc/indicatorresult/components/form.vue
+++ b/src/views/mes/qc/indicatorresult/components/form.vue
@@ -6,10 +6,11 @@
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
-import { MesQcResultValueType } from '@vben/constants';
+import { MesQcIndicatorItemType, MesQcResultValueType } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import {
+ Button,
Form as AForm,
Divider,
Input,
@@ -36,11 +37,36 @@
qcType: number;
}
+/** 鍙紪杈戞祴閲忔壒娆★紙鍦ㄦ帴鍙g粨鏋勪笂澧炲姞鏁板�肩粦瀹氬瓧娈碉級 */
+interface EditMeasure {
+ detailId?: number;
+ resultId?: number;
+ measureNo: number;
+ value?: string;
+ valueNumber?: number;
+ remark?: string;
+}
+
+/** 鍙紪杈戞楠岄」锛堟爲褰㈣妭鐐癸級 */
+interface EditItem {
+ indicatorId?: number;
+ indicatorName?: string;
+ valueType?: number;
+ valueSpecification?: string;
+ parentId?: number;
+ itemType?: number;
+ formulaText?: string;
+ unitText?: string;
+ sortOrder?: number;
+ measures: EditMeasure[];
+ children?: EditItem[];
+}
+
defineOptions({ name: 'QcIndicatorResultForm' });
const emit = defineEmits(['success']);
const formType = ref<FormType>('create');
-const items = ref<MesQcIndicatorResultApi.IndicatorResultDetail[]>([]);
+const roots = ref<EditItem[]>([]);
const ctxData = ref<CtxData>();
const getTitle = computed(() =>
@@ -63,6 +89,19 @@
wrapperClass: 'grid-cols-2',
});
+/** 鏄惁鍒嗙粍椤癸紙鍒嗙粍椤逛笉褰曞叆鍊硷紝浠呮壙杞藉眰绾т笌鍏紡鏂囨湰锛� */
+function isGroup(item: EditItem) {
+ return item.itemType === MesQcIndicatorItemType.GROUP;
+}
+
+/** 鏄惁鏁板�煎瀷褰曞叆锛堝喅瀹� InputNumber 涓庢枃鏈帶浠讹級 */
+function isNumeric(item: EditItem) {
+ return (
+ item.valueType === MesQcResultValueType.FLOAT ||
+ item.valueType === MesQcResultValueType.INTEGER
+ );
+}
+
/** 鑾峰彇瀛楀吀閫夐」锛坴alueSpecification 涓虹郴缁熷瓧鍏哥被鍨嬪悕锛� */
function getValueOptions(dictType?: string) {
return dictType
@@ -71,6 +110,75 @@
value: String(value),
}))
: [];
+}
+
+/** 灏嗘帴鍙h繑鍥炵殑鏄庣粏鏍戣浆鎹负鍙紪杈戠粨鏋勶細鍒嗙粍閫掑綊銆佸綍鍏ラ」淇濊瘉鑷冲皯涓�涓祴閲忔壒娆� */
+function toEditItems(
+ items: MesQcIndicatorResultApi.IndicatorResultDetail[] = [],
+): EditItem[] {
+ return items.map((item) => {
+ const node: EditItem = {
+ indicatorId: item.indicatorId,
+ indicatorName: item.indicatorName,
+ valueType: item.valueType,
+ valueSpecification: item.valueSpecification,
+ parentId: item.parentId,
+ itemType: item.itemType,
+ formulaText: item.formulaText,
+ unitText: item.unitText,
+ sortOrder: item.sortOrder,
+ measures: [],
+ children: item.children ? toEditItems(item.children) : undefined,
+ };
+ if (isGroup(node)) {
+ return node;
+ }
+ const measures = (item.measures ?? []).map((m) => ({
+ detailId: m.detailId,
+ resultId: m.resultId,
+ measureNo: m.measureNo ?? 1,
+ value: m.value,
+ valueNumber:
+ isNumeric(node) && m.value !== null && m.value !== undefined
+ ? Number(m.value)
+ : undefined,
+ remark: m.remark,
+ }));
+ node.measures = measures.length > 0 ? measures : [{ measureNo: 1 }];
+ return node;
+ });
+}
+
+/** 鏀堕泦鎵�鏈夊綍鍏ラ」锛堝彾瀛愶級鐢ㄤ簬鎻愪氦涓庡睍绀虹粺璁� */
+function collectLeaves(nodes: EditItem[]): EditItem[] {
+ const leaves: EditItem[] = [];
+ const walk = (list: EditItem[]) => {
+ list.forEach((n) => {
+ if (isGroup(n)) {
+ walk(n.children ?? []);
+ } else {
+ leaves.push(n);
+ }
+ });
+ };
+ walk(nodes);
+ return leaves;
+}
+
+/** 鏂板涓�涓祴閲忔壒娆� */
+function addMeasure(item: EditItem) {
+ const nextNo =
+ item.measures.reduce((max, m) => Math.max(max, m.measureNo ?? 0), 0) + 1;
+ item.measures.push({ measureNo: nextNo });
+}
+
+/** 鍒犻櫎鎸囧畾娴嬮噺鎵规锛堣嚦灏戜繚鐣欎竴涓級 */
+function removeMeasure(item: EditItem, index: number) {
+ if (item.measures.length <= 1) {
+ message.warning('鑷冲皯淇濈暀涓�涓祴閲忔壒娆�');
+ return;
+ }
+ item.measures.splice(index, 1);
}
const [Modal, modalApi] = useVbenModal({
@@ -83,25 +191,26 @@
return;
}
modalApi.lock();
- // 鎻愪氦琛ㄥ崟
const head =
(await formApi.getValues()) as MesQcIndicatorResultApi.IndicatorResult;
- const submitItems = items.value.map((item) => {
- const submit: MesQcIndicatorResultApi.IndicatorResultDetail = {
- id: item.id,
- indicatorId: item.indicatorId,
- remark: item.remark,
- };
- if (
- item.valueType === MesQcResultValueType.FLOAT ||
- item.valueType === MesQcResultValueType.INTEGER
- ) {
- submit.value =
- item.valueNumber === null ? undefined : String(item.valueNumber);
- } else {
- submit.value = item.value;
- }
- return submit;
+ // 灞曞紑锛氭瘡涓綍鍏ラ」鐨勬瘡涓祴閲忔壒娆� 鈫� 涓�鏉℃槑缁�
+ const submitItems: MesQcIndicatorResultApi.IndicatorResultDetail[] = [];
+ collectLeaves(roots.value).forEach((item) => {
+ item.measures.forEach((measure) => {
+ const value = isNumeric(item)
+ ? measure.valueNumber === null || measure.valueNumber === undefined
+ ? undefined
+ : String(measure.valueNumber)
+ : measure.value;
+ submitItems.push({
+ id: measure.detailId,
+ resultId: measure.resultId,
+ indicatorId: item.indicatorId,
+ measureNo: measure.measureNo,
+ value,
+ remark: measure.remark,
+ });
+ });
});
const payload: MesQcIndicatorResultApi.IndicatorResult = {
...head,
@@ -117,7 +226,6 @@
await createIndicatorResult(payload);
message.success($t('common.createSuccess'));
}
- // 鍏抽棴骞舵彁绀�
await modalApi.close();
emit('success');
} finally {
@@ -127,11 +235,10 @@
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
ctxData.value = undefined;
- items.value = [];
+ roots.value = [];
return;
}
formApi.setState({ schema: useQcIndicatorResultFormSchema(formApi) });
- // 鍔犺浇鏁版嵁
const data = modalApi.getData<CtxData>();
ctxData.value = data;
formType.value = data.formType;
@@ -142,17 +249,7 @@
data.qcType,
data.id,
);
- // 鍥炲~鏁板�煎瓧娈碉紙鐢ㄤ簬 InputNumber 缁戝畾锛�
- items.value = (detail.items ?? []).map((item) => ({
- ...item,
- valueNumber:
- (item.valueType === MesQcResultValueType.FLOAT ||
- item.valueType === MesQcResultValueType.INTEGER) &&
- item.value !== null
- ? Number(item.value)
- : undefined,
- }));
- // 璁剧疆鍒� values
+ roots.value = toEditItems(detail.items);
await formApi.setValues({
id: detail.id,
code: detail.code,
@@ -164,6 +261,18 @@
}
},
});
+
+/** 鍗曚釜娴嬮噺鍊艰緭鍏ユ帶浠剁粦瀹氾紙鏁板�煎瀷璧� valueNumber锛屽叾浣欒蛋 value锛� */
+function bindMeasure(item: EditItem, measure: EditMeasure) {
+ return isNumeric(item) ? measure.valueNumber : measure.value;
+}
+function onMeasureInput(item: EditItem, measure: EditMeasure, v: any) {
+ if (isNumeric(item)) {
+ measure.valueNumber = v === null ? undefined : Number(v);
+ } else {
+ measure.value = v;
+ }
+}
</script>
<template>
@@ -171,48 +280,182 @@
<div class="px-4">
<Form />
<Divider>妫�娴嬪��</Divider>
- <!-- 妫�娴嬪�兼帶浠剁敱姣忚 valueType 椹卞姩锛岄�愯娓叉煋锛欶LOAT/INTEGER 璧� InputNumber锛孌ICT 璧� Select 骞舵寜 valueSpecification 瑙f瀽閫夐」 -->
- <AForm :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
- <div
- v-for="(item, index) in items"
- :key="item.indicatorId ?? index"
- class="mb-2"
- >
- <AForm.Item
- :label="`妫�娴嬮」${index + 1}锛�${item.indicatorName ?? ''}`"
- >
- <InputNumber
- v-if="
- item.valueType === MesQcResultValueType.FLOAT ||
- item.valueType === MesQcResultValueType.INTEGER
- "
- v-model:value="item.valueNumber"
- :precision="item.valueType === MesQcResultValueType.FLOAT ? 4 : 0"
- class="!w-full"
- placeholder="璇疯緭鍏�"
- />
- <Textarea
- v-else-if="item.valueType === MesQcResultValueType.TEXT"
- v-model:value="item.value"
- :rows="2"
- placeholder="璇疯緭鍏ユ娴嬪��"
- />
- <Select
- v-else-if="item.valueType === MesQcResultValueType.DICT"
- v-model:value="item.value"
- allow-clear
- :options="getValueOptions(item.valueSpecification)"
- placeholder="璇烽�夋嫨"
- />
- <Input
- v-else-if="item.valueType === MesQcResultValueType.FILE"
- v-model:value="item.value"
- placeholder="璇疯緭鍏ユ枃浠跺湴鍧�"
- />
- <Input v-else v-model:value="item.value" placeholder="璇疯緭鍏�" />
- </AForm.Item>
- </div>
- </AForm>
+ <div v-if="roots.length === 0" class="py-4 text-center text-gray-400">
+ 璇ヨ川妫�鍗曟湭閰嶇疆妫�娴嬫寚鏍�
+ </div>
+ <div v-else class="qc-result-tree">
+ <template v-for="(node, ni) in roots" :key="node.indicatorId ?? ni">
+ <!-- 鍒嗙粍椤癸細鏍囬琛� + 鍏紡鏂囨湰锛屼笅鎸傚瓙褰曞叆椤� -->
+ <div v-if="isGroup(node)">
+ <div
+ class="mb-2 flex items-center justify-between rounded bg-gray-100 px-3 py-1.5 font-medium"
+ >
+ <span>{{ node.indicatorName }}</span>
+ <span v-if="node.formulaText" class="text-xs text-gray-500">
+ {{ node.formulaText }}
+ </span>
+ </div>
+ <div
+ v-for="(child, ci) in node.children ?? []"
+ :key="child.indicatorId ?? ci"
+ class="mb-3 ml-4"
+ >
+ <div class="mb-1 flex items-center justify-between">
+ <span class="font-medium">
+ {{ child.indicatorName }}
+ <span v-if="child.unitText" class="text-xs text-gray-500">
+ 锛坽{ child.unitText }}锛�
+ </span>
+ </span>
+ <Button
+ size="small"
+ type="link"
+ @click="addMeasure(child)"
+ >
+ + 娴嬮噺鎵规
+ </Button>
+ </div>
+ <div
+ v-for="(measure, mi) in child.measures"
+ :key="mi"
+ class="mb-1 flex items-center gap-2"
+ >
+ <span class="w-16 shrink-0 text-right text-xs text-gray-500">
+ 绗� {{ measure.measureNo }} 娆�
+ </span>
+ <InputNumber
+ v-if="child.valueType === MesQcResultValueType.FLOAT || child.valueType === MesQcResultValueType.INTEGER"
+ :value="bindMeasure(child, measure)"
+ :precision="child.valueType === MesQcResultValueType.FLOAT ? 4 : 0"
+ class="!w-48"
+ placeholder="璇疯緭鍏�"
+ @update:value="(v) => onMeasureInput(child, measure, v)"
+ />
+ <Textarea
+ v-else-if="child.valueType === MesQcResultValueType.TEXT"
+ v-model:value="measure.value"
+ :rows="1"
+ class="!w-48"
+ placeholder="璇疯緭鍏ユ娴嬪��"
+ />
+ <Select
+ v-else-if="child.valueType === MesQcResultValueType.DICT"
+ v-model:value="measure.value"
+ allow-clear
+ class="!w-48"
+ :options="getValueOptions(child.valueSpecification)"
+ placeholder="璇烽�夋嫨"
+ />
+ <Input
+ v-else-if="child.valueType === MesQcResultValueType.FILE"
+ v-model:value="measure.value"
+ class="!w-48"
+ placeholder="璇疯緭鍏ユ枃浠跺湴鍧�"
+ />
+ <Input
+ v-else
+ v-model:value="measure.value"
+ class="!w-48"
+ placeholder="璇疯緭鍏�"
+ />
+ <Input
+ v-model:value="measure.remark"
+ class="flex-1"
+ placeholder="澶囨敞"
+ />
+ <Button
+ size="small"
+ type="text"
+ danger
+ :disabled="child.measures.length <= 1"
+ @click="removeMeasure(child, mi)"
+ >
+ 鍒犻櫎
+ </Button>
+ </div>
+ </div>
+ </div>
+ <!-- 椤剁骇褰曞叆椤� -->
+ <div v-else class="mb-3">
+ <div class="mb-1 flex items-center justify-between">
+ <span class="font-medium">
+ {{ node.indicatorName }}
+ <span v-if="node.unitText" class="text-xs text-gray-500">
+ 锛坽{ node.unitText }}锛�
+ </span>
+ </span>
+ <Button size="small" type="link" @click="addMeasure(node)">
+ + 娴嬮噺鎵规
+ </Button>
+ </div>
+ <div
+ v-for="(measure, mi) in node.measures"
+ :key="mi"
+ class="mb-1 flex items-center gap-2"
+ >
+ <span class="w-16 shrink-0 text-right text-xs text-gray-500">
+ 绗� {{ measure.measureNo }} 娆�
+ </span>
+ <InputNumber
+ v-if="node.valueType === MesQcResultValueType.FLOAT || node.valueType === MesQcResultValueType.INTEGER"
+ :value="bindMeasure(node, measure)"
+ :precision="node.valueType === MesQcResultValueType.FLOAT ? 4 : 0"
+ class="!w-48"
+ placeholder="璇疯緭鍏�"
+ @update:value="(v) => onMeasureInput(node, measure, v)"
+ />
+ <Textarea
+ v-else-if="node.valueType === MesQcResultValueType.TEXT"
+ v-model:value="measure.value"
+ :rows="1"
+ class="!w-48"
+ placeholder="璇疯緭鍏ユ娴嬪��"
+ />
+ <Select
+ v-else-if="node.valueType === MesQcResultValueType.DICT"
+ v-model:value="measure.value"
+ allow-clear
+ class="!w-48"
+ :options="getValueOptions(node.valueSpecification)"
+ placeholder="璇烽�夋嫨"
+ />
+ <Input
+ v-else-if="node.valueType === MesQcResultValueType.FILE"
+ v-model:value="measure.value"
+ class="!w-48"
+ placeholder="璇疯緭鍏ユ枃浠跺湴鍧�"
+ />
+ <Input
+ v-else
+ v-model:value="measure.value"
+ class="!w-48"
+ placeholder="璇疯緭鍏�"
+ />
+ <Input
+ v-model:value="measure.remark"
+ class="flex-1"
+ placeholder="澶囨敞"
+ />
+ <Button
+ size="small"
+ type="text"
+ danger
+ :disabled="node.measures.length <= 1"
+ @click="removeMeasure(node, mi)"
+ >
+ 鍒犻櫎
+ </Button>
+ </div>
+ </div>
+ </template>
+ </div>
</div>
</Modal>
</template>
+
+<style scoped>
+.qc-result-tree {
+ max-height: 60vh;
+ overflow-y: auto;
+}
+</style>
diff --git a/src/views/mes/qc/ipqc/data.ts b/src/views/mes/qc/ipqc/data.ts
index 86522b9..78363f1 100644
--- a/src/views/mes/qc/ipqc/data.ts
+++ b/src/views/mes/qc/ipqc/data.ts
@@ -369,6 +369,57 @@
rules: 'required',
},
{
+ fieldName: 'samplingLocation',
+ label: '閲囨牱鍦扮偣',
+ component: 'Input',
+ componentProps: {
+ maxlength: 100,
+ placeholder: '璇疯緭鍏ラ噰鏍峰湴鐐�',
+ },
+ },
+ {
+ fieldName: 'certificateNo',
+ label: '璇佷功缂栧彿',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '璇疯緭鍏ヨ瘉涔︾紪鍙�',
+ },
+ },
+ {
+ fieldName: 'executeStandard',
+ label: '鎵ц鏍囧噯',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '濡� GB/T 6027锛岄�夋嫨璐ㄦ鏂规鍚庤嚜鍔ㄥ甫鍏�',
+ },
+ },
+ {
+ fieldName: 'auditUserId',
+ label: '澶嶆牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨澶嶆牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'approveUserId',
+ label: '瀹℃牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨瀹℃牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
fieldName: 'checkResult',
label: '妫�娴嬬粨鏋�',
component: 'Select',
diff --git a/src/views/mes/qc/ipqc/index.vue b/src/views/mes/qc/ipqc/index.vue
index 832a85a..58eda85 100644
--- a/src/views/mes/qc/ipqc/index.vue
+++ b/src/views/mes/qc/ipqc/index.vue
@@ -2,8 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcIpqcApi } from '#/api/mes/qc/ipqc';
+import { ref } from 'vue';
+
import { Page, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum } from '@vben/constants';
+import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, message } from 'ant-design-vue';
@@ -11,6 +13,7 @@
import { deleteIpqc, exportIpqc, getIpqcPage } from '#/api/mes/qc/ipqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
+import QcReportPrint from '#/views/mes/qc/components/qc-report-print.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -25,9 +28,16 @@
destroyOnClose: true,
});
+const printRef = ref<InstanceType<typeof QcReportPrint> | null>(null);
+
/** 鍒锋柊琛ㄦ牸 */
function handleRefresh() {
gridApi.query();
+}
+
+/** 鎵撳嵃妫�楠屾姤鍛� */
+async function handlePrint(row: MesQcIpqcApi.Ipqc) {
+ await printRef.value?.print(row.id!, MesQcTypeEnum.IPQC);
}
/** 鍒涘缓杩囩▼妫�楠屽崟 */
@@ -106,6 +116,7 @@
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<ExportRangeModal />
+ <QcReportPrint ref="printRef" />
<Grid table-title="杩囩▼妫�楠屽崟鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -156,6 +167,13 @@
},
},
{
+ label: '鎵撳嵃鎶ュ憡',
+ type: 'link',
+ icon: 'ant-design:printer-outlined',
+ auth: ['mes:qc-ipqc:query'],
+ onClick: handlePrint.bind(null, row),
+ },
+ {
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
diff --git a/src/views/mes/qc/iqc/data.ts b/src/views/mes/qc/iqc/data.ts
index a59f777..4b85dab 100644
--- a/src/views/mes/qc/iqc/data.ts
+++ b/src/views/mes/qc/iqc/data.ts
@@ -274,6 +274,57 @@
rules: 'required',
},
{
+ fieldName: 'samplingLocation',
+ label: '閲囨牱鍦扮偣',
+ component: 'Input',
+ componentProps: {
+ maxlength: 100,
+ placeholder: '璇疯緭鍏ラ噰鏍峰湴鐐�',
+ },
+ },
+ {
+ fieldName: 'certificateNo',
+ label: '璇佷功缂栧彿',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '璇疯緭鍏ヨ瘉涔︾紪鍙�',
+ },
+ },
+ {
+ fieldName: 'executeStandard',
+ label: '鎵ц鏍囧噯',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '濡� GB/T 6027锛岄�夋嫨璐ㄦ鏂规鍚庤嚜鍔ㄥ甫鍏�',
+ },
+ },
+ {
+ fieldName: 'auditUserId',
+ label: '澶嶆牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨澶嶆牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'approveUserId',
+ label: '瀹℃牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨瀹℃牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
fieldName: 'checkResult',
label: '妫�娴嬬粨鏋�',
component: 'Select',
diff --git a/src/views/mes/qc/iqc/index.vue b/src/views/mes/qc/iqc/index.vue
index 761305f..6a42897 100644
--- a/src/views/mes/qc/iqc/index.vue
+++ b/src/views/mes/qc/iqc/index.vue
@@ -2,11 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcIqcApi } from '#/api/mes/qc/iqc';
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum } from '@vben/constants';
+import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, message } from 'ant-design-vue';
@@ -14,6 +14,7 @@
import { deleteIqc, exportIqc, getIqcPage } from '#/api/mes/qc/iqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
+import QcReportPrint from '#/views/mes/qc/components/qc-report-print.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -21,6 +22,7 @@
defineOptions({ name: 'MesQcIqc' });
const route = useRoute();
+const printRef = ref<InstanceType<typeof QcReportPrint> | null>(null);
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -65,6 +67,11 @@
} finally {
hideLoading();
}
+}
+
+/** 鎵撳嵃妫�楠屾姤鍛� */
+async function handlePrint(row: MesQcIqcApi.Iqc) {
+ await printRef.value?.print(row.id!, MesQcTypeEnum.IQC);
}
/** 瀵煎嚭琛ㄦ牸锛堝脊绐楅�夋楠屾椂闂磋寖鍥达級 */
@@ -120,6 +127,7 @@
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<ExportRangeModal />
+ <QcReportPrint ref="printRef" />
<Grid table-title="鏉ユ枡妫�楠屽崟鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -170,6 +178,13 @@
},
},
{
+ label: '鎵撳嵃鎶ュ憡',
+ type: 'link',
+ icon: 'ant-design:printer-outlined',
+ auth: ['mes:qc-iqc:query'],
+ onClick: handlePrint.bind(null, row),
+ },
+ {
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
diff --git a/src/views/mes/qc/oqc/data.ts b/src/views/mes/qc/oqc/data.ts
index 54b6766..c7d0af2 100644
--- a/src/views/mes/qc/oqc/data.ts
+++ b/src/views/mes/qc/oqc/data.ts
@@ -300,6 +300,57 @@
rules: 'required',
},
{
+ fieldName: 'samplingLocation',
+ label: '閲囨牱鍦扮偣',
+ component: 'Input',
+ componentProps: {
+ maxlength: 100,
+ placeholder: '璇疯緭鍏ラ噰鏍峰湴鐐�',
+ },
+ },
+ {
+ fieldName: 'certificateNo',
+ label: '璇佷功缂栧彿',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '璇疯緭鍏ヨ瘉涔︾紪鍙�',
+ },
+ },
+ {
+ fieldName: 'executeStandard',
+ label: '鎵ц鏍囧噯',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '濡� GB/T 6027锛岄�夋嫨璐ㄦ鏂规鍚庤嚜鍔ㄥ甫鍏�',
+ },
+ },
+ {
+ fieldName: 'auditUserId',
+ label: '澶嶆牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨澶嶆牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'approveUserId',
+ label: '瀹℃牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨瀹℃牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
fieldName: 'checkResult',
label: '妫�娴嬬粨鏋�',
component: 'Select',
diff --git a/src/views/mes/qc/oqc/index.vue b/src/views/mes/qc/oqc/index.vue
index f9043b1..c687183 100644
--- a/src/views/mes/qc/oqc/index.vue
+++ b/src/views/mes/qc/oqc/index.vue
@@ -2,8 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcOqcApi } from '#/api/mes/qc/oqc';
+import { ref } from 'vue';
+
import { Page, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum } from '@vben/constants';
+import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, message } from 'ant-design-vue';
@@ -11,6 +13,7 @@
import { deleteOqc, exportOqc, getOqcPage } from '#/api/mes/qc/oqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
+import QcReportPrint from '#/views/mes/qc/components/qc-report-print.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -25,9 +28,16 @@
destroyOnClose: true,
});
+const printRef = ref<InstanceType<typeof QcReportPrint> | null>(null);
+
/** 鍒锋柊琛ㄦ牸 */
function handleRefresh() {
gridApi.query();
+}
+
+/** 鎵撳嵃妫�楠屾姤鍛� */
+async function handlePrint(row: MesQcOqcApi.Oqc) {
+ await printRef.value?.print(row.id!, MesQcTypeEnum.OQC);
}
/** 鍒涘缓鍑鸿揣妫�楠屽崟 */
@@ -106,6 +116,7 @@
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<ExportRangeModal />
+ <QcReportPrint ref="printRef" />
<Grid table-title="鍑鸿揣妫�楠屽崟鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -156,6 +167,13 @@
},
},
{
+ label: '鎵撳嵃鎶ュ憡',
+ type: 'link',
+ icon: 'ant-design:printer-outlined',
+ auth: ['mes:qc-oqc:query'],
+ onClick: handlePrint.bind(null, row),
+ },
+ {
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
diff --git a/src/views/mes/qc/rqc/data.ts b/src/views/mes/qc/rqc/data.ts
index ab9af37..be672b5 100644
--- a/src/views/mes/qc/rqc/data.ts
+++ b/src/views/mes/qc/rqc/data.ts
@@ -267,6 +267,57 @@
rules: 'selectRequired',
},
{
+ fieldName: 'samplingLocation',
+ label: '閲囨牱鍦扮偣',
+ component: 'Input',
+ componentProps: {
+ maxlength: 100,
+ placeholder: '璇疯緭鍏ラ噰鏍峰湴鐐�',
+ },
+ },
+ {
+ fieldName: 'certificateNo',
+ label: '璇佷功缂栧彿',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '璇疯緭鍏ヨ瘉涔︾紪鍙�',
+ },
+ },
+ {
+ fieldName: 'executeStandard',
+ label: '鎵ц鏍囧噯',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '濡� GB/T 6027锛岄�夋嫨璐ㄦ鏂规鍚庤嚜鍔ㄥ甫鍏�',
+ },
+ },
+ {
+ fieldName: 'auditUserId',
+ label: '澶嶆牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨澶嶆牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'approveUserId',
+ label: '瀹℃牳浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨瀹℃牳浜�',
+ valueField: 'id',
+ },
+ },
+ {
fieldName: 'checkResult',
label: '妫�娴嬬粨鏋�',
component: 'Select',
diff --git a/src/views/mes/qc/rqc/index.vue b/src/views/mes/qc/rqc/index.vue
index 2771b4c..bc1ea4e 100644
--- a/src/views/mes/qc/rqc/index.vue
+++ b/src/views/mes/qc/rqc/index.vue
@@ -2,8 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcRqcApi } from '#/api/mes/qc/rqc';
+import { ref } from 'vue';
+
import { Page, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum } from '@vben/constants';
+import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
import { Button, message } from 'ant-design-vue';
@@ -11,6 +13,7 @@
import { deleteRqc, exportRqc, getRqcPage } from '#/api/mes/qc/rqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
+import QcReportPrint from '#/views/mes/qc/components/qc-report-print.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -25,9 +28,16 @@
destroyOnClose: true,
});
+const printRef = ref<InstanceType<typeof QcReportPrint> | null>(null);
+
/** 鍒锋柊琛ㄦ牸 */
function handleRefresh() {
gridApi.query();
+}
+
+/** 鎵撳嵃妫�楠屾姤鍛� */
+async function handlePrint(row: MesQcRqcApi.Rqc) {
+ await printRef.value?.print(row.id!, MesQcTypeEnum.RQC);
}
/** 鍒涘缓閫�璐ф楠屽崟 */
@@ -106,6 +116,7 @@
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<ExportRangeModal />
+ <QcReportPrint ref="printRef" />
<Grid table-title="閫�璐ф楠屽崟鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -156,6 +167,13 @@
},
},
{
+ label: '鎵撳嵃鎶ュ憡',
+ type: 'link',
+ icon: 'ant-design:printer-outlined',
+ auth: ['mes:qc-rqc:query'],
+ onClick: handlePrint.bind(null, row),
+ },
+ {
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
diff --git a/src/views/mes/qc/template/data.ts b/src/views/mes/qc/template/data.ts
index 2403139..0d3f941 100644
--- a/src/views/mes/qc/template/data.ts
+++ b/src/views/mes/qc/template/data.ts
@@ -91,6 +91,15 @@
rules: z.array(z.number()).min(1, '妫�娴嬬绫讳笉鑳戒负绌�'),
},
{
+ fieldName: 'defaultExecuteStandard',
+ label: '榛樿鎵ц鏍囧噯',
+ component: 'Input',
+ componentProps: {
+ maxlength: 50,
+ placeholder: '濡� GB/T 6027锛屾柊寤烘楠屽崟閫夋嫨鏈柟妗堝悗鑷姩甯﹀叆鎵ц鏍囧噯',
+ },
+ },
+ {
fieldName: 'remark',
label: '澶囨敞',
component: 'Textarea',
@@ -249,6 +258,17 @@
width: 100,
},
{
+ field: 'sortOrder',
+ title: '鎺掑簭鍙�',
+ width: 80,
+ },
+ {
+ field: 'judgeFlag',
+ title: '鍙備笌鍒ゅ畾',
+ width: 90,
+ slots: { default: 'judgeFlag' },
+ },
+ {
title: '鎿嶄綔',
width: 130,
fixed: 'right',
@@ -336,6 +356,29 @@
},
},
{
+ fieldName: 'sortOrder',
+ label: '鎺掑簭鍙�',
+ component: 'InputNumber',
+ defaultValue: 0,
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 0,
+ placeholder: '鏂规鍐呭睍绀洪『搴忥紝灏忕殑鍦ㄥ墠',
+ },
+ },
+ {
+ fieldName: 'judgeFlag',
+ label: '鍙備笌鍒ゅ畾',
+ component: 'Switch',
+ defaultValue: true,
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ help: '鍒嗙粍琛�/涓棿鍙傛暟琛岃涓哄惁锛屼粎鏈�缁堢粨鏋滈」鍙備笌鍚堟牸鍒ゅ畾鎻愮ず',
+ },
+ {
fieldName: 'remark',
label: '澶囨敞',
component: 'Textarea',
diff --git a/src/views/mes/qc/template/modules/indicator-list.vue b/src/views/mes/qc/template/modules/indicator-list.vue
index 3b9790a..6ad5681 100644
--- a/src/views/mes/qc/template/modules/indicator-list.vue
+++ b/src/views/mes/qc/template/modules/indicator-list.vue
@@ -4,12 +4,13 @@
import { useVbenModal } from '@vben/common-ui';
-import { message } from 'ant-design-vue';
+import { message, Switch } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplateIndicator,
getTemplateIndicatorPage,
+ updateTemplateIndicator,
} from '#/api/mes/qc/template/indicator';
import { $t } from '#/locales';
@@ -39,6 +40,16 @@
/** 缂栬緫鎸囨爣椤� */
function handleEdit(row: MesQcTemplateIndicatorApi.TemplateIndicator) {
formModalApi.setData({ ...row, templateId: props.templateId }).open();
+}
+
+/** 鍒囨崲鏄惁鍙備笌鍒ゅ畾 */
+async function handleJudgeFlagChange(
+ row: MesQcTemplateIndicatorApi.TemplateIndicator,
+ checked: boolean,
+) {
+ await updateTemplateIndicator({ ...row, judgeFlag: checked });
+ message.success(`妫�娴嬮」[${row.indicatorName}]宸�${checked ? '鍙備笌' : '涓嶅弬涓�'}鍒ゅ畾`);
+ handleRefresh();
}
/** 鍒犻櫎鎸囨爣椤� */
@@ -100,6 +111,17 @@
]"
/>
</template>
+ <template #judgeFlag="{ row }">
+ <Switch
+ :checked="row.judgeFlag !== false"
+ :disabled="readonly"
+ checked-children="鏄�"
+ un-checked-children="鍚�"
+ @change="
+ (checked: boolean) => handleJudgeFlagChange(row, checked)
+ "
+ />
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
--
Gitblit v1.9.3