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