Merge branch 'refs/heads/dev_pro2.0' into fix/hsy/20260812
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | @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 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | @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 |
| | |
| | | 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', |
| | | }, |
| | | }; |
| | |
| | | match = 'VITE_GLOB_', |
| | | confFiles = getConfFiles(), |
| | | ) { |
| | | let envConfig = {}; |
| | | let envConfig: Record<string, string> = {}; |
| | | |
| | | for (const confFile of confFiles) { |
| | | try { |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | totalCount?: number; // å计æ°é |
| | | totalPrice?: number; // å计éé¢ï¼åä½ï¼å
|
| | | totalProductPrice?: number; // 产åéé¢ï¼åä½ï¼å
|
| | | totalTaxPrice?: number; // å计ç¨é¢ï¼åä½ï¼å
|
| | | discountPercent?: number; // 伿 çï¼ç¾åæ¯ |
| | | discountPrice?: number; // 伿 éé¢ï¼åä½ï¼å
|
| | | depositPrice?: number; // å®ééé¢ï¼åä½ï¼å
|
| | |
| | | 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 |
| | |
| | | userName?: string; |
| | | deptId?: number; |
| | | deptName?: string; |
| | | phone?: string; |
| | | date?: string; |
| | | clockInTime?: string; |
| | | clockOutTime?: string; |
| | |
| | | employeeStatus?: number; |
| | | annualLeaveBalance?: number; |
| | | sickLeaveBalance?: number; |
| | | salaryStructureId?: number; |
| | | baseSalary?: number; |
| | | roleIds?: number[]; |
| | | roleNames?: string[]; |
| | |
| | | /** å é¤ç´§æ¥è系人 */ |
| | | export function deleteEmergencyContact(id: number) { |
| | | return requestClient.delete(`/hrm/employee/emergency-contact/delete?id=${id}`); |
| | | } |
| | | } |
| | |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢è¯·åç±»åé
ç½® */ |
| | | 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}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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}`, |
| | | ); |
| | | } |
| | | |
| | | /** è·åå½åç³è¯·äººå
³èçåå·¥æ¡£æ¡ */ |
| | | 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', |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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}`); |
| | | } |
| | |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | documentVersion?: string; |
| | | /** æä»¶å */ |
| | | fileName?: string; |
| | | /** æä»¶å°å */ |
| | | fileUrl?: string; |
| | | /** ä¸ä¼ 人ï¼ç¨æ·IDï¼ */ |
| | | uploadUserId?: number; |
| | | /** ä¸ä¼ 人åç§° */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | |
| | | ); |
| | | } |
| | | |
| | | /** å
Œ
±æä»¶ä¸ä¼ ï¼æ°¸ä¹
ææï¼è¿åæ°¸ä¹
å
¬å¼ 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[]>( |
| | |
| | | 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(); |
| | |
| | | // é
ç½® pinia-store |
| | | await initStores(app, { namespace }); |
| | | |
| | | // å è½½å
¬å¸åçä¿¡æ¯ï¼ä»å端ï¼å
ç»å½æ¥å£ï¼ï¼èå¨ç»å½é¡µ/èåæ /ç½é¡µæ é¢ã |
| | | // æ³¨ï¼æ¤æ¶ pinia å°æªå¨ç»ä»¶å¤æ¿æ´»ï¼è¿éç¨çº¯å½æ°ï¼ä¸ä¾èµ storeã |
| | | try { |
| | | const companyConfig = await getCompanyConfig(); |
| | | if (companyConfig) { |
| | | applyCompanyConfig(companyConfig); |
| | | } |
| | | } catch { |
| | | // å端ä¸å¯ç¨ææªé
ç½®ï¼ä¿æç¼è¯æé»è®¤ |
| | | } |
| | | |
| | | // å®è£
æéæä»¤ |
| | | registerAccessDirective(app); |
| | | |
| | |
| | | <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 ä¼å
åæ°æ®åºé
ç½®ï¼preferences.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> |
| | |
| | | :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> --> |
| | |
| | | |
| | | 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[]; |
| | | /** æ¯å¦å
è®¸æ¨ªåæ»å¨ï¼tab è¾å¤æ¶å¯ç¨ï¼å tab æå
容宽度æåï¼è¶
åºé¨å坿»å¨ */ |
| | | scrollable?: boolean; |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | defaultValue: '', |
| | | tabs: () => [], |
| | | scrollable: false, |
| | | }); |
| | | |
| | | const activeTab = defineModel<string>(); |
| | |
| | | }); |
| | | |
| | | 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)}%`, |
| | | }; |
| | |
| | | <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> |
| | |
| | | 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; |
| | | |
| | | /** çç¹ç±»åæä¸¾ */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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); |
| | | |
| | | // æå¼åºåæ¶ä»å端æåææ°å
¬å¸é
置并å填表åï¼store åå¼å¯è½ä¸ºç©ºï¼å å¯å¨å è½½èµ°çæ¯çº¯å½æ°ï¼ |
| | | 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> |
| | |
| | | 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'; |
| | |
| | | Breadcrumb, |
| | | BuiltinTheme, |
| | | ColorMode, |
| | | Company, |
| | | Content, |
| | | Copyright, |
| | | Custom, |
| | |
| | | label: $t('preferences.general'), |
| | | value: 'general', |
| | | }, |
| | | { |
| | | label: $t('preferences.company.title'), |
| | | value: 'company', |
| | | }, |
| | | ]; |
| | | |
| | | if (showCustomTab.value) { |
| | |
| | | <VbenSegmented |
| | | v-model="activeTab" |
| | | :tabs="tabs" |
| | | scrollable |
| | | :class="{ |
| | | 'sticky-tabs-header': appEnableStickyPreferencesNavigationBar, |
| | | }" |
| | |
| | | /> |
| | | </Block> |
| | | </template> |
| | | <template #company> |
| | | <Block :title="$t('preferences.company.title')"> |
| | | <Company /> |
| | | </Block> |
| | | </template> |
| | | <template #custom> |
| | | <Block :title="customTabTitle"> |
| | | <Custom |
| | |
| | | "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" |
| | | } |
| | | } |
| | |
| | | "placeholder": "请è¾å
¥æ¥è¡¨é»è®¤æ é¢" |
| | | } |
| | | } |
| | | }, |
| | | "company": { |
| | | "title": "å
¬å¸ä¿¡æ¯", |
| | | "companyName": "å
¬å¸åç§°", |
| | | "companyNamePlaceholder": "请è¾å
¥å
¬å¸åç§°", |
| | | "companyNameRequired": "å
¬å¸åç§°ä¸è½ä¸ºç©º", |
| | | "loginTitle": "ç»å½é¡µæ é¢", |
| | | "loginTitlePlaceholder": "请è¾å
¥ç»å½é¡µæ é¢ï¼å¯éï¼", |
| | | "loginDescription": "ç»å½é¡µæè¿°", |
| | | "loginDescriptionPlaceholder": "请è¾å
¥ç»å½é¡µæè¿°ï¼å¯éï¼", |
| | | "logo": "å
¬å¸ Logo", |
| | | "uploadLogo": "ä¸ä¼ Logo", |
| | | "uploadSuccess": "Logo ä¸ä¼ æå", |
| | | "uploadFailed": "Logo ä¸ä¼ 失败", |
| | | "uploading": "ä¸ä¼ ä¸...", |
| | | "logoEmpty": "æªè®¾ç½® Logo", |
| | | "save": "ä¿å", |
| | | "saveSuccess": "å
¬å¸ä¿¡æ¯ä¿åæå", |
| | | "saveFailed": "å
¬å¸ä¿¡æ¯ä¿å失败" |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, |
| | | }; |
| | | }); |
| | |
| | | export * from './auth'; |
| | | export * from './company-config'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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ï¼åºç¡æ é¢ï¼dynamicTitle å¼å¯æ¶è·¯ç±æ é¢å¨æ¤åºç¡ä¸æ¼æ¥ï¼ |
| | | */ |
| | | 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; |
| | | } |
| | | } |
| | |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç©æå¯¼å
¥ç表å */ |
| | | export function useImportFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'file', |
| | | label: 'ç©ææ°æ®', |
| | | component: 'Upload', |
| | | rules: 'required', |
| | | help: 'ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶', |
| | | }, |
| | | { |
| | | fieldName: 'updateSupport', |
| | | label: 'æ¯å¦è¦ç', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | help: 'æ¯å¦æ´æ°å·²ç»åå¨çç©ææ°æ®', |
| | | }, |
| | | ]; |
| | | } |
| | |
| | | |
| | | 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, |
| | | }); |
| | | |
| | |
| | | /** åå»ºç©æ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** 导å
¥ç©æ */ |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** ç¼è¾ç©æ */ |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <Grid table-title="ç©æå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | 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', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 计éåä½å¯¼å
¥ç表å */ |
| | | export function useImportFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'file', |
| | | label: 'å使°æ®', |
| | | component: 'Upload', |
| | | rules: 'required', |
| | | help: 'ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶', |
| | | }, |
| | | { |
| | | fieldName: 'updateSupport', |
| | | label: 'æ¯å¦è¦ç', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | help: 'æ¯å¦æ´æ°å·²ç»åå¨çå使°æ®', |
| | | }, |
| | | ]; |
| | | } |
| | |
| | | |
| | | 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, |
| | | }); |
| | | |
| | |
| | | /** å建计éåä½ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** 导å
¥è®¡éåä½ */ |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®¡éåä½ */ |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <Grid table-title="计éåä½å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | 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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 120, |
| | | width: 180, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | 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'; |
| | | |
| | |
| | | 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(); |
| | |
| | | <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), |
| | |
| | | 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', |
| | |
| | | id: undefined, |
| | | no: '', |
| | | supplierId: undefined, |
| | | accountId: undefined, |
| | | accountName: undefined, |
| | | financeUserId: undefined, |
| | | paymentTime: undefined, |
| | | remark: '', |
| | |
| | | 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; |
| | | } |
| | |
| | | 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'; |
| | | |
| | |
| | | export type FormType = 'create' | 'detail' | 'edit'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | export function useFormSchema( |
| | | orderTotalTaxPrice?: Ref<number | undefined>, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | |
| | | }, |
| | | 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; |
| | | } |
| | | }, |
| | | }), |
| | | }, |
| | |
| | | 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'); |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | |
| | | { |
| | | title: 'æ¥ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'price', |
| | | minWidth: 150, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'å©ä½å¯ä»éé¢', |
| | | field: 'remainingInvoicePrice', |
| | | minWidth: 150, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | |
| | | 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') { |
| | |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | schema: useFormSchema(orderTotalTaxPrice), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | |
| | | 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; |
| | |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'totalTaxPrice', |
| | | title: 'æªå¼ç¥¨éé¢', |
| | | formatter: 'formatAmount2', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | |
| | | deletePurchaseOrder, |
| | | exportPurchaseOrder, |
| | | getPurchaseOrderPage, |
| | | updatePurchaseOrderStatus, |
| | | } from '#/api/erp/purchase/order'; |
| | | import { $t } from '#/locales'; |
| | | |
| | |
| | | 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(); |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="éè´è®¢åå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | 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: '确认æ¶è´§', |
| | |
| | | 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 }, |
| | | { |
| | |
| | | } |
| | | 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(); |
| | | } |
| | |
| | | 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'; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | 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', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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: 'æ£é¤ç¨é¤æ¶é´', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ£é¤', |
| | | unCheckedChildren: '䏿£é¤', |
| | | }, |
| | | defaultValue: false, |
| | | }, |
| | | { |
| | | fieldName: 'mealDeductionHours', |
| | | label: 'ç¨é¤æ£é¤æ¶é¿', |
| | | 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' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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: 'ç¨é¤æ£é¤', field: 'mealDeduction', render: (value) => value ? 'æ¯' : 'å¦' }, |
| | | { label: 'æ£é¤æ¶é¿', 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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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: 'æ£å¨å é¤å çç³è¯·...', 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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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' ? 'ä¿®æ¹å çç³è¯·' : 'æ°å¢å çç³è¯·'; |
| | | }); |
| | | |
| | | /** æ ¹æ®æ¶æ®µã跨天æ è¯åç¨é¤æ£é¤è®¡ç®å çæ¶é¿ï¼å¹¶æ 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 å°æ¶ï¼è¯·æ£æ¥æ¶æ®µæç¨é¤æ£é¤'); |
| | | 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> |
| | |
| | | import type { HrmLeaveTypeConfigApi } from '#/api/hrm/leave-type-config'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'update'; |
| | | export type FormType = 'create' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹è¯·åæ£æ¬¾é
ç½®ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | 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', |
| | | }, |
| | | { |
| | |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | defaultValue: 0, |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'å¯ç¨', value: 0 }, |
| | |
| | | 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'; |
| | |
| | | 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', ['è¯·åæ£æ¬¾é
ç½®'])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | <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: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)}%` }} |
| | |
| | | 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', ['è¯·åæ£æ¬¾é
ç½®']), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | <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' ? 'ç¼è¾æ£æ¬¾æ¯ä¾' : 'æ°å¢è¯·åæ£æ¬¾é
ç½®', |
| | | ); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | |
| | | 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')); |
| | |
| | | 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="ç¼è¾æ£æ¬¾æ¯ä¾" class="w-[600px]"> |
| | | <Modal :title="getTitle" class="w-[600px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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 æ¶ï¼ä»
该类åä¸è®¡ç®å çå·¥èµï¼å®¡æ¹éè¿çå çæ¶é¿ä»æ£å¸¸ç»è®¡ã" |
| | | message="å çå·¥èµè®¡ç®è¯´æ" |
| | | show-icon |
| | | type="info" |
| | | /> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç©æå类导å
¥ç表å */ |
| | | export function useImportFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'file', |
| | | label: 'åç±»æ°æ®', |
| | | component: 'Upload', |
| | | rules: 'required', |
| | | help: 'ä»
å
许导å
¥ xlsãxlsx æ ¼å¼æä»¶', |
| | | }, |
| | | { |
| | | fieldName: 'updateSupport', |
| | | label: 'æ¯å¦è¦ç', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | help: 'æ¯å¦æ´æ°å·²ç»åå¨çåç±»æ°æ®', |
| | | }, |
| | | ]; |
| | | } |
| | |
| | | |
| | | 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, |
| | | }); |
| | | |
| | |
| | | /** å建åç±» */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** 导å
¥åç±» */ |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** æ·»å ä¸çº§åç±» */ |
| | |
| | | </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 |
| | |
| | | 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, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | 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'), |
| | |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 130, |
| | | labelWidth: 150, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | |
| | | 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'; |
| | | |
| | |
| | | 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: [ |
| | |
| | | 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"), |
| | |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 130, |
| | | labelWidth: 150, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | |
| | | } 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, |
| | |
| | | { |
| | | fieldName: 'vendorId', |
| | | label: 'ä¾åºå', |
| | | component: markRaw(ErpSupplierSelect), |
| | | component: markRaw(SrmSupplierSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | }, |
| | |
| | | const valueValid = |
| | | values.type === WlsStockTakingParamTypeEnum.QUALITY_STATUS |
| | | ? !!values.valueCode |
| | | : values.valueId !== null; |
| | | : values.valueId !== undefined && values.valueId !== null; |
| | | if (!valueValid) { |
| | | message.warning('è¯·éæ©æ¡ä»¶å¼'); |
| | | return; |
| | |
| | | __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); |
| | | } |
| | |
| | | proxy: { |
| | | "/admin-api": { |
| | | changeOrigin: true, |
| | | target: "http://192.168.0.10:48080", |
| | | target: "http://36.213.90.123:9500", |
| | | ws: true, |
| | | }, |
| | | }, |