From fee2f2532144fca6c81b1523aeef54165c5263b2 Mon Sep 17 00:00:00 2001
From: hsy <1029150275@qq.com>
Date: 星期二, 25 八月 2026 16:50:38 +0800
Subject: [PATCH] Merge branch 'refs/heads/dev_pro2.0' into fix/hsy/20260812

---
 src/api/mdm/unit/index.ts                                                       |   13 
 src/views/erp/finance/payment/data.ts                                           |   12 
 src/packages/locales/src/langs/en-US/preferences.json                           |   19 
 src/views/hrm/salary/leave-type-config/data.ts                                  |   27 
 src/views/basicData/mdm/unit/data.ts                                            |   24 
 src/views/wls/materialstock/components/stock-adjust-modal.vue                   |    4 
 src/api/hrm/overtime/index.ts                                                   |   89 +
 src/api/hrm/salary/structure/index.ts                                           |   50 
 src/views/hrm/salary/structure/data.ts                                          |  163 ++
 src/views/hrm/overtime/data.ts                                                  |  320 +++++
 src/views/hrm/employee/data.ts                                                  |   14 
 src/packages/constants/src/biz-wms-enum.ts                                      |   14 
 src/views/mes/pro/route/data.ts                                                 |    9 
 src/api/hrm/leave-type-config/index.ts                                          |   10 
 src/views/basicData/mdm/unit/index.vue                                          |   19 
 src/api/erp/purchase/order/index.ts                                             |    8 
 src/views/mes/process-design/route/data.ts                                      |    9 
 src/views/erp/purchase/order/index.vue                                          |   36 
 src/views/hrm/overtime/detail/approve/index.vue                                 |   83 +
 src/views/erp/purchase/invoice/modules/form.vue                                 |    6 
 src/views/erp/purchase/invoice/data.ts                                          |   48 
 src/views/mes/pro/workorder/modules/process-form.vue                            |   11 
 src/api/system/company-config/index.ts                                          |   27 
 WTXC.bat                                                                        |  451 +++++++
 src/api/hrm/attendance/record/index.ts                                          |    1 
 src/views/basicData/mdm/modules/import-form.vue                                 |  105 +
 src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue |  151 ++
 src/views/hrm/salary/leave-type-config/index.vue                                |   43 
 src/views/basicData/mdm/data.ts                                                 |   24 
 src/api/mes/md/item/type/index.ts                                               |   13 
 src/views/wls/stocktaking/plan/modules/param-form.vue                           |    2 
 src/views/hrm/overtime/index.vue                                                |  200 +++
 YZFX.bat                                                                        |  451 +++++++
 src/api/hrm/employee/index.ts                                                   |    3 
 src/views/hrm/overtime/modules/form.vue                                         |  159 ++
 src/views/hrm/salary/structure/index.vue                                        |  137 ++
 src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue     |   11 
 src/views/hrm/salary/leave-type-config/modules/form.vue                         |   59 
 company.config.ts                                                               |    8 
 src/views/erp/purchase/invoice/modules/purchase-order-select.vue                |    6 
 src/views/hrm/salary/structure/modules/form.vue                                 |   95 +
 vite.config.ts                                                                  |    3 
 src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue      |   25 
 src/views/bpm/task/done/data.ts                                                 |    2 
 src/store/company-config.ts                                                     |   71 +
 src/api/system/storage/index.ts                                                 |   11 
 src/views/basicData/mdm/index.vue                                               |   19 
 src/views/hrm/attendance/record/modules/import-form.vue                         |   12 
 src/api/mdm/item/index.ts                                                       |   13 
 src/views/erp/finance/payment/modules/form.vue                                  |    6 
 internal/vite-config/src/utils/env.ts                                           |    9 
 src/views/mes/pro/route/modules/process-form.vue                                |    2 
 src/bootstrap.ts                                                                |   14 
 src/store/index.ts                                                              |    1 
 src/views/mes/md/item/type/modules/import-form.vue                              |  105 +
 src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts            |    1 
 src/packages/locales/src/langs/zh-CN/preferences.json                           |   19 
 src/views/basicData/mdm/unit/modules/import-form.vue                            |  105 +
 src/views/hrm/attendance/record/data.ts                                         |    1 
 src/api/mes/pd/document/index.ts                                                |    2 
 src/views/mes/md/item/type/data.ts                                              |   24 
 src/views/mes/md/item/type/index.vue                                            |   20 
 src/layouts/auth.vue                                                            |   24 
 src/utils/company-config.ts                                                     |   32 
 src/views/bpm/task/done/index.vue                                               |   35 
 src/views/mes/process-design/route/modules/process-form.vue                     |    2 
 66 files changed, 3,393 insertions(+), 99 deletions(-)

diff --git a/WTXC.bat b/WTXC.bat
new file mode 100644
index 0000000..215373b
--- /dev/null
+++ b/WTXC.bat
@@ -0,0 +1,451 @@
+@echo off
+setlocal EnableExtensions EnableDelayedExpansion
+
+REM ============================================================
+REM Vue Frontend Deploy Script
+REM ============================================================
+
+REM =========================
+REM Configuration
+REM =========================
+
+set "COMPANY=WTXC"
+
+set "REMOTE_USER=root"
+set "REMOTE_HOST=42.63.71.140"
+set "REMOTE_PORT=22"
+
+set "PASSWORD=Nxwtxc123."
+
+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/YZFX.bat b/YZFX.bat
new file mode 100644
index 0000000..d4b3432
--- /dev/null
+++ b/YZFX.bat
@@ -0,0 +1,451 @@
+@echo off
+setlocal EnableExtensions EnableDelayedExpansion
+
+REM ============================================================
+REM Vue Frontend Deploy Script
+REM ============================================================
+
+REM =========================
+REM Configuration
+REM =========================
+
+set "COMPANY=YZFX"
+
+set "REMOTE_USER=root"
+set "REMOTE_HOST=42.63.70.231"
+set "REMOTE_PORT=22"
+
+set "PASSWORD=Nxyzfxxcl1@"
+
+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 3ca35b2..9efaf03 100644
--- a/company.config.ts
+++ b/company.config.ts
@@ -30,9 +30,15 @@
     apiUrl: 'http://42.63.70.90:9001/admin-api',
   },
   YZFX: {
-    name: '瀹佸鑻辨辰闄ら攬',
+    name: '瀹佸鑻辨辰闃查攬',
     logo: '/logos/YZFXLogo.png',
     favicon: '/logos/YZFXLogo.ico',
     apiUrl: 'http://42.63.70.231:9001/admin-api',
   },
+  WTXC: {
+    name: '瀹佸涓囬�氭柊鏉�',
+    logo: '/logos/YZFXLogo.png',
+    favicon: '/logos/YZFXLogo.ico',
+    apiUrl: 'http://42.63.71.140:9001/admin-api',
+  },
 };
diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts
index f342216..d14019b 100644
--- a/internal/vite-config/src/utils/env.ts
+++ b/internal/vite-config/src/utils/env.ts
@@ -38,7 +38,7 @@
   match = 'VITE_GLOB_',
   confFiles = getConfFiles(),
 ) {
-  let envConfig = {};
+  let envConfig: Record<string, string> = {};
 
   for (const confFile of confFiles) {
     try {
@@ -60,6 +60,13 @@
       Reflect.deleteProperty(envConfig, key);
     }
   });
+  // 鍚堝苟杩涚▼鐜鍙橀噺锛屼娇 vite.config.ts 鎸夊叕鍙告敞鍏ョ殑 VITE_GLOB_API_URL 鑳借繘鍏� _app-config 鏂囦欢
+  Object.keys(process.env).forEach((key) => {
+    const value = process.env[key];
+    if (reg.test(key) && value) {
+      envConfig[key] = value;
+    }
+  });
   return envConfig as T;
 }
 
diff --git a/src/api/erp/purchase/order/index.ts b/src/api/erp/purchase/order/index.ts
index 5ea3dff..4508346 100644
--- a/src/api/erp/purchase/order/index.ts
+++ b/src/api/erp/purchase/order/index.ts
@@ -13,6 +13,7 @@
     totalCount?: number; // 鍚堣鏁伴噺
     totalPrice?: number; // 鍚堣閲戦锛屽崟浣嶏細鍏�
     totalProductPrice?: number; // 浜у搧閲戦锛屽崟浣嶏細鍏�
+    totalTaxPrice?: number; // 鍚堣绋庨锛屽崟浣嶏細鍏�
     discountPercent?: number; // 浼樻儬鐜囷紝鐧惧垎姣�
     discountPrice?: number; // 浼樻儬閲戦锛屽崟浣嶏細鍏�
     depositPrice?: number; // 瀹氶噾閲戦锛屽崟浣嶏細鍏�
@@ -103,6 +104,13 @@
   return requestClient.get<any[]>('/erp/purchase-order/approve-process-list');
 }
 
+/** 鍙嶅鎵癸紙鍗忓悓鍔炲叕宸插姙浠诲姟涓挙鍥炲凡瀹℃牳璁㈠崟锛屽洖閫�涓烘湭瀹℃牳锛� */
+export function reverseApprove(processInstanceId: string) {
+  return requestClient.post('/erp/purchase-order/reverse-approve', null, {
+    params: { processInstanceId },
+  });
+}
+
 /** 鑾峰彇閲囪喘瀹℃牳娴佺▼鍒楄〃鍝嶅簲 */
 export interface ProcessDefinition {
   id: string; // 娴佺▼瀹氫箟ID
diff --git a/src/api/hrm/attendance/record/index.ts b/src/api/hrm/attendance/record/index.ts
index 584cb2e..f5a1144 100644
--- a/src/api/hrm/attendance/record/index.ts
+++ b/src/api/hrm/attendance/record/index.ts
@@ -10,6 +10,7 @@
     userName?: string;
     deptId?: number;
     deptName?: string;
+    phone?: string;
     date?: string;
     clockInTime?: string;
     clockOutTime?: string;
diff --git a/src/api/hrm/employee/index.ts b/src/api/hrm/employee/index.ts
index 0287f85..6a659ab 100644
--- a/src/api/hrm/employee/index.ts
+++ b/src/api/hrm/employee/index.ts
@@ -29,6 +29,7 @@
     employeeStatus?: number;
     annualLeaveBalance?: number;
     sickLeaveBalance?: number;
+    salaryStructureId?: number;
     baseSalary?: number;
     roleIds?: number[];
     roleNames?: string[];
@@ -167,4 +168,4 @@
 /** 鍒犻櫎绱ф�ヨ仈绯讳汉 */
 export function deleteEmergencyContact(id: number) {
   return requestClient.delete(`/hrm/employee/emergency-contact/delete?id=${id}`);
-}
\ No newline at end of file
+}
diff --git a/src/api/hrm/leave-type-config/index.ts b/src/api/hrm/leave-type-config/index.ts
index 4c58a3f..bf2131a 100644
--- a/src/api/hrm/leave-type-config/index.ts
+++ b/src/api/hrm/leave-type-config/index.ts
@@ -22,7 +22,17 @@
   );
 }
 
+/** 鏂板璇峰亣绫诲瀷閰嶇疆 */
+export function createLeaveTypeConfig(data: HrmLeaveTypeConfigApi.LeaveTypeConfig) {
+  return requestClient.post<number>('/hrm/leave-type-config/create', data);
+}
+
 /** 鏇存柊璇峰亣绫诲瀷閰嶇疆 */
 export function updateLeaveTypeConfig(data: HrmLeaveTypeConfigApi.LeaveTypeConfig) {
   return requestClient.put('/hrm/leave-type-config/update', data);
+}
+
+/** 鍒犻櫎璇峰亣绫诲瀷閰嶇疆 */
+export function deleteLeaveTypeConfig(id: number) {
+  return requestClient.delete(`/hrm/leave-type-config/delete?id=${id}`);
 }
\ No newline at end of file
diff --git a/src/api/hrm/overtime/index.ts b/src/api/hrm/overtime/index.ts
new file mode 100644
index 0000000..37f5b15
--- /dev/null
+++ b/src/api/hrm/overtime/index.ts
@@ -0,0 +1,89 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace HrmOvertimeApi {
+  /** 鍔犵彮鐢宠 */
+  export interface Overtime {
+    id?: number;
+    employeeId?: number;
+    employeeName?: string;
+    employeeNo?: string;
+    deptName?: string;
+    postName?: string;
+    overtimeDate?: string;
+    startTime?: string;
+    endTime?: string;
+    duration?: number;
+    overtimeType?: number;
+    crossDay?: boolean;
+    mealDeduction?: boolean;
+    mealDeductionHours?: number;
+    reason?: string;
+    relatedType?: number;
+    relatedCode?: string;
+    workContent?: string;
+    status?: number;
+    processInstanceId?: string;
+    createTime?: string;
+  }
+
+  export interface ApproveProcess {
+    key: string;
+    name: string;
+  }
+}
+
+/** 鏌ヨ鍔犵彮鐢宠鍒嗛〉鍒楄〃 */
+export function getOvertimePage(params: PageParam) {
+  return requestClient.get<PageResult<HrmOvertimeApi.Overtime>>(
+    '/hrm/overtime/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鍔犵彮鐢宠璇︽儏 */
+export function getOvertime(id: number) {
+  return requestClient.get<HrmOvertimeApi.Overtime>(
+    `/hrm/overtime/get?id=${id}`,
+  );
+}
+
+/** 鑾峰彇褰撳墠鐢宠浜哄叧鑱旂殑鍛樺伐妗f */
+export function getCurrentOvertimeApplicant() {
+  return requestClient.get<HrmOvertimeApi.Overtime>('/hrm/overtime/current-applicant');
+}
+
+/** 鏂板鍔犵彮鐢宠锛堣崏绋匡級 */
+export function createOvertime(data: HrmOvertimeApi.Overtime) {
+  return requestClient.post('/hrm/overtime/create', data);
+}
+
+/** 鏇存柊鍔犵彮鐢宠 */
+export function updateOvertime(data: HrmOvertimeApi.Overtime) {
+  return requestClient.put('/hrm/overtime/update', data);
+}
+
+/** 鍒犻櫎鍔犵彮鐢宠 */
+export function deleteOvertime(id: number) {
+  return requestClient.delete(`/hrm/overtime/delete?id=${id}`);
+}
+
+/** 鎻愪氦鍔犵彮鐢宠锛堝彂璧峰鎵癸級 */
+export function submitOvertime(id: number, processDefinitionKey: string) {
+  return requestClient.post('/hrm/overtime/submit', null, {
+    params: { id, processDefinitionKey },
+  });
+}
+
+/** 鎾ら攢鍔犵彮鐢宠 */
+export function cancelOvertime(id: number) {
+  return requestClient.post(`/hrm/overtime/cancel?id=${id}`);
+}
+
+/** 鑾峰彇鍗忓悓鍔炲叕涓殑鍙敤鍔犵彮瀹℃壒娴佺▼ */
+export function getOvertimeApproveProcessDefinitionList() {
+  return requestClient.get<HrmOvertimeApi.ApproveProcess[]>(
+    '/hrm/overtime/approve-process-definition-list',
+  );
+}
diff --git a/src/api/hrm/salary/structure/index.ts b/src/api/hrm/salary/structure/index.ts
new file mode 100644
index 0000000..20a25b8
--- /dev/null
+++ b/src/api/hrm/salary/structure/index.ts
@@ -0,0 +1,50 @@
+import { requestClient } from '#/api/request';
+
+export namespace HrmSalaryStructureApi {
+  export interface SalaryStructure {
+    id?: number;
+    name?: string;
+    overtimePayRatio?: number;
+    restDayOvertimePayRatio?: number;
+    legalHolidayOvertimePayRatio?: number;
+    status?: number;
+    remark?: string;
+    createTime?: string;
+  }
+
+  export interface SalaryStructureQueryParam {
+    name?: string;
+    status?: number;
+  }
+}
+
+export function getSalaryStructureList(
+  params?: HrmSalaryStructureApi.SalaryStructureQueryParam,
+) {
+  return requestClient.get<HrmSalaryStructureApi.SalaryStructure[]>(
+    '/hrm/salary/structure/list',
+    { params },
+  );
+}
+
+export function getSalaryStructure(id: number) {
+  return requestClient.get<HrmSalaryStructureApi.SalaryStructure>(
+    `/hrm/salary/structure/get?id=${id}`,
+  );
+}
+
+export function createSalaryStructure(
+  data: HrmSalaryStructureApi.SalaryStructure,
+) {
+  return requestClient.post('/hrm/salary/structure/create', data);
+}
+
+export function updateSalaryStructure(
+  data: HrmSalaryStructureApi.SalaryStructure,
+) {
+  return requestClient.put('/hrm/salary/structure/update', data);
+}
+
+export function deleteSalaryStructure(id: number) {
+  return requestClient.delete(`/hrm/salary/structure/delete?id=${id}`);
+}
diff --git a/src/api/mdm/item/index.ts b/src/api/mdm/item/index.ts
index d950b6e..9b0d538 100644
--- a/src/api/mdm/item/index.ts
+++ b/src/api/mdm/item/index.ts
@@ -116,3 +116,16 @@
 export function exportItem(params: Partial<MdmItemApi.Item>) {
   return requestClient.download('/mdm/item/export-excel', { params });
 }
+
+/** 涓嬭浇鐗╂枡瀵煎叆妯℃澘 */
+export function importItemTemplate() {
+  return requestClient.download('/mdm/item/get-import-template');
+}
+
+/** 瀵煎叆鐗╂枡 */
+export function importItem(file: File, updateSupport: boolean) {
+  return requestClient.upload('/mdm/item/import', {
+    file,
+    updateSupport,
+  });
+}
diff --git a/src/api/mdm/unit/index.ts b/src/api/mdm/unit/index.ts
index 6b437ba..5041a55 100644
--- a/src/api/mdm/unit/index.ts
+++ b/src/api/mdm/unit/index.ts
@@ -47,3 +47,16 @@
 export function deleteUnit(id: number) {
   return requestClient.delete(`/mdm/unit/delete?id=${id}`);
 }
+
+/** 涓嬭浇璁¢噺鍗曚綅瀵煎叆妯℃澘 */
+export function importUnitTemplate() {
+  return requestClient.download('/mdm/unit/get-import-template');
+}
+
+/** 瀵煎叆璁¢噺鍗曚綅 */
+export function importUnit(file: File, updateSupport: boolean) {
+  return requestClient.upload('/mdm/unit/import', {
+    file,
+    updateSupport,
+  });
+}
diff --git a/src/api/mes/md/item/type/index.ts b/src/api/mes/md/item/type/index.ts
index e9367ee..bd99b95 100644
--- a/src/api/mes/md/item/type/index.ts
+++ b/src/api/mes/md/item/type/index.ts
@@ -52,3 +52,16 @@
 export function deleteItemType(id: number) {
   return requestClient.delete(`/mes/md/item-type/delete?id=${id}`);
 }
+
+/** 涓嬭浇鐗╂枡浜у搧鍒嗙被瀵煎叆妯℃澘 */
+export function importItemTypeTemplate() {
+  return requestClient.download('/mes/md/item-type/get-import-template');
+}
+
+/** 瀵煎叆鐗╂枡浜у搧鍒嗙被 */
+export function importItemType(file: File, updateSupport: boolean) {
+  return requestClient.upload('/mes/md/item-type/import', {
+    file,
+    updateSupport,
+  });
+}
diff --git a/src/api/mes/pd/document/index.ts b/src/api/mes/pd/document/index.ts
index 9ee756c..73d4d5e 100644
--- a/src/api/mes/pd/document/index.ts
+++ b/src/api/mes/pd/document/index.ts
@@ -17,6 +17,8 @@
     documentVersion?: string;
     /** 鏂囦欢鍚� */
     fileName?: string;
+    /** 鏂囦欢鍦板潃 */
+    fileUrl?: string;
     /** 涓婁紶浜猴紙鐢ㄦ埛ID锛� */
     uploadUserId?: number;
     /** 涓婁紶浜哄悕绉� */
diff --git a/src/api/system/company-config/index.ts b/src/api/system/company-config/index.ts
new file mode 100644
index 0000000..dbd1405
--- /dev/null
+++ b/src/api/system/company-config/index.ts
@@ -0,0 +1,27 @@
+import { requestClient } from '#/api/request';
+
+export namespace SystemCompanyConfigApi {
+  /** 鍏徃鍝佺墝淇℃伅 */
+  export interface CompanyConfig {
+    id?: number;
+    companyName?: string;
+    logoUrl?: string;
+    logoBlobId?: number;
+    loginTitle?: string;
+    loginDescription?: string;
+  }
+}
+
+/** 鑾峰緱鍏徃鍝佺墝淇℃伅锛堝厤鐧诲綍鍏紑锛岀櫥褰曢〉 / 搴旂敤鍚姩鏃朵娇鐢級 */
+export async function getCompanyConfig() {
+  return requestClient.get<SystemCompanyConfigApi.CompanyConfig | null>(
+    '/system/company-config/get',
+  );
+}
+
+/** 淇濆瓨鍏徃鍝佺墝淇℃伅 */
+export async function saveCompanyConfig(
+  data: SystemCompanyConfigApi.CompanyConfig,
+) {
+  return requestClient.post<number>('/system/company-config/save', data);
+}
diff --git a/src/api/system/storage/index.ts b/src/api/system/storage/index.ts
index 994d3ad..d1c3557 100644
--- a/src/api/system/storage/index.ts
+++ b/src/api/system/storage/index.ts
@@ -45,6 +45,17 @@
   );
 }
 
+/** 鍏叡鏂囦欢涓婁紶锛堟案涔呮湁鏁堬紝杩斿洖姘镐箙鍏紑 previewURL锛岀敤浜庣櫥褰曢〉绛夊厤鐧诲綍灞曠ず鐨勫満鏅級 */
+export function publicUploadFile(files: globalThis.File[]) {
+  const formData = new FormData();
+  files.forEach((file) => formData.append('files', file));
+  return requestClient.post<SystemStorageApi.StorageBlob[]>(
+    '/system/storage-blob/public-upload',
+    formData,
+    { headers: { 'Content-Type': 'multipart/form-data' } },
+  );
+}
+
 /** 鏌ヨ闄勪欢鍒楄〃 */
 export function listAttachments(params: SystemStorageApi.AttachmentListParams) {
   return requestClient.get<SystemStorageApi.StorageBlob[]>(
diff --git a/src/bootstrap.ts b/src/bootstrap.ts
index 6a97710..bbfb549 100644
--- a/src/bootstrap.ts
+++ b/src/bootstrap.ts
@@ -18,6 +18,9 @@
 import App from './app.vue';
 import { router } from './router';
 
+import { getCompanyConfig } from '#/api/system/company-config';
+import { applyCompanyConfig } from '#/utils/company-config';
+
 async function bootstrap(namespace: string) {
   // 鍒濆鍖栫粍浠堕�傞厤鍣�
   await initComponentAdapter();
@@ -49,6 +52,17 @@
   // 閰嶇疆 pinia-store
   await initStores(app, { namespace });
 
+  // 鍔犺浇鍏徃鍝佺墝淇℃伅锛堜粠鍚庣锛屽厤鐧诲綍鎺ュ彛锛夛紝鑱斿姩鐧诲綍椤�/鑿滃崟鏍�/缃戦〉鏍囬銆�
+  // 娉細姝ゆ椂 pinia 灏氭湭鍦ㄧ粍浠跺婵�娲伙紝杩欓噷鐢ㄧ函鍑芥暟锛屼笉渚濊禆 store銆�
+  try {
+    const companyConfig = await getCompanyConfig();
+    if (companyConfig) {
+      applyCompanyConfig(companyConfig);
+    }
+  } catch {
+    // 鍚庣涓嶅彲鐢ㄦ垨鏈厤缃紝淇濇寔缂栬瘧鏈熼粯璁�
+  }
+
   // 瀹夎鏉冮檺鎸囦护
   registerAccessDirective(app);
 
diff --git a/src/layouts/auth.vue b/src/layouts/auth.vue
index 1cdccb9..7b6ab12 100644
--- a/src/layouts/auth.vue
+++ b/src/layouts/auth.vue
@@ -1,16 +1,32 @@
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, onMounted } from 'vue';
 
 import { AuthPageLayout } from '@vben/layouts';
 import { preferences } from '@vben/preferences';
 
 import { $t } from '#/locales';
+import { useCompanyConfigStore } from '#/store/company-config';
 
 declare const __APP_LOGO__: string;
 
+const companyConfigStore = useCompanyConfigStore();
+
+onMounted(() => {
+  // 鐧诲綍椤靛睍绀� loginTitle锛氬惎鍔ㄥ姞杞借蛋鐨勬槸绾嚱鏁帮紙涓嶅~ store锛夛紝杩涘叆鐧诲綍椤垫椂鎷夊彇涓�娆�
+  companyConfigStore.loadFromServer();
+});
+
 const appName = computed(() => preferences.copyright.companyName);
-const logo = computed(() => __APP_LOGO__ || preferences.logo.source);
+// 鐧诲綍椤� logo 浼樺厛鍙栨暟鎹簱閰嶇疆锛坧references.logo.source锛夛紝鏈厤缃椂鍥為��缂栬瘧鏈熼粯璁� __APP_LOGO__
+const logo = computed(() => preferences.logo.source || __APP_LOGO__);
 const logoDark = computed(() => preferences.logo.sourceDark);
+// 鐧诲綍椤垫爣棰�/鎻忚堪浼樺厛鍙栧悗绔厤缃紝鏈厤缃椂鍥為��鍒� i18n 榛樿
+const pageTitle = computed(
+  () => companyConfigStore.loginTitle || $t('authentication.pageTitle'),
+);
+const pageDescription = computed(
+  () => companyConfigStore.loginDescription || $t('authentication.pageDesc'),
+);
 </script>
 
 <template>
@@ -18,8 +34,8 @@
     :app-name="appName"
     :logo="logo"
     :logo-dark="logoDark"
-    :page-description="$t('authentication.pageDesc')"
-    :page-title="$t('authentication.pageTitle')"
+    :page-description="pageDescription"
+    :page-title="pageTitle"
   >
     <!-- 鑷畾涔夊伐鍏锋爮 -->
     <!-- <template #toolbar></template> -->
diff --git a/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue b/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
index 8898556..fbaa8a3 100644
--- a/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
+++ b/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
@@ -5,17 +5,22 @@
 
 import { TabsTrigger } from 'reka-ui';
 
+import { cn } from '../../../../../base/shared/src/utils';
+
 import { Tabs, TabsContent, TabsList } from '../../ui';
 import TabsIndicator from './tabs-indicator.vue';
 
 interface Props {
   defaultValue?: string;
   tabs?: SegmentedItem[];
+  /** 鏄惁鍏佽妯悜婊氬姩锛歵ab 杈冨鏃跺惎鐢紝鍚� tab 鎸夊唴瀹瑰搴︽帓鍒楋紝瓒呭嚭閮ㄥ垎鍙粦鍔� */
+  scrollable?: boolean;
 }
 
 const props = withDefaults(defineProps<Props>(), {
   defaultValue: '',
   tabs: () => [],
+  scrollable: false,
 });
 
 const activeTab = defineModel<string>();
@@ -25,12 +30,18 @@
 });
 
 const tabsStyle = computed(() => {
+  if (props.scrollable) {
+    return undefined;
+  }
   return {
     'grid-template-columns': `repeat(${props.tabs.length}, minmax(0, 1fr))`,
   };
 });
 
 const tabsIndicatorStyle = computed(() => {
+  if (props.scrollable) {
+    return undefined;
+  }
   return {
     width: `${(100 / props.tabs.length).toFixed(0)}%`,
   };
@@ -45,14 +56,22 @@
   <Tabs v-model="activeTab" :default-value="getDefaultValue">
     <TabsList
       :style="tabsStyle"
-      class="bg-accent outline-heavy! relative grid w-full outline-2!"
+      :class="
+        cn(
+          'bg-accent outline-heavy! relative outline-2!',
+          // 鍙粴鍔ㄦā寮忥細宸﹀榻愶紙瑕嗙洊 reka-ui 鐨� justify-center锛屽惁鍒欏唴瀹硅秴瀹芥椂宸︿晶琚尋鍑轰笖鏃犳硶婊氬姩锛夈�佸叏瀹姐�佹í鍚戞粴鍔�
+          scrollable
+            ? 'flex! w-full! overflow-x-auto justify-start!'
+            : 'grid w-full',
+        )
+      "
     >
-      <TabsIndicator :style="tabsIndicatorStyle" />
+      <TabsIndicator v-if="!scrollable" :style="tabsIndicatorStyle" />
       <template v-for="tab in tabs" :key="tab.value">
         <TabsTrigger
           :value="tab.value"
           :class="activeClass(tab.value)"
-          class="hover:text-primary z-20 inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap disabled:pointer-events-none disabled:opacity-50"
+          class="hover:text-primary z-20 inline-flex shrink-0 items-center justify-center rounded-md px-2 py-1 text-sm font-medium whitespace-nowrap disabled:pointer-events-none disabled:opacity-50"
         >
           {{ tab.label }}
         </TabsTrigger>
diff --git a/src/packages/constants/src/biz-wms-enum.ts b/src/packages/constants/src/biz-wms-enum.ts
index 51df2da..abc02e8 100644
--- a/src/packages/constants/src/biz-wms-enum.ts
+++ b/src/packages/constants/src/biz-wms-enum.ts
@@ -57,14 +57,14 @@
   return prefix + result;
 }
 
-/** 鐩樼偣鏉′欢绫诲瀷鏋氫妇 */
+/** 鐩樼偣鏉′欢绫诲瀷鏋氫妇锛堜笌鍚庣 MesBizTypeConstants / 瀛楀吀鍊间繚鎸佷竴鑷达級 */
 export const WlsStockTakingParamTypeEnum = {
-  WAREHOUSE: 1, // 浠撳簱
-  LOCATION: 2, // 搴撳尯
-  AREA: 3, // 搴撲綅
-  ITEM: 4, // 鐗╂枡
-  BATCH: 5, // 鎵规
-  QUALITY_STATUS: 6, // 璐ㄩ噺鐘舵��
+  WAREHOUSE: 102, // 浠撳簱
+  LOCATION: 103, // 搴撳尯
+  AREA: 104, // 搴撲綅
+  ITEM: 600, // 鐗╂枡
+  BATCH: 107, // 鎵规
+  QUALITY_STATUS: 900, // 璐ㄩ噺鐘舵��
 } as const;
 
 /** 鐩樼偣绫诲瀷鏋氫妇 */
diff --git a/src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue b/src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue
new file mode 100644
index 0000000..6a7d235
--- /dev/null
+++ b/src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue
@@ -0,0 +1,151 @@
+<script setup lang="ts">
+import { message } from 'ant-design-vue';
+import { Button, Input, Upload } from 'ant-design-vue';
+import { onMounted, ref } from 'vue';
+
+// 娉ㄦ剰锛氭湰鏂囦欢浣嶄簬 @vben 鐨� packages 鍐呴儴锛屾棤娉曚娇鐢ㄥ簲鐢ㄥ眰鍒悕 #/锛岄渶鐢ㄧ浉瀵硅矾寰勶紙涓庡悓鐩綍 custom.vue 淇濇寔涓�鑷达級
+import { publicUploadFile } from '../../../../../../../../api/system/storage';
+import { $t } from '../../../../../../../locales/src';
+import { useCompanyConfigStore } from '../../../../../../../../store/company-config';
+
+defineOptions({
+  name: 'PreferenceCompanyConfig',
+});
+
+const companyConfigStore = useCompanyConfigStore();
+
+const companyName = ref(companyConfigStore.companyName);
+const loginTitle = ref(companyConfigStore.loginTitle);
+const loginDescription = ref(companyConfigStore.loginDescription);
+const logoUrl = ref(companyConfigStore.logoUrl);
+const uploading = ref(false);
+
+// 鎵撳紑鍖哄潡鏃朵粠鍚庣鎷夊彇鏈�鏂板叕鍙搁厤缃苟鍥炲~琛ㄥ崟锛坰tore 鍒濆�煎彲鑳戒负绌猴紝鍥犲惎鍔ㄥ姞杞借蛋鐨勬槸绾嚱鏁帮級
+onMounted(async () => {
+  await companyConfigStore.loadFromServer();
+  companyName.value = companyConfigStore.companyName;
+  loginTitle.value = companyConfigStore.loginTitle;
+  loginDescription.value = companyConfigStore.loginDescription;
+  logoUrl.value = companyConfigStore.logoUrl;
+});
+
+/** 涓婁紶 logo锛堟案涔呭叕寮�锛屼緵鐧诲綍椤靛厤鐧诲綍灞曠ず锛� */
+async function handleBeforeUpload(file: File) {
+  uploading.value = true;
+  try {
+    const blobs = await publicUploadFile([file]);
+    const blob = blobs?.[0];
+    if (blob?.previewURL || blob?.url) {
+      logoUrl.value = blob.previewURL || blob.url || '';
+      message.success($t('preferences.company.uploadSuccess'));
+    } else {
+      message.error($t('preferences.company.uploadFailed'));
+    }
+  } catch {
+    message.error($t('preferences.company.uploadFailed'));
+  } finally {
+    uploading.value = false;
+  }
+  // 杩斿洖 false锛岄樆姝㈢粍浠堕粯璁や笂浼狅紙宸查�氳繃鎺ュ彛涓婁紶锛�
+  return false;
+}
+
+async function handleSave() {
+  if (!companyName.value.trim()) {
+    message.warning($t('preferences.company.companyNameRequired'));
+    return;
+  }
+  try {
+    await companyConfigStore.save({
+      companyName: companyName.value.trim(),
+      logoUrl: logoUrl.value || undefined,
+      loginTitle: loginTitle.value,
+      loginDescription: loginDescription.value,
+    });
+    message.success($t('preferences.company.saveSuccess'));
+  } catch {
+    message.error($t('preferences.company.saveFailed'));
+  }
+}
+</script>
+
+<template>
+  <div class="flex flex-col">
+    <!-- 鍏徃鍚嶇О -->
+    <div class="my-2 flex w-full items-center justify-between gap-4 rounded-md px-2 py-2">
+      <span class="shrink-0 text-sm text-foreground/80">
+        {{ $t('preferences.company.companyName') }}
+      </span>
+      <Input
+        v-model:value="companyName"
+        :placeholder="$t('preferences.company.companyNamePlaceholder')"
+        class="w-41.25"
+        allow-clear
+      />
+    </div>
+
+    <!-- 鐧诲綍椤垫爣棰� -->
+    <div class="my-2 flex w-full items-center justify-between gap-4 rounded-md px-2 py-2">
+      <span class="shrink-0 text-sm text-foreground/80">
+        {{ $t('preferences.company.loginTitle') }}
+      </span>
+      <Input
+        v-model:value="loginTitle"
+        :placeholder="$t('preferences.company.loginTitlePlaceholder')"
+        class="w-41.25"
+        allow-clear
+      />
+    </div>
+
+    <!-- 鐧诲綍椤垫弿杩� -->
+    <div class="my-2 flex w-full items-center justify-between gap-4 rounded-md px-2 py-2">
+      <span class="shrink-0 text-sm text-foreground/80">
+        {{ $t('preferences.company.loginDescription') }}
+      </span>
+      <Input
+        v-model:value="loginDescription"
+        :placeholder="$t('preferences.company.loginDescriptionPlaceholder')"
+        class="w-41.25"
+        allow-clear
+      />
+    </div>
+
+    <!-- 鍏徃 Logo -->
+    <div class="my-2 flex w-full items-center justify-between gap-4 rounded-md px-2 py-2">
+      <span class="shrink-0 text-sm text-foreground/80">
+        {{ $t('preferences.company.logo') }}
+      </span>
+      <div class="flex items-center gap-2">
+        <Upload
+          :show-upload-list="false"
+          accept="image/*"
+          :before-upload="handleBeforeUpload"
+        >
+          <Button size="small" :loading="uploading">
+            {{ $t('preferences.company.uploadLogo') }}
+          </Button>
+        </Upload>
+        <img
+          v-if="logoUrl"
+          :src="logoUrl"
+          alt="company-logo"
+          class="h-9 w-9 rounded border border-border object-contain"
+        />
+        <span v-else class="text-xs text-muted-foreground">
+          {{ $t('preferences.company.logoEmpty') }}
+        </span>
+      </div>
+    </div>
+
+    <div v-if="uploading" class="px-2 pb-1 text-xs text-muted-foreground">
+      {{ $t('preferences.company.uploading') }}
+    </div>
+
+    <!-- 淇濆瓨 -->
+    <div class="mt-3 px-2">
+      <Button type="primary" class="w-full" @click="handleSave">
+        {{ $t('preferences.company.save') }}
+      </Button>
+    </div>
+  </div>
+</template>
diff --git a/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts b/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts
index b1b8eac..5b62459 100644
--- a/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts
+++ b/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts
@@ -1,4 +1,5 @@
 export { default as Block } from './block.vue';
+export { default as Company } from './company/company.vue';
 export { default as Custom } from './custom/custom.vue';
 export { default as Animation } from './general/animation.vue';
 export { default as General } from './general/general.vue';
diff --git a/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue b/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
index 12f4076..5b3a05e 100644
--- a/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
+++ b/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
@@ -43,6 +43,7 @@
   Breadcrumb,
   BuiltinTheme,
   ColorMode,
+  Company,
   Content,
   Copyright,
   Custom,
@@ -256,6 +257,10 @@
       label: $t('preferences.general'),
       value: 'general',
     },
+    {
+      label: $t('preferences.company.title'),
+      value: 'company',
+    },
   ];
 
   if (showCustomTab.value) {
@@ -352,6 +357,7 @@
         <VbenSegmented
           v-model="activeTab"
           :tabs="tabs"
+          scrollable
           :class="{
             'sticky-tabs-header': appEnableStickyPreferencesNavigationBar,
           }"
@@ -526,6 +532,11 @@
               />
             </Block>
           </template>
+          <template #company>
+            <Block :title="$t('preferences.company.title')">
+              <Company />
+            </Block>
+          </template>
           <template #custom>
             <Block :title="customTabTitle">
               <Custom
diff --git a/src/packages/locales/src/langs/en-US/preferences.json b/src/packages/locales/src/langs/en-US/preferences.json
index d027227..853651a 100644
--- a/src/packages/locales/src/langs/en-US/preferences.json
+++ b/src/packages/locales/src/langs/en-US/preferences.json
@@ -229,5 +229,24 @@
         "placeholder": "Please enter the default report title"
       }
     }
+  },
+  "company": {
+    "title": "Company Info",
+    "companyName": "Company Name",
+    "companyNamePlaceholder": "Please enter company name",
+    "companyNameRequired": "Company name is required",
+    "loginTitle": "Login Page Title",
+    "loginTitlePlaceholder": "Enter login page title (optional)",
+    "loginDescription": "Login Page Description",
+    "loginDescriptionPlaceholder": "Enter login page description (optional)",
+    "logo": "Company Logo",
+    "uploadLogo": "Upload Logo",
+    "uploadSuccess": "Logo uploaded successfully",
+    "uploadFailed": "Logo upload failed",
+    "uploading": "Uploading...",
+    "logoEmpty": "No logo set",
+    "save": "Save",
+    "saveSuccess": "Company info saved successfully",
+    "saveFailed": "Failed to save company info"
   }
 }
diff --git a/src/packages/locales/src/langs/zh-CN/preferences.json b/src/packages/locales/src/langs/zh-CN/preferences.json
index 8cbc607..aa36730 100644
--- a/src/packages/locales/src/langs/zh-CN/preferences.json
+++ b/src/packages/locales/src/langs/zh-CN/preferences.json
@@ -229,5 +229,24 @@
         "placeholder": "璇疯緭鍏ユ姤琛ㄩ粯璁ゆ爣棰�"
       }
     }
+  },
+  "company": {
+    "title": "鍏徃淇℃伅",
+    "companyName": "鍏徃鍚嶇О",
+    "companyNamePlaceholder": "璇疯緭鍏ュ叕鍙稿悕绉�",
+    "companyNameRequired": "鍏徃鍚嶇О涓嶈兘涓虹┖",
+    "loginTitle": "鐧诲綍椤垫爣棰�",
+    "loginTitlePlaceholder": "璇疯緭鍏ョ櫥褰曢〉鏍囬锛堝彲閫夛級",
+    "loginDescription": "鐧诲綍椤垫弿杩�",
+    "loginDescriptionPlaceholder": "璇疯緭鍏ョ櫥褰曢〉鎻忚堪锛堝彲閫夛級",
+    "logo": "鍏徃 Logo",
+    "uploadLogo": "涓婁紶 Logo",
+    "uploadSuccess": "Logo 涓婁紶鎴愬姛",
+    "uploadFailed": "Logo 涓婁紶澶辫触",
+    "uploading": "涓婁紶涓�...",
+    "logoEmpty": "鏈缃� Logo",
+    "save": "淇濆瓨",
+    "saveSuccess": "鍏徃淇℃伅淇濆瓨鎴愬姛",
+    "saveFailed": "鍏徃淇℃伅淇濆瓨澶辫触"
   }
 }
diff --git a/src/store/company-config.ts b/src/store/company-config.ts
new file mode 100644
index 0000000..1668f86
--- /dev/null
+++ b/src/store/company-config.ts
@@ -0,0 +1,71 @@
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+
+import {
+  getCompanyConfig,
+  saveCompanyConfig as saveCompanyConfigApi,
+} from '#/api/system/company-config';
+import { applyCompanyConfig } from '#/utils/company-config';
+
+/**
+ * 鍏徃鍝佺墝淇℃伅鍏ㄥ眬閰嶇疆
+ *
+ * 鍚姩鏃朵粠鍚庣鍔犺浇锛屼繚瀛樻椂鍐欏洖鍚庣锛涘苟灏嗗叕鍙稿悕/logo 鍚屾鍒� preferences锛�
+ * 浠庤�岃仈鍔ㄧ櫥褰曢〉銆佽彍鍗曟爮銆佺綉椤垫爣棰樼瓑鎵�鏈夋秷璐圭偣銆�
+ */
+export const useCompanyConfigStore = defineStore('companyConfig', () => {
+  const companyName = ref('');
+  const logoUrl = ref('');
+  const loginTitle = ref('');
+  const loginDescription = ref('');
+
+  /** 灏嗗叕鍙镐俊鎭簲鐢ㄥ埌 preferences锛岃仈鍔ㄧ櫥褰曢〉/鑿滃崟鏍�/鏍囬 */
+  function applyToPreferences() {
+    applyCompanyConfig({
+      companyName: companyName.value,
+      logoUrl: logoUrl.value,
+    });
+  }
+
+  /** 浠庡悗绔姞杞藉叕鍙搁厤缃苟搴旂敤 */
+  async function loadFromServer() {
+    try {
+      const config = await getCompanyConfig();
+      if (config) {
+        companyName.value = config.companyName ?? '';
+        logoUrl.value = config.logoUrl ?? '';
+        loginTitle.value = config.loginTitle ?? '';
+        loginDescription.value = config.loginDescription ?? '';
+        applyToPreferences();
+      }
+    } catch {
+      // 鍚庣涓嶅彲鐢ㄦ垨鏈厤缃紝淇濇寔缂栬瘧鏈熼粯璁�
+    }
+  }
+
+  /** 淇濆瓨鍏徃閰嶇疆鍒板悗绔苟搴旂敤 */
+  async function save(data: {
+    companyName: string;
+    logoUrl?: string;
+    logoBlobId?: number;
+    loginTitle?: string;
+    loginDescription?: string;
+  }) {
+    await saveCompanyConfigApi(data);
+    companyName.value = data.companyName;
+    logoUrl.value = data.logoUrl ?? '';
+    loginTitle.value = data.loginTitle ?? '';
+    loginDescription.value = data.loginDescription ?? '';
+    applyToPreferences();
+  }
+
+  return {
+    companyName,
+    logoUrl,
+    loginTitle,
+    loginDescription,
+    applyToPreferences,
+    loadFromServer,
+    save,
+  };
+});
diff --git a/src/store/index.ts b/src/store/index.ts
index 269586e..10d8c3d 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1 +1,2 @@
 export * from './auth';
+export * from './company-config';
diff --git a/src/utils/company-config.ts b/src/utils/company-config.ts
new file mode 100644
index 0000000..4f57dcc
--- /dev/null
+++ b/src/utils/company-config.ts
@@ -0,0 +1,32 @@
+import { preferencesManager } from '@vben/preferences';
+
+/** 鍏徃鍝佺墝淇℃伅锛堜笌鍚庣 system_company_config 琛ㄥ瓧娈靛搴旓級 */
+export interface CompanyConfigData {
+  companyName?: string;
+  logoUrl?: string;
+  loginTitle?: string;
+  loginDescription?: string;
+}
+
+/**
+ * 灏嗗叕鍙稿搧鐗屼俊鎭簲鐢ㄥ埌鍏ㄥ眬锛�
+ * - 鍏徃鍚� 鈫� preferences.app.name / copyright.companyName锛堣仈鍔ㄨ彍鍗曟爮銆佺櫥褰曢〉銆佺綉椤垫爣棰樸�佺増鏉冿級
+ * - logo 鈫� preferences.logo.source锛堣仈鍔ㄧ櫥褰曢〉銆佷晶杈规爮 logo锛�
+ * - 鏈夊叕鍙稿悕鏃讹紝璁剧疆 document.title锛堝熀纭�鏍囬锛宒ynamicTitle 寮�鍚椂璺敱鏍囬鍦ㄦ鍩虹涓婃嫾鎺ワ級
+ */
+export function applyCompanyConfig(config: CompanyConfigData) {
+  const updates: Record<string, any> = {};
+  if (config.logoUrl) {
+    updates.logo = { source: config.logoUrl };
+  }
+  if (config.companyName) {
+    updates.app = { name: config.companyName };
+    updates.copyright = { companyName: config.companyName };
+  }
+  if (Object.keys(updates).length > 0) {
+    preferencesManager.updatePreferences(updates);
+  }
+  if (config.companyName) {
+    document.title = config.companyName;
+  }
+}
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index a2fb42c..ca625c3 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -630,3 +630,27 @@
     },
   ];
 }
+
+/** 鐗╂枡瀵煎叆鐨勮〃鍗� */
+export function useImportFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'file',
+      label: '鐗╂枡鏁版嵁',
+      component: 'Upload',
+      rules: 'required',
+      help: '浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢',
+    },
+    {
+      fieldName: 'updateSupport',
+      label: '鏄惁瑕嗙洊',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+      help: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勭墿鏂欐暟鎹�',
+    },
+  ];
+}
diff --git a/src/views/basicData/mdm/index.vue b/src/views/basicData/mdm/index.vue
index 8995f78..99cea03 100644
--- a/src/views/basicData/mdm/index.vue
+++ b/src/views/basicData/mdm/index.vue
@@ -24,11 +24,17 @@
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
+import ImportForm from './modules/import-form.vue';
 
 defineOptions({ name: 'MdmItem' });
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ImportModal, importModalApi] = useVbenModal({
+  connectedComponent: ImportForm,
   destroyOnClose: true,
 });
 
@@ -84,6 +90,11 @@
 /** 鍒涘缓鐗╂枡 */
 function handleCreate() {
   formModalApi.setData(null).open();
+}
+
+/** 瀵煎叆鐗╂枡 */
+function handleImport() {
+  importModalApi.open();
 }
 
 /** 缂栬緫鐗╂枡 */
@@ -211,6 +222,7 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
+    <ImportModal @success="handleRefresh" />
     <Grid table-title="鐗╂枡鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -222,6 +234,13 @@
               auth: ['mdm:item:create'],
               onClick: handleCreate,
             },
+            {
+              label: $t('ui.actionTitle.import', ['鐗╂枡']),
+              type: 'primary',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['mdm:item:import'],
+              onClick: handleImport,
+            },
             // {
             //   label: $t('ui.actionTitle.export'),
             //   type: 'primary',
diff --git a/src/views/basicData/mdm/modules/import-form.vue b/src/views/basicData/mdm/modules/import-form.vue
new file mode 100644
index 0000000..5cc47a2
--- /dev/null
+++ b/src/views/basicData/mdm/modules/import-form.vue
@@ -0,0 +1,105 @@
+<script lang="ts" setup>
+import type { FileType } from 'ant-design-vue/es/upload/interface';
+
+import { h } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, notification, Upload } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { importItem, importItemTemplate } from '#/api/mdm/item';
+import { $t } from '#/locales';
+
+import { useImportFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useImportFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = await formApi.getValues();
+    try {
+      const response = (await importItem(data.file, data.updateSupport)) as any;
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+
+      const resultData = response?.data || response || {};
+      const failureList = resultData.failureList || {};
+
+      if (Object.keys(failureList).length > 0) {
+        const errorNodes = Object.entries(failureList).map(([name, reason]) => {
+          return h('div', `${name}: ${reason}`);
+        });
+
+        notification.warning({
+          message: '瀵煎叆瀹屾垚锛岄儴鍒嗙墿鏂欏鍏ュけ璐�',
+          description: () => h('div', errorNodes),
+          duration: null,
+        });
+      } else {
+        message.success($t('ui.actionMessage.operationSuccess'));
+      }
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+
+/** 涓婁紶鍓� */
+function beforeUpload(file: FileType) {
+  formApi.setFieldValue('file', file);
+  return false;
+}
+
+/** 涓嬭浇妯$増 */
+async function handleDownload() {
+  const data = await importItemTemplate();
+  downloadFileFromBlobPart({ fileName: '鐗╂枡瀵煎叆妯℃澘.xls', source: data });
+}
+</script>
+
+<template>
+  <Modal title="瀵煎叆鐗╂枡" class="w-1/3">
+    <Form class="mx-4">
+      <template #file>
+        <div class="w-full">
+          <Upload
+            :max-count="1"
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+          >
+            <Button type="primary"> 閫夋嫨 Excel 鏂囦欢 </Button>
+          </Upload>
+        </div>
+      </template>
+    </Form>
+    <div class="mx-4 mt-2 mb-4 text-gray-500 text-xs">
+      鎻愮ず锛氭ā鏉夸腑鈥滅墿鏂欏垎绫荤紪鐮佲�濄�佲�滆閲忓崟浣嶇紪鐮佲�濄�佲�滆緟鍗曚綅缂栫爜鈥濊濉啓瀵瑰簲涓绘暟鎹殑缂栫爜锛屽彲鐣欑┖銆�
+      <div>鈥滄槸鍚︽壒娆$鐞嗏�濄�佲�滄槸鍚﹀悓姝ュ埌MES鈥濆~ true/false銆�</div>
+      <div>鈥滅姸鎬佲�濆~ 0锛堝惎鐢級/1锛堢鐢級銆�</div>
+    </div>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center">
+        <Button @click="handleDownload"> 涓嬭浇瀵煎叆妯℃澘 </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/basicData/mdm/unit/data.ts b/src/views/basicData/mdm/unit/data.ts
index b9d0be3..6e15fe9 100644
--- a/src/views/basicData/mdm/unit/data.ts
+++ b/src/views/basicData/mdm/unit/data.ts
@@ -128,4 +128,28 @@
       slots: { default: 'actions' },
     },
   ];
+}
+
+/** 璁¢噺鍗曚綅瀵煎叆鐨勮〃鍗� */
+export function useImportFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'file',
+      label: '鍗曚綅鏁版嵁',
+      component: 'Upload',
+      rules: 'required',
+      help: '浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢',
+    },
+    {
+      fieldName: 'updateSupport',
+      label: '鏄惁瑕嗙洊',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+      help: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勫崟浣嶆暟鎹�',
+    },
+  ];
 }
\ No newline at end of file
diff --git a/src/views/basicData/mdm/unit/index.vue b/src/views/basicData/mdm/unit/index.vue
index e2d3a44..07cd0db 100644
--- a/src/views/basicData/mdm/unit/index.vue
+++ b/src/views/basicData/mdm/unit/index.vue
@@ -15,11 +15,17 @@
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
+import ImportForm from './modules/import-form.vue';
 
 defineOptions({ name: 'MdmUnit' });
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ImportModal, importModalApi] = useVbenModal({
+  connectedComponent: ImportForm,
   destroyOnClose: true,
 });
 
@@ -31,6 +37,11 @@
 /** 鍒涘缓璁¢噺鍗曚綅 */
 function handleCreate() {
   formModalApi.setData(null).open();
+}
+
+/** 瀵煎叆璁¢噺鍗曚綅 */
+function handleImport() {
+  importModalApi.open();
 }
 
 /** 缂栬緫璁¢噺鍗曚綅 */
@@ -87,6 +98,7 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
+    <ImportModal @success="handleRefresh" />
     <Grid table-title="璁¢噺鍗曚綅鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -98,6 +110,13 @@
               auth: ['mdm:unit:create'],
               onClick: handleCreate,
             },
+            {
+              label: $t('ui.actionTitle.import', ['璁¢噺鍗曚綅']),
+              type: 'primary',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['mdm:unit-measure:import'],
+              onClick: handleImport,
+            },
           ]"
         />
       </template>
diff --git a/src/views/basicData/mdm/unit/modules/import-form.vue b/src/views/basicData/mdm/unit/modules/import-form.vue
new file mode 100644
index 0000000..de36691
--- /dev/null
+++ b/src/views/basicData/mdm/unit/modules/import-form.vue
@@ -0,0 +1,105 @@
+<script lang="ts" setup>
+import type { FileType } from 'ant-design-vue/es/upload/interface';
+
+import { h } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, notification, Upload } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { importUnit, importUnitTemplate } from '#/api/mdm/unit';
+import { $t } from '#/locales';
+
+import { useImportFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useImportFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = await formApi.getValues();
+    try {
+      const response = (await importUnit(data.file, data.updateSupport)) as any;
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+
+      const resultData = response?.data || response || {};
+      const failureList = resultData.failureList || {};
+
+      if (Object.keys(failureList).length > 0) {
+        const errorNodes = Object.entries(failureList).map(([name, reason]) => {
+          return h('div', `${name}: ${reason}`);
+        });
+
+        notification.warning({
+          message: '瀵煎叆瀹屾垚锛岄儴鍒嗚閲忓崟浣嶅鍏ュけ璐�',
+          description: () => h('div', errorNodes),
+          duration: null,
+        });
+      } else {
+        message.success($t('ui.actionMessage.operationSuccess'));
+      }
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+
+/** 涓婁紶鍓� */
+function beforeUpload(file: FileType) {
+  formApi.setFieldValue('file', file);
+  return false;
+}
+
+/** 涓嬭浇妯$増 */
+async function handleDownload() {
+  const data = await importUnitTemplate();
+  downloadFileFromBlobPart({ fileName: '璁¢噺鍗曚綅瀵煎叆妯℃澘.xls', source: data });
+}
+</script>
+
+<template>
+  <Modal title="瀵煎叆璁¢噺鍗曚綅" class="w-1/3">
+    <Form class="mx-4">
+      <template #file>
+        <div class="w-full">
+          <Upload
+            :max-count="1"
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+          >
+            <Button type="primary"> 閫夋嫨 Excel 鏂囦欢 </Button>
+          </Upload>
+        </div>
+      </template>
+    </Form>
+    <div class="mx-4 mt-2 mb-4 text-gray-500 text-xs">
+      鎻愮ず锛氭ā鏉夸腑鐨勨�滀富鍗曚綅缂栫爜鈥濊濉啓涓诲崟浣嶇殑缂栫爜锛堝 PC锛夛紝涓诲崟浣嶆湰韬鍒楃暀绌哄嵆鍙��
+      <div>鈥滄槸鍚︿富鍗曚綅鈥濆~ true/false銆�</div>
+      <div>鈥滅姸鎬佲�濆~ 0锛堝惎鐢級/1锛堢鐢級銆�</div>
+    </div>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center">
+        <Button @click="handleDownload"> 涓嬭浇瀵煎叆妯℃澘 </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/bpm/task/done/data.ts b/src/views/bpm/task/done/data.ts
index 3c5e9f6..66aae84 100644
--- a/src/views/bpm/task/done/data.ts
+++ b/src/views/bpm/task/done/data.ts
@@ -146,7 +146,7 @@
     },
     {
       title: '鎿嶄綔',
-      width: 120,
+      width: 180,
       fixed: 'right',
       slots: { default: 'actions' },
     },
diff --git a/src/views/bpm/task/done/index.vue b/src/views/bpm/task/done/index.vue
index 753eda8..4bf1c3a 100644
--- a/src/views/bpm/task/done/index.vue
+++ b/src/views/bpm/task/done/index.vue
@@ -7,6 +7,7 @@
 import { message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { reverseApprove } from '#/api/erp/purchase/order';
 import { getTaskDonePage, withdrawTask } from '#/api/bpm/task';
 import { router } from '#/router';
 
@@ -34,6 +35,28 @@
   try {
     await withdrawTask(row.id);
     message.success('鎾ゅ洖鎴愬姛');
+    await gridApi.query();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鏄惁閲囪喘璁㈠崟瀹℃壒浠诲姟锛堢敤浜庢樉绀�"鍙嶅鎵�"銆侀殣钘忛�氱敤"鎾ゅ洖"锛� */
+function isPurchaseOrderApprove(row: BpmTaskApi.Task) {
+  return (row.processInstance?.processDefinitionId || '').startsWith(
+    'purchase_order_approve',
+  );
+}
+
+/** 鍙嶅鎵归噰璐鍗曪紙宸插鏍� -> 鏈鏍革紝鍦ㄥ崗鍚屽姙鍏凡鍔炰换鍔′腑鎿嶄綔锛� */
+async function handleReverseApprove(row: BpmTaskApi.Task) {
+  const hideLoading = message.loading({
+    content: '鍙嶅鎵逛腑...',
+    duration: 0,
+  });
+  try {
+    await reverseApprove(row.processInstance.id);
+    message.success('鍙嶅鎵规垚鍔燂紝璁㈠崟宸查噸鏂拌繘鍏ュ緟鍔炲鎵�');
     await gridApi.query();
   } finally {
     hideLoading();
@@ -77,10 +100,22 @@
         <TableAction
           :actions="[
             {
+              label: '鍙嶅鎵�',
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.AUDIT,
+              ifShow: () => isPurchaseOrderApprove(row),
+              popConfirm: {
+                title: '纭畾瑕佸弽瀹℃壒璇ラ噰璐鍗曞悧锛熻鍗曞皢閲嶆柊杩涘叆寰呭姙瀹℃壒',
+                confirm: handleReverseApprove.bind(null, row),
+              },
+            },
+            {
               label: '鎾ゅ洖',
               type: 'link',
               danger: true,
               icon: ACTION_ICON.DELETE,
+              ifShow: () => !isPurchaseOrderApprove(row),
               popConfirm: {
                 title: '纭畾瑕佹挙鍥炶浠诲姟鍚楋紵',
                 confirm: handleWithdraw.bind(null, row),
diff --git a/src/views/erp/finance/payment/data.ts b/src/views/erp/finance/payment/data.ts
index 5e15d1e..2d40c12 100644
--- a/src/views/erp/finance/payment/data.ts
+++ b/src/views/erp/finance/payment/data.ts
@@ -152,18 +152,14 @@
       formItemClass: 'col-span-3',
     },
     {
-      fieldName: 'accountId',
+      fieldName: 'accountName',
       label: '浠樻璐︽埛',
-      component: 'ApiSelect',
+      component: 'Input',
+      rules: 'required',
       componentProps: {
-        placeholder: '璇烽�夋嫨浠樻璐︽埛',
+        placeholder: '璇疯緭鍏ヤ粯娆捐处鎴�',
         allowClear: true,
-        showSearch: true,
-        api: getAccountSimpleList,
-        labelField: 'name',
-        valueField: 'id',
       },
-      help: '浠樻璐︽埛鏁版嵁鏉ヨ嚜銆愯储鍔$鐞� 鈫� 缁撶畻璐︽埛銆戣彍鍗曠淮鎶ょ殑寮�鍚姸鎬佽处鎴凤紝鏃犻�夐」鏃惰鍏堝湪缁撶畻璐︽埛椤甸潰鏂板銆�',
     },
     {
       fieldName: 'totalPrice',
diff --git a/src/views/erp/finance/payment/modules/form.vue b/src/views/erp/finance/payment/modules/form.vue
index b7652ed..3a29e85 100644
--- a/src/views/erp/finance/payment/modules/form.vue
+++ b/src/views/erp/finance/payment/modules/form.vue
@@ -29,7 +29,7 @@
   id: undefined,
   no: '',
   supplierId: undefined,
-  accountId: undefined,
+  accountName: undefined,
   financeUserId: undefined,
   paymentTime: undefined,
   remark: '',
@@ -176,11 +176,11 @@
     formApi.setDisabled(formType.value === 'detail');
     formApi.updateSchema(useFormSchema(formType.value));
     if (!data || !data.id) {
-      // 鏂板鏃讹紝榛樿閫変腑璐︽埛
+      // 鏂板鏃讹紝榛樿濉叆榛樿缁撶畻璐︽埛鍚嶇О
       const accountList = await getAccountSimpleList();
       const defaultAccount = accountList.find((item) => item.defaultStatus);
       if (defaultAccount) {
-        await formApi.setValues({ accountId: defaultAccount.id });
+        await formApi.setValues({ accountName: defaultAccount.name });
       }
       return;
     }
diff --git a/src/views/erp/purchase/invoice/data.ts b/src/views/erp/purchase/invoice/data.ts
index a371839..018c084 100644
--- a/src/views/erp/purchase/invoice/data.ts
+++ b/src/views/erp/purchase/invoice/data.ts
@@ -1,7 +1,10 @@
+import type { Ref } from 'vue';
 import type { VbenFormSchema } from '#/adapter/form';
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 
 import { getSupplierSimpleList } from '#/api/srm/supplier';
+
+import { z } from '#/adapter/form';
 
 import { markRaw } from 'vue';
 
@@ -11,7 +14,9 @@
 export type FormType = 'create' | 'detail' | 'edit';
 
 /** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
+export function useFormSchema(
+  orderTotalTaxPrice?: Ref<number | undefined>,
+): VbenFormSchema[] {
   return [
     {
       fieldName: 'id',
@@ -41,8 +46,15 @@
       },
       componentProps: (_values, form) => ({
         placeholder: '璇烽�夋嫨閲囪喘璁㈠崟',
-        onChange: (order: { supplierName?: string } | undefined) => {
+        onChange: (
+          order:
+            | { supplierName?: string; totalTaxPrice?: number }
+            | undefined,
+        ) => {
           form.setFieldValue('supplierName', order?.supplierName);
+          if (orderTotalTaxPrice) {
+            orderTotalTaxPrice.value = order?.totalTaxPrice;
+          }
         },
       }),
     },
@@ -78,11 +90,37 @@
       label: '鏉ョエ閲戦锛堝厓锛�',
       component: 'InputNumber',
       rules: 'required',
+      description: () =>
+        orderTotalTaxPrice?.value != null
+          ? `閲囪喘璁㈠崟鏈紑绁ㄩ噾棰濓細${Number(orderTotalTaxPrice.value).toFixed(2)} 鍏僠
+          : '',
       componentProps: {
         class: '!w-full',
         placeholder: '璇疯緭鍏ユ潵绁ㄩ噾棰�',
         min: 0,
         precision: 2,
+      },
+      dependencies: {
+        triggerFields: ['purchaseOrderId'],
+        componentProps: () => ({
+          class: '!w-full',
+          placeholder: '璇疯緭鍏ユ潵绁ㄩ噾棰�',
+          min: 0,
+          max: orderTotalTaxPrice?.value ?? undefined,
+          precision: 2,
+        }),
+        rules: () => {
+          const max = orderTotalTaxPrice?.value;
+          if (max != null) {
+            return z
+              .number({ message: '璇疯緭鍏ユ潵绁ㄩ噾棰�' })
+              .min(0, '鏉ョエ閲戦涓嶈兘灏忎簬 0')
+              .max(max, `鏉ョエ閲戦涓嶈兘澶т簬鏈紑绁ㄩ噾棰� ${Number(max).toFixed(2)}`);
+          }
+          return z
+            .number({ message: '璇疯緭鍏ユ潵绁ㄩ噾棰�' })
+            .min(0, '鏉ョエ閲戦涓嶈兘灏忎簬 0');
+        },
       },
     },
     {
@@ -180,12 +218,6 @@
     {
       title: '鏉ョエ閲戦锛堝厓锛�',
       field: 'price',
-      minWidth: 150,
-      formatter: 'formatAmount2',
-    },
-    {
-      title: '鍓╀綑鍙粯閲戦',
-      field: 'remainingInvoicePrice',
       minWidth: 150,
       formatter: 'formatAmount2',
     },
diff --git a/src/views/erp/purchase/invoice/modules/form.vue b/src/views/erp/purchase/invoice/modules/form.vue
index 74d0462..12036c8 100644
--- a/src/views/erp/purchase/invoice/modules/form.vue
+++ b/src/views/erp/purchase/invoice/modules/form.vue
@@ -22,6 +22,7 @@
 const emit = defineEmits(['success']);
 const formData = ref<ErpPurchaseInvoiceApi.PurchaseInvoice>();
 const formType = ref<FormType>('create');
+const orderTotalTaxPrice = ref<number>(); // 鎵�閫夐噰璐鍗曞悎璁$◣棰濓紙鏈紑绁ㄩ噾棰濓紝鐢ㄤ簬鎻愮ず涓庢牎楠岋級
 
 const getTitle = computed(() => {
   if (formType.value === 'create') {
@@ -42,7 +43,7 @@
   },
   wrapperClass: 'grid-cols-2',
   layout: 'horizontal',
-  schema: useFormSchema(),
+  schema: useFormSchema(orderTotalTaxPrice),
   showDefaultActions: false,
 });
 
@@ -71,12 +72,13 @@
   async onOpenChange(isOpen: boolean) {
     if (!isOpen) {
       formData.value = undefined;
+      orderTotalTaxPrice.value = undefined;
       return;
     }
     const data = modalApi.getData<{ formType: FormType; id?: number }>();
     formType.value = data.formType;
     formApi.setDisabled(formType.value === 'detail');
-    formApi.updateSchema(useFormSchema());
+    formApi.updateSchema(useFormSchema(orderTotalTaxPrice));
     await formApi.resetForm();
     if (!data || !data.id) {
       return;
diff --git a/src/views/erp/purchase/invoice/modules/purchase-order-select.vue b/src/views/erp/purchase/invoice/modules/purchase-order-select.vue
index a3e1109..ddfc9e5 100644
--- a/src/views/erp/purchase/invoice/modules/purchase-order-select.vue
+++ b/src/views/erp/purchase/invoice/modules/purchase-order-select.vue
@@ -92,6 +92,12 @@
         minWidth: 120,
       },
       {
+        field: 'totalTaxPrice',
+        title: '鏈紑绁ㄩ噾棰�',
+        formatter: 'formatAmount2',
+        minWidth: 120,
+      },
+      {
         field: 'status',
         title: '鐘舵��',
         minWidth: 100,
diff --git a/src/views/erp/purchase/order/index.vue b/src/views/erp/purchase/order/index.vue
index c56c84d..79a6a6c 100644
--- a/src/views/erp/purchase/order/index.vue
+++ b/src/views/erp/purchase/order/index.vue
@@ -16,7 +16,6 @@
   deletePurchaseOrder,
   exportPurchaseOrder,
   getPurchaseOrderPage,
-  updatePurchaseOrderStatus,
 } from '#/api/erp/purchase/order';
 import { $t } from '#/locales';
 
@@ -63,24 +62,6 @@
   try {
     await deletePurchaseOrder(ids);
     message.success($t('ui.actionMessage.deleteSuccess'));
-    handleRefresh();
-  } finally {
-    hideLoading();
-  }
-}
-
-/** 瀹℃壒/鍙嶅鎵规搷浣� */
-async function handleUpdateStatus(
-  row: ErpPurchaseOrderApi.PurchaseOrder,
-  status: number,
-) {
-  const hideLoading = message.loading({
-    content: `纭畾${status === 20 ? '瀹℃壒' : '鍙嶅鎵�'}璇ヨ鍗曞悧锛焋,
-    duration: 0,
-  });
-  try {
-    await updatePurchaseOrderStatus(row.id!, status);
-    message.success(`${status === 20 ? '瀹℃壒' : '鍙嶅鎵�'}鎴愬姛`);
     handleRefresh();
   } finally {
     hideLoading();
@@ -159,7 +140,8 @@
 </script>
 
 <template>
-  <Page auto-content-height><FormModal @success="handleRefresh" />
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
     <Grid table-title="閲囪喘璁㈠崟鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -215,20 +197,6 @@
               auth: ['erp:purchase-order:update'],
               ifShow: () => row.status !== 20,
               onClick: handleEdit.bind(null, row),
-            },
-            {
-              label: row.status === 10 ? '瀹℃壒' : '鍙嶅鎵�',
-              type: 'link',
-              icon: ACTION_ICON.AUDIT,
-              auth: ['erp:purchase-order:update-status'],
-              popConfirm: {
-                title: `纭${row.status === 10 ? '瀹℃壒' : '鍙嶅鎵�'}${row.no}鍚楋紵`,
-                confirm: handleUpdateStatus.bind(
-                  null,
-                  row,
-                  row.status === 10 ? 20 : 10,
-                ),
-              },
             },
             {
               label: '纭鏀惰揣',
diff --git a/src/views/hrm/attendance/record/data.ts b/src/views/hrm/attendance/record/data.ts
index 933798f..98581f2 100644
--- a/src/views/hrm/attendance/record/data.ts
+++ b/src/views/hrm/attendance/record/data.ts
@@ -75,6 +75,7 @@
   return [
     { field: 'userName', title: '鍛樺伐濮撳悕', minWidth: 100 },
     { field: 'deptName', title: '閮ㄩ棬', minWidth: 120 },
+    { field: 'phone', title: '鎵嬫満鍙�', width: 130 },
     { field: 'date', title: '鑰冨嫟鏃ユ湡', width: 110 },
     { field: 'clockInTime', title: '涓婄彮鎵撳崱鏃堕棿', width: 160 },
     {
diff --git a/src/views/hrm/attendance/record/modules/import-form.vue b/src/views/hrm/attendance/record/modules/import-form.vue
index 486e53c..ccc0e50 100644
--- a/src/views/hrm/attendance/record/modules/import-form.vue
+++ b/src/views/hrm/attendance/record/modules/import-form.vue
@@ -23,10 +23,18 @@
     }
     modalApi.lock();
     try {
-      await importAttendanceRecord(fileList.value[0] as File, updateSupport.value);
+      const res: string = await importAttendanceRecord(
+        fileList.value[0] as File,
+        updateSupport.value,
+      );
       await modalApi.close();
       emit('success');
-      message.success('瀵煎叆鎴愬姛');
+      // 鍚庣杩斿洖瀵煎叆缁撴灉锛堝惈澶辫触鍘熷洜锛屼緥濡傗�滀互涓嬪憳宸ヤ笉瀛樺湪...锛涢渶琛ュ厖鎵嬫満鍙�...鈥濓級
+      if (res && res.length > 0) {
+        message[res.includes('鏈�') || res.includes('涓嶅畬鏁�') ? 'warning' : 'success'](res);
+      } else {
+        message.success('瀵煎叆鎴愬姛');
+      }
     } finally {
       modalApi.unlock();
     }
diff --git a/src/views/hrm/employee/data.ts b/src/views/hrm/employee/data.ts
index 9affcfa..5e9404f 100644
--- a/src/views/hrm/employee/data.ts
+++ b/src/views/hrm/employee/data.ts
@@ -10,6 +10,7 @@
 import { Button } from 'ant-design-vue';
 
 import { z } from '#/adapter/form';
+import { getSalaryStructureList } from '#/api/hrm/salary/structure';
 import { getDeptList } from '#/api/system/dept';
 import { getSimplePostList } from '#/api/system/post';
 import { getSimpleRoleList } from '#/api/system/role';
@@ -242,6 +243,19 @@
       },
     },
     {
+      fieldName: 'salaryStructureId',
+      label: '钖叕缁撴瀯',
+      component: 'ApiSelect',
+      componentProps: {
+        api: () => getSalaryStructureList({ status: 0 }),
+        labelField: 'name',
+        valueField: 'id',
+        allowClear: true,
+        placeholder: '璇烽�夋嫨钖叕缁撴瀯',
+      },
+      help: '鍐冲畾宸ヤ綔鏃ュ姞鐝�嶇巼锛涙湭閫夋嫨鏃朵娇鐢ㄧ郴缁熼粯璁� 1.5 鍊�',
+    },
+    {
       fieldName: 'baseSalary',
       label: '鍩烘湰宸ヨ祫',
       component: 'InputNumber',
diff --git a/src/views/hrm/overtime/data.ts b/src/views/hrm/overtime/data.ts
new file mode 100644
index 0000000..a4985b5
--- /dev/null
+++ b/src/views/hrm/overtime/data.ts
@@ -0,0 +1,320 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+
+import { DICT_TYPE } from '#/packages/constants/src';
+import { getDictOptions } from '#/packages/effects/hooks/src';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+export const OVERTIME_TYPE_OPTIONS = [
+  { label: '宸ヤ綔鏃ュ姞鐝�', value: 1 },
+  { label: '浼戞伅鏃ュ姞鐝�', value: 2 },
+  { label: '娉曞畾鑺傚亣鏃ュ姞鐝�', value: 3 },
+];
+
+export const RELATED_TYPE_OPTIONS = [
+  { label: '椤圭洰', value: 1 },
+  { label: '宸ュ崟', value: 2 },
+];
+
+export function getOvertimeTypeLabel(type?: number) {
+  return OVERTIME_TYPE_OPTIONS.find((item) => item.value === type)?.label || '-';
+}
+
+/** 鏂板/淇敼鍔犵彮鐢宠鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: { triggerFields: [''], show: () => false },
+    },
+    {
+      fieldName: 'employeeId',
+      component: 'Input',
+      dependencies: { triggerFields: [''], show: () => false },
+    },
+    {
+      fieldName: 'baseInfoDivider',
+      label: '鐢宠浜哄熀纭�淇℃伅',
+      component: 'Divider',
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'employeeName',
+      label: '鐢宠浜�',
+      component: 'Input',
+      componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+    },
+    {
+      fieldName: 'employeeNo',
+      label: '宸ュ彿',
+      component: 'Input',
+      componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+    },
+    {
+      fieldName: 'deptName',
+      label: '鎵�灞為儴闂�',
+      component: 'Input',
+      componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+    },
+    {
+      fieldName: 'postName',
+      label: '宀椾綅/鑱岀骇',
+      component: 'Input',
+      componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+    },
+    {
+      fieldName: 'overtimeInfoDivider',
+      label: '鍔犵彮鏍稿績淇℃伅',
+      component: 'Divider',
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'overtimeDate',
+      label: '鍔犵彮鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鍔犵彮鏃ユ湡',
+        valueFormat: 'YYYY-MM-DD',
+        style: { width: '100%' },
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'overtimeType',
+      label: '鍔犵彮绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: OVERTIME_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨鍔犵彮绫诲瀷',
+      },
+      help: '涓嶅悓绫诲瀷瀵瑰簲涓嶅悓鐨勮柂璧勮绠楄鍒�',
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'startTime',
+      label: '寮�濮嬫椂闂�',
+      component: 'TimePicker',
+      componentProps: {
+        format: 'HH:mm',
+        minuteStep: 30,
+        placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+        valueFormat: 'HH:mm:ss',
+        style: { width: '100%' },
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'endTime',
+      label: '缁撴潫鏃堕棿',
+      component: 'TimePicker',
+      componentProps: {
+        format: 'HH:mm',
+        minuteStep: 30,
+        placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
+        valueFormat: 'HH:mm:ss',
+        style: { width: '100%' },
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'crossDay',
+      label: '鏄惁璺ㄥぉ',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        disabled: true,
+        unCheckedChildren: '鍚�',
+      },
+      defaultValue: false,
+      help: '缁撴潫鏃堕棿鏃╀簬鎴栫瓑浜庡紑濮嬫椂闂存椂锛岀郴缁熻嚜鍔ㄦ爣璁颁负璺ㄥぉ',
+    },
+    {
+      fieldName: 'mealDeduction',
+      label: '鎵i櫎鐢ㄩ鏃堕棿',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鎵i櫎',
+        unCheckedChildren: '涓嶆墸闄�',
+      },
+      defaultValue: false,
+    },
+    {
+      fieldName: 'mealDeductionHours',
+      label: '鐢ㄩ鎵i櫎鏃堕暱',
+      component: 'InputNumber',
+      componentProps: {
+        min: 0.5,
+        max: 4,
+        precision: 1,
+        step: 0.5,
+        style: { width: '100%' },
+      },
+      defaultValue: 1,
+      suffix: '灏忔椂',
+      dependencies: {
+        triggerFields: ['mealDeduction'],
+        show: (values) => Boolean(values.mealDeduction),
+      },
+    },
+    {
+      fieldName: 'duration',
+      label: '鍔犵彮鏃堕暱',
+      component: 'InputNumber',
+      componentProps: {
+        disabled: true,
+        min: 0,
+        precision: 1,
+        step: 0.5,
+        style: { width: '100%' },
+      },
+      help: '鎸夊疄闄呮椂娈垫墸闄ょ敤椁愭椂闂村悗锛岃嚜鍔ㄥ彇鏁村埌 0.5 灏忔椂',
+      suffix: '灏忔椂',
+      rules: 'required',
+    },
+    {
+      fieldName: 'reasonInfoDivider',
+      label: '鍔犵彮浜嬬敱',
+      component: 'Divider',
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'reason',
+      label: '鍔犵彮鍘熷洜',
+      component: 'Textarea',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        maxlength: 500,
+        placeholder: '璇疯鏄庡姞鐝殑蹇呰鎬у拰鍘熷洜',
+        rows: 3,
+        showCount: true,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'relatedType',
+      label: '鍏宠仈绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: RELATED_TYPE_OPTIONS,
+        placeholder: '鍙�夛細椤圭洰鎴栧伐鍗�',
+      },
+    },
+    {
+      fieldName: 'relatedCode',
+      label: '椤圭洰/宸ュ崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        maxlength: 100,
+        placeholder: '璇疯緭鍏ュ叧鑱旈」鐩垨宸ュ崟缂栧彿',
+      },
+      dependencies: {
+        triggerFields: ['relatedType'],
+        componentProps: (values) => ({
+          allowClear: true,
+          maxlength: 100,
+          placeholder: values.relatedType === 1
+            ? '璇疯緭鍏ラ」鐩紪鍙�'
+            : values.relatedType === 2
+              ? '璇疯緭鍏ュ伐鍗曠紪鍙�'
+              : '璇峰厛閫夋嫨鍏宠仈绫诲瀷',
+          disabled: !values.relatedType,
+        }),
+      },
+    },
+    {
+      fieldName: 'workContent',
+      label: '宸ヤ綔鍐呭绠�杩�',
+      component: 'Textarea',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        maxlength: 1000,
+        placeholder: '璇风畝瑕佹弿杩板姞鐝湡闂磋鍒掑畬鎴愮殑鍏蜂綋宸ヤ綔',
+        rows: 4,
+        showCount: true,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'overtimeDate',
+      label: '鍔犵彮鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇烽�夋嫨鍔犵彮鏃ユ湡',
+        valueFormat: 'YYYY-MM-DD',
+      },
+    },
+    {
+      fieldName: 'overtimeType',
+      label: '鍔犵彮绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: OVERTIME_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨鍔犵彮绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '瀹℃壒鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.HRM_AUDIT_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨瀹℃壒鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃瀛楁 */
+export function useGridColumns(): VxeTableGridOptions<HrmOvertimeApi.Overtime>['columns'] {
+  return [
+    { field: 'id', title: '鐢宠缂栧彿', width: 90 },
+    { field: 'employeeName', title: '鐢宠浜�', minWidth: 100 },
+    { field: 'employeeNo', title: '宸ュ彿', minWidth: 110 },
+    { field: 'deptName', title: '鎵�灞為儴闂�', minWidth: 120 },
+    { field: 'overtimeDate', title: '鍔犵彮鏃ユ湡', width: 120 },
+    {
+      field: 'overtimeType',
+      title: '鍔犵彮绫诲瀷',
+      width: 130,
+      slots: { default: 'overtimeType' },
+    },
+    { field: 'startTime', title: '寮�濮嬫椂闂�', width: 100 },
+    { field: 'endTime', title: '缁撴潫鏃堕棿', width: 100 },
+    { field: 'duration', title: '鏃堕暱(灏忔椂)', width: 100 },
+    {
+      field: 'status',
+      title: '瀹℃壒鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.HRM_AUDIT_STATUS },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 200,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/hrm/overtime/detail/approve/index.vue b/src/views/hrm/overtime/detail/approve/index.vue
new file mode 100644
index 0000000..bfab940
--- /dev/null
+++ b/src/views/hrm/overtime/detail/approve/index.vue
@@ -0,0 +1,83 @@
+<script lang="ts" setup>
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, onMounted, ref } from 'vue';
+
+import { ContentWrap } from '@vben/common-ui';
+import { DICT_TYPE } from '@vben/constants';
+import { formatDateTime } from '@vben/utils';
+
+import { Spin, Tag } from 'ant-design-vue';
+
+import { getOvertime } from '#/api/hrm/overtime';
+import { DictTag } from '#/components/dict-tag';
+import { useDescription } from '#/components/description';
+import { getOvertimeTypeLabel } from '#/views/hrm/overtime/data';
+
+defineOptions({ name: 'HrmOvertimeApproveDetail' });
+
+const props = defineProps<{ id: string }>();
+const loading = ref(false);
+const formData = ref<HrmOvertimeApi.Overtime>();
+
+function textRender(value: unknown) {
+  return value === 0 || value ? String(value) : '-';
+}
+
+const [Descriptions] = useDescription({
+  bordered: true,
+  column: 2,
+  schema: [
+    { label: '鐢宠浜�', field: 'employeeName', render: textRender },
+    { label: '宸ュ彿', field: 'employeeNo', render: textRender },
+    { label: '鎵�灞為儴闂�', field: 'deptName', render: textRender },
+    { label: '宀椾綅/鑱岀骇', field: 'postName', render: textRender },
+    { label: '鍔犵彮鏃ユ湡', field: 'overtimeDate', render: textRender },
+    {
+      label: '鍔犵彮绫诲瀷',
+      field: 'overtimeType',
+      render: (value) => h(Tag, { color: value === 3 ? 'red' : value === 2 ? 'orange' : 'blue' }, () => getOvertimeTypeLabel(Number(value))),
+    },
+    { label: '寮�濮嬫椂闂�', field: 'startTime', render: textRender },
+    { label: '缁撴潫鏃堕棿', field: 'endTime', render: textRender },
+    { label: '鍔犵彮鏃堕暱', field: 'duration', render: (value) => `${textRender(value)} 灏忔椂` },
+    { label: '鏄惁璺ㄥぉ', field: 'crossDay', render: (value) => value ? '鏄�' : '鍚�' },
+    { label: '鐢ㄩ鎵i櫎', field: 'mealDeduction', render: (value) => value ? '鏄�' : '鍚�' },
+    { label: '鎵i櫎鏃堕暱', field: 'mealDeductionHours', render: (value) => value ? `${value} 灏忔椂` : '-' },
+    { label: '鍔犵彮鍘熷洜', field: 'reason', render: textRender },
+    { label: '鍏宠仈绫诲瀷', field: 'relatedType', render: (value) => value === 1 ? '椤圭洰' : value === 2 ? '宸ュ崟' : '-' },
+    { label: '椤圭洰/宸ュ崟缂栧彿', field: 'relatedCode', render: textRender },
+    { label: '宸ヤ綔鍐呭绠�杩�', field: 'workContent', render: textRender },
+    {
+      label: '瀹℃壒鐘舵��',
+      field: 'status',
+      render: (value) => h(DictTag, { type: DICT_TYPE.HRM_AUDIT_STATUS, value }),
+    },
+    { label: '鍒涘缓鏃堕棿', field: 'createTime', render: (value) => formatDateTime(value) as string },
+  ] as DescriptionItemSchema[],
+});
+
+onMounted(async () => {
+  loading.value = true;
+  try {
+    formData.value = await getOvertime(Number(props.id));
+  } finally {
+    loading.value = false;
+  }
+});
+</script>
+
+<template>
+  <ContentWrap class="m-2">
+    <Spin :spinning="loading" tip="鍔犺浇涓�...">
+      <div class="rounded-lg border border-border bg-background p-4">
+        <div class="mb-4 flex items-center gap-2">
+          <span class="inline-block h-4 w-1 rounded bg-primary" />
+          <span class="text-base font-semibold">鍔犵彮鐢宠淇℃伅</span>
+        </div>
+        <Descriptions :data="formData" />
+      </div>
+    </Spin>
+  </ContentWrap>
+</template>
diff --git a/src/views/hrm/overtime/index.vue b/src/views/hrm/overtime/index.vue
new file mode 100644
index 0000000..9b58111
--- /dev/null
+++ b/src/views/hrm/overtime/index.vue
@@ -0,0 +1,200 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { message, Modal, Select, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelOvertime,
+  deleteOvertime,
+  getOvertimeApproveProcessDefinitionList,
+  getOvertimePage,
+  submitOvertime,
+} from '#/api/hrm/overtime';
+import { $t } from '#/locales';
+
+import {
+  getOvertimeTypeLabel,
+  useGridColumns,
+  useGridFormSchema,
+} from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+function handleRefresh() {
+  gridApi.query();
+}
+
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+function handleDetail(row: HrmOvertimeApi.Overtime) {
+  formModalApi.setData({ id: row.id, formType: 'detail' }).open();
+}
+
+function handleEdit(row: HrmOvertimeApi.Overtime) {
+  formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+async function handleDelete(row: HrmOvertimeApi.Overtime) {
+  const hideLoading = message.loading({ content: '姝e湪鍒犻櫎鍔犵彮鐢宠...', duration: 0 });
+  try {
+    await deleteOvertime(row.id!);
+    message.success('鍔犵彮鐢宠鍒犻櫎鎴愬姛');
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const submitModalVisible = ref(false);
+const currentRow = ref<HrmOvertimeApi.Overtime>();
+const processList = ref<HrmOvertimeApi.ApproveProcess[]>([]);
+const selectedProcessKey = ref<string>();
+const submitLoading = ref(false);
+
+async function handleSubmit(row: HrmOvertimeApi.Overtime) {
+  processList.value = await getOvertimeApproveProcessDefinitionList();
+  currentRow.value = row;
+  selectedProcessKey.value = undefined;
+  submitModalVisible.value = true;
+}
+
+async function confirmSubmit() {
+  if (!selectedProcessKey.value) {
+    message.warning('璇烽�夋嫨鍗忓悓鍔炲叕瀹℃壒娴佺▼');
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    await submitOvertime(currentRow.value!.id!, selectedProcessKey.value);
+    message.success('宸叉彁浜よ嚦鍗忓悓鍔炲叕瀹℃壒');
+    submitModalVisible.value = false;
+    handleRefresh();
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+async function handleCancel(row: HrmOvertimeApi.Overtime) {
+  await cancelOvertime(row.id!);
+  message.success('鎾ら攢鎴愬姛');
+  handleRefresh();
+}
+
+function getOvertimeTypeColor(type?: number) {
+  return type === 1 ? 'blue' : type === 2 ? 'orange' : type === 3 ? 'red' : 'default';
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: { schema: useGridFormSchema() },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) =>
+          await getOvertimePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          }),
+      },
+    },
+    rowConfig: { keyField: 'id', isHover: true },
+    toolbarConfig: { refresh: true, search: true },
+  } as VxeTableGridOptions<HrmOvertimeApi.Overtime>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <Grid table-title="鍔犵彮鐢宠鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鍔犵彮鐢宠']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['hrm:overtime:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #overtimeType="{ row }">
+        <Tag :color="getOvertimeTypeColor(row.overtimeType)">
+          {{ getOvertimeTypeLabel(row.overtimeType) }}
+        </Tag>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            { label: '鏌ョ湅', type: 'link', onClick: handleDetail.bind(null, row) },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['hrm:overtime:update'],
+              ifShow: row.status === 0,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: '鎻愪氦',
+              type: 'link',
+              auth: ['hrm:overtime:submit'],
+              ifShow: row.status === 0,
+              onClick: handleSubmit.bind(null, row),
+            },
+            {
+              label: '鎾ら攢',
+              type: 'link',
+              auth: ['hrm:overtime:cancel'],
+              ifShow: row.status === 10,
+              onClick: handleCancel.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['hrm:overtime:delete'],
+              ifShow: row.status === 0 || row.status === 40,
+              popConfirm: {
+                title: '纭鍒犻櫎璇ュ姞鐝敵璇峰悧锛�',
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </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>
diff --git a/src/views/hrm/overtime/modules/form.vue b/src/views/hrm/overtime/modules/form.vue
new file mode 100644
index 0000000..4613de8
--- /dev/null
+++ b/src/views/hrm/overtime/modules/form.vue
@@ -0,0 +1,159 @@
+<script lang="ts" setup>
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+import type { FormType } from '../data';
+
+import { computed, ref } from 'vue';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createOvertime,
+  getCurrentOvertimeApplicant,
+  getOvertime,
+  updateOvertime,
+} from '#/api/hrm/overtime';
+import { $t } from '#/locales';
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+
+const isDetail = computed(() => formType.value === 'detail');
+const getTitle = computed(() => {
+  if (isDetail.value) return '鏌ョ湅鍔犵彮鐢宠';
+  return formType.value === 'update' ? '淇敼鍔犵彮鐢宠' : '鏂板鍔犵彮鐢宠';
+});
+
+/** 鏍规嵁鏃舵銆佽法澶╂爣璇嗗拰鐢ㄩ鎵i櫎璁$畻鍔犵彮鏃堕暱锛屽苟鎸� 0.5 灏忔椂鍙栨暣 */
+function calculateDuration(values: Record<string, any>) {
+  const { startTime, endTime } = values;
+  if (!startTime || !endTime) return { crossDay: false, duration: 0 };
+
+  const toMinutes = (time: string) => {
+    const [hour = 0, minute = 0] = time.split(':').map(Number);
+    return hour * 60 + minute;
+  };
+  const startMinutes = toMinutes(startTime);
+  let endMinutes = toMinutes(endTime);
+  const crossDay = endMinutes <= startMinutes;
+  if (crossDay) endMinutes += 24 * 60;
+
+  const deductionMinutes = values.mealDeduction
+    ? Number(values.mealDeductionHours || 0) * 60
+    : 0;
+  const actualHours = Math.max(0, (endMinutes - startMinutes - deductionMinutes) / 60);
+  return {
+    crossDay,
+    duration: Math.round(actualHours * 2) / 2,
+  };
+}
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 120,
+  },
+  wrapperClass: 'grid-cols-2',
+  layout: 'horizontal',
+  schema: [],
+  scrollToFirstError: true,
+  showDefaultActions: false,
+  handleValuesChange: (values, changedFields) => {
+    const durationFields = [
+      'startTime',
+      'endTime',
+      'mealDeduction',
+      'mealDeductionHours',
+    ];
+    if (changedFields.some((field) => durationFields.includes(field))) {
+      const result = calculateDuration(values);
+      void formApi.setValues(result);
+    }
+    if (changedFields.includes('relatedType') && !values.relatedType) {
+      void formApi.setFieldValue('relatedCode', undefined);
+    }
+  },
+});
+
+async function setCurrentEmployee() {
+  const employee = await getCurrentOvertimeApplicant();
+  await formApi.setValues({
+    employeeId: employee.employeeId,
+    employeeName: employee.employeeName,
+    employeeNo: employee.employeeNo,
+    deptName: employee.deptName,
+    postName: employee.postName,
+    mealDeduction: false,
+    mealDeductionHours: 1,
+    crossDay: false,
+    duration: 0,
+  });
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (isDetail.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) return;
+
+    const data = (await formApi.getValues()) as HrmOvertimeApi.Overtime;
+    if (!data.employeeId) {
+      message.warning('褰撳墠璐﹀彿鏈叧鑱斿憳宸ユ。妗堬紝鏆傛椂鏃犳硶鎻愪氦鍔犵彮鐢宠');
+      return;
+    }
+    if (!data.duration || data.duration <= 0) {
+      message.warning('鍔犵彮鏃堕暱蹇呴』澶т簬 0 灏忔椂锛岃妫�鏌ユ椂娈垫垨鐢ㄩ鎵i櫎');
+      return;
+    }
+    if (data.relatedType && !data.relatedCode?.trim()) {
+      message.warning('璇烽�夋嫨鍏宠仈绫诲瀷鍚庡~鍐欏搴旂殑椤圭洰鎴栧伐鍗曠紪鍙�');
+      return;
+    }
+
+    modalApi.lock();
+    try {
+      if (data.id) await updateOvertime(data);
+      else await createOvertime(data);
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) return;
+
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema() });
+    formApi.setDisabled(isDetail.value);
+    modalApi.setState({ showConfirmButton: !isDetail.value });
+
+    if (!data.id) {
+      await setCurrentEmployee();
+      return;
+    }
+
+    modalApi.lock();
+    try {
+      await formApi.setValues(await getOvertime(data.id));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-[900px]">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/hrm/salary/leave-type-config/data.ts b/src/views/hrm/salary/leave-type-config/data.ts
index f8e53f1..14cb42d 100644
--- a/src/views/hrm/salary/leave-type-config/data.ts
+++ b/src/views/hrm/salary/leave-type-config/data.ts
@@ -3,7 +3,7 @@
 import type { HrmLeaveTypeConfigApi } from '#/api/hrm/leave-type-config';
 
 /** 琛ㄥ崟绫诲瀷 */
-export type FormType = 'update';
+export type FormType = 'create' | 'update';
 
 /** 鏂板/淇敼璇峰亣鎵f閰嶇疆鐨勮〃鍗� */
 export function useFormSchema(): VbenFormSchema[] {
@@ -17,13 +17,29 @@
       },
     },
     {
-      fieldName: 'name',
+      fieldName: 'leaveType',
       label: '璇峰亣绫诲瀷',
-      component: 'Input',
+      component: 'Select',
       componentProps: {
-        disabled: true,
-        placeholder: '',
+        options: [
+          { label: '浜嬪亣', value: 1 },
+          { label: '鐥呭亣', value: 2 },
+          { label: '骞村亣', value: 3 },
+          { label: '濠氬亣', value: 4 },
+          { label: '浜у亣', value: 5 },
+          { label: '涓у亣', value: 6 },
+        ],
+        placeholder: '璇烽�夋嫨璇峰亣绫诲瀷',
+        style: { width: '100%' },
       },
+      dependencies: {
+        triggerFields: ['id'],
+        // 缂栬緫鏃朵笉鍏佽淇敼璇峰亣绫诲瀷
+        componentProps: (values: Record<string, unknown>) => ({
+          disabled: Boolean(values.id),
+        }),
+      },
+      rules: 'required',
       formItemClass: 'col-span-2',
     },
     {
@@ -47,6 +63,7 @@
       fieldName: 'status',
       label: '鐘舵��',
       component: 'RadioGroup',
+      defaultValue: 0,
       componentProps: {
         options: [
           { label: '鍚敤', value: 0 },
diff --git a/src/views/hrm/salary/leave-type-config/index.vue b/src/views/hrm/salary/leave-type-config/index.vue
index a22d4c7..c92af92 100644
--- a/src/views/hrm/salary/leave-type-config/index.vue
+++ b/src/views/hrm/salary/leave-type-config/index.vue
@@ -7,7 +7,10 @@
 import { message, Tag } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getLeaveTypeConfigList } from '#/api/hrm/leave-type-config';
+import {
+  deleteLeaveTypeConfig,
+  getLeaveTypeConfigList,
+} from '#/api/hrm/leave-type-config';
 import { $t } from '#/locales';
 
 import { useGridColumns } from './data';
@@ -23,9 +26,21 @@
   gridApi.query();
 }
 
+/** 鏂板閰嶇疆 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
 /** 缂栬緫閰嶇疆 */
 function handleEdit(row: HrmLeaveTypeConfigApi.LeaveTypeConfig) {
-  formModalApi.setData({ id: row.id, row }).open();
+  formModalApi.setData({ formType: 'update', row }).open();
+}
+
+/** 鍒犻櫎閰嶇疆 */
+async function handleDelete(row: HrmLeaveTypeConfigApi.LeaveTypeConfig) {
+  await deleteLeaveTypeConfig(row.id!);
+  message.success($t('ui.actionMessage.deleteSuccess', ['璇峰亣鎵f閰嶇疆']));
+  handleRefresh();
 }
 
 const [Grid, gridApi] = useVbenVxeGrid({
@@ -56,6 +71,19 @@
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
     <Grid table-title="璇峰亣鎵f閰嶇疆鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['璇峰亣鎵f閰嶇疆']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['hrm:leave-type-config:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
       <template #deductionRatio="{ row }">
         <Tag :color="row.deductionRatio === 0 ? 'success' : row.deductionRatio === 1 ? 'error' : 'warning'">
           {{ row.deductionRatio === 0 ? '涓嶆墸娆�' : `${(row.deductionRatio * 100).toFixed(0)}%` }}
@@ -76,6 +104,17 @@
               auth: ['hrm:leave-type-config:update'],
               onClick: handleEdit.bind(null, row),
             },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['hrm:leave-type-config:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', ['璇峰亣鎵f閰嶇疆']),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
           ]"
         />
       </template>
diff --git a/src/views/hrm/salary/leave-type-config/modules/form.vue b/src/views/hrm/salary/leave-type-config/modules/form.vue
index f016033..dbe2aab 100644
--- a/src/views/hrm/salary/leave-type-config/modules/form.vue
+++ b/src/views/hrm/salary/leave-type-config/modules/form.vue
@@ -1,19 +1,28 @@
 <script lang="ts" setup>
+import type { FormType } from '../data';
 import type { HrmLeaveTypeConfigApi } from '#/api/hrm/leave-type-config';
 
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
 
 import { useVbenModal } from '#/packages/effects/common-ui/src';
 
 import { message } from 'ant-design-vue';
 
 import { useVbenForm } from '#/adapter/form';
-import { updateLeaveTypeConfig } from '#/api/hrm/leave-type-config';
+import {
+  createLeaveTypeConfig,
+  updateLeaveTypeConfig,
+} from '#/api/hrm/leave-type-config';
 import { $t } from '#/locales';
 
 import { useFormSchema } from '../data';
 
 const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+
+const getTitle = computed(() =>
+  formType.value === 'update' ? '缂栬緫鎵f姣斾緥' : '鏂板璇峰亣鎵f閰嶇疆',
+);
 
 const [Form, formApi] = useVbenForm({
   commonConfig: {
@@ -36,11 +45,35 @@
       return;
     }
     modalApi.lock();
-    const data = await formApi.getValues();
+    const data = (await formApi.getValues()) as HrmLeaveTypeConfigApi.LeaveTypeConfig;
     // 杞崲姣斾緥涓哄皬鏁�
-    data.deductionRatio = data.deductionRatio / 100;
+    const deductionRatio = data.deductionRatio! / 100;
     try {
-      await updateLeaveTypeConfig(data);
+      if (formType.value === 'update') {
+        await updateLeaveTypeConfig({
+          id: data.id,
+          deductionRatio,
+          remark: data.remark,
+          status: data.status,
+        });
+      } else {
+        // 璇峰亣绫诲瀷鍚嶇О鐢辨墍閫夌被鍨嬭嚜鍔ㄥ甫鍑猴紝鏃犻渶鎵嬪姩濉啓
+        const NAME_MAP: Record<number, string> = {
+          1: '浜嬪亣',
+          2: '鐥呭亣',
+          3: '骞村亣',
+          4: '濠氬亣',
+          5: '浜у亣',
+          6: '涓у亣',
+        };
+        await createLeaveTypeConfig({
+          leaveType: data.leaveType,
+          name: NAME_MAP[data.leaveType!] ?? '',
+          deductionRatio,
+          remark: data.remark,
+          status: data.status,
+        });
+      }
       await modalApi.close();
       emit('success');
       message.success($t('ui.actionMessage.operationSuccess'));
@@ -52,16 +85,24 @@
     if (!isOpen) {
       return;
     }
-    const data = modalApi.getData<{ id: number; row: HrmLeaveTypeConfigApi.LeaveTypeConfig }>();
+    const data = modalApi.getData<{ formType: FormType; row: HrmLeaveTypeConfigApi.LeaveTypeConfig }>();
+    formType.value = data.formType;
+    if (data.formType === 'create' || !data.row) {
+      await formApi.resetForm();
+      return;
+    }
     // 杞崲姣斾緥涓虹櫨鍒嗘瘮鏄剧ず
-    const row = { ...data.row, deductionRatio: (data.row.deductionRatio || 0) * 100 };
+    const row = {
+      ...data.row,
+      deductionRatio: (data.row.deductionRatio || 0) * 100,
+    };
     await formApi.setValues(row);
   },
 });
 </script>
 
 <template>
-  <Modal title="缂栬緫鎵f姣斾緥" class="w-[600px]">
+  <Modal :title="getTitle" class="w-[600px]">
     <Form class="mx-4" />
   </Modal>
-</template>
\ No newline at end of file
+</template>
diff --git a/src/views/hrm/salary/structure/data.ts b/src/views/hrm/salary/structure/data.ts
new file mode 100644
index 0000000..68d1ddf
--- /dev/null
+++ b/src/views/hrm/salary/structure/data.ts
@@ -0,0 +1,163 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmSalaryStructureApi } from '#/api/hrm/salary/structure';
+
+export type FormType = 'create' | 'update';
+
+const statusOptions = [
+  { label: '鍚敤', value: 0 },
+  { label: '绂佺敤', value: 1 },
+];
+
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: { triggerFields: [''], show: () => false },
+    },
+    {
+      fieldName: 'name',
+      label: '缁撴瀯鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        maxlength: 100,
+        placeholder: '渚嬪锛氭爣鍑嗗憳宸ヨ柂閰�佺鐞嗗眰钖叕',
+        showCount: true,
+      },
+      rules: 'required',
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'overtimePayRatio',
+      label: '宸ヤ綔鏃ュ�嶇巼',
+      component: 'InputNumber',
+      componentProps: {
+        addonAfter: '鍊�',
+        max: 10,
+        min: 0,
+        precision: 2,
+        step: 0.1,
+        placeholder: '0 琛ㄧず涓嶈鍔犵彮宸ヨ祫',
+        style: { width: '100%' },
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'restDayOvertimePayRatio',
+      label: '浼戞伅鏃ュ�嶇巼',
+      component: 'InputNumber',
+      componentProps: {
+        addonAfter: '鍊�',
+        max: 10,
+        min: 0,
+        precision: 2,
+        step: 0.1,
+        placeholder: '0 琛ㄧず涓嶈璇ョ被鍨嬪姞鐝伐璧�',
+        style: { width: '100%' },
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'legalHolidayOvertimePayRatio',
+      label: '娉曞畾鑺傚亣鏃ュ�嶇巼',
+      component: 'InputNumber',
+      componentProps: {
+        addonAfter: '鍊�',
+        max: 10,
+        min: 0,
+        precision: 2,
+        step: 0.1,
+        placeholder: '0 琛ㄧず涓嶈璇ョ被鍨嬪姞鐝伐璧�',
+        style: { width: '100%' },
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'RadioGroup',
+      componentProps: { options: statusOptions },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        maxlength: 500,
+        placeholder: '濉啓閫傜敤宀椾綅銆佽亴绾ф垨鐗规畩璁¤柂璇存槑',
+        rows: 3,
+        showCount: true,
+      },
+      formItemClass: 'col-span-2',
+    },
+  ];
+}
+
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'name',
+      label: '缁撴瀯鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ョ粨鏋勫悕绉�',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: statusOptions,
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+  ];
+}
+
+export function useGridColumns(): VxeTableGridOptions<HrmSalaryStructureApi.SalaryStructure>['columns'] {
+  return [
+    { field: 'name', title: '缁撴瀯鍚嶇О', minWidth: 180 },
+    {
+      field: 'overtimePayRatio',
+      title: '宸ヤ綔鏃ュ姞鐝�嶇巼',
+      width: 145,
+      slots: { default: 'overtimePayRatio' },
+    },
+    {
+      field: 'restDayOvertimePayRatio',
+      title: '浼戞伅鏃ュ姞鐝�嶇巼',
+      width: 145,
+      slots: { default: 'restDayOvertimePayRatio' },
+    },
+    {
+      field: 'legalHolidayOvertimePayRatio',
+      title: '娉曞畾鑺傚亣鏃ュ�嶇巼',
+      width: 165,
+      slots: { default: 'legalHolidayOvertimePayRatio' },
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 90,
+      slots: { default: 'status' },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'actions',
+      title: '鎿嶄綔',
+      fixed: 'right',
+      slots: { default: 'actions' },
+      width: 150,
+    },
+  ];
+}
diff --git a/src/views/hrm/salary/structure/index.vue b/src/views/hrm/salary/structure/index.vue
new file mode 100644
index 0000000..4427175
--- /dev/null
+++ b/src/views/hrm/salary/structure/index.vue
@@ -0,0 +1,137 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmSalaryStructureApi } from '#/api/hrm/salary/structure';
+
+import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { Alert, message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteSalaryStructure,
+  getSalaryStructureList,
+} from '#/api/hrm/salary/structure';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'HrmSalaryStructure' });
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+function handleRefresh() {
+  gridApi.query();
+}
+
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+function handleEdit(row: HrmSalaryStructureApi.SalaryStructure) {
+  formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+async function handleDelete(row: HrmSalaryStructureApi.SalaryStructure) {
+  await deleteSalaryStructure(row.id!);
+  message.success($t('ui.actionMessage.deleteSuccess', ['钖叕缁撴瀯']));
+  handleRefresh();
+}
+
+function formatRatio(value?: number) {
+  return value === undefined || value === null ? '-' : `${value.toFixed(2)} 鍊峘;
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: { schema: useGridFormSchema() },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    pagerConfig: { enabled: false },
+    proxyConfig: {
+      ajax: {
+        query: async (_params, formValues) =>
+          await getSalaryStructureList(formValues),
+      },
+    },
+    rowConfig: { keyField: 'id', isHover: true },
+    toolbarConfig: { refresh: true, search: true },
+  } as VxeTableGridOptions<HrmSalaryStructureApi.SalaryStructure>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <Alert
+      class="mb-4"
+      description="鍩烘湰宸ヨ祫鍙栧憳宸ユ。妗堬紱绀句繚鍜屽叕绉噾鍩烘暟鍙栧憳宸ョぞ淇濇。妗堛�傝繖閲屼粎缁存姢宸ヤ綔鏃ャ�佷紤鎭棩鍜屾硶瀹氳妭鍋囨棩鍔犵彮鍊嶇巼銆�"
+      message="钖叕缁撴瀯缁存姢鍔犵彮璁¤柂瑙勫垯"
+      show-icon
+      type="info"
+    />
+    <Grid table-title="钖叕缁撴瀯鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['钖叕缁撴瀯']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['hrm:salary-structure:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #overtimePayRatio="{ row }">
+        <Tag :color="(row.overtimePayRatio ?? 0) > 0 ? 'blue' : 'default'">
+          {{ (row.overtimePayRatio ?? 0) > 0 ? formatRatio(row.overtimePayRatio) : '涓嶈鍔犵彮宸ヨ祫' }}
+        </Tag>
+      </template>
+      <template #restDayOvertimePayRatio="{ row }">
+        <Tag :color="(row.restDayOvertimePayRatio ?? 0) > 0 ? 'blue' : 'default'">
+          {{ (row.restDayOvertimePayRatio ?? 0) > 0 ? formatRatio(row.restDayOvertimePayRatio) : '涓嶈鍔犵彮宸ヨ祫' }}
+        </Tag>
+      </template>
+      <template #legalHolidayOvertimePayRatio="{ row }">
+        <Tag :color="(row.legalHolidayOvertimePayRatio ?? 0) > 0 ? 'blue' : 'default'">
+          {{ (row.legalHolidayOvertimePayRatio ?? 0) > 0 ? formatRatio(row.legalHolidayOvertimePayRatio) : '涓嶈鍔犵彮宸ヨ祫' }}
+        </Tag>
+      </template>
+      <template #status="{ row }">
+        <Tag :color="row.status === 0 ? 'success' : 'default'">
+          {{ row.status === 0 ? '鍚敤' : '绂佺敤' }}
+        </Tag>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['hrm:salary-structure:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['hrm:salary-structure:delete'],
+              popConfirm: {
+                title: '纭鍒犻櫎璇ヨ柂閰粨鏋勫悧锛熷凡琚憳宸ヤ娇鐢ㄧ殑缁撴瀯涓嶅彲鍒犻櫎銆�',
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/hrm/salary/structure/modules/form.vue b/src/views/hrm/salary/structure/modules/form.vue
new file mode 100644
index 0000000..0330821
--- /dev/null
+++ b/src/views/hrm/salary/structure/modules/form.vue
@@ -0,0 +1,95 @@
+<script lang="ts" setup>
+import type { HrmSalaryStructureApi } from '#/api/hrm/salary/structure';
+import type { FormType } from '../data';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { Alert, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createSalaryStructure,
+  getSalaryStructure,
+  updateSalaryStructure,
+} from '#/api/hrm/salary/structure';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+
+const getTitle = computed(() =>
+  formType.value === 'update' ? '淇敼钖叕缁撴瀯' : '鏂板钖叕缁撴瀯',
+);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  wrapperClass: 'grid-cols-2',
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) return;
+    modalApi.lock();
+    try {
+      const data =
+        (await formApi.getValues()) as HrmSalaryStructureApi.SalaryStructure;
+      if (data.id) {
+        await updateSalaryStructure(data);
+      } else {
+        await createSalaryStructure(data);
+      }
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) return;
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    await formApi.resetForm();
+    if (!data.id) {
+      await formApi.setValues({
+        legalHolidayOvertimePayRatio: 3,
+        overtimePayRatio: 1.5,
+        restDayOvertimePayRatio: 2,
+        status: 0,
+      });
+      return;
+    }
+    modalApi.lock();
+    try {
+      await formApi.setValues(await getSalaryStructure(data.id));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-[680px]">
+    <Alert
+      class="mx-4 mb-4"
+      description="涓夌鍔犵彮绫诲瀷鍒嗗埆璁剧疆鍊嶇巼锛涙煇涓�椤硅涓� 0 鏃讹紝浠呰绫诲瀷涓嶈绠楀姞鐝伐璧勶紝瀹℃壒閫氳繃鐨勫姞鐝椂闀夸粛姝e父缁熻銆�"
+      message="鍔犵彮宸ヨ祫璁$畻璇存槑"
+      show-icon
+      type="info"
+    />
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/md/item/type/data.ts b/src/views/mes/md/item/type/data.ts
index 3858003..f36b63a 100644
--- a/src/views/mes/md/item/type/data.ts
+++ b/src/views/mes/md/item/type/data.ts
@@ -223,3 +223,27 @@
     },
   ];
 }
+
+/** 鐗╂枡鍒嗙被瀵煎叆鐨勮〃鍗� */
+export function useImportFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'file',
+      label: '鍒嗙被鏁版嵁',
+      component: 'Upload',
+      rules: 'required',
+      help: '浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢',
+    },
+    {
+      fieldName: 'updateSupport',
+      label: '鏄惁瑕嗙洊',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+      help: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勫垎绫绘暟鎹�',
+    },
+  ];
+}
diff --git a/src/views/mes/md/item/type/index.vue b/src/views/mes/md/item/type/index.vue
index 062b7d9..d3765d9 100644
--- a/src/views/mes/md/item/type/index.vue
+++ b/src/views/mes/md/item/type/index.vue
@@ -14,9 +14,15 @@
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
+import ImportForm from './modules/import-form.vue';
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ImportModal, importModalApi] = useVbenModal({
+  connectedComponent: ImportForm,
   destroyOnClose: true,
 });
 
@@ -36,6 +42,11 @@
 /** 鍒涘缓鍒嗙被 */
 function handleCreate() {
   formModalApi.setData(null).open();
+}
+
+/** 瀵煎叆鍒嗙被 */
+function handleImport() {
+  importModalApi.open();
 }
 
 /** 娣诲姞涓嬬骇鍒嗙被 */
@@ -101,7 +112,7 @@
 </script>
 
 <template>
-  <Page auto-content-height><FormModal @success="handleRefresh" />
+  <Page auto-content-height><FormModal @success="handleRefresh" /><ImportModal @success="handleRefresh" />
     <Grid table-title="鐗╂枡鍒嗙被鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -114,6 +125,13 @@
               onClick: handleCreate,
             },
             {
+              label: $t('ui.actionTitle.import', ['鐗╂枡鍒嗙被']),
+              type: 'primary',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['mes:md-item-type:import'],
+              onClick: handleImport,
+            },
+            {
               label: isExpanded ? '鏀剁缉' : '灞曞紑',
               type: 'primary',
               onClick: handleExpand,
diff --git a/src/views/mes/md/item/type/modules/import-form.vue b/src/views/mes/md/item/type/modules/import-form.vue
new file mode 100644
index 0000000..db5c74e
--- /dev/null
+++ b/src/views/mes/md/item/type/modules/import-form.vue
@@ -0,0 +1,105 @@
+<script lang="ts" setup>
+import type { FileType } from 'ant-design-vue/es/upload/interface';
+
+import { h } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, notification, Upload } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { importItemType, importItemTypeTemplate } from '#/api/mes/md/item/type';
+import { $t } from '#/locales';
+
+import { useImportFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useImportFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = await formApi.getValues();
+    try {
+      const response = (await importItemType(data.file, data.updateSupport)) as any;
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+
+      const resultData = response?.data || response || {};
+      const failureList = resultData.failureList || {};
+
+      if (Object.keys(failureList).length > 0) {
+        const errorNodes = Object.entries(failureList).map(([name, reason]) => {
+          return h('div', `${name}: ${reason}`);
+        });
+
+        notification.warning({
+          message: '瀵煎叆瀹屾垚锛岄儴鍒嗙墿鏂欏垎绫诲鍏ュけ璐�',
+          description: () => h('div', errorNodes),
+          duration: null,
+        });
+      } else {
+        message.success($t('ui.actionMessage.operationSuccess'));
+      }
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+
+/** 涓婁紶鍓� */
+function beforeUpload(file: FileType) {
+  formApi.setFieldValue('file', file);
+  return false;
+}
+
+/** 涓嬭浇妯$増 */
+async function handleDownload() {
+  const data = await importItemTypeTemplate();
+  downloadFileFromBlobPart({ fileName: '鐗╂枡鍒嗙被瀵煎叆妯℃澘.xls', source: data });
+}
+</script>
+
+<template>
+  <Modal title="瀵煎叆鐗╂枡鍒嗙被" class="w-1/3">
+    <Form class="mx-4">
+      <template #file>
+        <div class="w-full">
+          <Upload
+            :max-count="1"
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+          >
+            <Button type="primary"> 閫夋嫨 Excel 鏂囦欢 </Button>
+          </Upload>
+        </div>
+      </template>
+    </Form>
+    <div class="mx-4 mt-2 mb-4 text-gray-500 text-xs">
+      鎻愮ず锛氭ā鏉夸腑鐨勨�滅埗鍒嗙被缂栫爜鈥濊濉啓鐖跺垎绫荤殑缂栫爜锛堝 RAW锛夛紝椤剁骇鍒嗙被璇ュ垪鐣欑┖鍗冲彲銆�
+      <div>鈥滅墿鏂�/浜у搧鏍囪瘑鈥濆~ ITEM锛堢墿鏂欙級鎴� PRODUCT锛堜骇鍝侊級銆�</div>
+      <div>鈥滅姸鎬佲�濆~ 0锛堝惎鐢級/1锛堢鐢級銆�</div>
+    </div>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center">
+        <Button @click="handleDownload"> 涓嬭浇瀵煎叆妯℃澘 </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/mes/pro/route/data.ts b/src/views/mes/pro/route/data.ts
index d59d436..0ad7caa 100644
--- a/src/views/mes/pro/route/data.ts
+++ b/src/views/mes/pro/route/data.ts
@@ -219,6 +219,15 @@
       fieldName: 'linkType',
       label: '涓庝笅閬撳伐搴忓叧绯�',
       component: 'Select',
+      help: () =>
+        h(
+          'div',
+          {
+            style:
+              'white-space: pre-line; max-width: 320px; line-height: 1.6;',
+          },
+          '0=寮�濮�-寮�濮�(SS)锛氭湰宸ュ簭寮�濮嬪悗锛屼笅閬撳伐搴忔墠鑳藉紑濮嬶紙鍙噸鍙犲苟琛屽紑宸ワ級\n1=缁撴潫-缁撴潫(FF)锛氫笅閬撳伐搴忓繀椤荤瓑鍒版湰宸ュ簭缁撴潫鎵嶈兘缁撴潫锛堝苟琛屼絾鍚屾瀹屽伐锛塡n2=寮�濮�-缁撴潫(SF)锛氭湰宸ュ簭寮�濮嬪悗锛屼笅閬撳伐搴忔墠鑳界粨鏉焅n3=缁撴潫-寮�濮�(FS)锛氭湰宸ュ簭缁撴潫鍚庯紝涓嬮亾宸ュ簭鎵嶈兘寮�濮嬶紙榛樿鍊硷級',
+        ),
       componentProps: {
         allowClear: true,
         options: getDictOptions(DICT_TYPE.MES_PRO_LINK_TYPE, 'number'),
diff --git a/src/views/mes/pro/route/modules/process-form.vue b/src/views/mes/pro/route/modules/process-form.vue
index b742c63..6dcab06 100644
--- a/src/views/mes/pro/route/modules/process-form.vue
+++ b/src/views/mes/pro/route/modules/process-form.vue
@@ -31,7 +31,7 @@
   commonConfig: {
     componentProps: { class: 'w-full' },
     formItemClass: 'col-span-1',
-    labelWidth: 130,
+    labelWidth: 150,
   },
   layout: 'horizontal',
   schema: [],
diff --git a/src/views/mes/pro/workorder/modules/process-form.vue b/src/views/mes/pro/workorder/modules/process-form.vue
index bdbdcaa..bbe10c0 100644
--- a/src/views/mes/pro/workorder/modules/process-form.vue
+++ b/src/views/mes/pro/workorder/modules/process-form.vue
@@ -2,7 +2,7 @@
 import type { VbenFormSchema } from '#/adapter/form';
 import type { MesProWorkOrderProcessApi } from '#/api/mes/pro/workorder/process';
 
-import { computed, ref } from 'vue';
+import { computed, h, ref } from 'vue';
 
 import { useVbenModal } from '@vben/common-ui';
 
@@ -91,6 +91,15 @@
       fieldName: 'linkType',
       label: '宸ュ簭鍏崇郴',
       component: 'Select',
+      help: () =>
+        h(
+          'div',
+          {
+            style:
+              'white-space: pre-line; max-width: 320px; line-height: 1.6;',
+          },
+          '0=寮�濮�-寮�濮�(SS)锛氭湰宸ュ簭寮�濮嬪悗锛屼笅閬撳伐搴忔墠鑳藉紑濮嬶紙鍙噸鍙犲苟琛屽紑宸ワ級\n1=缁撴潫-缁撴潫(FF)锛氫笅閬撳伐搴忓繀椤荤瓑鍒版湰宸ュ簭缁撴潫鎵嶈兘缁撴潫锛堝苟琛屼絾鍚屾瀹屽伐锛塡n2=寮�濮�-缁撴潫(SF)锛氭湰宸ュ簭寮�濮嬪悗锛屼笅閬撳伐搴忔墠鑳界粨鏉焅n3=缁撴潫-寮�濮�(FS)锛氭湰宸ュ簭缁撴潫鍚庯紝涓嬮亾宸ュ簭鎵嶈兘寮�濮嬶紙榛樿鍊硷級',
+        ),
       componentProps: {
         allowClear: true,
         options: [
diff --git a/src/views/mes/process-design/route/data.ts b/src/views/mes/process-design/route/data.ts
index 941a2be..7990b1e 100644
--- a/src/views/mes/process-design/route/data.ts
+++ b/src/views/mes/process-design/route/data.ts
@@ -205,6 +205,15 @@
       fieldName: "linkType",
       label: "涓庝笅閬撳伐搴忓叧绯�",
       component: "Select",
+      help: () =>
+        h(
+          "div",
+          {
+            style:
+              "white-space: pre-line; max-width: 320px; line-height: 1.6;",
+          },
+          "0=寮�濮�-寮�濮�(SS)锛氭湰宸ュ簭寮�濮嬪悗锛屼笅閬撳伐搴忔墠鑳藉紑濮嬶紙鍙噸鍙犲苟琛屽紑宸ワ級\n1=缁撴潫-缁撴潫(FF)锛氫笅閬撳伐搴忓繀椤荤瓑鍒版湰宸ュ簭缁撴潫鎵嶈兘缁撴潫锛堝苟琛屼絾鍚屾瀹屽伐锛塡n2=寮�濮�-缁撴潫(SF)锛氭湰宸ュ簭寮�濮嬪悗锛屼笅閬撳伐搴忔墠鑳界粨鏉焅n3=缁撴潫-寮�濮�(FS)锛氭湰宸ュ簭缁撴潫鍚庯紝涓嬮亾宸ュ簭鎵嶈兘寮�濮嬶紙榛樿鍊硷級",
+        ),
       componentProps: {
         allowClear: true,
         options: getDictOptions(DICT_TYPE.MES_PRO_LINK_TYPE, "number"),
diff --git a/src/views/mes/process-design/route/modules/process-form.vue b/src/views/mes/process-design/route/modules/process-form.vue
index b742c63..6dcab06 100644
--- a/src/views/mes/process-design/route/modules/process-form.vue
+++ b/src/views/mes/process-design/route/modules/process-form.vue
@@ -31,7 +31,7 @@
   commonConfig: {
     componentProps: { class: 'w-full' },
     formItemClass: 'col-span-1',
-    labelWidth: 130,
+    labelWidth: 150,
   },
   layout: 'horizontal',
   schema: [],
diff --git a/src/views/wls/materialstock/components/stock-adjust-modal.vue b/src/views/wls/materialstock/components/stock-adjust-modal.vue
index aeb5883..3028b7d 100644
--- a/src/views/wls/materialstock/components/stock-adjust-modal.vue
+++ b/src/views/wls/materialstock/components/stock-adjust-modal.vue
@@ -20,7 +20,7 @@
 } from '#/api/mes/wm/materialstock';
 import { getItem } from '#/api/mdm/item';
 import { MdmItemSelect } from '#/views/basicData/mdm/components';
-import { ErpSupplierSelect } from '#/views/erp/purchase/supplier/components';
+import { SrmSupplierSelect } from '#/views/srm/supplier/components';
 import BatchConfigDisplay from './batch-config-display.vue';
 import {
   WmWarehouseAreaSelect,
@@ -297,7 +297,7 @@
     {
       fieldName: 'vendorId',
       label: '渚涘簲鍟�',
-      component: markRaw(ErpSupplierSelect),
+      component: markRaw(SrmSupplierSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨渚涘簲鍟�',
       },
diff --git a/src/views/wls/stocktaking/plan/modules/param-form.vue b/src/views/wls/stocktaking/plan/modules/param-form.vue
index b2a87bc..228f32e 100644
--- a/src/views/wls/stocktaking/plan/modules/param-form.vue
+++ b/src/views/wls/stocktaking/plan/modules/param-form.vue
@@ -52,7 +52,7 @@
     const valueValid =
       values.type === WlsStockTakingParamTypeEnum.QUALITY_STATUS
         ? !!values.valueCode
-        : values.valueId !== null;
+        : values.valueId !== undefined && values.valueId !== null;
     if (!valueValid) {
       message.warning('璇烽�夋嫨鏉′欢鍊�');
       return;
diff --git a/vite.config.ts b/vite.config.ts
index 7b0c254..b9e690e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -25,6 +25,7 @@
     __APP_NAME__: JSON.stringify(appName),
   };
   if (!isDev && appApiUrl) {
+    process.env.VITE_GLOB_API_URL = appApiUrl;
     defineVars['import.meta.env.VITE_GLOB_API_URL'] = JSON.stringify(appApiUrl);
     defineVars['import.meta.env.VITE_BASE_URL'] = JSON.stringify(new URL(appApiUrl).origin);
   }
@@ -82,7 +83,7 @@
         proxy: {
           "/admin-api": {
             changeOrigin: true,
-            target: "http://192.168.0.10:48080",
+            target: "http://36.213.90.123:9500",
             ws: true,
           },
         },

--
Gitblit v1.9.3