From a7dc7b230fcc6abc6ee5a7bfadb90789b03d2ca8 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 07 九月 2026 16:45:37 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0

---
 ZSJC.bat                                                |  451 ++++++++++++++++++++
 src/views/mes/qc/iqc/index.vue                          |   25 
 src/views/mes/qc/ipqc/data.ts                           |   43 +
 src/views/mes/qc/iqc/data.ts                            |   28 +
 src/views/wls/returnissue/modules/form.vue              |    2 
 company.config.ts                                       |    6 
 src/views/wls/productissue/components/select.vue        |  141 ++++++
 src/views/wls/returnissue/modules/line-form.vue         |   10 
 src/views/mes/qc/rqc/index.vue                          |   25 
 src/views/wls/returnissue/data.ts                       |   36 
 src/api/mes/wm/productissue/line/index.ts               |    8 
 src/views/mes/qc/components/export-range-modal.vue      |   89 ++++
 src/views/hrm/leave/modules/form.vue                    |    6 
 src/views/wls/productissue/components/select-dialog.vue |  106 ++++
 src/api/hrm/leave/index.ts                              |   20 
 src/views/hrm/leave/detail/approve/index.vue            |    2 
 src/views/hrm/leave/data.ts                             |   15 
 src/views/wls/productissue/components/index.ts          |    2 
 src/views/mes/qc/oqc/data.ts                            |   37 +
 src/views/mes/qc/ipqc/index.vue                         |   25 
 src/views/wls/productissue/data.ts                      |   45 ++
 src/views/mes/qc/oqc/index.vue                          |   25 
 src/api/mes/qc/template/item/index.ts                   |   28 +
 src/views/wls/returnissue/modules/line-list.vue         |    5 
 src/views/hrm/leave/index.vue                           |   50 ++
 src/api/mes/wm/returnissue/line/index.ts                |    1 
 src/views/mes/qc/rqc/data.ts                            |   37 +
 27 files changed, 1,203 insertions(+), 65 deletions(-)

diff --git a/ZSJC.bat b/ZSJC.bat
new file mode 100644
index 0000000..355c255
--- /dev/null
+++ b/ZSJC.bat
@@ -0,0 +1,451 @@
+@echo off
+setlocal EnableExtensions EnableDelayedExpansion
+
+REM ============================================================
+REM Vue Frontend Deploy Script
+REM ============================================================
+
+REM =========================
+REM Configuration
+REM =========================
+
+set "COMPANY=ZSJC"
+
+set "REMOTE_USER=root"
+set "REMOTE_HOST=116.172.73.239"
+set "REMOTE_PORT=22"
+
+set "PASSWORD=ecs-QGY8ecs-QGY8"
+
+set "ZIP_FILE=dist.zip"
+set "REMOTE_DIR=/home/vue/dist"
+
+set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3"
+
+set "RETRY_COUNT=3"
+
+set "SSHPASS_DIR=%~dp0bin"
+
+
+REM ============================================================
+REM Start
+REM ============================================================
+
+echo.
+echo ============================================================
+echo              Vue Dist Auto Deploy
+echo ============================================================
+echo.
+echo Company    : %COMPANY%
+echo Server     : %REMOTE_USER%@%REMOTE_HOST%
+echo Port       : %REMOTE_PORT%
+echo Remote Dir : %REMOTE_DIR%
+echo Package    : %ZIP_FILE%
+echo.
+
+
+REM ============================================================
+REM Check sshpass
+REM ============================================================
+
+echo [1/7] Checking sshpass...
+
+if exist "%SSHPASS_DIR%\sshpass.exe" (
+    set "PATH=%SSHPASS_DIR%;%PATH%"
+)
+
+where sshpass >nul 2>&1
+
+if not errorlevel 1 (
+    echo [OK] sshpass found.
+    goto CHECK_PNPM
+)
+
+echo [WARN] sshpass not found.
+
+if not exist "%SSHPASS_DIR%" (
+    mkdir "%SSHPASS_DIR%" >nul 2>&1
+)
+
+if exist "%SSHPASS_DIR%\sshpass.exe" (
+    set "PATH=%SSHPASS_DIR%;%PATH%"
+    echo [OK] sshpass found.
+    goto CHECK_PNPM
+)
+
+echo [INFO] Downloading sshpass...
+
+powershell -NoProfile -ExecutionPolicy Bypass -Command "$url='https://github.com/xhcoding/sshpass-win32/releases/download/v1.0.3/sshpass.exe'; $out='%SSHPASS_DIR%\sshpass.exe'; Invoke-WebRequest -Uri $url -OutFile $out"
+
+if errorlevel 1 (
+    echo [ERROR] Failed to download sshpass.
+    echo [ERROR] Please install sshpass manually.
+    goto ERROR_EXIT
+)
+
+set "PATH=%SSHPASS_DIR%;%PATH%"
+
+echo [OK] sshpass installed.
+
+
+REM ============================================================
+REM Check pnpm
+REM ============================================================
+
+:CHECK_PNPM
+
+echo.
+echo [2/7] Checking pnpm...
+
+where pnpm >nul 2>&1
+
+if errorlevel 1 (
+    echo [ERROR] pnpm not found.
+    echo [HINT] npm install -g pnpm
+    goto ERROR_EXIT
+)
+
+call pnpm --version
+
+if errorlevel 1 (
+    echo [ERROR] pnpm execution failed.
+    goto ERROR_EXIT
+)
+
+
+REM ============================================================
+REM Check node
+REM ============================================================
+
+echo.
+echo [3/7] Checking Node.js...
+
+where node >nul 2>&1
+
+if errorlevel 1 (
+    echo [ERROR] Node.js not found.
+    goto ERROR_EXIT
+)
+
+call node --version
+
+if errorlevel 1 (
+    echo [ERROR] Node.js execution failed.
+    goto ERROR_EXIT
+)
+
+
+REM ============================================================
+REM Check node_modules
+REM ============================================================
+
+if not exist "node_modules" (
+    echo.
+    echo [INFO] node_modules not found.
+    echo [INFO] Running pnpm install...
+
+    call pnpm install
+
+    if errorlevel 1 (
+        echo [ERROR] pnpm install failed.
+        goto ERROR_EXIT
+    )
+
+    echo [OK] pnpm install completed.
+) else (
+    echo [OK] node_modules exists.
+)
+
+
+REM ============================================================
+REM Build
+REM ============================================================
+
+echo.
+echo [4/7] Building project...
+
+if exist "%ZIP_FILE%" (
+    echo.
+    echo [WARN] %ZIP_FILE% already exists.
+    set "REBUILD="
+    set /p "REBUILD=Rebuild? Y/N (default Y): "
+
+    if /I "!REBUILD!"=="N" (
+        echo [INFO] Skip build.
+        goto CHECK_ZIP
+    )
+)
+
+echo [INFO] Company: %COMPANY%
+echo [INFO] Command: pnpm run build
+echo.
+
+set "VITE_COMPANY=%COMPANY%"
+
+call pnpm run build
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] Build failed.
+    goto ERROR_EXIT
+)
+
+echo.
+echo [OK] Build completed.
+
+
+REM ============================================================
+REM Check ZIP
+REM ============================================================
+
+:CHECK_ZIP
+
+echo.
+echo [5/7] Checking package...
+
+if not exist "%ZIP_FILE%" (
+    echo [ERROR] %ZIP_FILE% not found.
+    echo [HINT] Please check whether your build generates dist.zip.
+    goto ERROR_EXIT
+)
+
+for %%F in ("%ZIP_FILE%") do set "ZIP_BYTES=%%~zF"
+
+set /a ZIP_MB=ZIP_BYTES/1024/1024
+
+echo [OK] Package found.
+echo [INFO] File size: !ZIP_BYTES! bytes
+echo [INFO] File size: !ZIP_MB! MB
+
+
+REM ============================================================
+REM Set sshpass password
+REM ============================================================
+
+set "SSHPASS=%PASSWORD%"
+
+
+REM ============================================================
+REM Test SSH
+REM ============================================================
+
+echo.
+echo [6/7] Testing SSH connection...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "echo SSH_OK"
+
+if errorlevel 1 (
+    echo [ERROR] SSH connection failed.
+    goto ERROR_EXIT
+)
+
+echo [OK] SSH connection successful.
+
+
+REM ============================================================
+REM Create remote directory
+REM ============================================================
+
+echo.
+echo [INFO] Creating remote directory...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "mkdir -p '%REMOTE_DIR%'"
+
+if errorlevel 1 (
+    echo [ERROR] Failed to create remote directory.
+    goto ERROR_EXIT
+)
+
+echo [OK] Remote directory ready.
+
+
+REM ============================================================
+REM Upload temporary file
+REM ============================================================
+
+echo.
+echo [INFO] Uploading package...
+
+set "REMOTE_TMP=%REMOTE_DIR%/.dist.zip.uploading"
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "rm -f '%REMOTE_TMP%'"
+
+sshpass -e scp -P %REMOTE_PORT% %SSH_OPTS% "%ZIP_FILE%" %REMOTE_USER%@%REMOTE_HOST%:"%REMOTE_TMP%"
+
+if errorlevel 1 (
+    echo [ERROR] Upload failed.
+    goto ERROR_EXIT
+)
+
+echo [OK] Upload completed.
+
+
+REM ============================================================
+REM Check remote file
+REM ============================================================
+
+echo.
+echo [INFO] Checking remote package...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "test -f '%REMOTE_TMP%'"
+
+if errorlevel 1 (
+    echo [ERROR] Remote package does not exist.
+    goto ERROR_EXIT
+)
+
+echo [OK] Remote package exists.
+
+
+REM ============================================================
+REM Compare file size
+REM ============================================================
+
+echo.
+echo [INFO] Checking package size...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "stat -c %%s '%REMOTE_TMP%'"
+
+echo.
+
+
+REM ============================================================
+REM Rename temporary file
+REM ============================================================
+
+echo [INFO] Activating package...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "mv -f '%REMOTE_TMP%' '%REMOTE_DIR%/%ZIP_FILE%'"
+
+if errorlevel 1 (
+    echo [ERROR] Failed to activate package.
+    goto ERROR_EXIT
+)
+
+echo [OK] Package activated.
+
+
+REM ============================================================
+REM Check unzip
+REM ============================================================
+
+echo.
+echo [INFO] Checking unzip...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "command -v unzip"
+
+if errorlevel 1 (
+    echo [ERROR] unzip is not installed on server.
+    echo.
+    echo CentOS/RHEL:
+    echo yum install -y unzip
+    echo.
+    echo Ubuntu/Debian:
+    echo apt install -y unzip
+    goto ERROR_EXIT
+)
+
+echo [OK] unzip found.
+
+
+REM ============================================================
+REM Test ZIP
+REM ============================================================
+
+echo.
+echo [INFO] Testing remote ZIP...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "cd '%REMOTE_DIR%' && unzip -t '%ZIP_FILE%'"
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] ZIP test failed.
+    echo [INFO] Remote ZIP has been kept.
+    goto ERROR_EXIT
+)
+
+echo [OK] ZIP test passed.
+
+
+REM ============================================================
+REM Extract
+REM ============================================================
+
+echo.
+echo [INFO] Extracting package...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "cd '%REMOTE_DIR%' && unzip -o '%ZIP_FILE%'"
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] Remote unzip failed.
+    echo [INFO] Remote ZIP has been kept.
+    goto ERROR_EXIT
+)
+
+echo.
+echo [OK] Extract completed.
+
+
+REM ============================================================
+REM Remove ZIP
+REM ============================================================
+
+echo.
+echo [INFO] Removing remote ZIP...
+
+sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "rm -f '%REMOTE_DIR%/%ZIP_FILE%'"
+
+if errorlevel 1 (
+    echo [WARN] Failed to remove remote ZIP.
+) else (
+    echo [OK] Remote ZIP removed.
+)
+
+
+REM ============================================================
+REM Success
+REM ============================================================
+
+echo.
+echo ============================================================
+echo                 DEPLOY SUCCESS
+echo ============================================================
+echo.
+echo Company    : %COMPANY%
+echo Server     : %REMOTE_HOST%:%REMOTE_PORT%
+echo Remote Dir : %REMOTE_DIR%
+echo Package    : %ZIP_FILE%
+echo Size       : !ZIP_MB! MB
+echo.
+echo Deployment completed successfully.
+echo.
+
+goto END
+
+
+REM ============================================================
+REM Error
+REM ============================================================
+
+:ERROR_EXIT
+
+echo.
+echo ============================================================
+echo                  DEPLOY FAILED
+echo ============================================================
+echo.
+echo Please check the error message above.
+echo.
+
+goto END
+
+
+REM ============================================================
+REM End
+REM ============================================================
+
+:END
+
+pause
+endlocal
\ No newline at end of file
diff --git a/company.config.ts b/company.config.ts
index 2256fd5..74029f4 100644
--- a/company.config.ts
+++ b/company.config.ts
@@ -53,4 +53,10 @@
     favicon: '/logos/YZFXLogo.ico',
     apiUrl: 'http://42.63.71.140:9004/admin-api',
   },
+  ZSJC: {
+    name: '瀹佸涓洓寤烘潗绉戞妧鏈夐檺鍏徃',
+    logo: '/logos/YZFXLogo.png',
+    favicon: '/logos/YZFXLogo.ico',
+    apiUrl: 'http://116.172.73.239:9001/admin-api',
+  },
 };
diff --git a/src/api/hrm/leave/index.ts b/src/api/hrm/leave/index.ts
index e280286..ee16a80 100644
--- a/src/api/hrm/leave/index.ts
+++ b/src/api/hrm/leave/index.ts
@@ -7,8 +7,8 @@
   /** 璇峰亣鐢宠 */
   export interface Leave {
     id?: number;
-    employeeId?: number;
-    employeeName?: string;
+    userId?: number;
+    userName?: string;
     deptName?: string;
     leaveType?: number;
     startTime?: string;
@@ -21,6 +21,12 @@
     processInstanceId?: string;
     remark?: string;
     createTime?: string;
+  }
+
+  /** 瀹℃壒娴佺▼ */
+  export interface ApproveProcess {
+    key: string;
+    name: string;
   }
 }
 
@@ -53,8 +59,10 @@
 }
 
 /** 鎻愪氦璇峰亣鐢宠锛堝彂璧峰鎵癸級 */
-export function submitLeave(id: number) {
-  return requestClient.post(`/hrm/leave/submit?id=${id}`);
+export function submitLeave(id: number, processDefinitionKey: string) {
+  return requestClient.post('/hrm/leave/submit', null, {
+    params: { id, processDefinitionKey },
+  });
 }
 
 /** 鎾ら攢璇峰亣鐢宠 */
@@ -64,5 +72,7 @@
 
 /** 鑾峰彇鍙敤娴佺▼瀹氫箟鍒楄〃 */
 export function getLeaveApproveProcessDefinitionList() {
-  return requestClient.get<any[]>('/hrm/leave/approve-process-definition-list');
+  return requestClient.get<HrmLeaveApi.ApproveProcess[]>(
+    '/hrm/leave/approve-process-definition-list',
+  );
 }
\ No newline at end of file
diff --git a/src/api/mes/qc/template/item/index.ts b/src/api/mes/qc/template/item/index.ts
index a7b92c1..d10c641 100644
--- a/src/api/mes/qc/template/item/index.ts
+++ b/src/api/mes/qc/template/item/index.ts
@@ -21,6 +21,21 @@
   }
 }
 
+/** 璐ㄦ鏂规閫夐」锛堟柟妗�-浜у搧鍏宠仈锛� */
+export namespace MesQcTemplateItemOptionApi {
+  export interface Option {
+    id?: number; // 浜у搧鍏宠仈缂栧彿
+    templateId?: number; // 璐ㄦ鏂规 ID
+    templateCode?: string; // 璐ㄦ鏂规缂栧彿
+    templateName?: string; // 璐ㄦ鏂规鍚嶇О
+    quantityCheck?: number; // 鏈�浣庢娴嬫暟
+    quantityUnqualified?: number; // 鏈�澶т笉鍚堟牸鏁�
+    criticalRate?: number; // 鏈�澶ц嚧鍛界己闄风巼锛�%锛�
+    majorRate?: number; // 鏈�澶т弗閲嶇己闄风巼锛�%锛�
+    minorRate?: number; // 鏈�澶ц交寰己闄风巼锛�%锛�
+  }
+}
+
 /** 鏌ヨ浜у搧鍏宠仈鍒嗛〉 */
 export function getTemplateItemPage(
   params: PageParam & { templateId?: number },
@@ -52,3 +67,16 @@
 export function deleteTemplateItem(id: number) {
   return requestClient.delete(`/mes/qc/template/item/delete?id=${id}`);
 }
+
+/** 鏍规嵁鐗╂枡鎴栧伐鍗�/浠诲姟鍜屾娴嬬绫昏幏鍙栧彲鐢ㄨ川妫�鏂规鍒楄〃 */
+export function getTemplateItemOptionList(params: {
+  itemId?: number;
+  qcType: number;
+  taskId?: number;
+  workOrderId?: number;
+}) {
+  return requestClient.get<MesQcTemplateItemOptionApi.Option[]>(
+    '/mes/qc/template/item/list-by-item-and-type',
+    { params },
+  );
+}
diff --git a/src/api/mes/wm/productissue/line/index.ts b/src/api/mes/wm/productissue/line/index.ts
index 285d37a..9b79b2f 100644
--- a/src/api/mes/wm/productissue/line/index.ts
+++ b/src/api/mes/wm/productissue/line/index.ts
@@ -69,3 +69,11 @@
     { params: { issueId } },
   );
 }
+
+/** 鏌ヨ鍙��鏂欑殑棰嗘枡鍗曡鍒楄〃锛堟寜鐢熶骇璁㈠崟杩囨护锛屼粎杩斿洖宸插畬鎴愰鏂欑殑璁板綍锛� */
+export function getReturnableIssueLines(workOrderId: number) {
+  return requestClient.get<MesWmProductIssueLineApi.ProductIssueLine[]>(
+      '/mes/wm/product-issue-line/returnable-list',
+      { params: { workOrderId } },
+  );
+}
diff --git a/src/api/mes/wm/returnissue/line/index.ts b/src/api/mes/wm/returnissue/line/index.ts
index 05bef07..d1bb469 100644
--- a/src/api/mes/wm/returnissue/line/index.ts
+++ b/src/api/mes/wm/returnissue/line/index.ts
@@ -7,6 +7,7 @@
   export interface ReturnIssueLine {
     id?: number; // 琛岀紪鍙�
     issueId?: number; // 閫�鏂欏崟缂栧彿
+    issueLineId?: number; // 棰嗘枡鍗曡缂栧彿锛堝叧鑱旀潵婧愰鏂欒褰曪級
     itemId?: number; // 鐗╂枡缂栧彿
     itemCode?: string; // 鐗╂枡缂栫爜
     itemName?: string; // 鐗╂枡鍚嶇О
diff --git a/src/views/hrm/leave/data.ts b/src/views/hrm/leave/data.ts
index c6d5154..6e8570e 100644
--- a/src/views/hrm/leave/data.ts
+++ b/src/views/hrm/leave/data.ts
@@ -2,8 +2,12 @@
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { HrmLeaveApi } from '#/api/hrm/leave';
 
+import { markRaw } from 'vue';
+
 import { DICT_TYPE } from '#/packages/constants/src';
 import { getDictOptions } from '#/packages/effects/hooks/src';
+
+import { UserSelect } from '#/views/system/user/components';
 
 /** 琛ㄥ崟绫诲瀷 */
 export type FormType = 'create' | 'detail' | 'update';
@@ -18,6 +22,15 @@
         triggerFields: [''],
         show: () => false,
       },
+    },
+    {
+      fieldName: 'userId',
+      label: '鐢宠浜�',
+      component: markRaw(UserSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢宠浜�',
+      },
+      rules: 'selectRequired',
     },
     {
       fieldName: 'leaveType',
@@ -134,7 +147,7 @@
 export function useGridColumns(): VxeTableGridOptions<HrmLeaveApi.Leave>['columns'] {
   return [
     { field: 'id', title: '鐢宠缂栧彿', width: 80 },
-    { field: 'employeeName', title: '鐢宠浜�', minWidth: 100 },
+    { field: 'userName', title: '鐢宠浜�', minWidth: 100 },
     { field: 'deptName', title: '閮ㄩ棬', minWidth: 120 },
     {
       field: 'leaveType',
diff --git a/src/views/hrm/leave/detail/approve/index.vue b/src/views/hrm/leave/detail/approve/index.vue
index 59248e4..0573837 100644
--- a/src/views/hrm/leave/detail/approve/index.vue
+++ b/src/views/hrm/leave/detail/approve/index.vue
@@ -30,7 +30,7 @@
   bordered: true,
   column: 2,
   schema: [
-    { label: '鍛樺伐濮撳悕', field: 'employeeName', render: textRender },
+    { label: '鐢宠浜�', field: 'userName', render: textRender },
     { label: '鎵�灞為儴闂�', field: 'deptName', render: textRender },
     {
       label: '璇峰亣绫诲瀷',
diff --git a/src/views/hrm/leave/index.vue b/src/views/hrm/leave/index.vue
index e7ae028..d088fc2 100644
--- a/src/views/hrm/leave/index.vue
+++ b/src/views/hrm/leave/index.vue
@@ -2,9 +2,11 @@
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { HrmLeaveApi } from '#/api/hrm/leave';
 
+import { ref } from 'vue';
+
 import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
 
-import { message, Tag } from 'ant-design-vue';
+import { message, Modal, Select, Tag } from 'ant-design-vue';
 
 import { DICT_TYPE } from '#/packages/constants/src';
 import { getDictLabel } from '#/packages/effects/hooks/src';
@@ -13,6 +15,7 @@
 import {
   cancelLeave,
   deleteLeave,
+  getLeaveApproveProcessDefinitionList,
   getLeavePage,
   submitLeave,
 } from '#/api/hrm/leave';
@@ -61,11 +64,34 @@
   }
 }
 
-/** 鎻愪氦璇峰亣鐢宠 */
+const submitModalVisible = ref(false);
+const currentRow = ref<HrmLeaveApi.Leave>();
+const processList = ref<HrmLeaveApi.ApproveProcess[]>([]);
+const selectedProcessKey = ref<string>();
+const submitLoading = ref(false);
+
+/** 鎻愪氦璇峰亣鐢宠锛堝厛閫夋嫨瀹℃壒娴佺▼锛� */
 async function handleSubmit(row: HrmLeaveApi.Leave) {
-  await submitLeave(row.id!);
-  message.success('鎻愪氦鎴愬姛');
-  handleRefresh();
+  processList.value = await getLeaveApproveProcessDefinitionList();
+  currentRow.value = row;
+  selectedProcessKey.value = undefined;
+  submitModalVisible.value = true;
+}
+
+async function confirmSubmit() {
+  if (!selectedProcessKey.value) {
+    message.warning('璇烽�夋嫨璇峰亣瀹℃壒娴佺▼');
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    await submitLeave(currentRow.value!.id!, selectedProcessKey.value);
+    message.success('鎻愪氦鎴愬姛');
+    submitModalVisible.value = false;
+    handleRefresh();
+  } finally {
+    submitLoading.value = false;
+  }
 }
 
 /** 鎾ら攢璇峰亣鐢宠 */
@@ -187,5 +213,19 @@
         />
       </template>
     </Grid>
+    <Modal
+      v-model:open="submitModalVisible"
+      title="鎻愪氦鑷冲崗鍚屽姙鍏�"
+      width="420px"
+      :confirm-loading="submitLoading"
+      @ok="confirmSubmit"
+    >
+      <Select
+        v-model:value="selectedProcessKey"
+        class="w-full"
+        placeholder="璇烽�夋嫨璇峰亣瀹℃壒娴佺▼"
+        :options="processList.map((item) => ({ label: item.name, value: item.key }))"
+      />
+    </Modal>
   </Page>
 </template>
\ No newline at end of file
diff --git a/src/views/hrm/leave/modules/form.vue b/src/views/hrm/leave/modules/form.vue
index 4aab581..f701b81 100644
--- a/src/views/hrm/leave/modules/form.vue
+++ b/src/views/hrm/leave/modules/form.vue
@@ -4,6 +4,8 @@
 
 import { computed, ref } from 'vue';
 
+import { useUserStore } from '@vben/stores';
+
 import { useVbenModal } from '#/packages/effects/common-ui/src';
 
 import { message } from 'ant-design-vue';
@@ -16,6 +18,8 @@
 
 const emit = defineEmits(['success']);
 const formType = ref<FormType>('create');
+
+const userStore = useUserStore();
 
 const isDetail = computed(() => formType.value === 'detail');
 const getTitle = computed(() => {
@@ -74,6 +78,8 @@
     formApi.setDisabled(data.formType === 'detail');
     modalApi.setState({ showConfirmButton: data.formType !== 'detail' });
     if (!data?.id) {
+      // 鏂板鏃堕粯璁ょ敵璇蜂汉涓哄綋鍓嶇櫥褰曠敤鎴凤紝鍙慨鏀�
+      await formApi.setFieldValue('userId', userStore.userInfo?.id);
       return;
     }
     modalApi.lock();
diff --git a/src/views/mes/qc/components/export-range-modal.vue b/src/views/mes/qc/components/export-range-modal.vue
new file mode 100644
index 0000000..19fedfe
--- /dev/null
+++ b/src/views/mes/qc/components/export-range-modal.vue
@@ -0,0 +1,89 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { DatePicker, message } from 'ant-design-vue';
+
+import { getRangePickerDefaultProps } from '#/utils';
+
+/** 瀵煎嚭鍙傛暟锛堝悗绔� PageReqVO 浠ユ暟缁勮В鏋� inspectDate 璧锋锛� */
+interface ExportParams {
+  inspectDate?: [string, string];
+}
+
+interface ExportModalData {
+  /** 寮圭獥鏍囬 */
+  title?: string;
+  /** 妯″潡瀵煎嚭鍑芥暟锛歳equestClient.download(...) 杩斿洖 Blob */
+  exportApi: (params: ExportParams) => Promise<Blob>;
+  /** 瀵煎嚭鏂囦欢鍚� */
+  fileName: string;
+}
+
+defineOptions({ name: 'QcExportRangeModal' });
+
+const rangePickerProps = getRangePickerDefaultProps();
+
+const modalData = ref<ExportModalData>();
+const range = ref<[string, string] | undefined>(undefined);
+const exporting = ref(false);
+const title = ref('瀵煎嚭');
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (exporting.value) {
+      return;
+    }
+    const data = modalData.value;
+    if (!data) {
+      return;
+    }
+    exporting.value = true;
+    modalApi.lock();
+    try {
+      const params: ExportParams =
+        range.value && range.value.length === 2
+          ? { inspectDate: range.value }
+          : {};
+      const blob = await data.exportApi(params);
+      downloadFileFromBlobPart({ fileName: data.fileName, source: blob });
+      message.success('瀵煎嚭鎴愬姛');
+      await modalApi.close();
+    } finally {
+      exporting.value = false;
+      modalApi.unlock();
+    }
+  },
+  onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      modalData.value = undefined;
+      range.value = undefined;
+      return;
+    }
+    const data = modalApi.getData<ExportModalData>();
+    modalData.value = data;
+    range.value = undefined;
+    title.value = data?.title ?? '瀵煎嚭';
+    modalApi.setState({ confirmText: '瀵煎嚭' });
+  },
+});
+</script>
+
+<template>
+  <Modal :title="title" class="w-2/5" :mask-closable="false">
+    <div class="px-1 py-3">
+      <div class="mb-2 text-sm font-medium text-gray-700">妫�楠屾椂闂�</div>
+      <DatePicker.RangePicker
+        v-model:value="range"
+        v-bind="rangePickerProps"
+        allow-clear
+        class="w-full"
+      />
+      <div class="mt-2 text-xs text-gray-400">
+        涓嶉�夋椂闂村垯瀵煎嚭鍏ㄩ儴鏁版嵁锛涘鍑烘寜銆屾娴嬫棩鏈熴�嶈繃婊�
+      </div>
+    </div>
+  </Modal>
+</template>
diff --git a/src/views/mes/qc/ipqc/data.ts b/src/views/mes/qc/ipqc/data.ts
index 58f1e88..86522b9 100644
--- a/src/views/mes/qc/ipqc/data.ts
+++ b/src/views/mes/qc/ipqc/data.ts
@@ -16,7 +16,9 @@
 import { Button } from 'ant-design-vue';
 
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
 import { getSimpleUserList } from '#/api/system/user';
+import { getRangePickerDefaultProps } from '#/utils';
 import { MdItemSelect } from '#/views/mes/md/item/components';
 import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
 import { ProTaskSelect } from '#/views/mes/pro/task/components';
@@ -202,6 +204,39 @@
           statuses: [MesProTaskStatusEnum.PREPARE],
           workOrderId: values.workOrderId,
           workstationId: values.workstationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'templateId',
+      label: '璐ㄦ鏂规',
+      component: 'ApiSelect',
+      componentProps: {
+        api: getTemplateItemOptionList,
+        autoSelect: 'first',
+        labelField: 'templateName',
+        valueField: 'templateId',
+        placeholder: '璇烽�夋嫨璐ㄦ鏂规',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['workOrderId', 'taskId'],
+        componentProps: (values) => ({
+          api: getTemplateItemOptionList,
+          autoSelect: 'first',
+          labelField: 'templateName',
+          valueField: 'templateId',
+          disabled: formType !== 'create' || !values.workOrderId,
+          params: values.workOrderId
+            ? {
+                qcType: 2,
+                workOrderId: values.workOrderId,
+                taskId: values.taskId,
+              }
+            : {},
+          placeholder: values.workOrderId
+            ? '璇烽�夋嫨璐ㄦ鏂规'
+            : '璇峰厛閫夋嫨鐢熶骇璁㈠崟',
         }),
       },
     },
@@ -404,6 +439,14 @@
         placeholder: '璇烽�夋嫨妫�娴嬬粨鏋�',
       },
     },
+    {
+      fieldName: 'inspectDate',
+      label: '妫�娴嬫棩鏈�',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+      },
+    },
   ];
 }
 
diff --git a/src/views/mes/qc/ipqc/index.vue b/src/views/mes/qc/ipqc/index.vue
index 32d46c4..832a85a 100644
--- a/src/views/mes/qc/ipqc/index.vue
+++ b/src/views/mes/qc/ipqc/index.vue
@@ -4,19 +4,24 @@
 
 import { Page, useVbenModal } from '@vben/common-ui';
 import { MesQcStatusEnum } from '@vben/constants';
-import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 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 { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
+  connectedComponent: QcExportRangeModal,
   destroyOnClose: true,
 });
 
@@ -55,10 +60,15 @@
   }
 }
 
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
-  const data = await exportIpqc(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: '杩囩▼妫�楠屽崟.xls', source: data });
+/** 瀵煎嚭琛ㄦ牸锛堝脊绐楅�夋楠屾椂闂磋寖鍥达級 */
+function handleExport() {
+  exportRangeModalApi
+    .setData({
+      title: '鎸夋楠屾椂闂村鍑鸿繃绋嬫楠屽崟',
+      exportApi: exportIpqc,
+      fileName: '杩囩▼妫�楠屽崟.xls',
+    })
+    .open();
 }
 
 const [Grid, gridApi] = useVbenVxeGrid({
@@ -93,8 +103,9 @@
 </script>
 
 <template>
-  <Page auto-content-height><FormModal @success="handleRefresh" />
-
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <ExportRangeModal />
     <Grid table-title="杩囩▼妫�楠屽崟鍒楄〃">
       <template #toolbar-tools>
         <TableAction
diff --git a/src/views/mes/qc/iqc/data.ts b/src/views/mes/qc/iqc/data.ts
index 40cb523..a59f777 100644
--- a/src/views/mes/qc/iqc/data.ts
+++ b/src/views/mes/qc/iqc/data.ts
@@ -11,6 +11,7 @@
 import { Button } from 'ant-design-vue';
 
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
 import { getSupplierSimpleList } from '#/api/srm/supplier';
 import { getSimpleUserList } from '#/api/system/user';
 import { getRangePickerDefaultProps } from '#/utils';
@@ -135,6 +136,33 @@
       },
     },
     {
+      fieldName: 'templateId',
+      label: '璐ㄦ鏂规',
+      component: 'ApiSelect',
+      componentProps: {
+        api: getTemplateItemOptionList,
+        autoSelect: 'first',
+        labelField: 'templateName',
+        valueField: 'templateId',
+        placeholder: '璇烽�夋嫨璐ㄦ鏂规',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          api: getTemplateItemOptionList,
+          autoSelect: 'first',
+          labelField: 'templateName',
+          valueField: 'templateId',
+          disabled: formType !== 'create' || !values.itemId,
+          params: values.itemId
+            ? { itemId: values.itemId, qcType: 1 }
+            : {},
+          placeholder: values.itemId ? '璇烽�夋嫨璐ㄦ鏂规' : '璇峰厛閫夋嫨浜у搧鐗╂枡',
+        }),
+      },
+    },
+    {
       fieldName: 'vendorId',
       label: '渚涘簲鍟�',
       component: 'ApiSelect',
diff --git a/src/views/mes/qc/iqc/index.vue b/src/views/mes/qc/iqc/index.vue
index 44e0901..761305f 100644
--- a/src/views/mes/qc/iqc/index.vue
+++ b/src/views/mes/qc/iqc/index.vue
@@ -7,13 +7,13 @@
 
 import { Page, useVbenModal } from '@vben/common-ui';
 import { MesQcStatusEnum } from '@vben/constants';
-import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 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 { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
@@ -24,6 +24,11 @@
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
+  connectedComponent: QcExportRangeModal,
   destroyOnClose: true,
 });
 
@@ -62,10 +67,15 @@
   }
 }
 
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
-  const data = await exportIqc(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: '鏉ユ枡妫�楠屽崟.xls', source: data });
+/** 瀵煎嚭琛ㄦ牸锛堝脊绐楅�夋楠屾椂闂磋寖鍥达級 */
+function handleExport() {
+  exportRangeModalApi
+    .setData({
+      title: '鎸夋楠屾椂闂村鍑烘潵鏂欐楠屽崟',
+      exportApi: exportIqc,
+      fileName: '鏉ユ枡妫�楠屽崟.xls',
+    })
+    .open();
 }
 
 const [Grid, gridApi] = useVbenVxeGrid({
@@ -107,8 +117,9 @@
 </script>
 
 <template>
-  <Page auto-content-height><FormModal @success="handleRefresh" />
-
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <ExportRangeModal />
     <Grid table-title="鏉ユ枡妫�楠屽崟鍒楄〃">
       <template #toolbar-tools>
         <TableAction
diff --git a/src/views/mes/qc/oqc/data.ts b/src/views/mes/qc/oqc/data.ts
index 0183a35..54b6766 100644
--- a/src/views/mes/qc/oqc/data.ts
+++ b/src/views/mes/qc/oqc/data.ts
@@ -11,7 +11,9 @@
 import { Button } from 'ant-design-vue';
 
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
 import { getSimpleUserList } from '#/api/system/user';
+import { getRangePickerDefaultProps } from '#/utils';
 import CrmCustomerSelect from '#/components/crm-customer-select.vue';
 import { MdmItemSelect } from '#/views/basicData/mdm/components';
 
@@ -129,6 +131,33 @@
         componentProps: (values) => ({
           disabled: !!values.sourceDocId,
           placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+        }),
+      },
+    },
+    {
+      fieldName: 'templateId',
+      label: '璐ㄦ鏂规',
+      component: 'ApiSelect',
+      componentProps: {
+        api: getTemplateItemOptionList,
+        autoSelect: 'first',
+        labelField: 'templateName',
+        valueField: 'templateId',
+        placeholder: '璇烽�夋嫨璐ㄦ鏂规',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          api: getTemplateItemOptionList,
+          autoSelect: 'first',
+          labelField: 'templateName',
+          valueField: 'templateId',
+          disabled: formType !== 'create' || !values.itemId,
+          params: values.itemId
+            ? { itemId: values.itemId, qcType: 3 }
+            : {},
+          placeholder: values.itemId ? '璇烽�夋嫨璐ㄦ鏂规' : '璇峰厛閫夋嫨浜у搧鐗╂枡',
         }),
       },
     },
@@ -340,6 +369,14 @@
         placeholder: '璇烽�夋嫨妫�娴嬬粨鏋�',
       },
     },
+    {
+      fieldName: 'inspectDate',
+      label: '妫�娴嬫棩鏈�',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+      },
+    },
   ];
 }
 
diff --git a/src/views/mes/qc/oqc/index.vue b/src/views/mes/qc/oqc/index.vue
index 965cfda..f9043b1 100644
--- a/src/views/mes/qc/oqc/index.vue
+++ b/src/views/mes/qc/oqc/index.vue
@@ -4,19 +4,24 @@
 
 import { Page, useVbenModal } from '@vben/common-ui';
 import { MesQcStatusEnum } from '@vben/constants';
-import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 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 { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
+  connectedComponent: QcExportRangeModal,
   destroyOnClose: true,
 });
 
@@ -55,10 +60,15 @@
   }
 }
 
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
-  const data = await exportOqc(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: '鍑鸿揣妫�楠屽崟.xls', source: data });
+/** 瀵煎嚭琛ㄦ牸锛堝脊绐楅�夋楠屾椂闂磋寖鍥达級 */
+function handleExport() {
+  exportRangeModalApi
+    .setData({
+      title: '鎸夋楠屾椂闂村鍑鸿揣妫�楠屽崟',
+      exportApi: exportOqc,
+      fileName: '鍑鸿揣妫�楠屽崟.xls',
+    })
+    .open();
 }
 
 const [Grid, gridApi] = useVbenVxeGrid({
@@ -93,8 +103,9 @@
 </script>
 
 <template>
-  <Page auto-content-height><FormModal @success="handleRefresh" />
-
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <ExportRangeModal />
     <Grid table-title="鍑鸿揣妫�楠屽崟鍒楄〃">
       <template #toolbar-tools>
         <TableAction
diff --git a/src/views/mes/qc/rqc/data.ts b/src/views/mes/qc/rqc/data.ts
index 92d5e1e..ab9af37 100644
--- a/src/views/mes/qc/rqc/data.ts
+++ b/src/views/mes/qc/rqc/data.ts
@@ -15,7 +15,9 @@
 import { Button } from 'ant-design-vue';
 
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
 import { getSimpleUserList } from '#/api/system/user';
+import { getRangePickerDefaultProps } from '#/utils';
 import { MdmItemSelect } from '#/views/basicData/mdm/components';
 
 /** 琛ㄥ崟绫诲瀷 */
@@ -154,6 +156,33 @@
         componentProps: (values) => ({
           disabled: !!values.sourceDocId,
           placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+        }),
+      },
+    },
+    {
+      fieldName: 'templateId',
+      label: '璐ㄦ鏂规',
+      component: 'ApiSelect',
+      componentProps: {
+        api: getTemplateItemOptionList,
+        autoSelect: 'first',
+        labelField: 'templateName',
+        valueField: 'templateId',
+        placeholder: '璇烽�夋嫨璐ㄦ鏂规',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          api: getTemplateItemOptionList,
+          autoSelect: 'first',
+          labelField: 'templateName',
+          valueField: 'templateId',
+          disabled: formType !== 'create' || !values.itemId,
+          params: values.itemId
+            ? { itemId: values.itemId, qcType: 4 }
+            : {},
+          placeholder: values.itemId ? '璇烽�夋嫨璐ㄦ鏂规' : '璇峰厛閫夋嫨浜у搧鐗╂枡',
         }),
       },
     },
@@ -330,6 +359,14 @@
         valueField: 'id',
       },
     },
+    {
+      fieldName: 'inspectDate',
+      label: '妫�娴嬫棩鏈�',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+      },
+    },
   ];
 }
 
diff --git a/src/views/mes/qc/rqc/index.vue b/src/views/mes/qc/rqc/index.vue
index 0cfc6f2..2771b4c 100644
--- a/src/views/mes/qc/rqc/index.vue
+++ b/src/views/mes/qc/rqc/index.vue
@@ -4,19 +4,24 @@
 
 import { Page, useVbenModal } from '@vben/common-ui';
 import { MesQcStatusEnum } from '@vben/constants';
-import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 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 { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
+  connectedComponent: QcExportRangeModal,
   destroyOnClose: true,
 });
 
@@ -55,10 +60,15 @@
   }
 }
 
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
-  const data = await exportRqc(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: '閫�璐ф楠屽崟.xls', source: data });
+/** 瀵煎嚭琛ㄦ牸锛堝脊绐楅�夋楠屾椂闂磋寖鍥达級 */
+function handleExport() {
+  exportRangeModalApi
+    .setData({
+      title: '鎸夋楠屾椂闂村閫�璐ф楠屽崟',
+      exportApi: exportRqc,
+      fileName: '閫�璐ф楠屽崟.xls',
+    })
+    .open();
 }
 
 const [Grid, gridApi] = useVbenVxeGrid({
@@ -93,8 +103,9 @@
 </script>
 
 <template>
-  <Page auto-content-height><FormModal @success="handleRefresh" />
-
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <ExportRangeModal />
     <Grid table-title="閫�璐ф楠屽崟鍒楄〃">
       <template #toolbar-tools>
         <TableAction
diff --git a/src/views/wls/productissue/components/index.ts b/src/views/wls/productissue/components/index.ts
new file mode 100644
index 0000000..aa8b15e
--- /dev/null
+++ b/src/views/wls/productissue/components/index.ts
@@ -0,0 +1,2 @@
+export { default as WmProductIssueLineSelect } from './select.vue';
+export { default as WmProductIssueLineSelectDialog } from './select-dialog.vue';
diff --git a/src/views/wls/productissue/components/select-dialog.vue b/src/views/wls/productissue/components/select-dialog.vue
new file mode 100644
index 0000000..09bb732
--- /dev/null
+++ b/src/views/wls/productissue/components/select-dialog.vue
@@ -0,0 +1,106 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnableIssueLines } from '#/api/mes/wm/productissue/line';
+
+const props = defineProps<{
+  workOrderId?: number;
+}>();
+
+const emit = defineEmits<{
+  selected: [rows: MesWmProductIssueLineApi.ProductIssueLine[]];
+}>();
+
+const open = ref(false);
+const selectedRows = ref<MesWmProductIssueLineApi.ProductIssueLine[]>([]);
+
+const columns: VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>['columns'] = [
+  { type: 'radio', width: 60 },
+  { field: 'itemCode', title: '鐗╂枡缂栫爜', minWidth: 120 },
+  { field: 'itemName', title: '鐗╂枡鍚嶇О', minWidth: 140 },
+  { field: 'specification', title: '瑙勬牸鍨嬪彿', minWidth: 120 },
+  { field: 'unitMeasureName', title: '鍗曚綅', width: 80 },
+  { field: 'quantity', title: '鍙��鏁伴噺', width: 100 },
+  { field: 'batchCode', title: '鎵规鍙�', minWidth: 120 },
+];
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns,
+    height: 480,
+    keepSource: true,
+    radioConfig: { highlight: true, trigger: 'row' },
+    proxyConfig: {
+      ajax: {
+        query: async () => {
+          if (!props.workOrderId) {
+            return { list: [], total: 0 };
+          }
+          const list = await getReturnableIssueLines(props.workOrderId);
+          return { list, total: list.length };
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>,
+  gridEvents: {
+    radioChange: ({ row }: { row: MesWmProductIssueLineApi.ProductIssueLine }) => {
+      selectedRows.value = [row];
+    },
+    cellDblclick: async ({ row }: { row: MesWmProductIssueLineApi.ProductIssueLine }) => {
+      selectedRows.value = [row];
+      await gridApi.grid.setRadioRow(row);
+      handleConfirm();
+    },
+  },
+});
+
+async function openModal() {
+  open.value = true;
+  selectedRows.value = [];
+  await nextTick();
+  await gridApi.grid.clearRadioRow();
+  await gridApi.query();
+}
+
+function closeModal() {
+  open.value = false;
+  selectedRows.value = [];
+}
+
+function handleConfirm() {
+  if (selectedRows.value.length === 0) {
+    message.warning('璇烽�夋嫨涓�鏉¢鏂欒褰�');
+    return;
+  }
+  emit('selected', selectedRows.value);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    :destroy-on-close="true"
+    title="閫夋嫨棰嗘枡璁板綍"
+    width="70%"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Grid table-title="鍙��鏂欑殑棰嗘枡璁板綍" />
+  </Modal>
+</template>
diff --git a/src/views/wls/productissue/components/select.vue b/src/views/wls/productissue/components/select.vue
new file mode 100644
index 0000000..8a86b5f
--- /dev/null
+++ b/src/views/wls/productissue/components/select.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getProductIssueLine } from '#/api/mes/wm/productissue/line';
+
+import ProductIssueLineSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmProductIssueLineSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+    workOrderId?: number;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨棰嗘枡璁板綍',
+    workOrderId: undefined,
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmProductIssueLineApi.ProductIssueLine | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof ProductIssueLineSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmProductIssueLineApi.ProductIssueLine>();
+
+const displayLabel = computed(() => {
+  const item = selectedItem.value;
+  if (!item) {
+    return '';
+  }
+  return `${item.itemCode || '-'} | ${item.itemName || '-'} | 鏁伴噺:${item.quantity}`;
+});
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getProductIssueLine(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  dialogRef.value?.open();
+}
+
+function handleSelected(rows: MesWmProductIssueLineApi.ProductIssueLine[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+          <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+          <div>瑙勬牸鍨嬪彿锛歿{ selectedItem.specification || '-' }}</div>
+          <div>鍙��鏁伴噺锛歿{ selectedItem.quantity ?? '-' }}</div>
+          <div>鎵规鍙凤細{{ selectedItem.batchCode || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <ProductIssueLineSelectDialog
+    ref="dialogRef"
+    :work-order-id="workOrderId"
+    @selected="handleSelected"
+  />
+</template>
diff --git a/src/views/wls/productissue/data.ts b/src/views/wls/productissue/data.ts
index 2ee6dcc..896c624 100644
--- a/src/views/wls/productissue/data.ts
+++ b/src/views/wls/productissue/data.ts
@@ -1,5 +1,6 @@
 锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProTaskApi } from '#/api/mes/pro/task';
 import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
 import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
 import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
@@ -20,6 +21,7 @@
 import { getRangePickerDefaultProps } from '#/utils';
 import { MdItemSelect } from '#/views/mes/md/item/components';
 import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProTaskSelect } from '#/views/mes/pro/task/components';
 import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
 import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
 import {
@@ -110,7 +112,33 @@
       label: '鐢熶骇璁㈠崟',
       component: markRaw(ProWorkOrderSelect),
       componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
         status: MesProWorkOrderStatusEnum.CONFIRMED,
+        // 璁㈠崟鍙樻洿锛氭竻绌哄凡閫夌敓浜т换鍔★紝閬垮厤浠诲姟涓庤鍗曚笉涓�鑷�
+        onChange: async () => {
+          await formApi?.setFieldValue('taskId', undefined);
+        },
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'taskId',
+      label: '鐢熶骇浠诲姟',
+      component: markRaw(ProTaskSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇浠诲姟',
+        // 閫変腑浠诲姟鍚庤嚜鍔ㄥ甫鍑哄叾宸ヤ綔绔�
+        onChange: async (task?: MesProTaskApi.Task) => {
+          await formApi?.setFieldValue('workstationId', task?.workstationId);
+        },
+      },
+      dependencies: {
+        triggerFields: ['workOrderId'],
+        componentProps: (values) => ({
+          disabled: !values.workOrderId,
+          placeholder: values.workOrderId ? '璇烽�夋嫨鐢熶骇浠诲姟' : '璇峰厛閫夋嫨鐢熶骇璁㈠崟',
+          workOrderId: values.workOrderId,
+        }),
       },
       rules: 'selectRequired',
     },
@@ -198,6 +226,11 @@
     {
       field: 'workOrderCode',
       title: '鐢熶骇璁㈠崟',
+      minWidth: 140,
+    },
+    {
+      field: 'taskCode',
+      title: '鐢熶骇浠诲姟',
       minWidth: 140,
     },
     {
@@ -370,6 +403,14 @@
       },
     },
     {
+      fieldName: 'batchId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
       fieldName: 'itemId',
       label: '鐗╂枡',
       component: markRaw(MdItemSelect),
@@ -382,12 +423,13 @@
       label: '搴撳瓨璁板綍',
       component: markRaw(WmMaterialStockSelect),
       componentProps: {
-        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~鐗╂枡/浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺锛屽叏閮ㄤ互搴撳瓨璁板綍涓哄噯
         onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
           await formApi?.setValues({
             areaId: stock?.areaId,
             batchCode: stock?.batchCode,
             batchId: stock?.batchId,
+            itemId: stock?.itemId,
             locationId: stock?.locationId,
             quantity: stock?.quantity,
             quantityMax: stock?.quantity,
@@ -399,6 +441,7 @@
       dependencies: {
         triggerFields: ['itemId'],
         componentProps: (values) => ({
+          // 搴撳瓨閫夋嫨寮圭獥鍙瘑鍒� mdmItemId 鍏ュ弬锛屽苟鐢ㄥ畠浣滀负 itemId 杩囨护鏉′欢
           mdmItemId: values.itemId,
         }),
       },
diff --git a/src/views/wls/returnissue/data.ts b/src/views/wls/returnissue/data.ts
index 4ca6336..691b573 100644
--- a/src/views/wls/returnissue/data.ts
+++ b/src/views/wls/returnissue/data.ts
@@ -1,6 +1,7 @@
 锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
 import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
 import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
 import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
@@ -20,6 +21,7 @@
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
 import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
 import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmProductIssueLineSelect } from '#/views/wls/productissue/components';
 import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
 import {
   WmWarehouseAreaSelect,
@@ -316,27 +318,29 @@
 }
 
 /** 閫�鏂欏崟琛屾柊澧�/淇敼鐨勮〃鍗� */
-export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+export function useLineFormSchema(
+  formApi?: VbenFormApi,
+  workOrderId?: number,
+): VbenFormSchema[] {
   return [
     {
-      fieldName: 'materialStockId',
-      label: '搴撳瓨璁板綍',
-      component: markRaw(WmMaterialStockSelect),
+      fieldName: 'issueLineId',
+      label: '棰嗘枡璁板綍',
+      component: markRaw(WmProductIssueLineSelect),
       componentProps: {
-        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~鐗╂枡/鎵规/鏁伴噺
-        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+        workOrderId,
+        onChange: async (line?: MesWmProductIssueLineApi.ProductIssueLine) => {
           await formApi?.setValues({
-            batchCode: stock?.batchCode,
-            batchId: stock?.batchId,
-            itemId: stock?.itemId,
-            materialDisplayName: stock
-              ? [stock.itemName, stock.specification].filter(Boolean).join(' | ')
+            batchCode: line?.batchCode,
+            batchId: line?.batchId,
+            itemId: line?.itemId,
+            materialDisplayName: line
+              ? [line.itemName, line.specification].filter(Boolean).join(' | ')
               : undefined,
-            quantity: stock?.quantity,
-            quantityMax: stock?.quantity,
+            quantity: line?.quantity,
+            quantityMax: line?.quantity,
           });
         },
-        virtualFilter: 'only',
       },
       rules: 'selectRequired',
     },
@@ -390,7 +394,7 @@
       component: 'Input',
       componentProps: {
         disabled: true,
-        placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�',
+        placeholder: '閫夋嫨棰嗘枡璁板綍鍚庤嚜鍔ㄥ甫鍑�',
       },
     },
     {
@@ -399,7 +403,7 @@
       component: 'Input',
       componentProps: {
         disabled: true,
-        placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�',
+        placeholder: '閫夋嫨棰嗘枡璁板綍鍚庤嚜鍔ㄥ甫鍑�',
       },
     },
     {
diff --git a/src/views/wls/returnissue/modules/form.vue b/src/views/wls/returnissue/modules/form.vue
index 1546dd4..2d340e9 100644
--- a/src/views/wls/returnissue/modules/form.vue
+++ b/src/views/wls/returnissue/modules/form.vue
@@ -198,7 +198,7 @@
     <template v-if="formData?.id">
       <Divider>鐗╂枡淇℃伅</Divider>
       <div class="mx-4">
-        <LineList :form-type="formType" :issue-id="formData.id" />
+        <LineList :form-type="formType" :issue-id="formData.id" :work-order-id="formData.workOrderId" />
       </div>
     </template>
     <template #prepend-footer>
diff --git a/src/views/wls/returnissue/modules/line-form.vue b/src/views/wls/returnissue/modules/line-form.vue
index fc72468..4e14a8d 100644
--- a/src/views/wls/returnissue/modules/line-form.vue
+++ b/src/views/wls/returnissue/modules/line-form.vue
@@ -19,7 +19,8 @@
 
 const emit = defineEmits(['success']);
 const formData = ref<MesWmReturnIssueLineApi.ReturnIssueLine>();
-const issueId = ref<number>(); // 鎵�灞為��鏂欏崟缂栧彿
+const issueId = ref<number>();
+const workOrderId = ref<number>();
 
 const getTitle = computed(() => {
   return formData.value?.id
@@ -69,17 +70,16 @@
       formData.value = undefined;
       return;
     }
-    formApi.setState({ schema: useLineFormSchema(formApi) });
-    // 鍔犺浇鏁版嵁
-    const data = modalApi.getData<{ id?: number; issueId: number }>();
+    const data = modalApi.getData<{ id?: number; issueId: number; workOrderId?: number }>();
     issueId.value = data.issueId;
+    workOrderId.value = data.workOrderId;
+    formApi.setState({ schema: useLineFormSchema(formApi, workOrderId.value) });
     if (!data.id) {
       return;
     }
     modalApi.lock();
     try {
       formData.value = await getReturnIssueLine(data.id);
-      // 璁剧疆鍒� values锛屽苟鏋勫缓鐗╂枡灞曠ず鍚�
       await formApi.setValues({
         ...formData.value,
         materialDisplayName: [formData.value.itemName, formData.value.specification]
diff --git a/src/views/wls/returnissue/modules/line-list.vue b/src/views/wls/returnissue/modules/line-list.vue
index 98c6682..14c7ccf 100644
--- a/src/views/wls/returnissue/modules/line-list.vue
+++ b/src/views/wls/returnissue/modules/line-list.vue
@@ -29,6 +29,7 @@
 const props = defineProps<{
   formType: FormType;
   issueId: number;
+  workOrderId?: number;
 }>();
 
 const isEditable = computed(() =>
@@ -61,12 +62,12 @@
 
 /** 娣诲姞鐗╂枡 */
 function handleCreate() {
-  lineFormModalApi.setData({ issueId: props.issueId }).open();
+  lineFormModalApi.setData({ issueId: props.issueId, workOrderId: props.workOrderId }).open();
 }
 
 /** 缂栬緫鐗╂枡 */
 function handleEdit(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
-  lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+  lineFormModalApi.setData({ id: row.id, issueId: props.issueId, workOrderId: props.workOrderId }).open();
 }
 
 /** 鍒犻櫎鐗╂枡 */

--
Gitblit v1.9.3