Merge branch 'refs/heads/dev_pro2.0' into fix/hsy/20260812
| | |
| | | VITE_BASE=/ |
| | | |
| | | # 请æ±è·¯å¾ |
| | | VITE_BASE_URL=http://42.63.70.90:9003 |
| | | VITE_BASE_URL=http://36.213.180.217:9501 |
| | | # æ¥å£å°å |
| | | VITE_GLOB_API_URL=http://42.63.70.90:9003/admin-api |
| | | VITE_GLOB_API_URL=http://36.213.180.217:9501/admin-api |
| | | # æä»¶ä¸ä¼ ç±»åï¼server - å端ä¸ä¼ ï¼ client - å端ç´è¿ä¸ä¼ ï¼ä»
æ¯æS3æå¡ |
| | | VITE_UPLOAD_TYPE=server |
| | | |
| | |
| | | name: 'æµè¯å
¬å¸', |
| | | logo: '/logos/TEST.png', |
| | | favicon: '/logos/TEST.ico', |
| | | apiUrl: 'http://42.63.70.90:9003/admin-api', |
| | | apiUrl: 'http://36.213.180.217:9501/admin-api', |
| | | }, |
| | | JHHG: { |
| | | name: 'å®å¤ä¹æ³åå·¥', |
| | | logo: '/logos/TEST.png', |
| | | favicon: '/logos/TEST.ico', |
| | | apiUrl: 'http://42.63.70.90:9001/admin-api', |
| | | }, |
| | | YZFX: { |
| | | name: 'å®å¤è±æ³½é¤é', |
| | | logo: '/logos/YZFXLogo.png', |
| | | favicon: '/logos/YZFXLogo.ico', |
| | | apiUrl: 'http://42.63.70.231:9001/admin-api', |
| | | }, |
| | | }; |
| | |
| | | @echo off |
| | | chcp 65001 >nul |
| | | setlocal enabledelayedexpansion |
| | | |
| | | :: ================= Config ================= |
| | | set "COMPANY=TEST" |
| | | set "USER=root" |
| | | set "PASS=Xdrjkj@10086#" |
| | | set "IP=36.213.180.217" |
| | | set "PORT=65022" |
| | | set "DIST_DIR=dist" |
| | | set "REMOTE_DIR=/data/project/mom/vue/test" |
| | | set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10" |
| | | :: =========================================== |
| | | |
| | | echo ========================================== |
| | | echo Vue Dist Auto Deploy Script |
| | | echo ========================================== |
| | | echo. |
| | | |
| | | :: Check if sshpass is available |
| | | set "SSHPASS_DIR=%~dp0bin" |
| | | if exist "!SSHPASS_DIR!\sshpass.exe" ( |
| | | set "PATH=!SSHPASS_DIR!;%PATH%" |
| | | ) |
| | | |
| | | where sshpass >nul 2>&1 |
| | | if %errorlevel% equ 0 goto :check_node |
| | | |
| | | echo [WARN] sshpass not found, trying to install... |
| | | echo. |
| | | |
| | | where bash >nul 2>&1 |
| | | if %errorlevel% equ 0 ( |
| | | echo [INFO] Git Bash detected, trying pacman install... |
| | | bash -c "pacman -S --noconfirm sshpass" 2>nul |
| | | if !errorlevel! equ 0 ( |
| | | echo [OK] sshpass installed via pacman! |
| | | goto :check_node |
| | | ) |
| | | ) |
| | | |
| | | if not exist "!SSHPASS_DIR!" mkdir "!SSHPASS_DIR!" |
| | | |
| | | if exist "!SSHPASS_DIR!\sshpass.exe" ( |
| | | set "PATH=!SSHPASS_DIR!;%PATH%" |
| | | echo [OK] sshpass found at: !SSHPASS_DIR! |
| | | goto :check_node |
| | | ) |
| | | |
| | | echo [INFO] Downloading sshpass... |
| | | powershell -NoProfile -Command "$url='https://github.com/xhcoding/sshpass-win32/releases/download/v1.0.3/sshpass.exe'; $out='!SSHPASS_DIR!\sshpass.exe'; Write-Host \"Downloading to: $out\"; try { Invoke-WebRequest -Uri $url -OutFile $out -UseBasicParsing; exit 0 } catch { Write-Host $_.Exception.Message; exit 1 }" |
| | | if !errorlevel! neq 0 ( |
| | | echo [ERROR] Failed to download sshpass! |
| | | echo [HINT] Please install sshpass manually or use Git Bash: pacman -S sshpass |
| | | goto :error_exit |
| | | ) |
| | | |
| | | set "PATH=!SSHPASS_DIR!;%PATH%" |
| | | echo [OK] sshpass downloaded to: !SSHPASS_DIR! |
| | | echo. |
| | | |
| | | :check_node |
| | | :: Check if pnpm is available |
| | | where pnpm >nul 2>&1 |
| | | if %errorlevel% neq 0 ( |
| | | echo [ERROR] pnpm not found! Please install pnpm first. |
| | | echo [HINT] npm install -g pnpm |
| | | goto :error_exit |
| | | ) |
| | | |
| | | :: Check if node_modules exists |
| | | if not exist "node_modules" ( |
| | | echo [INFO] node_modules not found, running pnpm install... |
| | | echo. |
| | | call pnpm install |
| | | if !errorlevel! neq 0 ( |
| | | echo [ERROR] pnpm install failed! |
| | | goto :error_exit |
| | | ) |
| | | echo. |
| | | ) |
| | | |
| | | :: Check if dist exists and ask user |
| | | if exist "%DIST_DIR%" ( |
| | | echo [WARN] dist directory already exists. |
| | | set /p "REBUILD=Do you want to rebuild? (Y/N, default Y): " |
| | | if /i "!REBUILD!"=="N" ( |
| | | echo [INFO] Skip build, using existing dist |
| | | goto :upload |
| | | ) |
| | | ) |
| | | |
| | | :: Build the project |
| | | echo [STEP 1/3] Building project for %COMPANY%... |
| | | echo [INFO] Running: pnpm run build |
| | | echo. |
| | | set "COMPANY=!COMPANY!" && call pnpm run build |
| | | if !errorlevel! neq 0 ( |
| | | echo [ERROR] Build failed! Check your project configuration. |
| | | goto :error_exit |
| | | ) |
| | | echo [OK] Build completed! |
| | | echo. |
| | | |
| | | :upload |
| | | if not exist "%DIST_DIR%" ( |
| | | echo [ERROR] Dist directory not found: %DIST_DIR% |
| | | echo [HINT] Build may have failed or output directory is incorrect. |
| | | goto :error_exit |
| | | ) |
| | | |
| | | :: Count files and calculate size |
| | | set "FILE_COUNT=0" |
| | | set "TOTAL_SIZE=0" |
| | | for /r "%DIST_DIR%" %%F in (*) do ( |
| | | set /a "FILE_COUNT+=1" |
| | | set /a "TOTAL_SIZE+=%%~zF" |
| | | ) |
| | | set /a "TOTAL_SIZE_MB=TOTAL_SIZE/1024/1024" |
| | | |
| | | echo [INFO] Dist directory: %DIST_DIR% |
| | | echo [INFO] Files count: %FILE_COUNT% |
| | | echo [INFO] Total size: %TOTAL_SIZE_MB% MB |
| | | echo [INFO] Target: %USER%@%IP%:%PORT% |
| | | echo [INFO] Remote dir: %REMOTE_DIR% |
| | | echo. |
| | | |
| | | :: Optional: Ask for confirmation before upload |
| | | set /p "CONFIRM=Proceed with upload? (Y/N, default Y): " |
| | | if /i "!CONFIRM!"=="N" ( |
| | | echo [INFO] Upload cancelled by user. |
| | | goto :end |
| | | ) |
| | | |
| | | :: Upload dist directory |
| | | echo [STEP 2/3] Uploading dist directory... |
| | | sshpass -p "%PASS%" scp -P %PORT% %SSH_OPTS% -r "%DIST_DIR%" %USER%@%IP%:%REMOTE_DIR%/ |
| | | if %errorlevel% neq 0 ( |
| | | echo [ERROR] Upload failed! Check network or credentials. |
| | | goto :error_exit |
| | | ) |
| | | echo [OK] Upload completed! |
| | | echo. |
| | | |
| | | :: Optional: Remote restart/cleanup (uncomment if needed) |
| | | :: echo [STEP 3/3] Restarting remote service... |
| | | :: sshpass -p "%PASS%" ssh -p %PORT% %SSH_OPTS% %USER%@%IP% "cd %REMOTE_DIR% && npm run restart" |
| | | |
| | | echo. |
| | | echo ========================================== |
| | | echo [SUCCESS] Deploy completed! |
| | | echo ========================================== |
| | | echo. |
| | | echo Summary: |
| | | echo - Build: Completed |
| | | echo - Upload: %FILE_COUNT% files (%TOTAL_SIZE_MB% MB) |
| | | echo - Target: %USER%@%IP%:%PORT%%REMOTE_DIR% |
| | | echo. |
| | | |
| | | goto :end |
| | | |
| | | :error_exit |
| | | echo. |
| | | echo ========================================== |
| | | echo [FAILED] Deploy failed! |
| | | echo ========================================== |
| | | |
| | | :end |
| | | pause |
| | | @echo off
|
| | | setlocal enabledelayedexpansion
|
| | |
|
| | | :: ================= Config =================
|
| | | set "COMPANY=TEST"
|
| | | set "USER=root"
|
| | | set "PASS=Xdrjkj@10086#"
|
| | | set "IP=36.213.180.217"
|
| | | set "PORT=65022"
|
| | | set "ZIP_FILE=dist.zip"
|
| | | set "REMOTE_DIR=/data/project/mom/vue/test/dist"
|
| | | set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10"
|
| | | :: ===========================================
|
| | |
|
| | | echo ==========================================
|
| | | echo Vue Dist Auto Deploy Script
|
| | | echo ==========================================
|
| | | echo.
|
| | |
|
| | | :: Check if sshpass is available
|
| | | set "SSHPASS_DIR=%~dp0bin"
|
| | | if exist "!SSHPASS_DIR!\sshpass.exe" (
|
| | | set "PATH=!SSHPASS_DIR!;%PATH%"
|
| | | )
|
| | |
|
| | | where sshpass >nul 2>&1
|
| | | if %errorlevel% equ 0 goto :check_node
|
| | |
|
| | | echo [WARN] sshpass not found, trying to install...
|
| | | echo.
|
| | |
|
| | | where bash >nul 2>&1
|
| | | if %errorlevel% equ 0 (
|
| | | echo [INFO] Git Bash detected, trying pacman install...
|
| | | bash -c "pacman -S --noconfirm sshpass" 2>nul
|
| | | if !errorlevel! equ 0 (
|
| | | echo [OK] sshpass installed via pacman!
|
| | | goto :check_node
|
| | | )
|
| | | )
|
| | |
|
| | | if not exist "!SSHPASS_DIR!" mkdir "!SSHPASS_DIR!"
|
| | |
|
| | | if exist "!SSHPASS_DIR!\sshpass.exe" (
|
| | | set "PATH=!SSHPASS_DIR!;%PATH%"
|
| | | echo [OK] sshpass found at: !SSHPASS_DIR!
|
| | | goto :check_node
|
| | | )
|
| | |
|
| | | echo [INFO] Downloading sshpass...
|
| | | powershell -NoProfile -Command "$url='https://github.com/xhcoding/sshpass-win32/releases/download/v1.0.3/sshpass.exe'; $out='!SSHPASS_DIR!\sshpass.exe'; Write-Host \"Downloading to: $out\"; try { Invoke-WebRequest -Uri $url -OutFile $out -UseBasicParsing; exit 0 } catch { Write-Host $_.Exception.Message; exit 1 }"
|
| | | if !errorlevel! neq 0 (
|
| | | echo [ERROR] Failed to download sshpass!
|
| | | echo [HINT] Please install sshpass manually or use Git Bash: pacman -S sshpass
|
| | | goto :error_exit
|
| | | )
|
| | |
|
| | | set "PATH=!SSHPASS_DIR!;%PATH%"
|
| | | echo [OK] sshpass downloaded to: !SSHPASS_DIR!
|
| | | echo.
|
| | |
|
| | | :check_node
|
| | | :: Check if pnpm is available
|
| | | where pnpm >nul 2>&1
|
| | | if %errorlevel% neq 0 (
|
| | | echo [ERROR] pnpm not found! Please install pnpm first.
|
| | | echo [HINT] npm install -g pnpm
|
| | | goto :error_exit
|
| | | )
|
| | |
|
| | | :: Check if node_modules exists
|
| | | if not exist "node_modules" (
|
| | | echo [INFO] node_modules not found, running pnpm install...
|
| | | echo.
|
| | | call pnpm install
|
| | | if !errorlevel! neq 0 (
|
| | | echo [ERROR] pnpm install failed!
|
| | | goto :error_exit
|
| | | )
|
| | | echo.
|
| | | )
|
| | |
|
| | | :: Check if dist.zip exists and ask user
|
| | | if exist "%ZIP_FILE%" (
|
| | | echo [WARN] %ZIP_FILE% already exists.
|
| | | set /p "REBUILD=Do you want to rebuild? (Y/N, default Y): "
|
| | | if /i "!REBUILD!"=="N" (
|
| | | echo [INFO] Skip build, using existing %ZIP_FILE%
|
| | | goto :upload
|
| | | )
|
| | | )
|
| | |
|
| | | :: Build the project
|
| | | echo [STEP 1/3] Building project for %COMPANY%...
|
| | | echo [INFO] Running: pnpm run build
|
| | | echo.
|
| | | :: Pass VITE_COMPANY so vite.config.ts picks the right company (vite reads VITE_ prefixed env vars)
|
| | | set "VITE_COMPANY=!COMPANY!" && call pnpm run build
|
| | | if !errorlevel! neq 0 (
|
| | | echo [ERROR] Build failed! Check your project configuration.
|
| | | goto :error_exit
|
| | | )
|
| | | echo [OK] Build completed!
|
| | | echo.
|
| | |
|
| | | :upload
|
| | | if not exist "%ZIP_FILE%" (
|
| | | echo [ERROR] %ZIP_FILE% not found.
|
| | | echo [HINT] Build may have failed or VITE_ARCHIVER is not enabled in .env.production.
|
| | | goto :error_exit
|
| | | )
|
| | |
|
| | | :: Calculate zip size
|
| | | for %%F in ("%ZIP_FILE%") do set "ZIP_BYTES=%%~zF"
|
| | | set /a "ZIP_SIZE_MB=ZIP_BYTES/1024/1024"
|
| | |
|
| | | echo [INFO] Zip file: %ZIP_FILE%
|
| | | echo [INFO] Size: %ZIP_SIZE_MB% MB
|
| | | echo [INFO] Target: %USER%@%IP%:%PORT%
|
| | | echo [INFO] Remote dir: %REMOTE_DIR%
|
| | | echo.
|
| | |
|
| | | :: Optional: Ask for confirmation before upload
|
| | | set /p "CONFIRM=Proceed with upload? (Y/N, default Y): "
|
| | | if /i "!CONFIRM!"=="N" (
|
| | | echo [INFO] Upload cancelled by user.
|
| | | goto :end
|
| | | )
|
| | |
|
| | | :: Upload dist.zip
|
| | | echo [STEP 2/3] Uploading %ZIP_FILE%...
|
| | | sshpass -p "%PASS%" scp -P %PORT% %SSH_OPTS% "%ZIP_FILE%" %USER%@%IP%:%REMOTE_DIR%/
|
| | | if %errorlevel% neq 0 (
|
| | | echo [ERROR] Upload failed! Check network or credentials.
|
| | | goto :error_exit
|
| | | )
|
| | | echo [OK] Upload completed!
|
| | | echo.
|
| | |
|
| | | :: Remote unzip: cd to %REMOTE_DIR% then unzip -o ./dist.zip
|
| | | echo [STEP 3/3] Running remote: cd %REMOTE_DIR% && unzip -o ./%ZIP_FILE%
|
| | | sshpass -p "%PASS%" ssh -P %PORT% %SSH_OPTS% %USER%@%IP% "cd %REMOTE_DIR% && unzip -o ./%ZIP_FILE% && rm -f ./%ZIP_FILE%"
|
| | | if %errorlevel% neq 0 (
|
| | | echo [ERROR] Remote unzip failed! Check if 'unzip' is installed on server.
|
| | | goto :error_exit
|
| | | )
|
| | | echo [OK] Unzip completed! %ZIP_FILE% removed from remote.
|
| | | echo.
|
| | |
|
| | | echo ==========================================
|
| | | echo [SUCCESS] Deploy completed!
|
| | | echo ==========================================
|
| | | echo.
|
| | | echo Summary:
|
| | | echo - Build: Completed
|
| | | echo - Upload: %ZIP_FILE% (%ZIP_SIZE_MB% MB)
|
| | | echo - Target: %USER%@%IP%:%PORT%%REMOTE_DIR%
|
| | | echo.
|
| | |
|
| | | goto :end
|
| | |
|
| | | :error_exit
|
| | | echo.
|
| | | echo ==========================================
|
| | | echo [FAILED] Deploy failed!
|
| | | echo ==========================================
|
| | |
|
| | | :end
|
| | | pause
|
| | |
| | | "@vben/tailwind-config": "workspace:*", |
| | | "@vben/tsconfig": "workspace:*", |
| | | "@vben/vite-config": "workspace:*", |
| | | "cross-env": "catalog:", |
| | | "jiti": "^2.7.0", |
| | | "tsdown": "^0.22.3", |
| | | "tsx": "^4.22.4", |
| | | "typescript": "catalog:", |
| | | "vite": "catalog:" |
| | | "vite": "catalog:", |
| | | "vue-tsc": "catalog:" |
| | | }, |
| | | "packageManager": "pnpm@11.0.9+sha512.34ce82e6780233cf9cad8685029a8f81d2e06196c5a9bad98879f7424940c6817c4e4524fb7d38b8553ceed48b9758b8ebaf1abd3600c232c4c8cf7366086f38" |
| | | } |
| | |
| | | cropperjs: |
| | | specifier: ^1.6.2 |
| | | version: 1.6.2 |
| | | cross-env: |
| | | specifier: ^10.1.0 |
| | | version: 10.1.0 |
| | | crypto-js: |
| | | specifier: ^4.2.0 |
| | | version: 4.2.0 |
| | |
| | | vue-tippy: |
| | | specifier: ^6.7.1 |
| | | version: 6.7.1 |
| | | vue-tsc: |
| | | specifier: ^3.3.9 |
| | | version: 3.3.9 |
| | | vue3-print-nb: |
| | | specifier: ^0.1.4 |
| | | version: 0.1.4 |
| | |
| | | '@vben/vite-config': |
| | | specifier: workspace:* |
| | | version: link:internal/vite-config |
| | | cross-env: |
| | | specifier: 'catalog:' |
| | | version: 10.1.0 |
| | | jiti: |
| | | specifier: ^2.7.0 |
| | | version: 2.7.0 |
| | | tsdown: |
| | | specifier: ^0.22.3 |
| | | version: 0.22.3(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) |
| | | version: 0.22.3(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) |
| | | tsx: |
| | | specifier: ^4.22.4 |
| | | version: 4.22.4 |
| | |
| | | vite: |
| | | specifier: 'catalog:' |
| | | version: 8.0.10(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) |
| | | vue-tsc: |
| | | specifier: 'catalog:' |
| | | version: 3.3.9(typescript@6.0.3) |
| | | |
| | | internal/lint-configs/commitlint-config: |
| | | dependencies: |
| | |
| | | |
| | | '@emotion/unitless@0.8.1': |
| | | resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} |
| | | |
| | | '@epic-web/invariant@1.0.0': |
| | | resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} |
| | | |
| | | '@es-joy/jsdoccomment@0.84.0': |
| | | resolution: {integrity: sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==} |
| | |
| | | '@vue/devtools-shared@8.1.3': |
| | | resolution: {integrity: sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA==} |
| | | |
| | | '@vue/language-core@3.3.9': |
| | | resolution: {integrity: sha512-in/68oAa4BCtVY6n/nkuhLIkV8DHYd2UivedJ6cMZ6UYtlq9jaoaSNUBHYCVO44z3nKg7MdE5OBoHKt5SxeBKQ==} |
| | | |
| | | '@vue/reactivity@3.5.38': |
| | | resolution: {integrity: sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==} |
| | | |
| | |
| | | |
| | | ajv@8.20.0: |
| | | resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} |
| | | |
| | | alien-signals@3.2.1: |
| | | resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} |
| | | |
| | | ansi-regex@5.0.1: |
| | | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} |
| | |
| | | |
| | | cropperjs@1.6.2: |
| | | resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} |
| | | |
| | | cross-env@10.1.0: |
| | | resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} |
| | | engines: {node: '>=20'} |
| | | hasBin: true |
| | | |
| | | cross-spawn@7.0.6: |
| | | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} |
| | |
| | | peerDependencies: |
| | | vue: ^3.2.0 |
| | | |
| | | vue-tsc@3.3.9: |
| | | resolution: {integrity: sha512-TS3Y1ux/IRoE8OCP2PpACAeOseuIs0UvWrcr7u+w3PmfY+SlCfEf8zjrBgnQksHUgLpthi5vHlffcQTQTdPBZA==} |
| | | hasBin: true |
| | | peerDependencies: |
| | | typescript: '>=5.0.0' |
| | | |
| | | vue-types@3.0.0: |
| | | resolution: {integrity: sha512-xzq2D7zLGbzn3bPQ0CWWYx4W78JWdGYZAFfRkzxz1aTm9DaNoH0qxYDiLXBofBEQ5X78l32uIq/G9INQ2E8X/Q==} |
| | | engines: {node: '>=10.15.0'} |
| | |
| | | '@emotion/hash@0.9.2': {} |
| | | |
| | | '@emotion/unitless@0.8.1': {} |
| | | |
| | | '@epic-web/invariant@1.0.0': {} |
| | | |
| | | '@es-joy/jsdoccomment@0.84.0': |
| | | dependencies: |
| | |
| | | |
| | | '@vue/devtools-shared@8.1.3': {} |
| | | |
| | | '@vue/language-core@3.3.9': |
| | | dependencies: |
| | | '@volar/language-core': 2.4.28 |
| | | '@vue/compiler-dom': 3.5.38 |
| | | '@vue/shared': 3.5.38 |
| | | alien-signals: 3.2.1 |
| | | muggle-string: 0.4.1 |
| | | path-browserify: 1.0.1 |
| | | picomatch: 4.0.4 |
| | | |
| | | '@vue/reactivity@3.5.38': |
| | | dependencies: |
| | | '@vue/shared': 3.5.38 |
| | |
| | | fast-uri: 3.1.2 |
| | | json-schema-traverse: 1.0.0 |
| | | require-from-string: 2.0.2 |
| | | |
| | | alien-signals@3.2.1: {} |
| | | |
| | | ansi-regex@5.0.1: {} |
| | | |
| | |
| | | croner@10.0.1: {} |
| | | |
| | | cropperjs@1.6.2: {} |
| | | |
| | | cross-env@10.1.0: |
| | | dependencies: |
| | | '@epic-web/invariant': 1.0.0 |
| | | cross-spawn: 7.0.6 |
| | | |
| | | cross-spawn@7.0.6: |
| | | dependencies: |
| | |
| | | |
| | | robust-predicates@3.0.3: {} |
| | | |
| | | rolldown-plugin-dts@0.26.0(rolldown@1.1.2)(typescript@6.0.3): |
| | | rolldown-plugin-dts@0.26.0(rolldown@1.1.2)(typescript@6.0.3)(vue-tsc@3.3.9(typescript@6.0.3)): |
| | | dependencies: |
| | | '@babel/generator': 8.0.0 |
| | | '@babel/helper-validator-identifier': 8.0.2 |
| | |
| | | rolldown: 1.1.2 |
| | | optionalDependencies: |
| | | typescript: 6.0.3 |
| | | vue-tsc: 3.3.9(typescript@6.0.3) |
| | | transitivePeerDependencies: |
| | | - oxc-resolver |
| | | |
| | |
| | | minimist: 1.2.8 |
| | | strip-bom: 3.0.0 |
| | | |
| | | tsdown@0.22.3(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)): |
| | | tsdown@0.22.3(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)): |
| | | dependencies: |
| | | ansis: 4.3.1 |
| | | cac: 7.0.0 |
| | |
| | | obug: 2.1.3 |
| | | picomatch: 4.0.4 |
| | | rolldown: 1.1.2 |
| | | rolldown-plugin-dts: 0.26.0(rolldown@1.1.2)(typescript@6.0.3) |
| | | rolldown-plugin-dts: 0.26.0(rolldown@1.1.2)(typescript@6.0.3)(vue-tsc@3.3.9(typescript@6.0.3)) |
| | | semver: 7.8.5 |
| | | tinyexec: 1.2.4 |
| | | tinyglobby: 0.2.17 |
| | |
| | | tippy.js: 6.3.7 |
| | | vue: 3.5.38(typescript@6.0.3) |
| | | |
| | | vue-tsc@3.3.9(typescript@6.0.3): |
| | | dependencies: |
| | | '@volar/typescript': 2.4.28 |
| | | '@vue/language-core': 3.3.9 |
| | | typescript: 6.0.3 |
| | | |
| | | vue-types@3.0.0(vue@3.5.38(typescript@6.0.3)): |
| | | dependencies: |
| | | is-plain-object: 3.0.1 |
| | |
| | | vue-json-pretty: ^2.6.0 |
| | | vue-router: ^5.1.0 |
| | | vue-tippy: ^6.7.1 |
| | | vue-tsc: ^3.3.3 |
| | | vue-tsc: ^3.3.9 |
| | | vue3-print-nb: ^0.1.4 |
| | | vue3-signature: ^0.4.4 |
| | | vuedraggable: ^4.1.0 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace BpmBusinessAttachApi { |
| | | /** ä¸å¡éä»¶é
ç½® */ |
| | | export interface BusinessAttach { |
| | | id: number; |
| | | processDefinitionKey: string; |
| | | businessType?: string; |
| | | componentPath: string; |
| | | status: number; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¸å¡éä»¶é
ç½®å页 */ |
| | | export async function getBusinessAttachPage(params: PageParam) { |
| | | return requestClient.get<PageResult<BpmBusinessAttachApi.BusinessAttach>>( |
| | | '/bpm/business-attach/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¸å¡éä»¶é
置详æ
*/ |
| | | export async function getBusinessAttach(id: number) { |
| | | return requestClient.get<BpmBusinessAttachApi.BusinessAttach>( |
| | | `/bpm/business-attach/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢ä¸å¡éä»¶é
ç½® */ |
| | | export async function createBusinessAttach( |
| | | data: BpmBusinessAttachApi.BusinessAttach, |
| | | ) { |
| | | return requestClient.post<number>('/bpm/business-attach/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹ä¸å¡éä»¶é
ç½® */ |
| | | export async function updateBusinessAttach( |
| | | data: BpmBusinessAttachApi.BusinessAttach, |
| | | ) { |
| | | return requestClient.put<boolean>('/bpm/business-attach/update', data); |
| | | } |
| | | |
| | | /** å é¤ä¸å¡éä»¶é
ç½® */ |
| | | export async function deleteBusinessAttach(id: number) { |
| | | return requestClient.delete<boolean>( |
| | | `/bpm/business-attach/delete?id=${id}`, |
| | | ); |
| | | } |
| | |
| | | invoiceTitle?: string; |
| | | price?: number; |
| | | invoiceTime?: string; |
| | | auditStatus?: number; |
| | | processInstanceId?: string; |
| | | remark?: string; |
| | | attachmentList?: { id: number; name?: string; url?: string }[]; |
| | | blobIds?: number[]; |
| | |
| | | price?: number; |
| | | supplierId?: number; |
| | | purchaseOrderId?: number; |
| | | auditStatus?: number; |
| | | hasAttachment?: boolean; |
| | | remainingInvoicePrice?: number; |
| | | } |
| | |
| | | return requestClient.download('/erp/purchase-invoice/export-excel', { params }); |
| | | } |
| | | |
| | | /** æ´æ°æ¥ç¥¨çç¶æï¼å®¡æ ¸/åå®¡æ ¸ï¼ */ |
| | | export function updatePurchaseInvoiceStatus(id: number, status: number) { |
| | | return requestClient.put('/erp/purchase-invoice/update-status', null, { |
| | | params: { id, status }, |
| | | }); |
| | | } |
| | | |
| | | /** è·å¾å¾
å®¡æ ¸æ¥ç¥¨æ°é */ |
| | | export function getAuditPurchaseInvoiceCount() { |
| | | return requestClient.get<number>('/erp/purchase-invoice/audit-count'); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¥ç¥¨ä¸æåè¡¨ï¼æä¾åºåå¯éæ¥ç¥¨ï¼ä»
å®¡æ ¸éè¿ä¸å·²ä¸ä¼ å票éä»¶ï¼ */ |
| | | /** æ¥è¯¢æ¥ç¥¨ä¸æåè¡¨ï¼æä¾åºåå¯éæ¥ç¥¨ï¼å·²ä¸ä¼ å票éä»¶ä¸å©ä½å¯ä»éé¢å¤§äº 0ï¼ */ |
| | | export function getPurchaseInvoiceSimpleList( |
| | | supplierId: number, |
| | | purchaseOrderId?: number, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesDvMeteringApi { |
| | | /** MES 计éå¨å
· */ |
| | | export interface Metering { |
| | | id?: number; // 主é®ID |
| | | code?: string; // 计éå¨å
·ç¼ç |
| | | name?: string; // 计éå¨å
·åç§° |
| | | specification?: string; // è§æ ¼åå· |
| | | brand?: string; // åç/ç产åå®¶ |
| | | category?: number; // å¨å
·ç±»å«ï¼åå
¸ï¼ |
| | | precision?: string; // 精度 |
| | | range?: string; // éç¨ |
| | | deptId?: number; // 使ç¨é¨é¨ç¼å· |
| | | deptName?: string; // 使ç¨é¨é¨åç§° |
| | | chargeUserId?: number; // 责任人ç¼å· |
| | | chargeUserNickname?: string; // 责任人æµç§° |
| | | checkCycleCount?: number; // æ ¡åå¨æï¼æï¼ |
| | | useDate?: string; // æå
¥ä½¿ç¨æ¥æ |
| | | lastCheckDate?: string; // 䏿¬¡æ£æµæ¥æ |
| | | nextCheckDate?: string; // 䏿¬¡æ£æµæ¥æï¼å°ææ¥ï¼ |
| | | status?: number; // ç¶æï¼åå
¸ï¼ |
| | | expireStatus?: number; // å°æç¶æï¼1 å·²è¿æ / 2 å³å°å°æ / 3 æ£å¸¸ |
| | | expireDays?: number; // è·å°æå¤©æ° |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢è®¡éå¨å
·å页 */ |
| | | export function getMeteringPage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesDvMeteringApi.Metering>>( |
| | | '/mes/dv/metering/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢è®¡éå¨å
·è¯¦æ
*/ |
| | | export function getMetering(id: number) { |
| | | return requestClient.get<MesDvMeteringApi.Metering>( |
| | | `/mes/dv/metering/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢è®¡éå¨å
· */ |
| | | export function createMetering(data: MesDvMeteringApi.Metering) { |
| | | return requestClient.post<number>('/mes/dv/metering/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹è®¡éå¨å
· */ |
| | | export function updateMetering(data: MesDvMeteringApi.Metering) { |
| | | return requestClient.put('/mes/dv/metering/update', data); |
| | | } |
| | | |
| | | /** å é¤è®¡éå¨å
· */ |
| | | export function deleteMetering(id: number) { |
| | | return requestClient.delete(`/mes/dv/metering/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºè®¡éå¨å
· */ |
| | | export function exportMetering(params: any) { |
| | | return requestClient.download('/mes/dv/metering/export-excel', { params }); |
| | | } |
| | | |
| | | /** æ¥è¯¢è®¡éå¨å
·ç²¾ç®å表ï¼ä¸æéé¡¹ï¼ */ |
| | | export function getMeteringSimpleList() { |
| | | return requestClient.get<MesDvMeteringApi.Metering[]>( |
| | | '/mes/dv/metering/simple-list', |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesDvMeteringCheckApi { |
| | | /** MES è®¡éæ£æµè®°å½ */ |
| | | export interface MeteringCheck { |
| | | id?: number; // 主é®ID |
| | | meteringId?: number; // 计éå¨å
·ID |
| | | meteringCode?: string; // 计éå¨å
·ç¼ç |
| | | meteringName?: string; // 计éå¨å
·åç§° |
| | | checkCode?: string; // æ£æµç¼å· |
| | | checkDate?: string; // æ£æµæ¥æ |
| | | checkOrg?: string; // æ£æµæºæ |
| | | checkResult?: number; // æ£æµç»è®ºï¼åå
¸ï¼ |
| | | certNo?: string; // è¯ä¹¦ç¼å· |
| | | checkUserId?: number; // æ£æµäººç¼å· |
| | | checkUserNickname?: string; // æ£æµäººæµç§° |
| | | nextCheckDate?: string; // 䏿¬¡æ£æµæ¥æ |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢è®¡éæ£æµè®°å½å页 */ |
| | | export function getMeteringCheckPage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesDvMeteringCheckApi.MeteringCheck>>( |
| | | '/mes/dv/metering-check/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢è®¡éæ£æµè®°å½è¯¦æ
*/ |
| | | export function getMeteringCheck(id: number) { |
| | | return requestClient.get<MesDvMeteringCheckApi.MeteringCheck>( |
| | | `/mes/dv/metering-check/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢è®¡éæ£æµè®°å½ */ |
| | | export function createMeteringCheck(data: MesDvMeteringCheckApi.MeteringCheck) { |
| | | return requestClient.post<number>('/mes/dv/metering-check/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹è®¡éæ£æµè®°å½ */ |
| | | export function updateMeteringCheck(data: MesDvMeteringCheckApi.MeteringCheck) { |
| | | return requestClient.put('/mes/dv/metering-check/update', data); |
| | | } |
| | | |
| | | /** å é¤è®¡éæ£æµè®°å½ */ |
| | | export function deleteMeteringCheck(id: number) { |
| | | return requestClient.delete(`/mes/dv/metering-check/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºè®¡éæ£æµè®°å½ */ |
| | | export function exportMeteringCheck(params: any) { |
| | | return requestClient.download('/mes/dv/metering-check/export-excel', { |
| | | params, |
| | | }); |
| | | } |
| | |
| | | return requestClient.put('/mes/md/item/update', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹ç©æäº§åç¶æ */ |
| | | export function updateItemStatus(id: number, status: number) { |
| | | return requestClient.put('/mes/md/item/update-status', undefined, { |
| | | params: { id, status }, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤ç©æäº§å */ |
| | | export function deleteItem(id: number) { |
| | | return requestClient.delete(`/mes/md/item/delete?id=${id}`); |
| | |
| | | remark?: string; |
| | | /** å建æ¶é´ */ |
| | | createTime?: number; |
| | | /** BPM æµç¨å®ä¾ç¼å· */ |
| | | processInstanceId?: string; |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | |
| | | }); |
| | | } |
| | | |
| | | /** å®¡æ ¸ï¼å¾
å®¡æ ¸âå¾
彿¡£ï¼ */ |
| | | export function auditPdProject(id: number) { |
| | | return requestClient.put('/mes/pd/project/audit', undefined, { |
| | | params: { id }, |
| | | /** æäº¤å®¡æ¹ï¼å¾
å®¡æ ¸â审æ¹ä¸ï¼ */ |
| | | export function submitPdProjectApproval(id: number, processDefinitionKey: string) { |
| | | return requestClient.put('/mes/pd/project/submit-approval', undefined, { |
| | | params: { id, processDefinitionKey }, |
| | | }); |
| | | } |
| | | |
| | | /** è·å¾å®¡æ¹æµç¨å表 */ |
| | | export function getPdProjectApproveProcessList() { |
| | | return requestClient.get<Array<{ id: string; name: string; key: string }>>( |
| | | '/mes/pd/project/approve-process-list', |
| | | ); |
| | | } |
| | | |
| | | /** 彿¡£ï¼å¾
彿¡£â已彿¡£ï¼ï¼å端èªå¨åå»ºå½æ¡£å°è´¦è®°å½ */ |
| | | export function archivePdProject(id: number) { |
| | | return requestClient.put('/mes/pd/project/archive', undefined, { |
| | |
| | | expireDate?: string[]; // è¿ææ¥æ |
| | | receiptDate?: string[]; // å
¥åºæ¥æ |
| | | } |
| | | |
| | | /** MES æ¹æ¬¡éè´æ¥æºï¼éè´å
¥åºå + IQCï¼ */ |
| | | export interface PurchaseSource { |
| | | receiptCode?: string; // éè´å
¥åºåå· |
| | | receiptDate?: Date; // å
¥åºæ¥æ |
| | | purchaseOrderCode?: string; // éè´è®¢åç¼å· |
| | | vendorId?: number; // ä¾åºå ID |
| | | vendorCode?: string; // ä¾åºåç¼ç |
| | | vendorName?: string; // ä¾åºååç§° |
| | | iqcCode?: string; // IQC æ£éªåå· |
| | | checkResult?: number; // IQC æ£éªç»è®ºï¼åå
¸ mes_qc_check_resultï¼ |
| | | itemId?: number; // ç©æ ID |
| | | itemCode?: string; // ç©æç¼ç |
| | | itemName?: string; // ç©æåç§° |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | receivedQuantity?: number; // å
¥åºæ°é |
| | | } |
| | | |
| | | /** MES æ¹æ¬¡éå®å»åï¼éå®åºåºå + OQCï¼ */ |
| | | export interface SalesTarget { |
| | | salesCode?: string; // éå®åºåºåå· |
| | | salesDate?: Date; // åºåºæ¥æ |
| | | salesOrderCode?: string; // éå®è®¢åç¼å· |
| | | clientId?: number; // å®¢æ· ID |
| | | clientCode?: string; // 客æ·ç¼ç |
| | | clientName?: string; // 客æ·åç§° |
| | | oqcCode?: string; // OQC æ£éªåå· |
| | | checkResult?: number; // OQC æ£éªç»è®ºï¼åå
¸ mes_qc_check_resultï¼ |
| | | itemId?: number; // ç©æ ID |
| | | itemCode?: string; // ç©æç¼ç |
| | | itemName?: string; // ç©æåç§° |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | quantity?: number; // åºåºæ°é |
| | | qualityStatus?: number; // è´¨éç¶æï¼åå
¸ mes_wm_quality_statusï¼ |
| | | } |
| | | |
| | | /** MES æ¹æ¬¡å·¥åºæµè½¬ï¼äº§åº/æå
¥äºä»¶ï¼ */ |
| | | export interface ProcessTrace { |
| | | eventType?: 'OUT' | 'IN'; // äºä»¶ç±»åï¼OUT-äº§åº / IN-æå
¥ |
| | | workOrderId?: number; // ç产工å ID |
| | | workOrderCode?: string; // ç产工åç¼å· |
| | | processId?: number; // å·¥åº ID |
| | | processCode?: string; // å·¥åºç¼ç |
| | | processName?: string; // å·¥åºåç§° |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | quantity?: number; // æ°é |
| | | qualityStatus?: number; // è´¨éç¶æï¼åå
¸ mes_wm_quality_statusï¼ï¼ä»
产åºä¾§æå¼ |
| | | eventTime?: Date; // äºä»¶æ¶é´ |
| | | processSort?: number; // å·¥åºåºå· |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¹æ¬¡è¯¦æ
*/ |
| | |
| | | { params: { code } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¹æ¬¡éè´æ¥æºï¼éè´å
¥åºå + IQCï¼ */ |
| | | export function getPurchaseSourceList(code: string) { |
| | | return requestClient.get<MesWmBatchApi.PurchaseSource[]>( |
| | | '/mes/wm/batch/purchase-sources', |
| | | { params: { code } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¹æ¬¡éå®å»åï¼éå®åºåºå + OQCï¼ */ |
| | | export function getSalesTargetList(code: string) { |
| | | return requestClient.get<MesWmBatchApi.SalesTarget[]>( |
| | | '/mes/wm/batch/sales-targets', |
| | | { params: { code } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¹æ¬¡å·¥åºæµè½¬ï¼äº§åº/æå
¥äºä»¶ï¼ */ |
| | | export function getProcessTraceList(code: string) { |
| | | return requestClient.get<MesWmBatchApi.ProcessTrace[]>( |
| | | '/mes/wm/batch/process-trace', |
| | | { params: { code } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ® SN ç åæ¥æ¹æ¬¡ */ |
| | | export function getBatchBySn(sn: string) { |
| | | return requestClient.get<MesWmBatchApi.Batch | null>('/mes/wm/batch/get-by-sn', { |
| | | params: { sn }, |
| | | }); |
| | | } |
| | |
| | | return requestClient.get<SrmSupplierApi.SupplierVO[]>('/srm/supplier/simple-list'); |
| | | } |
| | | |
| | | /** è·åä¾åºåæ¡£æ¡å表ï¼ä¸è¿æ»¤åå
¥ç¶æï¼ */ |
| | | export function getSupplierList() { |
| | | return requestClient.get<SrmSupplierApi.SupplierVO[]>('/srm/supplier/list'); |
| | | } |
| | | |
| | | /** è·åä¾åºå详æ
*/ |
| | | export function getSupplier(id: number) { |
| | | return requestClient.get<SrmSupplierApi.SupplierVO>(`/srm/supplier/get?id=${id}`); |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import type { SystemStorageApi } from '#/api/system/storage'; |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmSupplierCertificateApi { |
| | |
| | | status?: number; |
| | | remark?: string; |
| | | createTime?: string; |
| | | blobIds?: number[]; |
| | | attachmentList?: SystemStorageApi.StorageBlob[]; |
| | | } |
| | | } |
| | | |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import type { SystemStorageApi } from '#/api/system/storage'; |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmTenderApi { |
| | |
| | | purchaseOrderNo?: string; |
| | | biddingMode?: string; |
| | | createTime?: string; |
| | | blobIds?: number[]; |
| | | attachmentList?: SystemStorageApi.StorageBlob[]; |
| | | } |
| | | |
| | | export interface TenderMaterialVO { |
| | |
| | | id: number; |
| | | tenderDesc?: string; |
| | | qualificationRequirements?: string; |
| | | blobIds?: number[]; |
| | | } |
| | | } |
| | | |
| | |
| | | if (preferences.app.dynamicTitle) { |
| | | const routeTitle = router.currentRoute.value.meta?.title; |
| | | const pageTitle = |
| | | (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; |
| | | (routeTitle ? `${$t(routeTitle)} - ` : '') + |
| | | preferences.copyright.companyName; |
| | | useTitle(pageTitle); |
| | | } |
| | | }); |
| | |
| | | } |
| | | |
| | | /** è§£ç 表å Conf */ |
| | | export function decodeConf(conf: string) { |
| | | export function decodeConf(conf?: string | null) { |
| | | if (!conf) { |
| | | return {}; |
| | | } |
| | | return formCreate.parseJson(conf); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** è§£ç 表å Fields */ |
| | | export function decodeFields(fields: string[]) { |
| | | export function decodeFields(fields?: string[] | null) { |
| | | const rule: Rule[] = []; |
| | | fields.forEach((item) => { |
| | | (fields || []).forEach((item) => { |
| | | rule.push(formCreate.parseJson(item)); |
| | | }); |
| | | return rule; |
| | |
| | | |
| | | declare const __APP_LOGO__: string; |
| | | |
| | | const appName = computed(() => preferences.app.name); |
| | | const appName = computed(() => preferences.copyright.companyName); |
| | | const logo = computed(() => __APP_LOGO__ || preferences.logo.source); |
| | | const logoDark = computed(() => preferences.logo.sourceDark); |
| | | </script> |
| | |
| | | |
| | | declare const __APP_LOGO__: string; |
| | | declare const __APP_FAVICON__: string; |
| | | declare const __APP_NAME__: string; |
| | | |
| | | /** |
| | | * åºç¨åå§å宿ä¹ååè¿è¡é¡µé¢å 载渲æ |
| | |
| | | if (__APP_LOGO__) { |
| | | preferencesManager.updatePreferences({ logo: { source: __APP_LOGO__ } }); |
| | | } |
| | | // 强å¶è¦çå
¬å¸åç§°ï¼ç»è¿ localStorage ç¼å |
| | | if (__APP_NAME__) { |
| | | preferencesManager.updatePreferences({ |
| | | app: { name: __APP_NAME__ }, |
| | | copyright: { companyName: __APP_NAME__ }, |
| | | }); |
| | | } |
| | | if (__APP_FAVICON__) { |
| | | const link = document.querySelector('link[rel="icon"]'); |
| | | if (link) { |
| | |
| | | ABNORMAL: 2, |
| | | } as const; |
| | | |
| | | /** MES 计éå¨å
·ç¶ææä¸¾ */ |
| | | export const MesDvMeteringStatusEnum = { |
| | | IN_USE: 1, // å¨ç¨ |
| | | STOPPED: 2, // åç¨ |
| | | SCRAPPED: 3, // æ¥åº |
| | | } as const; |
| | | |
| | | /** MES è®¡éæ£æµç»è®ºæä¸¾ */ |
| | | export const MesDvMeteringCheckResultEnum = { |
| | | QUALIFIED: 1, // åæ ¼ |
| | | UNQUALIFIED: 2, // ä¸åæ ¼ |
| | | } as const; |
| | | |
| | | /** MES 计éå¨å
·å°æç¶ææä¸¾ */ |
| | | export const MesDvMeteringExpireStatusEnum = { |
| | | OVERDUE: 1, // å·²è¿æ |
| | | WITHIN: 2, // å³å°å°æ |
| | | NORMAL: 3, // æ£å¸¸ |
| | | } as const; |
| | | |
| | | /** MES 维修工åç¶ææä¸¾ */ |
| | | export const MesDvRepairStatusEnum = { |
| | | PREPARE: MesOrderStatusConstants.DRAFT, |
| | |
| | | RIGHT: 2, // å³è¡¥é½ |
| | | } as const; |
| | | |
| | | /** MES 产å设计项ç®ç¶ææä¸¾ï¼çº¿æ§æµè½¬ï¼å¾
åå¸âå¾
ä¸ä¼ âå¾
å®¡æ ¸âå¾
彿¡£â已彿¡£ï¼ */ |
| | | /** MES 产å设计项ç®ç¶ææä¸¾ï¼çº¿æ§æµè½¬ï¼å¾
åå¸âå¾
ä¸ä¼ âå¾
å®¡æ ¸â审æ¹ä¸âå¾
彿¡£â已彿¡£ï¼ */ |
| | | export const MesPdProjectStatusEnum = { |
| | | PENDING_PUBLISH: 0, // å¾
åå¸ |
| | | PENDING_UPLOAD: 1, // å¾
ä¸ä¼ |
| | | PENDING_AUDIT: 2, // å¾
å®¡æ ¸ |
| | | APPROVING: 5, // 审æ¹ä¸ |
| | | PENDING_ARCHIVE: 3, // å¾
彿¡£ |
| | | ARCHIVED: 4, // 已彿¡£ |
| | | } as const; |
| | |
| | | MES_DV_REPAIR_RESULT: 'mes_dv_repair_result', // MES ç»´ä¿®ç»æ |
| | | MES_DV_CHECK_RECORD_STATUS: 'mes_dv_check_record_status', // MES ç¹æ£è®°å½ç¶æ |
| | | MES_DV_CHECK_RESULT: 'mes_dv_check_result', // MES ç¹æ£ç»æ |
| | | MES_DV_METERING_CATEGORY: 'mes_dv_metering_category', // MES 计éå¨å
·ç±»å« |
| | | MES_DV_METERING_STATUS: 'mes_dv_metering_status', // MES 计éå¨å
·ç¶æ |
| | | MES_DV_METERING_CHECK_RESULT: 'mes_dv_metering_check_result', // MES è®¡éæ£æµç»è®º |
| | | MES_PRO_LINK_TYPE: 'mes_pro_link_type', // MES å·¥åºå
³ç³»ç±»å |
| | | MES_PRO_WORK_ORDER_STATUS: 'mes_pro_work_order_status', // MES ç产工åç¶æ |
| | | ORDER_PRODUCTION_STATUS: 'order_production_status', // ç产订åç¶æ |
| | |
| | | :collapsed="logoCollapsed" |
| | | :src="preferences.logo.source" |
| | | :src-dark="preferences.logo.sourceDark" |
| | | :text="preferences.app.name" |
| | | :text="preferences.copyright.companyName" |
| | | :theme="logoTheme" |
| | | @click="clickLogo" |
| | | > |
| | |
| | | <VbenLogo |
| | | v-if="preferences.logo.enable" |
| | | :fit="preferences.logo.fit" |
| | | :text="preferences.app.name" |
| | | :text="preferences.copyright.companyName" |
| | | :theme="sidebarThemeSub" |
| | | > |
| | | <template v-if="$slots['logo-text']" #text> |
| | |
| | | } from './packages/preferences/src'; |
| | | |
| | | declare const __APP_LOGO__: string; |
| | | declare const __APP_NAME__: string; |
| | | |
| | | interface WebAntdPreferencesExtension { |
| | | defaultTableSize: number; |
| | |
| | | app: { |
| | | /** åç«¯è·¯ç±æ¨¡å¼ */ |
| | | accessMode: 'backend', |
| | | name: import.meta.env.VITE_APP_TITLE, |
| | | name: __APP_NAME__ || import.meta.env.VITE_APP_TITLE, |
| | | enableRefreshToken: true, |
| | | }, |
| | | footer: { |
| | |
| | | fixed: false, |
| | | }, |
| | | copyright: { |
| | | companyName: import.meta.env.VITE_APP_TITLE, |
| | | companyName: __APP_NAME__ || import.meta.env.VITE_APP_TITLE, |
| | | companySiteLink: '#', |
| | | }, |
| | | logo: { |
| | |
| | | declare const __APP_LOGO__: string; |
| | | declare const __APP_FAVICON__: string; |
| | | declare const __APP_NAME__: string; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'processDefinitionKey', |
| | | label: 'æµç¨å®ä¹ Key', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æµç¨å®ä¹ Keyï¼å¦ purchase_approve', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'businessType', |
| | | label: 'ä¸å¡ç±»å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¸å¡ç±»åï¼å¦ erp_purchase_order', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'componentPath', |
| | | label: 'ä¸å¡è¯¦æ
ç»ä»¶è·¯å¾', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: |
| | | '请è¾å
¥ä¸å¡è¯¦æ
ç»ä»¶è·¯å¾ï¼å¦ /erp/purchase/order/detail/index.vue', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | rules: z.number().default(CommonStatusEnum.ENABLE), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'processDefinitionKey', |
| | | label: 'æµç¨å®ä¹ Key', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æµç¨å®ä¹ Key', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'businessType', |
| | | label: 'ä¸å¡ç±»å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¸å¡ç±»å', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | | field: 'id', |
| | | title: 'ç¼å·', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'processDefinitionKey', |
| | | title: 'æµç¨å®ä¹ Key', |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | | field: 'businessType', |
| | | title: 'ä¸å¡ç±»å', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'componentPath', |
| | | title: 'ä¸å¡è¯¦æ
ç»ä»¶è·¯å¾', |
| | | minWidth: 260, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 80, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 160, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { BpmBusinessAttachApi } from '#/api/bpm/businessAttach'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteBusinessAttach, |
| | | getBusinessAttachPage, |
| | | } from '#/api/bpm/businessAttach'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { 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(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾ä¸å¡éä»¶é
ç½® */ |
| | | function handleEdit(row: BpmBusinessAttachApi.BusinessAttach) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å é¤ä¸å¡éä»¶é
ç½® */ |
| | | async function handleDelete(row: BpmBusinessAttachApi.BusinessAttach) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.processDefinitionKey]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteBusinessAttach(row.id as number); |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getBusinessAttachPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<BpmBusinessAttachApi.BusinessAttach>, |
| | | }); |
| | | </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: ['bpm:business-attach:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['bpm:business-attach:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['bpm:business-attach:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [ |
| | | row.processDefinitionKey, |
| | | ]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { BpmBusinessAttachApi } from '#/api/bpm/businessAttach'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createBusinessAttach, |
| | | getBusinessAttach, |
| | | updateBusinessAttach, |
| | | } from '#/api/bpm/businessAttach'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<BpmBusinessAttachApi.BusinessAttach>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['ä¸å¡éä»¶é
ç½®']) |
| | | : $t('ui.actionTitle.create', ['ä¸å¡éä»¶é
ç½®']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 140, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = (await formApi.getValues()) as BpmBusinessAttachApi.BusinessAttach; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateBusinessAttach(data) |
| | | : createBusinessAttach(data)); |
| | | // å
³éå¹¶æç¤º |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<BpmBusinessAttachApi.BusinessAttach>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getBusinessAttach(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-2/3"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | <script lang="ts" setup> |
| | | import type { ModelCategoryInfo } from '#/api/bpm/model'; |
| | | |
| | | import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue'; |
| | | import { onActivated, onMounted, reactive, ref, useTemplateRef, watch } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | |
| | | } |
| | | |
| | | /** åå§å */ |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | /** æ¿æ´»æ¶å·æ°ï¼keep-alive ç¼ååºæ¯ä¸ä»ç¼è¾é¡µè¿åæ¶è§¦åï¼ */ |
| | | onActivated(() => { |
| | | getList(); |
| | | }); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { CrmContractApi } from '#/api/crm/contract'; |
| | | import type { DescriptionItemSchema } from '#/components/description'; |
| | | |
| | | import { h, nextTick, onMounted, ref } from 'vue'; |
| | | |
| | | import { ContentWrap } from '@vben/common-ui'; |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { |
| | | erpCountInputFormatter, |
| | | erpPriceInputFormatter, |
| | | formatDateTime, |
| | | } from '@vben/utils'; |
| | | |
| | | import { Spin } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getContract } from '#/api/crm/contract'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'CrmContractApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<CrmContractApi.Contract>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | /** åå产åæç»ï¼å®¡æ¹è¯¦æ
åªè¯»åï¼ */ |
| | | function useProductColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { type: 'seq', title: 'åºå·', width: 60 }, |
| | | { field: 'itemName', title: '产ååç§°', minWidth: 180 }, |
| | | { field: 'itemCode', title: 'ç©æç¼ç ', minWidth: 120 }, |
| | | { field: 'itemUnitName', title: 'åä½', minWidth: 80, formatter: ({ cellValue }) => textRender(cellValue) }, |
| | | { |
| | | field: 'contractPrice', |
| | | title: 'ååä»·æ ¼ï¼å
ï¼', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'count', |
| | | title: 'æ°é', |
| | | minWidth: 100, |
| | | formatter: ({ cellValue }) => erpCountInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'å计éé¢ï¼å
ï¼', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useProductColumns(), |
| | | data: [], |
| | | minHeight: 200, |
| | | autoResize: true, |
| | | border: true, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'ååç¼å·', field: 'no', render: textRender }, |
| | | { label: 'åååç§°', field: 'name', render: textRender }, |
| | | { label: '客æ·åç§°', field: 'customerName', render: textRender }, |
| | | { label: 'åæºåç§°', field: 'businessName', render: textRender }, |
| | | { |
| | | label: 'ååéé¢ï¼å
ï¼', |
| | | field: 'totalPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å®ééé¢ï¼å
ï¼', |
| | | field: 'depositPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: '忬¾éé¢ï¼å
ï¼', |
| | | field: 'totalReceivablePrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å·²å¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'totalInvoicePrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'æªå¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'unInvoicePrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'ä¸åæ¶é´', |
| | | field: 'orderDate', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: 'ååå¼å§æ¶é´', |
| | | field: 'startTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: 'ååç»ææ¶é´', |
| | | field: 'endTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '客æ·ç¾çº¦äºº', field: 'signContactName', render: textRender }, |
| | | { label: 'å
¬å¸ç¾çº¦äºº', field: 'signUserName', render: textRender }, |
| | | { label: 'éå®è®¢å', field: 'orderNo', render: textRender }, |
| | | { label: 'å建人', field: 'creatorName', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: 'ååç¶æ', |
| | | field: 'auditStatus', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.CRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getContract(Number(props.id)); |
| | | if (formData.value?.products?.length) { |
| | | await nextTick(); |
| | | await gridApi.grid.reloadData(formData.value.products); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- åååºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | <!-- 产åæç» --> |
| | | <div |
| | | v-if="formData?.products?.length" |
| | | 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> |
| | | <Grid /> |
| | | </div> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmInvoiceApi } from '#/api/crm/invoice'; |
| | | 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 { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { Spin } from 'ant-design-vue'; |
| | | |
| | | import { getInvoice } from '#/api/crm/invoice'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'CrmInvoiceApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<CrmInvoiceApi.Invoice>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'å¼ç¥¨ç¼å·', field: 'no', render: textRender }, |
| | | { label: '客æ·åç§°', field: 'customerName', render: textRender }, |
| | | { |
| | | label: 'ååç¼å·', |
| | | field: 'contract', |
| | | render: (_val, data) => (data?.contract?.no ? data.contract.no : '-'), |
| | | }, |
| | | { |
| | | label: 'å票类å', |
| | | field: 'invoiceType', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.CRM_INVOICE_TYPE, value: val }), |
| | | }, |
| | | { label: 'å票å·ç ', field: 'invoiceNo', render: textRender }, |
| | | { label: 'å票æ¬å¤´', field: 'invoiceTitle', render: textRender }, |
| | | { |
| | | label: 'å¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'price', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å©ä½å¯å款éé¢ï¼å
ï¼', |
| | | field: 'remainingInvoicePrice', |
| | | render: (val) => erpPriceInputFormatter(val ?? 0) || '-', |
| | | }, |
| | | { |
| | | label: 'å¼ç¥¨æ¥æ', |
| | | field: 'invoiceTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'auditStatus', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.CRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: 'è´è´£äºº', field: 'ownerUserName', render: textRender }, |
| | | { label: 'å建人', field: 'creatorName', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getInvoice(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- å¼ç¥¨åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| | |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: '忬¾ç¶æ', |
| | | field: 'auditStatus', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | field: 'actions', |
| | | width: 130, |
| | |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: '忬¾ç¶æ', |
| | | field: 'auditStatus', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | field: 'actions', |
| | | minWidth: 200, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmReceivableApi } from '#/api/crm/receivable'; |
| | | 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 { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { Spin } from 'ant-design-vue'; |
| | | |
| | | import { getReceivable } from '#/api/crm/receivable'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'CrmReceivableApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<CrmReceivableApi.Receivable>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | /** å
³èå¼ç¥¨å±ç¤º */ |
| | | function invoiceRender(_val: unknown, data?: CrmReceivableApi.Receivable) { |
| | | if (!data?.invoice?.no) { |
| | | return '-'; |
| | | } |
| | | return `${data.invoice.no} / ${data.invoice.invoiceNo ?? '-'} / ¥${ |
| | | data.invoice.price ?? 0 |
| | | }`; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: '忬¾ç¼å·', field: 'no', render: textRender }, |
| | | { label: '客æ·åç§°', field: 'customerName', render: textRender }, |
| | | { |
| | | label: 'ååç¼å·', |
| | | field: 'contract', |
| | | render: (_val, data) => (data?.contract?.no ? data.contract.no : '-'), |
| | | }, |
| | | { label: 'å
³èå¼ç¥¨', field: 'invoice', render: invoiceRender }, |
| | | { |
| | | label: '忬¾æ¥æ', |
| | | field: 'returnTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: '忬¾éé¢ï¼å
ï¼', |
| | | field: 'price', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: '忬¾æ¹å¼', |
| | | field: 'returnType', |
| | | render: (val) => |
| | | h(DictTag, { |
| | | type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, |
| | | value: val, |
| | | }), |
| | | }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'auditStatus', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.CRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: 'è´è´£äºº', field: 'ownerUserName', render: textRender }, |
| | | { label: 'å建人', field: 'creatorName', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getReceivable(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- 忬¾åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { ErpFinancePaymentApi } from '#/api/erp/finance/payment'; |
| | | import type { DescriptionItemSchema } from '#/components/description'; |
| | | |
| | | import { h, nextTick, onMounted, ref } from 'vue'; |
| | | |
| | | import { ContentWrap } from '@vben/common-ui'; |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { Spin } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getFinancePayment } from '#/api/erp/finance/payment'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'ErpFinancePaymentApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<ErpFinancePaymentApi.FinancePayment>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | /** 仿¬¾æç»ï¼å®¡æ¹è¯¦æ
åªè¯»åï¼ */ |
| | | function usePaymentItemColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { type: 'seq', title: 'åºå·', width: 60 }, |
| | | { field: 'bizNo', title: 'æ¥ç¥¨ç¼å·', minWidth: 200 }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'åºä»éé¢', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'paidPrice', |
| | | title: 'å·²ä»éé¢', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'paymentPrice', |
| | | title: 'æ¬æ¬¡ä»æ¬¾', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 150, |
| | | formatter: ({ cellValue }) => textRender(cellValue), |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: usePaymentItemColumns(), |
| | | data: [], |
| | | minHeight: 200, |
| | | autoResize: true, |
| | | border: true, |
| | | rowConfig: { |
| | | keyField: 'bizId', |
| | | isHover: true, |
| | | }, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: '仿¬¾åå·', field: 'no', render: textRender }, |
| | | { label: 'ä¾åºååç§°', field: 'supplierName', render: textRender }, |
| | | { |
| | | label: 'å
³èæ¥ç¥¨', |
| | | field: 'invoice', |
| | | render: (_val, data) => (data?.invoice?.no ? data.invoice.no : '-'), |
| | | }, |
| | | { |
| | | label: '仿¬¾æ¶é´', |
| | | field: 'paymentTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: 'å计éé¢ï¼å
ï¼', |
| | | field: 'totalPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: '伿 éé¢ï¼å
ï¼', |
| | | field: 'discountPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å®é
仿¬¾éé¢ï¼å
ï¼', |
| | | field: 'paymentPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { label: '仿¬¾è´¦æ·', field: 'accountName', render: textRender }, |
| | | { label: 'è´¢å¡äººå', field: 'financeUserName', render: textRender }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.ERP_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: 'å建人', field: 'creatorName', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getFinancePayment(Number(props.id)); |
| | | if (formData.value?.items?.length) { |
| | | await nextTick(); |
| | | await gridApi.grid.reloadData(formData.value.items); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- 仿¬¾ååºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | <!-- 仿¬¾æç» --> |
| | | <div |
| | | v-if="formData?.items?.length" |
| | | 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> |
| | | <Grid /> |
| | | </div> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| | |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'auditStatus', |
| | | label: 'å®¡æ ¸ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'æªå®¡æ ¸', value: 10 }, |
| | | { label: 'å·²å®¡æ ¸', value: 20 }, |
| | | ], |
| | | placeholder: 'è¯·éæ©å®¡æ ¸ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | slots: { default: 'attachment' }, |
| | | }, |
| | | { |
| | | title: '审æ¹ç¶æ', |
| | | field: 'auditStatus', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'auditStatus' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | field: 'actions', |
| | | minWidth: 280, |
| | | minWidth: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseInvoiceApi } from '#/api/erp/purchase/invoice'; |
| | | import type { DescriptionItemSchema } from '#/components/description'; |
| | | |
| | | import { onMounted, ref } from 'vue'; |
| | | |
| | | import { ContentWrap } from '@vben/common-ui'; |
| | | import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { Spin } from 'ant-design-vue'; |
| | | |
| | | import { getPurchaseInvoice } from '#/api/erp/purchase/invoice'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'ErpPurchaseInvoiceApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<ErpPurchaseInvoiceApi.PurchaseInvoice>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'æ¥ç¥¨ç¼å·', field: 'no', render: textRender }, |
| | | { label: 'ä¾åºååç§°', field: 'supplierName', render: textRender }, |
| | | { |
| | | label: 'éè´è®¢åå·', |
| | | field: 'order', |
| | | render: (_val, data) => (data?.order?.no ? data.order.no : '-'), |
| | | }, |
| | | { label: 'å票å·ç ', field: 'invoiceNo', render: textRender }, |
| | | { label: 'å票æ¬å¤´', field: 'invoiceTitle', render: textRender }, |
| | | { |
| | | label: 'æ¥ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'price', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å©ä½å¯ä»éé¢ï¼å
ï¼', |
| | | field: 'remainingInvoicePrice', |
| | | render: (val) => erpPriceInputFormatter(val ?? 0) || '-', |
| | | }, |
| | | { |
| | | label: 'å¼ç¥¨æ¥æ', |
| | | field: 'invoiceTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: 'å建人', field: 'creatorName', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getPurchaseInvoice(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- æ¥ç¥¨åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| | |
| | | deletePurchaseInvoice, |
| | | exportPurchaseInvoice, |
| | | getPurchaseInvoicePage, |
| | | updatePurchaseInvoiceStatus, |
| | | } from '#/api/erp/purchase/invoice'; |
| | | import { $t } from '#/locales'; |
| | | |
| | |
| | | try { |
| | | await deletePurchaseInvoice(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.no])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ´æ°æ¥ç¥¨ç¶æï¼å®¡æ ¸/åå®¡æ ¸ï¼ */ |
| | | async function handleUpdateStatus( |
| | | row: ErpPurchaseInvoiceApi.PurchaseInvoice, |
| | | status: number, |
| | | ) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æ´æ°ç¶æä¸...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await updatePurchaseInvoiceStatus(row.id!, status); |
| | | message.success('æ´æ°ç¶ææå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | |
| | | <Tag v-if="!row.hasAttachment" color="warning">æªä¸ä¼ </Tag> |
| | | <Tag v-else color="success">å·²ä¸ä¼ </Tag> |
| | | </template> |
| | | <template #auditStatus="{ row }"> |
| | | <Tag v-if="row.auditStatus === 10" color="warning">æªå®¡æ ¸</Tag> |
| | | <Tag v-else-if="row.auditStatus === 20" color="success">å·²å®¡æ ¸</Tag> |
| | | <Tag v-else color="default">--</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['erp:purchase-invoice:update'], |
| | | ifShow: row.auditStatus === 10, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'å®¡æ ¸', |
| | | type: 'link', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['erp:purchase-invoice:update-status'], |
| | | ifShow: row.auditStatus === 10, |
| | | onClick: handleUpdateStatus.bind(null, row, 20), |
| | | }, |
| | | { |
| | | label: 'åå®¡æ ¸', |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | auth: ['erp:purchase-invoice:update-status'], |
| | | ifShow: row.auditStatus === 20, |
| | | onClick: handleUpdateStatus.bind(null, row, 10), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['erp:purchase-invoice:delete'], |
| | | ifShow: row.auditStatus !== 20, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.no]), |
| | | confirm: handleDelete.bind(null, row), |
| | |
| | | try { |
| | | formData.value = await getPurchaseInvoice(data.id); |
| | | if (formData.value?.attachmentList?.length) { |
| | | // ç¨ attachmentList ä»£æ¿ blobIdsï¼è®©ä¸ä¼ ç»ä»¶è½å±ç¤ºæä»¶ååé¢è§é¾æ¥ |
| | | // 转为ä¸ä¼ ç»ä»¶çæä»¶å¯¹è±¡ï¼æäº¤æ¶ formApi 伿 valueKey æåéä»¶ ID |
| | | (formData.value as Record<string, unknown>).blobIds = |
| | | formData.value.attachmentList; |
| | | formData.value.attachmentList.map((item) => ({ |
| | | uid: String(item.id), |
| | | name: item.name || '', |
| | | url: item.url || '', |
| | | status: 'done', |
| | | id: item.id, |
| | | })); |
| | | } |
| | | await formApi.setValues(formData.value as any); |
| | | } finally { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; |
| | | 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 { |
| | | erpCountInputFormatter, |
| | | erpPriceInputFormatter, |
| | | formatDateTime, |
| | | } from '@vben/utils'; |
| | | |
| | | import { Spin, Tag } from 'ant-design-vue'; |
| | | |
| | | import { getPurchaseOrder } from '#/api/erp/purchase/order'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | import ItemForm from '../modules/item-form.vue'; |
| | | |
| | | defineOptions({ name: 'ErpPurchaseOrderDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<ErpPurchaseOrderApi.PurchaseOrder>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | /** å
¥åºç¶ææ¸²æ */ |
| | | function renderInStatus(val?: number) { |
| | | const map: Record<number, { text: string; color: string }> = { |
| | | 0: { text: 'æªå
¥åº', color: 'default' }, |
| | | 1: { text: 'é¨åå
¥åº', color: 'warning' }, |
| | | 2: { text: 'å
¨é¨å
¥åº', color: 'success' }, |
| | | }; |
| | | const item = map[val ?? 0]!; |
| | | return h(Tag, { color: item.color }, () => item.text); |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'éè´è®¢åå·', field: 'no' }, |
| | | { label: 'ä¾åºååç§°', field: 'supplierName', render: textRender }, |
| | | { |
| | | label: 'è®¢åæ¶é´', |
| | | field: 'orderTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: 'å计æ°é', |
| | | field: 'totalCount', |
| | | render: (val) => erpCountInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å计éé¢', |
| | | field: 'totalPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: '伿 éé¢', |
| | | field: 'discountPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å®ééé¢', |
| | | field: 'depositPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.ERP_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { |
| | | label: 'å
¥åºç¶æ', |
| | | field: 'inStatus', |
| | | render: (val) => renderInStatus(val as number), |
| | | }, |
| | | { label: 'å建人', field: 'creatorName', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getPurchaseOrder(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | <!-- éè´æç» --> |
| | | <div |
| | | v-if="formData?.items?.length" |
| | | 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> |
| | | <ItemForm :items="formData.items" :disabled="true" /> |
| | | </div> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request'; |
| | | import type { DescriptionItemSchema } from '#/components/description'; |
| | | |
| | | import { h, nextTick, onMounted, ref } from 'vue'; |
| | | |
| | | import { ContentWrap } from '@vben/common-ui'; |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { |
| | | erpCountInputFormatter, |
| | | erpPriceInputFormatter, |
| | | formatDateTime, |
| | | } from '@vben/utils'; |
| | | |
| | | import { Spin } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getPurchaseRequest } from '#/api/erp/purchase/request'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'ErpPurchaseRequestDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<ErpPurchaseRequestApi.PurchaseRequest>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | /** éè´ç³è¯·æç»ï¼å®¡æ¹è¯¦æ
åªè¯»åï¼ */ |
| | | function useDetailItemColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { type: 'seq', title: 'åºå·', width: 60 }, |
| | | { field: 'productName', title: '产ååç§°', minWidth: 180 }, |
| | | { field: 'productUnitName', title: 'åä½', minWidth: 80 }, |
| | | { |
| | | field: 'count', |
| | | title: 'æ°é', |
| | | minWidth: 100, |
| | | formatter: ({ cellValue }) => erpCountInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: 'åä»·ï¼åèä»·ï¼', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'é¢ä¼°éé¢', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'taxPercent', |
| | | title: 'ç¨ç(%)', |
| | | minWidth: 90, |
| | | formatter: ({ cellValue }) => (cellValue ? `${cellValue}%` : '-'), |
| | | }, |
| | | { field: 'demandTime', title: 'éæ±æ¥æ', minWidth: 110 }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 160, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'qcCheckFlag', |
| | | title: 'æ¯å¦è´¨æ£', |
| | | minWidth: 80, |
| | | formatter: ({ cellValue }) => (cellValue ? 'æ¯' : 'å¦'), |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useDetailItemColumns(), |
| | | data: [], |
| | | minHeight: 200, |
| | | autoResize: true, |
| | | border: true, |
| | | rowConfig: { |
| | | keyField: 'seq', |
| | | isHover: true, |
| | | }, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'ç³è¯·ç¼å·', field: 'no' }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.ERP_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: 'ç³è¯·äºº', field: 'requestUserName', render: textRender }, |
| | | { label: 'ç³è¯·é¨é¨', field: 'requestDeptName', render: textRender }, |
| | | { |
| | | label: 'ç³è¯·æ¶é´', |
| | | field: 'requestTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: 'ä¾åºååç§°', field: 'supplierName', render: textRender }, |
| | | { |
| | | label: 'å计æ°é', |
| | | field: 'totalCount', |
| | | render: (val) => erpCountInputFormatter(val) || '-', |
| | | }, |
| | | { |
| | | label: 'å计éé¢', |
| | | field: 'totalPrice', |
| | | render: (val) => erpPriceInputFormatter(val) || '-', |
| | | }, |
| | | { label: 'ç³è¯·çç±', field: 'requestReason', render: textRender }, |
| | | { label: 'çæéè´è®¢å', field: 'orderNo', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getPurchaseRequest(Number(props.id)); |
| | | if (formData.value?.items?.length) { |
| | | await nextTick(); |
| | | await gridApi.grid.reloadData(formData.value.items); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | <!-- ç³è¯·æç» --> |
| | | <div |
| | | v-if="formData?.items?.length" |
| | | 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> |
| | | <Grid /> |
| | | </div> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { HrmAttendanceExceptionApi } from '#/api/hrm/attendance/exception'; |
| | | 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 } from 'ant-design-vue'; |
| | | |
| | | import { getAttendanceException } from '#/api/hrm/attendance/exception'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'HrmAttendanceExceptionApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<HrmAttendanceExceptionApi.AttendanceException>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'åå·¥å§å', field: 'userName', render: textRender }, |
| | | { label: 'æå±é¨é¨', field: 'deptName', render: textRender }, |
| | | { |
| | | label: 'å¼å¸¸æ¥æ', |
| | | field: 'date', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: 'å¼å¸¸ç±»å', field: 'exceptionTypeName', render: textRender }, |
| | | { label: 'å¼å¸¸è¯´æ', field: 'reason', render: textRender }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getAttendanceException(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- èå¤å¼å¸¸åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { HrmLeaveApi } from '#/api/hrm/leave'; |
| | | 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 } from 'ant-design-vue'; |
| | | |
| | | import { getLeave } from '#/api/hrm/leave'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'HrmLeaveApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<HrmLeaveApi.Leave>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'åå·¥å§å', field: 'employeeName', render: textRender }, |
| | | { label: 'æå±é¨é¨', field: 'deptName', render: textRender }, |
| | | { |
| | | label: '请åç±»å', |
| | | field: 'leaveType', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_LEAVE_TYPE, value: val }), |
| | | }, |
| | | { |
| | | label: 'å¼å§æ¶é´', |
| | | field: 'startTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | label: 'ç»ææ¶é´', |
| | | field: 'endTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: 'è¯·åæ¶é¿', field: 'duration', render: textRender }, |
| | | { label: '请åäºç±', field: 'reason', render: textRender }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getLeave(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- 请ååºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { HrmResignationApi } from '#/api/hrm/resignation'; |
| | | 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 } from 'ant-design-vue'; |
| | | |
| | | import { getResignation } from '#/api/hrm/resignation'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'HrmResignationApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<HrmResignationApi.Resignation>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'åå·¥å§å', field: 'employeeName', render: textRender }, |
| | | { label: 'æå±é¨é¨', field: 'deptName', render: textRender }, |
| | | { label: 'èä½', field: 'postName', render: textRender }, |
| | | { |
| | | label: '离èç±»å', |
| | | field: 'resignationType', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_RESIGNATION_TYPE, value: val }), |
| | | }, |
| | | { |
| | | label: 'é¢è®¡ç¦»èæ¥æ', |
| | | field: 'expectedDate', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: '离èåå ', field: 'reason', render: textRender }, |
| | | { label: '交æ¥äºº', field: 'handoverUserName', render: textRender }, |
| | | { |
| | | label: '交æ¥ç¶æ', |
| | | field: 'handoverStatus', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_HANDOVER_STATUS, value: val }), |
| | | }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getResignation(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- 离èåºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { HrmTransferApi } from '#/api/hrm/transfer'; |
| | | 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 } from 'ant-design-vue'; |
| | | |
| | | import { getTransfer } from '#/api/hrm/transfer'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | defineOptions({ name: 'HrmTransferApproveDetail' }); |
| | | |
| | | const props = defineProps<{ id: string }>(); |
| | | |
| | | const loading = ref(false); |
| | | const formData = ref<HrmTransferApi.Transfer>(); |
| | | |
| | | /** ææ¬åæ®µç©ºå¼å ä½ */ |
| | | function textRender(val: unknown) { |
| | | return val === 0 || val ? String(val) : '-'; |
| | | } |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { label: 'åå·¥å§å', field: 'employeeName', render: textRender }, |
| | | { label: 'å½åé¨é¨', field: 'currentDeptName', render: textRender }, |
| | | { label: 'å½åèä½', field: 'currentPostName', render: textRender }, |
| | | { label: 'ç®æ é¨é¨', field: 'targetDeptName', render: textRender }, |
| | | { label: 'ç®æ èä½', field: 'targetPostName', render: textRender }, |
| | | { |
| | | label: 'è°å²ç±»å', |
| | | field: 'transferType', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_TRANSFER_TYPE, value: val }), |
| | | }, |
| | | { |
| | | label: 'çææ¥æ', |
| | | field: 'effectiveDate', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { label: 'è°å²åå ', field: 'reason', render: textRender }, |
| | | { |
| | | label: '审æ¹ç¶æ', |
| | | field: 'status', |
| | | render: (val) => |
| | | h(DictTag, { type: DICT_TYPE.HRM_AUDIT_STATUS, value: val }), |
| | | }, |
| | | { label: '夿³¨', field: 'remark', render: textRender }, |
| | | { |
| | | label: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | ] as DescriptionItemSchema[], |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | loading.value = true; |
| | | formData.value = await getTransfer(Number(props.id)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ContentWrap class="m-2"> |
| | | <Spin :spinning="loading" tip="å è½½ä¸..."> |
| | | <div class="space-y-4"> |
| | | <!-- è°å²åºæ¬ä¿¡æ¯ --> |
| | | <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> |
| | | </div> |
| | | </Spin> |
| | | </ContentWrap> |
| | | </template> |
| | |
| | | [() => conversationStore.getTotalUnreadCount, () => route.fullPath], |
| | | ([count]) => { |
| | | nextTick(() => { |
| | | const base = preferences.app.name |
| | | const base = preferences.copyright.companyName |
| | | document.title = count > 0 ? `(${count > 99 ? '99+' : count}æ¡æªè¯»)${base}` : base |
| | | }) |
| | | }, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | export { default as DvMeteringSelectDialog } from './select-dialog.vue'; |
| | | export { default as DvMeteringSelect } from './select.vue'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesDvMeteringApi } from '#/api/mes/dv/metering'; |
| | | |
| | | import { nextTick, ref } from 'vue'; |
| | | |
| | | import { message, Modal } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getMeteringPage } from '#/api/mes/dv/metering'; |
| | | |
| | | import { |
| | | useMeteringSelectGridColumns, |
| | | useMeteringSelectGridFormSchema, |
| | | } from '../data'; |
| | | |
| | | defineOptions({ name: 'DvMeteringSelectDialog' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | selected: [rows: MesDvMeteringApi.Metering[]]; |
| | | }>(); |
| | | |
| | | const open = ref(false); // å¼¹çªæ¯å¦æå¼ |
| | | const multiple = ref(false); // æ¯å¦å¤é |
| | | const selectedRows = ref<MesDvMeteringApi.Metering[]>([]); // å·²éå¨å
·å表 |
| | | const preSelectedIds = ref<number[]>([]); // é¢éå¨å
·ç¼å·å表 |
| | | |
| | | /** è·åå½åè¡¨æ ¼æ°æ® */ |
| | | function getTableRows() { |
| | | return gridApi.grid.getTableData().fullData as MesDvMeteringApi.Metering[]; |
| | | } |
| | | |
| | | /** è·åå¤éè®°å½ï¼å
å« VXE reserve è·¨é¡µè®°å½ */ |
| | | function getMultipleSelectedRows() { |
| | | const selectedMap = new Map<number, MesDvMeteringApi.Metering>(); |
| | | const records = [ |
| | | ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []), |
| | | ...(gridApi.grid.getCheckboxRecords?.() ?? []), |
| | | ] as MesDvMeteringApi.Metering[]; |
| | | records.forEach((row) => { |
| | | const rowId = row.id; |
| | | if (rowId !== undefined && rowId !== null) { |
| | | selectedMap.set(rowId, row); |
| | | } |
| | | }); |
| | | return [...selectedMap.values()]; |
| | | } |
| | | |
| | | /** å¤çå¾éåå */ |
| | | function handleCheckboxSelectChange() { |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | /** å¤çåéåå */ |
| | | function handleRadioChange(row: MesDvMeteringApi.Metering) { |
| | | selectedRows.value = [row]; |
| | | } |
| | | |
| | | /** å¤é模å¼ä¸åæ¢è¡å¾é */ |
| | | async function toggleMultipleRow(row: MesDvMeteringApi.Metering) { |
| | | const selected = gridApi.grid.isCheckedByCheckboxRow(row); |
| | | await gridApi.grid.setCheckboxRow(row, !selected); |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | /** å¤çè¡åå» */ |
| | | async function handleCellDblclick({ |
| | | row, |
| | | }: { |
| | | row: MesDvMeteringApi.Metering; |
| | | }) { |
| | | if (multiple.value) { |
| | | await toggleMultipleRow(row); |
| | | return; |
| | | } |
| | | selectedRows.value = [row]; |
| | | await gridApi.grid.setRadioRow(row); |
| | | handleConfirm(); |
| | | } |
| | | |
| | | /** åæ¾é¢éå¨å
· */ |
| | | async function applyPreSelection() { |
| | | if (preSelectedIds.value.length === 0) { |
| | | return; |
| | | } |
| | | const rows = getTableRows(); |
| | | for (const row of rows) { |
| | | if ( |
| | | row.id === undefined || |
| | | row.id === null || |
| | | !preSelectedIds.value.includes(row.id) |
| | | ) { |
| | | continue; |
| | | } |
| | | if (multiple.value) { |
| | | await gridApi.grid.setCheckboxRow(row, true); |
| | | } else { |
| | | await gridApi.grid.setRadioRow(row); |
| | | selectedRows.value = [row]; |
| | | return; |
| | | } |
| | | } |
| | | if (multiple.value) { |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useMeteringSelectGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useMeteringSelectGridColumns(false), |
| | | height: 480, |
| | | keepSource: true, |
| | | checkboxConfig: { |
| | | highlight: true, |
| | | range: true, |
| | | reserve: true, |
| | | }, |
| | | radioConfig: { |
| | | highlight: true, |
| | | trigger: 'row', |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getMeteringPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesDvMeteringApi.Metering>, |
| | | gridEvents: { |
| | | cellDblclick: handleCellDblclick, |
| | | checkboxAll: handleCheckboxSelectChange, |
| | | checkboxChange: handleCheckboxSelectChange, |
| | | radioChange: ({ row }: { row: MesDvMeteringApi.Metering }) => { |
| | | handleRadioChange(row); |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | /** éç½®æ¥è¯¢åéæ©ç¶æ */ |
| | | async function resetQueryState() { |
| | | selectedRows.value = []; |
| | | await gridApi.grid.clearCheckboxRow(); |
| | | await gridApi.grid.clearCheckboxReserve(); |
| | | await gridApi.grid.clearRadioRow(); |
| | | await gridApi.formApi.resetForm(); |
| | | } |
| | | |
| | | /** æå¼è®¡éå¨å
·éæ©å¼¹çª */ |
| | | async function openModal( |
| | | selectedIds?: number[], |
| | | options?: { multiple?: boolean }, |
| | | ) { |
| | | open.value = true; |
| | | multiple.value = options?.multiple ?? false; |
| | | preSelectedIds.value = selectedIds || []; |
| | | await nextTick(); |
| | | gridApi.setGridOptions({ |
| | | columns: useMeteringSelectGridColumns(multiple.value), |
| | | }); |
| | | await resetQueryState(); |
| | | await gridApi.query(); |
| | | await nextTick(); |
| | | await applyPreSelection(); |
| | | } |
| | | |
| | | /** å
³é计éå¨å
·éæ©å¼¹çª */ |
| | | async function closeModal() { |
| | | open.value = false; |
| | | await resetQueryState(); |
| | | } |
| | | |
| | | /** ç¡®è®¤éæ©è®¡éå¨å
· */ |
| | | function handleConfirm() { |
| | | const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value; |
| | | if (rows.length === 0) { |
| | | message.warning(multiple.value ? '请è³å°éæ©ä¸æ¡æ°æ®' : 'è¯·éæ©ä¸æ¡æ°æ®'); |
| | | return; |
| | | } |
| | | emit('selected', multiple.value ? rows : [rows[0]!]); |
| | | open.value = false; |
| | | } |
| | | |
| | | defineExpose({ open: openModal }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | v-model:open="open" |
| | | title="计éå¨å
·éæ©" |
| | | width="70%" |
| | | :destroy-on-close="true" |
| | | @ok="handleConfirm" |
| | | @cancel="closeModal" |
| | | > |
| | | <Grid table-title="计éå¨å
·å表" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesDvMeteringApi } from '#/api/mes/dv/metering'; |
| | | |
| | | import { computed, ref, useAttrs, watch } from 'vue'; |
| | | |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Input, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { getMetering } from '#/api/mes/dv/metering'; |
| | | |
| | | import DvMeteringSelectDialog from './select-dialog.vue'; |
| | | |
| | | defineOptions({ name: 'DvMeteringSelect', inheritAttrs: false }); |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | allowClear?: boolean; |
| | | disabled?: boolean; |
| | | modelValue?: number; |
| | | placeholder?: string; |
| | | }>(), |
| | | { |
| | | allowClear: true, |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | placeholder: 'è¯·éæ©è®¡éå¨å
·', |
| | | }, |
| | | ); |
| | | const emit = defineEmits<{ |
| | | change: [item: MesDvMeteringApi.Metering | undefined]; |
| | | 'update:modelValue': [value: number | undefined]; |
| | | }>(); |
| | | const attrs = useAttrs(); // éä¼ å±æ§ |
| | | const dialogRef = ref<InstanceType<typeof DvMeteringSelectDialog>>(); // 计éå¨å
·éæ©å¼¹çª |
| | | const hovering = ref(false); // æ¯å¦æ¬å |
| | | const selectedItem = ref<MesDvMeteringApi.Metering>(); // å½åéä¸ç计éå¨å
· |
| | | |
| | | const displayLabel = computed(() => selectedItem.value?.name ?? ''); // éæ©å¨å±ç¤ºåç§° |
| | | const showClear = computed( |
| | | () => |
| | | props.allowClear && |
| | | !props.disabled && |
| | | hovering.value && |
| | | props.modelValue !== null, |
| | | ); |
| | | |
| | | /** æ ¹æ®å¨å
·ç¼å·åæ¾éæ©å¨ */ |
| | | async function resolveItemById(id: number | undefined) { |
| | | if (!id) { |
| | | selectedItem.value = undefined; |
| | | return; |
| | | } |
| | | if (selectedItem.value?.id === id) { |
| | | return; |
| | | } |
| | | selectedItem.value = await getMetering(id); |
| | | } |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (value) => { |
| | | resolveItemById(value); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | |
| | | /** æ¸
空已éå¨å
· */ |
| | | function clearSelected() { |
| | | selectedItem.value = undefined; |
| | | emit('update:modelValue', undefined); |
| | | emit('change', undefined); |
| | | } |
| | | |
| | | /** æå¼è®¡éå¨å
·éæ©å¼¹çª */ |
| | | function handleClick(event: MouseEvent) { |
| | | if (props.disabled) { |
| | | return; |
| | | } |
| | | const target = event.target as HTMLElement; |
| | | if (showClear.value && target.closest('.ant-input-suffix')) { |
| | | event.stopPropagation(); |
| | | clearSelected(); |
| | | return; |
| | | } |
| | | const selectedIds = ( |
| | | props.modelValue === null ? [] : [props.modelValue] |
| | | ) as number[]; |
| | | dialogRef.value?.open(selectedIds, { multiple: false }); |
| | | } |
| | | |
| | | /** åå¡«éä¸ç计éå¨å
· */ |
| | | function handleSelected(rows: MesDvMeteringApi.Metering[]) { |
| | | const item = rows[0]; |
| | | if (!item) { |
| | | return; |
| | | } |
| | | selectedItem.value = item; |
| | | emit('update:modelValue', item.id); |
| | | emit('change', item); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div |
| | | v-bind="attrs" |
| | | class="w-full" |
| | | :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'" |
| | | @click="handleClick" |
| | | @mouseenter="hovering = true" |
| | | @mouseleave="hovering = false" |
| | | > |
| | | <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false"> |
| | | <template #title> |
| | | <div v-if="selectedItem" class="leading-6"> |
| | | <div>å¨å
·ç¼ç ï¼{{ selectedItem.code || '-' }}</div> |
| | | <div>å¨å
·åç§°ï¼{{ selectedItem.name || '-' }}</div> |
| | | <div v-if="selectedItem.specification"> |
| | | è§æ ¼åå·ï¼{{ selectedItem.specification }} |
| | | </div> |
| | | <div v-if="selectedItem.deptName"> |
| | | 使ç¨é¨é¨ï¼{{ selectedItem.deptName }} |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <Input |
| | | :disabled="disabled" |
| | | :placeholder="placeholder" |
| | | :value="displayLabel" |
| | | readonly |
| | | > |
| | | <template #suffix> |
| | | <IconifyIcon |
| | | class="size-4" |
| | | :icon="showClear ? 'lucide:circle-x' : 'lucide:search'" |
| | | /> |
| | | </template> |
| | | </Input> |
| | | </Tooltip> |
| | | </div> |
| | | <DvMeteringSelectDialog ref="dialogRef" @selected="handleSelected" /> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesDvMeteringApi } from '#/api/mes/dv/metering'; |
| | | |
| | | import { |
| | | DICT_TYPE, |
| | | MesDvMeteringExpireStatusEnum, |
| | | MesDvMeteringStatusEnum, |
| | | } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { handleTree } from '@vben/utils'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getSimpleDeptList } from '#/api/system/dept'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** å°æç¶æå±ç¤ºé
ç½® */ |
| | | export const METERING_EXPIRE_STATUS_OPTIONS: { |
| | | color: string; |
| | | label: string; |
| | | value: number; |
| | | }[] = [ |
| | | { |
| | | label: 'å·²è¿æ', |
| | | value: MesDvMeteringExpireStatusEnum.OVERDUE, |
| | | color: 'red', |
| | | }, |
| | | { |
| | | label: 'å³å°å°æ', |
| | | value: MesDvMeteringExpireStatusEnum.WITHIN, |
| | | color: 'orange', |
| | | }, |
| | | { |
| | | label: 'æ£å¸¸', |
| | | value: MesDvMeteringExpireStatusEnum.NORMAL, |
| | | color: 'green', |
| | | }, |
| | | ]; |
| | | |
| | | /** æ°å¢/ä¿®æ¹è®¡éå¨å
·ç表å */ |
| | | export function useFormSchema(formType: FormType): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'å¨å
·ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¨å
·ç¼ç ', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | | componentProps: (values) => ({ disabled: !!values.id }), |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'å¨å
·åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¨å
·åç§°', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'category', |
| | | label: 'å¨å
·ç±»å«', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DV_METERING_CATEGORY, 'number'), |
| | | placeholder: 'è¯·éæ©å¨å
·ç±»å«', |
| | | }, |
| | | help: 'å¦å¡å°ºãååè¡¨ãæ¸©åº¦è®¡çï¼ä¸åç±»å«æå¸¸è§çæ ¡å卿', |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'specification', |
| | | label: 'è§æ ¼åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è§æ ¼åå·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'brand', |
| | | label: 'åç/ç产åå®¶', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥åç/ç产åå®¶', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'precision', |
| | | label: '精度', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç²¾åº¦', |
| | | }, |
| | | help: 'æµé精度ï¼å¦ 0.01mm', |
| | | }, |
| | | { |
| | | fieldName: 'range', |
| | | label: 'éç¨', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥éç¨', |
| | | }, |
| | | help: 'æµéèå´ï¼å¦ 0~150mm', |
| | | }, |
| | | { |
| | | fieldName: 'deptId', |
| | | label: '使ç¨é¨é¨', |
| | | component: 'ApiTreeSelect', |
| | | componentProps: { |
| | | api: async () => handleTree(await getSimpleDeptList()), |
| | | childrenField: 'children', |
| | | labelField: 'name', |
| | | placeholder: 'è¯·éæ©ä½¿ç¨é¨é¨', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'chargeUserId', |
| | | label: '责任人', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | placeholder: 'è¯·éæ©è´£ä»»äºº', |
| | | valueField: 'id', |
| | | }, |
| | | help: '该å¨å
·çç»´æ¤è´£ä»»äººï¼å°ææéçç«å
ä¿¡ä¼åç»ä»', |
| | | }, |
| | | { |
| | | fieldName: 'checkCycleCount', |
| | | label: 'æ ¡å卿', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 1, |
| | | precision: 0, |
| | | placeholder: '请è¾å
¥æ ¡å卿', |
| | | suffix: 'æ', |
| | | }, |
| | | help: 'å¨å
·æ¯é N 个æé¡»æ ¡å/æ£å®ä¸æ¬¡ï¼æ°å¢æ£æµè®°å½æ¶ä¸å¡«ä¸æ¬¡æ£æµæ¥æï¼å°æãæ£æµæ¥æ + æ ¡å卿ãèªå¨è®¡ç®', |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'useDate', |
| | | label: 'æå
¥ä½¿ç¨æ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æå
¥ä½¿ç¨æ¥æ', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | help: 'å¨å
·å¼å§æå
¥ä½¿ç¨çæ¥æï¼ä»
ä½è®°å½ï¼', |
| | | }, |
| | | { |
| | | fieldName: 'lastCheckDate', |
| | | label: '䏿¬¡æ£æµæ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | disabled: true, |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: '䏿¬¡æ£æµæ¥æ', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | help: 'ç±ææ°ä¸æ¡æ£æµè®°å½èªå¨ååï¼ä¸å¯æå¨ä¿®æ¹', |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | | show: () => formType === 'detail', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'nextCheckDate', |
| | | label: '䏿¬¡æ£æµæ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | disabled: true, |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: '䏿¬¡æ£æµæ¥æ', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | help: 'å°ææ¥ï¼ç±æ£æµè®°å½èªå¨è®¡ç®ååï¼å°æå N 天ï¼åºç¡è®¾æ½é
ç½®ï¼å°æé责任人', |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | | show: () => formType === 'detail', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'å¨å
·ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.MES_DV_METERING_STATUS, 'number'), |
| | | }, |
| | | help: 'å¨ç¨/åç¨/æ¥åº', |
| | | rules: z.number().default(MesDvMeteringStatusEnum.IN_USE), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'å¨å
·ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å¨å
·ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'å¨å
·åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å¨å
·åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'category', |
| | | label: 'å¨å
·ç±»å«', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DV_METERING_CATEGORY, 'number'), |
| | | placeholder: 'è¯·éæ©å¨å
·ç±»å«', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'deptId', |
| | | label: '使ç¨é¨é¨', |
| | | component: 'ApiTreeSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: async () => handleTree(await getSimpleDeptList()), |
| | | childrenField: 'children', |
| | | labelField: 'name', |
| | | placeholder: 'è¯·éæ©ä½¿ç¨é¨é¨', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'chargeUserId', |
| | | label: '责任人', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | placeholder: 'è¯·éæ©è´£ä»»äºº', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'å¨å
·ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DV_METERING_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©å¨å
·ç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'expireStatus', |
| | | label: 'å°æç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: METERING_EXPIRE_STATUS_OPTIONS, |
| | | placeholder: 'è¯·éæ©å°æç¶æ', |
| | | }, |
| | | help: '䏿¬¡æ£æµæ¥æè·ä»å¤© ⤠æé天æ°ï¼åºç¡è®¾æ½é
ç½® mes.dv-metering.remind-daysï¼å³è§ä¸ºãå³å°å°æã', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesDvMeteringApi.Metering>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'å¨å
·ç¼ç ', |
| | | minWidth: 150, |
| | | slots: { |
| | | default: 'code', |
| | | }, |
| | | }, |
| | | { field: 'name', title: 'å¨å
·åç§°', minWidth: 150 }, |
| | | { |
| | | field: 'category', |
| | | title: 'å¨å
·ç±»å«', |
| | | width: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_DV_METERING_CATEGORY }, |
| | | }, |
| | | }, |
| | | { field: 'specification', title: 'è§æ ¼åå·', minWidth: 130 }, |
| | | { field: 'brand', title: 'åç/ç产åå®¶', minWidth: 130 }, |
| | | { field: 'deptName', title: '使ç¨é¨é¨', minWidth: 130 }, |
| | | { field: 'chargeUserNickname', title: '责任人', minWidth: 100 }, |
| | | { |
| | | field: 'expireStatus', |
| | | title: 'å°æç¶æ', |
| | | width: 110, |
| | | slots: { |
| | | default: 'expireStatus', |
| | | }, |
| | | }, |
| | | { |
| | | field: 'nextCheckDate', |
| | | title: '䏿¬¡æ£æµæ¥æ', |
| | | width: 130, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'å¨å
·ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_DV_METERING_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 210, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'actions', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 计éå¨å
·éæ©å¼¹çªçæç´¢è¡¨å */ |
| | | export function useMeteringSelectGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'å¨å
·ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å¨å
·ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'å¨å
·åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥å¨å
·åç§°', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 计éå¨å
·éæ©å¼¹çªçåæ®µ */ |
| | | export function useMeteringSelectGridColumns( |
| | | multiple = false, |
| | | ): VxeTableGridOptions<MesDvMeteringApi.Metering>['columns'] { |
| | | return [ |
| | | { type: multiple ? 'checkbox' : 'radio', width: 50 }, |
| | | { field: 'code', title: 'å¨å
·ç¼ç ', width: 140 }, |
| | | { field: 'name', title: 'å¨å
·åç§°', minWidth: 140 }, |
| | | { |
| | | field: 'category', |
| | | title: 'å¨å
·ç±»å«', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_DV_METERING_CATEGORY }, |
| | | }, |
| | | }, |
| | | { field: 'specification', title: 'è§æ ¼åå·', minWidth: 120 }, |
| | | { field: 'deptName', title: '使ç¨é¨é¨', width: 120 }, |
| | | { field: 'chargeUserNickname', title: '责任人', width: 100 }, |
| | | { |
| | | field: 'nextCheckDate', |
| | | title: '䏿¬¡æ£æµæ¥æ', |
| | | width: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'å¨å
·ç¶æ', |
| | | width: 90, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_DV_METERING_STATUS }, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesDvMeteringApi } from '#/api/mes/dv/metering'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteMetering, |
| | | exportMetering, |
| | | getMeteringPage, |
| | | } from '#/api/mes/dv/metering'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { |
| | | METERING_EXPIRE_STATUS_OPTIONS, |
| | | 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: MesDvMeteringApi.Metering) { |
| | | formModalApi.setData({ id: row.id, formType: 'detail' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®¡éå¨å
· */ |
| | | function handleEdit(row: MesDvMeteringApi.Metering) { |
| | | formModalApi.setData({ id: row.id, formType: 'update' }).open(); |
| | | } |
| | | |
| | | /** å é¤è®¡éå¨å
· */ |
| | | async function handleDelete(row: MesDvMeteringApi.Metering) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteMetering(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** 导åºè®¡éå¨å
· */ |
| | | async function handleExport() { |
| | | const data = await exportMetering(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '计éå¨å
·.xls', source: data }); |
| | | } |
| | | |
| | | /** å°æç¶æå±ç¤º */ |
| | | function getExpireStatusConfig(status?: number) { |
| | | return METERING_EXPIRE_STATUS_OPTIONS.find((item) => item.value === status); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | | await getMeteringPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }), |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesDvMeteringApi.Metering>, |
| | | }); |
| | | </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: ['mes:dv-metering:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:dv-metering:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.code }} |
| | | </Button> |
| | | </template> |
| | | <template #expireStatus="{ row }"> |
| | | <Tag |
| | | v-if="getExpireStatusConfig(row.expireStatus)" |
| | | :color="getExpireStatusConfig(row.expireStatus)!.color" |
| | | > |
| | | {{ getExpireStatusConfig(row.expireStatus)!.label }} |
| | | </Tag> |
| | | <span v-else>-</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:dv-metering:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:dv-metering:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('common.detail'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | auth: ['mes:dv-metering:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { MesDvMeteringApi } from '#/api/mes/dv/metering'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createMetering, |
| | | getMetering, |
| | | updateMetering, |
| | | } from '#/api/mes/dv/metering'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesDvMeteringApi.Metering>(); |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return 'æ¥ç计éå¨å
·'; |
| | | } |
| | | return formType.value === 'update' ? 'ä¿®æ¹è®¡éå¨å
·' : 'æ°å¢è®¡éå¨å
·'; |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 110, |
| | | }, |
| | | wrapperClass: 'grid-cols-3', |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesDvMeteringApi.Metering; |
| | | try { |
| | | if (formType.value === 'create') { |
| | | await createMetering(data); |
| | | } else { |
| | | await updateMetering(data); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setState({ schema: useFormSchema(formType.value) }); |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getMetering(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesDvMeteringCheckApi } from '#/api/mes/dv/meteringcheck'; |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { DvMeteringSelect } from '#/views/mes/dv/metering/components'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹è®¡éæ£æµè®°å½ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'meteringId', |
| | | label: '计éå¨å
·', |
| | | component: markRaw(DvMeteringSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è®¡éå¨å
·', |
| | | }, |
| | | help: 'éæ©å¨å
·åï¼å
¶æ ¡å卿å°ç¨äºèªå¨è®¡ç®ä¸æ¬¡æ£æµæ¥æ', |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'checkCode', |
| | | label: 'æ£æµç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ£æµç¼å·', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'checkDate', |
| | | label: 'æ£æµæ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æ£æµæ¥æ', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'checkOrg', |
| | | label: 'æ£æµæºæ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ£æµæºæ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'checkResult', |
| | | label: 'æ£æµç»è®º', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.MES_DV_METERING_CHECK_RESULT, 'number'), |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'certNo', |
| | | label: 'è¯ä¹¦ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è¯ä¹¦ç¼å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'checkUserId', |
| | | label: 'æ£æµäºº', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | placeholder: 'è¯·éæ©æ£æµäºº', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'nextCheckDate', |
| | | label: '䏿¬¡æ£æµæ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©ä¸æ¬¡æ£æµæ¥æ', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | help: 'å°ææ¥ï¼å»ºè®®ä¸å¡«ï¼å°æãæ£æµæ¥æ + æ ¡å卿ãèªå¨è®¡ç®å¹¶ååå°å¨å
·ï¼ä¹å¯æå¨æå®', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'meteringId', |
| | | label: '计éå¨å
·', |
| | | component: markRaw(DvMeteringSelect), |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: 'è¯·éæ©è®¡éå¨å
·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'checkCode', |
| | | label: 'æ£æµç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥æ£æµç¼å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'checkResult', |
| | | label: 'æ£æµç»è®º', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DV_METERING_CHECK_RESULT, 'number'), |
| | | placeholder: 'è¯·éæ©æ£æµç»è®º', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'checkDate', |
| | | label: 'æ£æµæ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | allowClear: true, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesDvMeteringCheckApi.MeteringCheck>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'meteringCode', |
| | | title: 'å¨å
·ç¼ç ', |
| | | minWidth: 140, |
| | | slots: { |
| | | default: 'meteringCode', |
| | | }, |
| | | }, |
| | | { field: 'meteringName', title: 'å¨å
·åç§°', minWidth: 150 }, |
| | | { field: 'checkCode', title: 'æ£æµç¼å·', minWidth: 140 }, |
| | | { |
| | | field: 'checkDate', |
| | | title: 'æ£æµæ¥æ', |
| | | width: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { field: 'checkOrg', title: 'æ£æµæºæ', minWidth: 140 }, |
| | | { |
| | | field: 'checkResult', |
| | | title: 'æ£æµç»è®º', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_DV_METERING_CHECK_RESULT }, |
| | | }, |
| | | }, |
| | | { field: 'certNo', title: 'è¯ä¹¦ç¼å·', minWidth: 130 }, |
| | | { field: 'checkUserNickname', title: 'æ£æµäºº', minWidth: 100 }, |
| | | { |
| | | field: 'nextCheckDate', |
| | | title: '䏿¬¡æ£æµæ¥æ', |
| | | width: 130, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'actions', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesDvMeteringCheckApi } from '#/api/mes/dv/meteringcheck'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteMeteringCheck, |
| | | exportMeteringCheck, |
| | | getMeteringCheckPage, |
| | | } from '#/api/mes/dv/meteringcheck'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { 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: MesDvMeteringCheckApi.MeteringCheck) { |
| | | formModalApi.setData({ id: row.id, formType: 'detail' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®¡éæ£æµè®°å½ */ |
| | | function handleEdit(row: MesDvMeteringCheckApi.MeteringCheck) { |
| | | formModalApi.setData({ id: row.id, formType: 'update' }).open(); |
| | | } |
| | | |
| | | /** å é¤è®¡éæ£æµè®°å½ */ |
| | | async function handleDelete(row: MesDvMeteringCheckApi.MeteringCheck) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.checkCode]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteMeteringCheck(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.checkCode])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** 导åºè®¡éæ£æµè®°å½ */ |
| | | async function handleExport() { |
| | | const data = await exportMeteringCheck(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'è®¡éæ£æµè®°å½.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | | await getMeteringCheckPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }), |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesDvMeteringCheckApi.MeteringCheck>, |
| | | }); |
| | | </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: ['mes:dv-metering-check:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:dv-metering-check:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #meteringCode="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.meteringCode }} |
| | | </Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:dv-metering-check:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:dv-metering-check:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.checkCode]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('common.detail'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | auth: ['mes:dv-metering-check:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | |
| | | import type { MesDvMeteringCheckApi } from '#/api/mes/dv/meteringcheck'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createMeteringCheck, |
| | | getMeteringCheck, |
| | | updateMeteringCheck, |
| | | } from '#/api/mes/dv/meteringcheck'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesDvMeteringCheckApi.MeteringCheck>(); |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return 'æ¥çè®¡éæ£æµè®°å½'; |
| | | } |
| | | return formType.value === 'update' ? 'ä¿®æ¹è®¡éæ£æµè®°å½' : 'æ°å¢è®¡éæ£æµè®°å½'; |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 110, |
| | | }, |
| | | wrapperClass: 'grid-cols-3', |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (isDetail.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesDvMeteringCheckApi.MeteringCheck; |
| | | try { |
| | | if (formType.value === 'create') { |
| | | await createMeteringCheck(data); |
| | | } else { |
| | | await updateMeteringCheck(data); |
| | | } |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setState({ schema: useFormSchema() }); |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | modalApi.setState({ showConfirmButton: formType.value !== 'detail' }); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getMeteringCheck(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | disabled: false, |
| | | disabled: true, |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | }, |
| | |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns( |
| | | onStatusChange?: ( |
| | | newStatus: number, |
| | | row: MesMdItemApi.Item, |
| | | ) => PromiseLike<boolean | undefined>, |
| | | ): VxeTableGridOptions<MesMdItemApi.Item>['columns'] { |
| | | export function useGridColumns(): VxeTableGridOptions<MesMdItemApi.Item>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 120, |
| | | width: 100, |
| | | align: 'center', |
| | | cellRender: { |
| | | attrs: { beforeChange: onStatusChange }, |
| | | name: 'CellSwitch', |
| | | props: { |
| | | checkedValue: CommonStatusEnum.ENABLE, |
| | | unCheckedValue: CommonStatusEnum.DISABLE, |
| | | }, |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | |
| | | import { ref } from "vue"; |
| | | |
| | | import { |
| | | confirm, |
| | | Page, |
| | | useVbenModal, |
| | | } from "@vben/common-ui"; |
| | | import { DICT_TYPE } from "@vben/constants"; |
| | | import { getDictLabel } from "@vben/hooks"; |
| | | import { downloadFileFromBlobPart } from "@vben/utils"; |
| | | |
| | | import { Button, Card, message } from "ant-design-vue"; |
| | |
| | | deleteItem, |
| | | exportItem, |
| | | getItemPage, |
| | | updateItemStatus, |
| | | } from "#/api/mes/md/item"; |
| | | import { $t } from "#/locales"; |
| | | import { MdItemTypeTree } from "#/views/mes/md/item/type/components"; |
| | |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** æ´æ°ç©æç¶æ */ |
| | | async function handleStatusChange( |
| | | newStatus: number, |
| | | row: MesMdItemApi.Item |
| | | ): Promise<boolean | undefined> { |
| | | try { |
| | | await confirm( |
| | | `确认è¦å°"${row.name}"ç©æåæ¢ä¸ºã${getDictLabel( |
| | | DICT_TYPE.COMMON_STATUS, |
| | | newStatus |
| | | )}ãåï¼` |
| | | ); |
| | | } catch { |
| | | return false; |
| | | } |
| | | await updateItemStatus(row.id!, newStatus); |
| | | message.success($t("ui.actionMessage.operationSuccess")); |
| | | return true; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(handleStatusChange), |
| | | columns: useGridColumns(), |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | |
| | | <Grid table-title="ç©æäº§åå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.import'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['mes:md-item:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | |
| | | |
| | | import { |
| | | archivePdProject, |
| | | auditPdProject, |
| | | getPdProject, |
| | | getPdProjectApproveProcessList, |
| | | publishPdProject, |
| | | uploadPdProject, |
| | | } from '#/api/mes/pd/project'; |
| | |
| | | import Form from '../modules/form.vue'; |
| | | import DocumentAuditList from '../modules/document-audit-list.vue'; |
| | | import DocumentList from '../modules/document-list.vue'; |
| | | import ProcessSelectModal from '../modules/process-select-modal.vue'; |
| | | import WorkflowTimeline from '../modules/workflow-timeline.vue'; |
| | | import TaskDraftBanner from '../modules/task-draft-banner.vue'; |
| | | import { useDetailSchema } from './data'; |
| | |
| | | const isPendingArchive = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.PENDING_ARCHIVE, |
| | | ); |
| | | const isApproving = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.APPROVING, |
| | | ); |
| | | const isArchived = computed( |
| | | () => project.value.status === MesPdProjectStatusEnum.ARCHIVED, |
| | | ); |
| | |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ProcessModal, processModalApi] = useVbenModal({ |
| | | connectedComponent: ProcessSelectModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | async function handleAudit() { |
| | | const hideLoading = message.loading({ content: 'å®¡æ ¸ä¸â¦', duration: 0 }); |
| | | /** æäº¤å®¡æ¹ - å
è·åæµç¨å表 */ |
| | | async function handleSubmit() { |
| | | const hideLoading = message.loading({ content: 'è·åå®¡æ¹æµç¨...', duration: 0 }); |
| | | try { |
| | | await auditPdProject(projectId.value); |
| | | message.success('å®¡æ ¸éè¿ï¼è¿å
¥å¾
彿¡£ç¶æ'); |
| | | tabs.closeCurrentTab(); |
| | | router.back(); |
| | | } finally { |
| | | const processList = await getPdProjectApproveProcessList(); |
| | | hideLoading(); |
| | | if (!processList || processList.length === 0) { |
| | | message.warning('ææ å¯ç¨å®¡æ¹æµç¨ï¼è¯·å
é
ç½® BPM æµç¨'); |
| | | return; |
| | | } |
| | | // æå¼æµç¨éæ©å¼¹çª |
| | | processModalApi.setData({ id: projectId.value, processList }).open(); |
| | | } catch { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¥ç审æ¹è¯¦æ
*/ |
| | | function handleProcessDetail() { |
| | | if (!project.value.processInstanceId) { |
| | | message.warning('该项ç®å°æªæäº¤å®¡æ¹'); |
| | | return; |
| | | } |
| | | router.push({ |
| | | name: 'BpmProcessInstanceDetail', |
| | | query: { id: project.value.processInstanceId }, |
| | | }); |
| | | } |
| | | |
| | | /** æäº¤å®¡æ¹æååå·æ°è¯¦æ
*/ |
| | | function handleSubmitSuccess() { |
| | | loadProjectDetail(); |
| | | } |
| | | |
| | | async function handleArchive() { |
| | |
| | | :title="project.taskName || '设计任å¡è¯¦æ
'" |
| | | > |
| | | <FormModal @success="loadProjectDetail" /> |
| | | <ProcessModal @success="handleSubmitSuccess" /> |
| | | |
| | | <template #extra> |
| | | <TableAction |
| | |
| | | onClick: handleUpload, |
| | | }, |
| | | { |
| | | label: 'å®¡æ ¸éè¿', |
| | | label: 'æäº¤å®¡æ¹', |
| | | type: 'primary', |
| | | ifShow: isPendingAudit, |
| | | onClick: handleAudit, |
| | | onClick: handleSubmit, |
| | | }, |
| | | { |
| | | label: 'æ¥ç审æ¹', |
| | | type: 'primary', |
| | | ifShow: isApproving, |
| | | onClick: handleProcessDetail, |
| | | }, |
| | | { |
| | | label: '彿¡£', |
| | |
| | | |
| | | deletePdProject, |
| | | exportPdProject, |
| | | getPdProjectApproveProcessList, |
| | | getPdProjectPage, |
| | | publishPdProject, |
| | | |
| | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import CreateForm from './modules/create-form.vue'; |
| | | import Form from './modules/form.vue'; |
| | | import ProcessSelectModal from './modules/process-select-modal.vue'; |
| | | |
| | | const router = useRouter(); |
| | | |
| | |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ProcessModal, processModalApi] = useVbenModal({ |
| | | connectedComponent: ProcessSelectModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æäº¤å®¡æ¹ - å
è·åæµç¨å表 */ |
| | | async function handleSubmit(row: MesPdProjectApi.Project) { |
| | | const hideLoading = message.loading({ |
| | | content: 'è·åå®¡æ¹æµç¨...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | const processList = await getPdProjectApproveProcessList(); |
| | | hideLoading(); |
| | | if (!processList || processList.length === 0) { |
| | | message.warning('ææ å¯ç¨å®¡æ¹æµç¨ï¼è¯·å
é
ç½® BPM æµç¨'); |
| | | return; |
| | | } |
| | | // æå¼æµç¨éæ©å¼¹çª |
| | | processModalApi.setData({ id: row.id, processList }).open(); |
| | | } catch { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¥ç审æ¹è¯¦æ
*/ |
| | | function handleProcessDetail(row: MesPdProjectApi.Project) { |
| | | if (!row.processInstanceId) { |
| | | message.warning('该项ç®å°æªæäº¤å®¡æ¹'); |
| | | return; |
| | | } |
| | | router.push({ |
| | | name: 'BpmProcessInstanceDetail', |
| | | query: { id: row.processInstanceId }, |
| | | }); |
| | | } |
| | | |
| | | async function handleArchive(row: MesPdProjectApi.Project) { |
| | |
| | | <Page auto-content-height> |
| | | <CreateFormModal @success="handleCreateSuccess" /> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ProcessModal @success="handleRefresh" /> |
| | | |
| | | <Grid table-title="设计任å¡"> |
| | | <template #toolbar-tools> |
| | |
| | | onClick: () => handleDetail(row), |
| | | }, |
| | | { |
| | | label: 'å®¡æ ¸', |
| | | label: 'æäº¤å®¡æ¹', |
| | | type: 'link', |
| | | auth: ['mes:pd-project:audit'], |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.PENDING_AUDIT, |
| | | onClick: () => handleDetail(row), |
| | | onClick: handleSubmit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æ¥ç审æ¹', |
| | | type: 'link', |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: row.status === MesPdProjectStatusEnum.APPROVING, |
| | | onClick: handleProcessDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: '彿¡£', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message, Select } from 'ant-design-vue'; |
| | | |
| | | import { submitPdProjectApproval } from '#/api/mes/pd/project'; |
| | | |
| | | interface ProcessDefinition { |
| | | id: string; |
| | | name: string; |
| | | key: string; |
| | | } |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const projectId = ref<number>(); |
| | | const processList = ref<ProcessDefinition[]>([]); |
| | | const selectedProcessKey = ref<string>(''); |
| | | |
| | | const processOptions = computed(() => { |
| | | return processList.value.map((p) => ({ |
| | | label: p.name, |
| | | value: p.key, |
| | | })); |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!selectedProcessKey.value) { |
| | | message.warning('è¯·éæ©å®¡æ¹æµç¨'); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | await submitPdProjectApproval(projectId.value!, selectedProcessKey.value); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success('æäº¤å®¡æ¹æå'); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | projectId.value = undefined; |
| | | selectedProcessKey.value = ''; |
| | | processList.value = []; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number; processList: ProcessDefinition[] }>(); |
| | | projectId.value = data.id; |
| | | processList.value = data.processList || []; |
| | | // é»è®¤éæ©ç¬¬ä¸ä¸ªæµç¨ |
| | | if (processList.value.length > 0) { |
| | | selectedProcessKey.value = processList.value[0].key; |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="éæ©å®¡æ¹æµç¨" class="w-1/3"> |
| | | <div class="mb-4"> |
| | | <span class="mr-2">å®¡æ¹æµç¨ï¼</span> |
| | | <Select |
| | | v-model:value="selectedProcessKey" |
| | | :options="processOptions" |
| | | style="width: 200px" |
| | | placeholder="è¯·éæ©å®¡æ¹æµç¨" |
| | | /> |
| | | </div> |
| | | </Modal> |
| | | </template> |
| | |
| | | |
| | | import { Steps } from 'ant-design-vue'; |
| | | |
| | | const STEPS = ['å¾
åå¸', 'å¾
ä¸ä¼ ', 'å¾
å®¡æ ¸', 'å¾
彿¡£', '已彿¡£'] as const; |
| | | import { MesPdProjectStatusEnum } from '#/packages/constants/src'; |
| | | |
| | | const STEPS = ['å¾
åå¸', 'å¾
ä¸ä¼ ', 'å¾
å®¡æ ¸', '审æ¹ä¸', 'å¾
彿¡£', '已彿¡£'] as const; |
| | | |
| | | // ç¶æå¼ â æ¶é´çº¿æ¥éª¤ç´¢å¼ï¼å®¡æ¹ä¸=5 æå¨å¾
å®¡æ ¸ä¹åï¼ |
| | | const STATUS_ORDER = [ |
| | | MesPdProjectStatusEnum.PENDING_PUBLISH, |
| | | MesPdProjectStatusEnum.PENDING_UPLOAD, |
| | | MesPdProjectStatusEnum.PENDING_AUDIT, |
| | | MesPdProjectStatusEnum.APPROVING, |
| | | MesPdProjectStatusEnum.PENDING_ARCHIVE, |
| | | MesPdProjectStatusEnum.ARCHIVED, |
| | | ]; |
| | | |
| | | const props = defineProps<{ |
| | | /** 项ç®å½åç¶æ (0-4) */ |
| | | /** 项ç®å½åç¶æ */ |
| | | status: number; |
| | | }>(); |
| | | |
| | | const current = computed(() => { |
| | | // ç¶æå¼å³å½å已宿å°ç¬¬å æ¥ï¼0=ç¬¬ä¸æ¥è¿è¡ä¸ï¼4=å
¨é¨å®æ |
| | | return props.status; |
| | | // æ ¹æ®ç¶æå¼æ å°å°æ¶é´çº¿æ¥éª¤ç´¢å¼ |
| | | const index = STATUS_ORDER.indexOf(props.status); |
| | | return index >= 0 ? index : 0; |
| | | }); |
| | | |
| | | const nodes = computed(() => |
| | | STEPS.map((name, index) => ({ |
| | | title: name, |
| | | status: index < props.status ? 'finish' : index === props.status ? 'process' : 'wait', |
| | | status: index < current.value ? 'finish' : index === current.value ? 'process' : 'wait', |
| | | })), |
| | | ); |
| | | </script> |
| | |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | |
| | | import { MdClientSelect } from '#/views/mes/md/client/components'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | import { MdVendorSelect } from '#/views/mes/md/vendor/components'; |
| | |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** éè´æ¥æºå表çåæ®µ */ |
| | | export function usePurchaseSourceColumns(): VxeTableGridOptions<MesWmBatchApi.PurchaseSource>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'receiptCode', |
| | | title: 'éè´å
¥åºåå·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'receiptDate', |
| | | title: 'å
¥åºæ¥æ', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'purchaseOrderCode', |
| | | title: 'éè´è®¢åç¼å·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'vendorCode', |
| | | title: 'ä¾åºåç¼ç ', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'vendorName', |
| | | title: 'ä¾åºååç§°', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'itemCode', |
| | | title: 'ç©æç¼ç ', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'itemName', |
| | | title: 'ç©æåç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'iqcCode', |
| | | title: 'IQC æ£éªåå·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'checkResult', |
| | | title: 'IQC æ£éªç»è®º', |
| | | width: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_QC_CHECK_RESULT }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'receivedQuantity', |
| | | title: 'å
¥åºæ°é', |
| | | width: 100, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** éå®å»åå表çåæ®µ */ |
| | | export function useSalesTargetColumns(): VxeTableGridOptions<MesWmBatchApi.SalesTarget>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'salesCode', |
| | | title: 'éå®åºåºåå·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'salesDate', |
| | | title: 'åºåºæ¥æ', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'salesOrderCode', |
| | | title: 'éå®è®¢åç¼å·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'clientName', |
| | | title: '客æ·åç§°', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'itemCode', |
| | | title: 'ç©æç¼ç ', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'itemName', |
| | | title: 'ç©æåç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'oqcCode', |
| | | title: 'OQC æ£éªåå·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'checkResult', |
| | | title: 'OQC æ£éªç»è®º', |
| | | width: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_QC_CHECK_RESULT }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'quantity', |
| | | title: 'åºåºæ°é', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'qualityStatus', |
| | | title: 'è´¨éç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS }, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å·¥åºæµè½¬å表çåæ®µ */ |
| | | export function useProcessTraceColumns(): VxeTableGridOptions<MesWmBatchApi.ProcessTrace>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'eventType', |
| | | title: 'äºä»¶ç±»å', |
| | | width: 90, |
| | | slots: { default: 'eventType' }, |
| | | }, |
| | | { |
| | | field: 'workOrderCode', |
| | | title: 'ç产工åå·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'processCode', |
| | | title: 'å·¥åºç¼ç ', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'processName', |
| | | title: 'å·¥åºåç§°', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'quantity', |
| | | title: 'æ°é', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'qualityStatus', |
| | | title: 'è´¨éç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'eventTime', |
| | | title: 'äºä»¶æ¶é´', |
| | | minWidth: 160, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | ]; |
| | | } |
| | |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { Input, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getBatchPage } from '#/api/mes/wm/batch'; |
| | | import { getBatchBySn, getBatchPage } from '#/api/mes/wm/batch'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import TraceDetail from './modules/trace-detail.vue'; |
| | |
| | | /** æå¼æ¹æ¬¡è¿½æº¯è¯¦æ
*/ |
| | | function handleTrace(row: MesWmBatchApi.Batch) { |
| | | detailModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** æ ¹æ® SN åæ¥æ¹æ¬¡å¹¶æå¼è¯¦æ
*/ |
| | | async function handleSearchBySn(sn: string) { |
| | | if (!sn) { |
| | | return; |
| | | } |
| | | const batch = await getBatchBySn(sn.trim()); |
| | | if (batch) { |
| | | handleTrace(batch); |
| | | } else { |
| | | message.warning(`æªæ¾å° SNã${sn}ã对åºçæ¹æ¬¡`); |
| | | } |
| | | } |
| | | |
| | | const [Grid] = useVbenVxeGrid({ |
| | |
| | | <template> |
| | | <Page auto-content-height><DetailModal /> |
| | | |
| | | <div class="mb-3 w-80"> |
| | | <Input.Search |
| | | placeholder="è¾å
¥ SN åºåå·åæ¥æ¹æ¬¡" |
| | | enter-button="忥" |
| | | allow-clear |
| | | @search="handleSearchBySn" |
| | | /> |
| | | </div> |
| | | |
| | | <Grid table-title="æ¹æ¬¡è¿½æº¯å表"> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesWmBatchApi } from '#/api/mes/wm/batch'; |
| | | |
| | | import { watch } from 'vue'; |
| | | |
| | | import { Tag } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getProcessTraceList } from '#/api/mes/wm/batch'; |
| | | |
| | | import { useProcessTraceColumns } from '../data'; |
| | | |
| | | const props = defineProps<{ |
| | | batchCode?: string; |
| | | }>(); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useProcessTraceColumns(), |
| | | minHeight: 240, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | }, |
| | | showOverflow: true, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | } as VxeTableGridOptions<MesWmBatchApi.ProcessTrace>, |
| | | }); |
| | | |
| | | /** å è½½å·¥åºæµè½¬å表 */ |
| | | async function getList() { |
| | | if (!props.batchCode) { |
| | | await gridApi.grid?.loadData([]); |
| | | return; |
| | | } |
| | | gridApi.setLoading(true); |
| | | try { |
| | | const list = await getProcessTraceList(props.batchCode); |
| | | await gridApi.grid?.loadData(list); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.batchCode, |
| | | () => { |
| | | getList(); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Grid class="w-full"> |
| | | <template #eventType="{ row }"> |
| | | <Tag v-if="row.eventType === 'OUT'" color="success">产åº</Tag> |
| | | <Tag v-else color="processing">æå
¥</Tag> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesWmBatchApi } from '#/api/mes/wm/batch'; |
| | | |
| | | import { watch } from 'vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getPurchaseSourceList } from '#/api/mes/wm/batch'; |
| | | |
| | | import { usePurchaseSourceColumns } from '../data'; |
| | | |
| | | const props = defineProps<{ |
| | | batchCode?: string; |
| | | }>(); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: usePurchaseSourceColumns(), |
| | | minHeight: 240, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | }, |
| | | showOverflow: true, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | } as VxeTableGridOptions<MesWmBatchApi.PurchaseSource>, |
| | | }); |
| | | |
| | | /** å è½½éè´æ¥æºå表 */ |
| | | async function getList() { |
| | | if (!props.batchCode) { |
| | | await gridApi.grid?.loadData([]); |
| | | return; |
| | | } |
| | | gridApi.setLoading(true); |
| | | try { |
| | | const list = await getPurchaseSourceList(props.batchCode); |
| | | await gridApi.grid?.loadData(list); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.batchCode, |
| | | () => { |
| | | getList(); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Grid class="w-full" /> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesWmBatchApi } from '#/api/mes/wm/batch'; |
| | | |
| | | import { watch } from 'vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getSalesTargetList } from '#/api/mes/wm/batch'; |
| | | |
| | | import { useSalesTargetColumns } from '../data'; |
| | | |
| | | const props = defineProps<{ |
| | | batchCode?: string; |
| | | }>(); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useSalesTargetColumns(), |
| | | minHeight: 240, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | }, |
| | | showOverflow: true, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | } as VxeTableGridOptions<MesWmBatchApi.SalesTarget>, |
| | | }); |
| | | |
| | | /** å è½½éå®å»åå表 */ |
| | | async function getList() { |
| | | if (!props.batchCode) { |
| | | await gridApi.grid?.loadData([]); |
| | | return; |
| | | } |
| | | gridApi.setLoading(true); |
| | | try { |
| | | const list = await getSalesTargetList(props.batchCode); |
| | | await gridApi.grid?.loadData(list); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.batchCode, |
| | | () => { |
| | | getList(); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Grid class="w-full" /> |
| | | </template> |
| | |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { Tabs } from 'ant-design-vue'; |
| | | import { Tabs, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | import { useDetailSchema } from '../data'; |
| | | import ProcessTraceList from './process-trace-list.vue'; |
| | | import PurchaseSourceList from './purchase-source-list.vue'; |
| | | import SalesTargetList from './sales-target-list.vue'; |
| | | import TraceList from './trace-list.vue'; |
| | | |
| | | const detailData = ref<MesWmBatchApi.Batch>(); // å½åæ¹æ¬¡è¯¦æ
|
| | | const subTabsName = ref<'backward' | 'forward'>('forward'); // å½åæ¿æ´»ç追溯æ¹å |
| | | const subTabsName = ref< |
| | | | 'backward' |
| | | | 'forward' |
| | | | 'purchase' |
| | | | 'sales' |
| | | | 'process' |
| | | >('forward'); // å½åæ¿æ´»çè¿½æº¯é¡µç¾ |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | |
| | | class="mx-4 mt-4" |
| | | type="card" |
| | | > |
| | | <Tabs.TabPane key="forward" tab="åå追溯"> |
| | | <Tabs.TabPane key="forward"> |
| | | <template #tab> |
| | | <Tooltip title="顺çç产æµå¾ä¸æ¸¸æ¥ï¼è¯¥æ¹æ¬¡è¢«åªäºå·¥åæ¶èåï¼äº§åºæäºåªäºæåæ¹æ¬¡"> |
| | | <span>åå追溯</span> |
| | | </Tooltip> |
| | | </template> |
| | | <TraceList :batch-code="detailData.code" direction="forward" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="backward" tab="åå追溯"> |
| | | <Tabs.TabPane key="backward"> |
| | | <template #tab> |
| | | <Tooltip title="éçç产æµå¾ä¸æ¸¸æ¥ï¼çäº§è¯¥æ¹æ¬¡æ¶ï¼æ¶èäºåªäºåææ¹æ¬¡"> |
| | | <span>åå追溯</span> |
| | | </Tooltip> |
| | | </template> |
| | | <TraceList :batch-code="detailData.code" direction="backward" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="purchase"> |
| | | <template #tab> |
| | | <Tooltip title="åæä¾§æ¥æºï¼è¯¥æ¹æ¬¡å¯¹åºçéè´å
¥åºåä¸ IQC æ¥ææ£éªè®°å½"> |
| | | <span>éè´æ¥æº</span> |
| | | </Tooltip> |
| | | </template> |
| | | <PurchaseSourceList :batch-code="detailData.code" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="sales"> |
| | | <template #tab> |
| | | <Tooltip title="æåä¾§å»åï¼è¯¥æ¹æ¬¡å¯¹åºçéå®åºåºåä¸ OQC åºåæ£éªè®°å½"> |
| | | <span>éå®å»å</span> |
| | | </Tooltip> |
| | | </template> |
| | | <SalesTargetList :batch-code="detailData.code" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="process"> |
| | | <template #tab> |
| | | <Tooltip title="åå·¥åºçæå
¥/产åºäºä»¶ï¼è¯¥æ¹æ¬¡å¨åªäºå·¥åºè¢«æå
¥æäº§åºï¼åæ°éåè´¨éç¶æ"> |
| | | <span>å·¥åºæµè½¬</span> |
| | | </Tooltip> |
| | | </template> |
| | | <ProcessTraceList :batch-code="detailData.code" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | </Modal> |
| | | </template> |
| | |
| | | </div> |
| | | <div class="rounded-lg bg-purple-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-purple-600">{{ stats.savedAmount?.toLocaleString() ?? 0 }}</div> |
| | | <div class="mt-2 text-gray-500">è约éé¢(å
)</div> |
| | | <div class="mt-2 flex items-center justify-center gap-1 text-gray-500"> |
| | | è约éé¢(å
) |
| | | <a-tooltip title="è约éé¢ = ææ æ»éé¢(é¢ç®éé¢) â 宿 æ»éé¢(䏿 éé¢)"> |
| | | <span |
| | | class="inline-flex h-3.5 w-3.5 cursor-help items-center justify-center rounded-full bg-gray-400 text-[10px] leading-none text-white" |
| | | >?</span> |
| | | </a-tooltip> |
| | | </div> |
| | | </div> |
| | | <div class="rounded-lg bg-teal-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-teal-600">{{ stats.winRate ?? 0 }}%</div> |
| | |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'blobIds', |
| | | label: 'è¯ä¹¦éä»¶', |
| | | component: 'FileUpload', |
| | | componentProps: { |
| | | valueKey: 'id', |
| | | maxNumber: 10, |
| | | multiple: true, |
| | | accept: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'], |
| | | helpText: 'æ¯æjpgãjpegãpngãpdfãdocãdocxæ ¼å¼ï¼å个æä»¶ä¸è¶
è¿2MB', |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | |
| | | { field: 'validEndDate', title: 'å°ææ¥æ', minWidth: 120 }, |
| | | { field: 'createTime', title: 'å建æ¶é´', minWidth: 160 }, |
| | | { |
| | | field: 'attachmentList', |
| | | title: 'éä»¶', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'attachment' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #attachment="{ row }"> |
| | | <div v-if="row.attachmentList && row.attachmentList.length" class="flex flex-col gap-1"> |
| | | <a |
| | | v-for="file in row.attachmentList" |
| | | :key="file.id" |
| | | :href="file.url" |
| | | target="_blank" |
| | | rel="noopener noreferrer" |
| | | class="text-blue-500 hover:underline" |
| | | > |
| | | {{ file.name || 'éä»¶-' + file.id }} |
| | | </a> |
| | | </div> |
| | | <Tag v-else color="default">--</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getCertificate(data.id); |
| | | if (formData.value?.attachmentList?.length) { |
| | | // 转为ä¸ä¼ ç»ä»¶çæä»¶å¯¹è±¡ï¼æäº¤æ¶ formApi 伿 valueKey æåéä»¶ ID |
| | | (formData.value as Record<string, unknown>).blobIds = |
| | | formData.value.attachmentList.map((item) => ({ |
| | | uid: String(item.id), |
| | | name: item.name || '', |
| | | url: item.url || '', |
| | | status: 'done', |
| | | id: item.id, |
| | | })); |
| | | } |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | |
| | | componentProps: { placeholder: '请è¾å
¥èµè´¨è¦æ±', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'blobIds', |
| | | label: 'èµè´¨è¦æ±éä»¶', |
| | | component: 'FileUpload', |
| | | componentProps: { |
| | | valueKey: 'id', |
| | | maxNumber: 10, |
| | | multiple: true, |
| | | accept: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'], |
| | | helpText: 'æ¯æjpgãjpegãpngãpdfãdocãdocxæ ¼å¼ï¼å个æä»¶ä¸è¶
è¿2MB', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder, |
| | | confirmTenderProject, |
| | | } from '#/api/srm/tender'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getSupplierList } from '#/api/srm/supplier'; |
| | | import { getItemSimpleList } from '#/api/mdm/item'; |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import { |
| | |
| | | onMounted(async () => { |
| | | const [items, suppliers] = await Promise.all([ |
| | | getItemSimpleList(), |
| | | getSupplierSimpleList(), |
| | | getSupplierList(), |
| | | ]); |
| | | mdmItemList.value = items; |
| | | supplierList.value = suppliers; |
| | |
| | | const typeLabel = computed(() => getLabel(TENDER_TYPE_CELLTAG.labels, project.value?.tenderType)); |
| | | const isDraft = computed(() => project.value?.tenderStatus === TENDER_STATUS.DRAFT); |
| | | const isPublished = computed(() => project.value?.tenderStatus === TENDER_STATUS.PUBLISHED); |
| | | const isBidding = computed(() => project.value?.tenderStatus === TENDER_STATUS.BIDDING); |
| | | const isPublishedOrBidding = computed(() => { |
| | | const s = project.value?.tenderStatus; |
| | | return s === TENDER_STATUS.PUBLISHED || s === TENDER_STATUS.BIDDING; |
| | |
| | | const bidColumns = [ |
| | | { title: 'ææ ç¼å·', dataIndex: 'bidNo', width: 140 }, |
| | | { title: 'ä¾åºå', dataIndex: 'supplierName', width: 200 }, |
| | | { title: 'ææ éé¢', dataIndex: 'bidTotalAmount', width: 120 }, |
| | | { title: 'ææ æ»éé¢', dataIndex: 'bidTotalAmount', width: 120 }, |
| | | { title: 'ææ æ¶é´', dataIndex: 'bidTime', width: 160 }, |
| | | { title: 'ç¶æ', dataIndex: 'bidStatus', width: 100, customRender: ({ text }: any) => BID_STATUS_MAP[text] || text }, |
| | | { title: 'æä½', key: 'actions', width: 240 }, |
| | |
| | | |
| | | const quoteColumns = [ |
| | | { title: 'ç©æ', dataIndex: 'materialName', width: 120 }, |
| | | { title: 'æ¥ä»·ä»·æ ¼', dataIndex: 'quotePrice', width: 100 }, |
| | | { title: 'æ¥ä»·åä»·', dataIndex: 'quotePrice', width: 100 }, |
| | | { title: 'ç¨ç(%)', dataIndex: 'taxRate', width: 80 }, |
| | | { title: '交æ(天)', dataIndex: 'deliveryCycle', width: 80 }, |
| | | { title: '仿¬¾æ¡ä»¶', dataIndex: 'paymentTerms', width: 120 }, |
| | |
| | | </div> |
| | | |
| | | <!-- ææ è¯´æ / èµè´¨è¦æ± --> |
| | | <template v-if="project.tenderDesc || project.qualificationRequirements"> |
| | | <template |
| | | v-if="project.tenderDesc || project.qualificationRequirements || (project.attachmentList && project.attachmentList.length)" |
| | | > |
| | | <a-divider class="!my-2" /> |
| | | <a-collapse :bordered="false" ghost expand-icon-position="end"> |
| | | <a-collapse-panel v-if="project.tenderDesc" key="desc" header="ææ è¯´æ"> |
| | | <p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.tenderDesc }}</p> |
| | | </a-collapse-panel> |
| | | <a-collapse-panel v-if="project.qualificationRequirements" key="req" header="èµè´¨è¦æ±"> |
| | | <p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p> |
| | | <a-collapse-panel |
| | | v-if="project.qualificationRequirements || (project.attachmentList && project.attachmentList.length)" |
| | | key="req" |
| | | header="èµè´¨è¦æ±" |
| | | > |
| | | <p v-if="project.qualificationRequirements" class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p> |
| | | <div v-if="project.attachmentList && project.attachmentList.length" class="mt-2 flex flex-col gap-1"> |
| | | <a |
| | | v-for="file in project.attachmentList" |
| | | :key="file.id" |
| | | :href="file.url" |
| | | target="_blank" |
| | | rel="noopener noreferrer" |
| | | class="text-blue-500 text-xs hover:underline" |
| | | > |
| | | {{ file.name || 'éä»¶-' + file.id }} |
| | | </a> |
| | | </div> |
| | | </a-collapse-panel> |
| | | </a-collapse> |
| | | </template> |
| | |
| | | </template> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <span class="text-xs text-gray-400">å
± <b class="text-gray-600">{{ bidCount }}</b> ä¸ªææ </span> |
| | | <a-button v-if="isPublishedOrBidding || isDraft || isPriceComparing" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true"> |
| | | <a-button v-if="isBidding || isDraft || isPriceComparing" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ°å¢ææ |
| | | </a-button> |
| | |
| | | </a-form-item> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="8"> |
| | | <a-form-item label="æ¥ä»·ä»·æ ¼" required><a-input-number v-model:value="quoteForm.quotePrice" :min="0" class="!w-full" placeholder="æ¥ä»·"><template #addonAfter>å
</template></a-input-number></a-form-item> |
| | | <a-form-item label="æ¥ä»·åä»·" required><a-input-number v-model:value="quoteForm.quotePrice" :min="0" class="!w-full" placeholder="æ¥ä»·"><template #addonAfter>å
</template></a-input-number></a-form-item> |
| | | </a-col> |
| | | <a-col :span="8"> |
| | | <a-form-item label="ç¨ç(%)"><a-input-number v-model:value="quoteForm.taxRate" :min="0" :max="100" class="!w-full" placeholder="å¦ 13" /></a-form-item> |
| | |
| | | </a-modal> |
| | | </template> |
| | | </Page> |
| | | </template> |
| | | </template> |
| | |
| | | id: data.id!, |
| | | tenderDesc: data.tenderDesc, |
| | | qualificationRequirements: data.qualificationRequirements, |
| | | blobIds: data.blobIds, |
| | | }); |
| | | } else { |
| | | await (formData.value?.id |
| | |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getTenderProject(data.id); |
| | | if (formData.value?.attachmentList?.length) { |
| | | // 转为ä¸ä¼ ç»ä»¶çæä»¶å¯¹è±¡ï¼æäº¤æ¶ formApi 伿 valueKey æåéä»¶ ID |
| | | (formData.value as Record<string, unknown>).blobIds = |
| | | formData.value.attachmentList.map((item) => ({ |
| | | uid: String(item.id), |
| | | name: item.name || '', |
| | | url: item.url || '', |
| | | status: 'done', |
| | | id: item.id, |
| | | })); |
| | | } |
| | | const partial = formData.value.tenderStatus !== undefined |
| | | && formData.value.tenderStatus !== TENDER_STATUS.DRAFT; |
| | | await formApi.setValues({ ...formData.value, _partial: partial || undefined }); |
| | |
| | | const defineVars: Record<string, string> = { |
| | | __APP_LOGO__: JSON.stringify(appLogo), |
| | | __APP_FAVICON__: JSON.stringify(appFavicon), |
| | | __APP_NAME__: JSON.stringify(company?.name ?? ''), |
| | | }; |
| | | if (!isDev && appApiUrl) { |
| | | defineVars['import.meta.env.VITE_GLOB_API_URL'] = JSON.stringify(appApiUrl); |
| | |
| | | proxy: { |
| | | "/admin-api": { |
| | | changeOrigin: true, |
| | | target: "http://192.168.0.10:48080", |
| | | target: "http://36.213.180.217:9501", |
| | | ws: true, |
| | | }, |
| | | }, |