已修改20个文件
1282 ■■■■ 文件已修改
.env.production 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
company.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy.bat 620 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pro/task/index.ts 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pro/workorder/index.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user/index.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/constants/src/dict-enum.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/data.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/data.ts 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/index.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/modules/schedule-form.vue 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/modules/task-list.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/data.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/index.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/TaskListPanel.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/TaskDetailTab.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/modules/po-select-dialog.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production
@@ -3,9 +3,9 @@
VITE_BASE=/
# 请求路径
VITE_BASE_URL=http://36.213.180.217:9501
VITE_BASE_URL=http://36.213.90.123:9501
# 接口地址
VITE_GLOB_API_URL=http://36.213.180.217:9501/admin-api
VITE_GLOB_API_URL=http://36.213.90.123:9501/admin-api
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
company.config.ts
@@ -21,7 +21,7 @@
    name: '测试公司',
    logo: '/logos/TEST.png',
    favicon: '/logos/TEST.ico',
    apiUrl: 'http://36.213.180.217:9501/admin-api',
    apiUrl: 'http://36.213.90.123:9501/admin-api',
  },
  JHHG: {
    name: '宁夏九泓化工',
deploy.bat
@@ -1,169 +1,451 @@
@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
@echo off
setlocal EnableExtensions EnableDelayedExpansion
REM ============================================================
REM Vue Frontend Deploy Script
REM ============================================================
REM =========================
REM Configuration
REM =========================
set "COMPANY=TEST"
set "REMOTE_USER=root"
set "REMOTE_HOST=36.213.90.123"
set "REMOTE_PORT=65022"
set "PASSWORD=Xdrjkj@10086#"
set "ZIP_FILE=dist.zip"
set "REMOTE_DIR=/data/project/mom/vue/test/dist"
set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3"
set "RETRY_COUNT=3"
set "SSHPASS_DIR=%~dp0bin"
REM ============================================================
REM Start
REM ============================================================
echo.
echo ============================================================
echo              Vue Dist Auto Deploy
echo ============================================================
echo.
echo Company    : %COMPANY%
echo Server     : %REMOTE_USER%@%REMOTE_HOST%
echo Port       : %REMOTE_PORT%
echo Remote Dir : %REMOTE_DIR%
echo Package    : %ZIP_FILE%
echo.
REM ============================================================
REM Check sshpass
REM ============================================================
echo [1/7] Checking sshpass...
if exist "%SSHPASS_DIR%\sshpass.exe" (
    set "PATH=%SSHPASS_DIR%;%PATH%"
)
where sshpass >nul 2>&1
if not errorlevel 1 (
    echo [OK] sshpass found.
    goto CHECK_PNPM
)
echo [WARN] sshpass not found.
if not exist "%SSHPASS_DIR%" (
    mkdir "%SSHPASS_DIR%" >nul 2>&1
)
if exist "%SSHPASS_DIR%\sshpass.exe" (
    set "PATH=%SSHPASS_DIR%;%PATH%"
    echo [OK] sshpass found.
    goto CHECK_PNPM
)
echo [INFO] Downloading sshpass...
powershell -NoProfile -ExecutionPolicy Bypass -Command "$url='https://github.com/xhcoding/sshpass-win32/releases/download/v1.0.3/sshpass.exe'; $out='%SSHPASS_DIR%\sshpass.exe'; Invoke-WebRequest -Uri $url -OutFile $out"
if errorlevel 1 (
    echo [ERROR] Failed to download sshpass.
    echo [ERROR] Please install sshpass manually.
    goto ERROR_EXIT
)
set "PATH=%SSHPASS_DIR%;%PATH%"
echo [OK] sshpass installed.
REM ============================================================
REM Check pnpm
REM ============================================================
:CHECK_PNPM
echo.
echo [2/7] Checking pnpm...
where pnpm >nul 2>&1
if errorlevel 1 (
    echo [ERROR] pnpm not found.
    echo [HINT] npm install -g pnpm
    goto ERROR_EXIT
)
call pnpm --version
if errorlevel 1 (
    echo [ERROR] pnpm execution failed.
    goto ERROR_EXIT
)
REM ============================================================
REM Check node
REM ============================================================
echo.
echo [3/7] Checking Node.js...
where node >nul 2>&1
if errorlevel 1 (
    echo [ERROR] Node.js not found.
    goto ERROR_EXIT
)
call node --version
if errorlevel 1 (
    echo [ERROR] Node.js execution failed.
    goto ERROR_EXIT
)
REM ============================================================
REM Check node_modules
REM ============================================================
if not exist "node_modules" (
    echo.
    echo [INFO] node_modules not found.
    echo [INFO] Running pnpm install...
    call pnpm install
    if errorlevel 1 (
        echo [ERROR] pnpm install failed.
        goto ERROR_EXIT
    )
    echo [OK] pnpm install completed.
) else (
    echo [OK] node_modules exists.
)
REM ============================================================
REM Build
REM ============================================================
echo.
echo [4/7] Building project...
if exist "%ZIP_FILE%" (
    echo.
    echo [WARN] %ZIP_FILE% already exists.
    set "REBUILD="
    set /p "REBUILD=Rebuild? Y/N (default Y): "
    if /I "!REBUILD!"=="N" (
        echo [INFO] Skip build.
        goto CHECK_ZIP
    )
)
echo [INFO] Company: %COMPANY%
echo [INFO] Command: pnpm run build
echo.
set "VITE_COMPANY=%COMPANY%"
call pnpm run build
if errorlevel 1 (
    echo.
    echo [ERROR] Build failed.
    goto ERROR_EXIT
)
echo.
echo [OK] Build completed.
REM ============================================================
REM Check ZIP
REM ============================================================
:CHECK_ZIP
echo.
echo [5/7] Checking package...
if not exist "%ZIP_FILE%" (
    echo [ERROR] %ZIP_FILE% not found.
    echo [HINT] Please check whether your build generates dist.zip.
    goto ERROR_EXIT
)
for %%F in ("%ZIP_FILE%") do set "ZIP_BYTES=%%~zF"
set /a ZIP_MB=ZIP_BYTES/1024/1024
echo [OK] Package found.
echo [INFO] File size: !ZIP_BYTES! bytes
echo [INFO] File size: !ZIP_MB! MB
REM ============================================================
REM Set sshpass password
REM ============================================================
set "SSHPASS=%PASSWORD%"
REM ============================================================
REM Test SSH
REM ============================================================
echo.
echo [6/7] Testing SSH connection...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "echo SSH_OK"
if errorlevel 1 (
    echo [ERROR] SSH connection failed.
    goto ERROR_EXIT
)
echo [OK] SSH connection successful.
REM ============================================================
REM Create remote directory
REM ============================================================
echo.
echo [INFO] Creating remote directory...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "mkdir -p '%REMOTE_DIR%'"
if errorlevel 1 (
    echo [ERROR] Failed to create remote directory.
    goto ERROR_EXIT
)
echo [OK] Remote directory ready.
REM ============================================================
REM Upload temporary file
REM ============================================================
echo.
echo [INFO] Uploading package...
set "REMOTE_TMP=%REMOTE_DIR%/.dist.zip.uploading"
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "rm -f '%REMOTE_TMP%'"
sshpass -e scp -P %REMOTE_PORT% %SSH_OPTS% "%ZIP_FILE%" %REMOTE_USER%@%REMOTE_HOST%:"%REMOTE_TMP%"
if errorlevel 1 (
    echo [ERROR] Upload failed.
    goto ERROR_EXIT
)
echo [OK] Upload completed.
REM ============================================================
REM Check remote file
REM ============================================================
echo.
echo [INFO] Checking remote package...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "test -f '%REMOTE_TMP%'"
if errorlevel 1 (
    echo [ERROR] Remote package does not exist.
    goto ERROR_EXIT
)
echo [OK] Remote package exists.
REM ============================================================
REM Compare file size
REM ============================================================
echo.
echo [INFO] Checking package size...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "stat -c %%s '%REMOTE_TMP%'"
echo.
REM ============================================================
REM Rename temporary file
REM ============================================================
echo [INFO] Activating package...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "mv -f '%REMOTE_TMP%' '%REMOTE_DIR%/%ZIP_FILE%'"
if errorlevel 1 (
    echo [ERROR] Failed to activate package.
    goto ERROR_EXIT
)
echo [OK] Package activated.
REM ============================================================
REM Check unzip
REM ============================================================
echo.
echo [INFO] Checking unzip...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "command -v unzip"
if errorlevel 1 (
    echo [ERROR] unzip is not installed on server.
    echo.
    echo CentOS/RHEL:
    echo yum install -y unzip
    echo.
    echo Ubuntu/Debian:
    echo apt install -y unzip
    goto ERROR_EXIT
)
echo [OK] unzip found.
REM ============================================================
REM Test ZIP
REM ============================================================
echo.
echo [INFO] Testing remote ZIP...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "cd '%REMOTE_DIR%' && unzip -t '%ZIP_FILE%'"
if errorlevel 1 (
    echo.
    echo [ERROR] ZIP test failed.
    echo [INFO] Remote ZIP has been kept.
    goto ERROR_EXIT
)
echo [OK] ZIP test passed.
REM ============================================================
REM Extract
REM ============================================================
echo.
echo [INFO] Extracting package...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "cd '%REMOTE_DIR%' && unzip -o '%ZIP_FILE%'"
if errorlevel 1 (
    echo.
    echo [ERROR] Remote unzip failed.
    echo [INFO] Remote ZIP has been kept.
    goto ERROR_EXIT
)
echo.
echo [OK] Extract completed.
REM ============================================================
REM Remove ZIP
REM ============================================================
echo.
echo [INFO] Removing remote ZIP...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "rm -f '%REMOTE_DIR%/%ZIP_FILE%'"
if errorlevel 1 (
    echo [WARN] Failed to remove remote ZIP.
) else (
    echo [OK] Remote ZIP removed.
)
REM ============================================================
REM Success
REM ============================================================
echo.
echo ============================================================
echo                 DEPLOY SUCCESS
echo ============================================================
echo.
echo Company    : %COMPANY%
echo Server     : %REMOTE_HOST%:%REMOTE_PORT%
echo Remote Dir : %REMOTE_DIR%
echo Package    : %ZIP_FILE%
echo Size       : !ZIP_MB! MB
echo.
echo Deployment completed successfully.
echo.
goto END
REM ============================================================
REM Error
REM ============================================================
:ERROR_EXIT
echo.
echo ============================================================
echo                  DEPLOY FAILED
echo ============================================================
echo.
echo Please check the error message above.
echo.
goto END
REM ============================================================
REM End
REM ============================================================
:END
pause
endlocal
src/api/mes/pro/task/index.ts
@@ -1,8 +1,21 @@
import type { PageParam, PageResult } from '@vben/request';
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesProTaskApi {
  export interface PauseRecord {
    id: number;
    pauseTime: string;
    pauseUserId: number;
    pauseUserName: string;
    reason: string;
    remark?: string;
    resumeTime?: string;
    resumeUserId?: number;
    resumeUserName?: string;
    taskId: number;
  }
  /** MES 生产任务 */
  export interface Task {
    id?: number;
@@ -41,6 +54,9 @@
    checkFlag?: boolean; // 是否质检(派生自工艺路线工序)
    backflushFlag?: boolean; // 是否倒冲(派生自工艺路线工序)
    remark?: string; // 备注
    pauseRecords?: PauseRecord[]; // 停工记录
    reportableQuantity?: number; // 可报工数量
    assigneeUserIds?: number[]; // 报工人
  }
  /** MES 生产任务分页查询参数 */
@@ -174,3 +190,20 @@
    { params: { workstationId, quantity } },
  );
}
/** 生产任务停工请求参数 */
export interface MesProTaskPauseReqVO {
  id: number;
  reason: string;
  remark?: string;
}
/** 生产任务停工 */
export function pauseTask(data: MesProTaskPauseReqVO) {
  return requestClient.put('/mes/pro/task/pause', data);
}
/** 生产任务复工 */
export function resumeTask(data: { id: number }) {
  return requestClient.put('/mes/pro/task/resume', data);
}
src/api/mes/pro/workorder/index.ts
@@ -1,4 +1,4 @@
import type { PageParam, PageResult } from '@vben/request';
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
@@ -34,6 +34,10 @@
    finishDate?: number; // 完成时间
    cancelDate?: number; // 取消时间
    status?: number; // 订单状态
    scheduleStatus?: number; // 排产状态
    productionStatus?: number; // 生产状态
    processProgressList?: ProcessProgress[]; // 工序生产进度
    completionProgress?: number; // 完成进度
    remark?: string; // 备注
    createTime?: number; // 创建时间
  }
@@ -59,6 +63,14 @@
  );
}
/** 获得生产工单调度状态分页 */
export function getWorkOrderSchedulePage(params: MesProWorkOrderApi.PageParams) {
  return requestClient.get<PageResult<MesProWorkOrderApi.WorkOrder>>(
      '/mes/pro/work-order/schedule-page',
      { params },
  );
}
/** 查询生产订单详情 */
export function getWorkOrder(id: number) {
  return requestClient.get<MesProWorkOrderApi.WorkOrder>(
src/api/system/user/index.ts
@@ -114,3 +114,10 @@
    params: { nickname },
  });
}
/** 通过角色标识获取用户精简信息列表 */
export function getSimpleUserListByRoleCode(code: string) {
  return requestClient.get<SystemUserApi.UserSimple[]>('/system/user/list-by-role-code', {
    params: { code },
  });
}
src/packages/constants/src/dict-enum.ts
@@ -239,6 +239,7 @@
  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', // 生产订单状态
  MES_PRO_WORK_ORDER_TYPE: 'mes_pro_work_order_type', // MES 工单类型
  MES_PRO_WORK_ORDER_SOURCE_TYPE: 'mes_pro_work_order_source_type', // MES 工单来源类型
  MES_PRO_TASK_STATUS: 'mes_pro_task_status', // MES 生产任务状态
src/views/mes/pro/feedback/data.ts
@@ -1,4 +1,4 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProFeedbackApi } from '#/api/mes/pro/feedback';
import type { MesProTaskApi } from '#/api/mes/pro/task';
@@ -264,6 +264,7 @@
            taskId: undefined,
            unitMeasureName: undefined,
            workstationId: undefined,
            reportableQuantity: undefined,
          });
        },
      },
@@ -298,6 +299,7 @@
            routeId: task.routeId,
            unitMeasureName: task.unitMeasureName,
            workstationId: task.workstationId,
            reportableQuantity: task.reportableQuantity,
          });
          // 工艺路线工序的 checkFlag 决定数量区域展示
          if (task.routeId && task.processId) {
@@ -377,6 +379,20 @@
      },
    },
    {
      fieldName: 'reportableQuantity',
      label: '可报工数量',
      component: 'InputNumber',
      componentProps: {
        class: 'w-full',
        disabled: true,
        precision: 2,
      },
      dependencies: {
        triggerFields: ['taskId'],
        show: (values) => !!values.taskId && ['create', 'update'].includes(formType),
      },
    },
    {
      fieldName: 'feedbackQuantity',
      label: '报工数量',
      component: 'InputNumber',
src/views/mes/pro/task/data.ts
@@ -1,5 +1,7 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { getSimpleUserListByRoleCode } from '#/api/system/user';
import type { MesProTaskApi } from '#/api/mes/pro/task';
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
@@ -130,15 +132,33 @@
      width: 80,
    },
    {
      field: 'status',
      title: '订单状态',
      width: 100,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.MES_PRO_WORK_ORDER_STATUS },
      },
    },
    {
      field: 'productionStatus',
      title: '生产状态',
      width: 100,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.ORDER_PRODUCTION_STATUS },
      },
    },
    {
      field: 'quantity',
      title: '订单数量',
      width: 100,
    },
    {
      field: 'quantityChanged',
      title: '调整数量',
      width: 100,
    },
    // {
    //   field: 'quantityChanged',
    //   title: '调整数量',
    //   width: 100,
    // },
    {
      field: 'quantityProduced',
      title: '已生产数量',
@@ -160,15 +180,7 @@
      width: 120,
      formatter: 'formatDate',
    },
    {
      field: 'status',
      title: '排产状态',
      width: 100,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.MES_PRO_WORK_ORDER_STATUS },
      },
    },
    {
      title: '操作',
      width: 160,
@@ -458,6 +470,20 @@
      },
    },
    {
      fieldName: 'assigneeUserIds',
      label: '报工人',
      component: 'ApiSelect',
      componentProps: {
        placeholder: '请选择报工人',
        api: getSimpleUserListByRoleCode,
        params: { code: 'production_submit_work' },
        labelField: 'nickname',
        valueField: 'id',
        mode: 'multiple',
      },
      rules: z.array(z.number()).min(1, '请选择报工人').default([]),
    },
    {
      fieldName: 'remark',
      label: '备注',
      component: 'Textarea',
src/views/mes/pro/task/index.vue
@@ -1,6 +1,6 @@
<script lang="ts" setup>
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
import {getWorkOrderSchedulePage, type MesProWorkOrderApi} from '#/api/mes/pro/workorder';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
@@ -11,7 +11,7 @@
  MesProWorkOrderTypeEnum,
} from '@vben/constants';
import { Button, Card, message } from 'ant-design-vue';
import { Button, Card, message, Tabs } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getGanttTaskList, previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task';
@@ -27,6 +27,8 @@
const ganttTasks = ref<any[]>([]); // 甘特图预览数据
const archiveVisible = ref(false);
const archiveProductCode = ref('');
const activeTab = ref('PENDING'); // 默认选中待排产
const [ScheduleModal, scheduleModalApi] = useVbenModal({
  connectedComponent: ScheduleForm,
@@ -106,10 +108,19 @@
    proxyConfig: {
      ajax: {
        query: async ({ page }, formValues) => {
          return await getWorkOrderPage({
          let scheduleStatus: number | undefined = undefined;
          if (activeTab.value === 'PENDING') {
            scheduleStatus = 0; // 待排产
          } else if (activeTab.value === 'FINISHED') {
            scheduleStatus = 1; // 已完成
          }
          // activeTab === 'ALL' 时不传
          return await getWorkOrderSchedulePage({
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            status: MesProWorkOrderStatusEnum.CONFIRMED,
            scheduleStatus,
            type: MesProWorkOrderTypeEnum.SELF,
            ...formValues,
          });
@@ -147,7 +158,17 @@
      <GanttChart :height="350" :readonly="true" :tasks="ganttTasks" />
    </Card>
    <Grid table-title="待排产订单">
    <Grid>
      <template #table-title>
        <div class="flex flex-col gap-2">
          <span class="text-base font-medium">生产订单列表</span>
          <Tabs v-model:activeKey="activeTab" @change="handleRefresh" :tabBarStyle="{ margin: 0, borderBottom: 0 }">
            <Tabs.TabPane key="ALL" tab="全部" />
            <Tabs.TabPane key="PENDING" tab="未完成" />
            <Tabs.TabPane key="FINISHED" tab="已完成" />
          </Tabs>
        </div>
      </template>
      <template #toolbar-tools>
        <TableAction
          :actions="[
src/views/mes/pro/task/modules/schedule-form.vue
@@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" setup>
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
import type { MesProWorkOrderProcessApi } from '#/api/mes/pro/workorder/process';
@@ -50,16 +50,19 @@
    message.warning('当前订单未配置工序,请先在工单中维护工序信息');
    return;
  }
  // 确定工艺路线ID:工单路线 → 产品路线
  let tempRouteId = 0;
  if (workOrder.value?.routeId) {
    tempRouteId = workOrder.value.routeId;
  } else {
    const processes = await getRouteProcessListByProduct(productId);
    tempRouteId = processes?.[0]?.routeId ?? 0;
  }
  currentRouteId.value = tempRouteId;
  routeProcessList.value = [...woProcesses].toSorted(
    (a, b) => (a.sort ?? 0) - (b.sort ?? 0),
  );
  // 确定工艺路线ID:工单路线 → 产品路线
  if (workOrder.value?.routeId) {
    currentRouteId.value = workOrder.value.routeId;
  } else {
    const processes = await getRouteProcessListByProduct(productId);
    currentRouteId.value = processes?.[0]?.routeId ?? 0;
  }
}
/** 完成订单 */
@@ -131,22 +134,22 @@
          :title="rp.processName"
        />
      </Steps>
      <Card
        v-for="(rp, index) in routeProcessList"
        v-show="activeProcessStep === index"
        :key="rp.processId"
        class="mx-4"
      >
        <TaskList
          :color-code="rp.colorCode"
          :disabled="isReadonly"
          :item-id="rp.outputItemId || workOrder.productId"
          :process-id="rp.processId!"
          :route-id="currentRouteId"
          :work-order-id="workOrder.id!"
          :work-order-quantity="workOrder.quantity"
        />
      </Card>
      <template v-for="(rp, index) in routeProcessList" :key="rp.processId">
        <Card
          v-if="activeProcessStep === index"
          class="mx-4"
        >
          <TaskList
            :color-code="rp.colorCode"
            :disabled="isReadonly"
            :item-id="rp.outputItemId || workOrder.productId"
            :process-id="rp.processId!"
            :route-id="currentRouteId"
            :work-order-id="workOrder.id!"
            :work-order-quantity="workOrder.quantity"
          />
        </Card>
      </template>
    </template>
    <template #prepend-footer>
      <div class="flex flex-auto items-center gap-2">
src/views/mes/pro/task/modules/task-list.vue
@@ -110,6 +110,8 @@
  () => props.processId,
  () => handleRefresh(),
);
</script>
<template>
src/views/mes/pro/workorder/data.ts
@@ -1,4 +1,4 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
import type { MesProWorkOrderBomApi } from '#/api/mes/pro/workorder/bom';
@@ -413,6 +413,27 @@
      },
    },
    {
      field: 'productionStatus',
      title: '生产状态',
      width: 100,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.ORDER_PRODUCTION_STATUS },
      },
    },
    {
      field: 'processProgressList',
      title: '工序生产进度',
      width: 350,
      slots: { default: 'processProgressList' },
    },
    {
      field: 'completionProgress',
      title: '完成进度',
      width: 200,
      slots: { default: 'completionProgress' },
    },
    {
      field: 'createTime',
      title: '创建时间',
      width: 180,
src/views/mes/pro/workorder/index.vue
@@ -11,7 +11,7 @@
  } from "@vben/constants";
  import { downloadFileFromBlobPart } from "@vben/utils";
  import { Button, message } from "ant-design-vue";
  import { Button, message, Progress, Steps, Popover, Modal } from "ant-design-vue";
  import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table";
  import {
@@ -63,6 +63,21 @@
  /** 完成订单 */
  function handleFinish(row: MesProWorkOrderApi.WorkOrder) {
    formModalApi.setData({ formType: "finish", id: row.id }).open();
  }
  /** 完成订单前校验 */
  function handleCheckFinish(row: MesProWorkOrderApi.WorkOrder) {
    if (row.productionStatus !== 2) {
      Modal.confirm({
        title: '操作确认',
        content: '该订单未完成,是否确认要继续完成?',
        onOk() {
          handleFinish(row);
        },
      });
    } else {
      handleFinish(row);
    }
  }
  /** 删除订单 */
@@ -139,6 +154,13 @@
      },
    } as VxeTableGridOptions<MesProWorkOrderApi.WorkOrder>,
  });
  /** 获取工序节点状态样式类 */
  function getProcessNodeStatus(percent: number) {
    if (percent >= 100) return 'completed';
    if (percent > 0) return 'in-progress';
    return 'pending';
  }
</script>
<template>
@@ -168,6 +190,29 @@
                @click="handleDetail(row)">
          {{ row.code }}
        </Button>
      </template>
      <template #processProgressList="{ row }">
        <div v-if="row.processProgressList && row.processProgressList.length > 0" class="process-node-list">
          <div
            v-for="(item, index) in row.processProgressList"
            :key="index"
            class="process-node-item"
          >
            <div class="process-node-dot" :class="getProcessNodeStatus(item.progressPercent || item.progress || 0)"></div>
            <div class="process-node-info">
              <span class="process-node-name" :title="item.processName ">{{ item.processName  }}</span>
              <span class="process-node-percent">{{ item.progress || 0 }}%</span>
            </div>
            <div v-if="index !== row.processProgressList.length - 1" class="process-node-line"></div>
          </div>
        </div>
        <span v-else>-</span>
      </template>
      <template #completionProgress="{ row }">
        <div style="display: flex; align-items: center; gap: 8px; width: 100%;">
          <Progress :percent="row.completionProgress || 0" :show-info="false" size="small" style="flex: 1; margin: 0;" />
          <span style="white-space: nowrap; min-width: 40px; text-align: left;">{{ row.completionProgress || 0 }}%</span>
        </div>
      </template>
      <template #actions="{ row }">
        <TableAction :actions="[
@@ -207,7 +252,7 @@
              ifShow:
                row.status === MesProWorkOrderStatusEnum.CONFIRMED &&
                !!row.quantityProduced,
              onClick: handleFinish.bind(null, row),
              onClick: handleCheckFinish.bind(null, row),
            },
            {
              label: '取消',
@@ -238,3 +283,79 @@
    <ArchiveDetail ref="archiveDetailRef" />
  </Page>
</template>
<style scoped>
.process-node-list {
  display: flex;
  align-items: center;
  overflow-x: auto;
  overflow-y: hidden;
  max-width: 100%;
  padding-bottom: 4px;
}
/* Custom scrollbar to make it sleek */
.process-node-list::-webkit-scrollbar {
  height: 4px;
}
.process-node-list::-webkit-scrollbar-thumb {
  background-color: #d9d9d9;
  border-radius: 4px;
}
.process-node-list::-webkit-scrollbar-track {
  background: transparent;
}
.process-node-item {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.process-node-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 6px;
}
.process-node-dot.completed {
  background-color: #52c41a;
}
.process-node-dot.in-progress {
  background-color: #1890ff;
}
.process-node-dot.pending {
  background-color: #d9d9d9;
}
.process-node-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-right: 12px;
  font-size: 12px;
  line-height: 1.2;
}
.process-node-name {
  color: #333;
  max-width: 80px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.process-node-percent {
  color: #888;
  font-size: 11px;
}
.process-node-line {
  width: 20px;
  height: 1px;
  background-color: #e8e8e8;
  margin-right: 12px;
}
</style>
src/views/mes/workbench/components/TaskListPanel.vue
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { PendingTask } from '../../types';
import { computed, ref } from 'vue';
@@ -15,7 +15,13 @@
const emit = defineEmits<{
  select: [task: PendingTask];
  refresh: [];
}>();
import { pauseTask, resumeTask } from '#/api/mes/pro/task';
import { Form, FormItem, Input, Modal, message, Button } from 'ant-design-vue';
import { confirm } from '@vben/common-ui';
import type {PendingTask} from "#/views/mes/workbench/types";
const searchKeyword = ref('');
@@ -41,6 +47,50 @@
  if (rate >= 100) return '#52c41a';
  if (rate >= 60) return '#1890ff';
  return '#faad14';
}
const pauseModalVisible = ref(false);
const pauseFormRef = ref();
const pauseFormState = ref({ id: 0, reason: '', remark: '' });
const pauseLoading = ref(false);
const pauseRules = {
  reason: [{ required: true, message: '请输入停工原因', trigger: 'blur' }],
};
function handlePause(item: PendingTask, event: Event) {
  event.stopPropagation();
  pauseFormState.value = { id: item.id!, reason: '', remark: '' };
  pauseModalVisible.value = true;
}
async function submitPause() {
  await pauseFormRef.value?.validate();
  pauseLoading.value = true;
  try {
    await pauseTask(pauseFormState.value);
    message.success('停工成功');
    pauseModalVisible.value = false;
    emit('refresh');
  } finally {
    pauseLoading.value = false;
  }
}
async function handleResume(item: PendingTask, event: Event) {
  event.stopPropagation();
  try {
    await confirm('确认要复工该任务吗?');
  } catch {
    return;
  }
  try {
    await resumeTask({ id: item.id! });
    message.success('复工成功');
    emit('refresh');
  } catch (error: any) {
    // Error is handled by request interceptor
  }
}
</script>
@@ -99,6 +149,27 @@
            >
              {{ item.feedbackRate ?? 0 }}%
            </span>
            <div class="task-card__actions">
              <Button
                v-if="item.status === 9"
                type="primary"
                size="small"
                class="task-card__action-btn resume-btn"
                @click="handleResume(item, $event)"
              >
                复工
              </Button>
              <Button
                v-else
                type="primary"
                danger
                size="small"
                class="task-card__action-btn pause-btn"
                @click="handlePause(item, $event)"
              >
                停工
              </Button>
            </div>
          </div>
          <div class="task-card__progress-row">
@@ -144,6 +215,42 @@
        </div>
      </div>
    </div>
    <Modal
      v-model:open="pauseModalVisible"
      title="生产任务停工"
      @ok="submitPause"
      :confirm-loading="pauseLoading"
      destroy-on-close
    >
      <Form
        ref="pauseFormRef"
        :model="pauseFormState"
        :rules="pauseRules"
        :label-col="{ span: 4 }"
        :wrapper-col="{ span: 20 }"
        style="margin-top: 20px"
      >
        <FormItem label="停工原因" name="reason">
          <Input.TextArea
            v-model:value="pauseFormState.reason"
            placeholder="请输入停工原因(如:设备故障、缺料等)"
            :rows="3"
            :maxlength="100"
            show-count
          />
        </FormItem>
        <FormItem label="备注" name="remark">
          <Input.TextArea
            v-model:value="pauseFormState.remark"
            placeholder="请输入备注说明"
            :rows="2"
            :maxlength="200"
            show-count
          />
        </FormItem>
      </Form>
    </Modal>
  </section>
</template>
@@ -290,6 +397,32 @@
  font-variant-numeric: tabular-nums;
}
.task-card__actions {
  display: flex;
  align-items: center;
  margin-left: auto;
}
.task-card__action-btn {
  font-size: 13px;
  border-radius: 4px;
}
.pause-btn {
  box-shadow: 0 2px 4px rgb(255 77 79 / 20%);
}
.resume-btn {
  background-color: #52c41a;
  border-color: #52c41a;
  box-shadow: 0 2px 4px rgb(82 196 26 / 20%);
}
.resume-btn:hover,
.resume-btn:focus {
  background-color: #73d13d;
  border-color: #73d13d;
}
.task-card__progress-row {
  display: flex;
  align-items: baseline;
src/views/mes/workbench/components/tabs/TaskDetailTab.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { PendingTask } from '../../../types';
import { Descriptions, Progress, Tag } from 'ant-design-vue';
import { Descriptions, Progress, Tag, Timeline, TimelineItem } from 'ant-design-vue';
defineOptions({ name: 'WorkbenchTaskDetailTab' });
@@ -91,6 +91,48 @@
        </Descriptions.Item>
        <Descriptions.Item label="备注" :span="2">{{ task.remark ?? '—' }}</Descriptions.Item>
      </Descriptions>
      <!-- 停工记录 -->
      <div v-if="task.pauseRecords && task.pauseRecords.length > 0" class="detail-pause-records">
        <div class="detail-pause-records__title">停工历史记录</div>
        <Timeline class="pause-timeline">
          <TimelineItem
            v-for="record in task.pauseRecords"
            :key="record.id"
            :color="record.resumeTime ? 'green' : 'red'"
          >
            <div class="pause-timeline-item">
              <div class="pause-timeline-time">
                <span class="time-label">{{ record.pauseTime }}</span>
                <span class="time-separator">至</span>
                <span class="time-label" :class="{ 'is-ongoing': !record.resumeTime }">
                  {{ record.resumeTime || '正在停工' }}
                </span>
              </div>
              <div class="pause-timeline-content">
                <div class="pause-info-row">
                  <span class="info-label">操作人员:</span>
                  <span class="info-value">
                    {{ record.pauseUserName }} (停工)
                    <template v-if="record.resumeTime">
                      <span style="margin: 0 4px; color: #d9d9d9">|</span>
                      {{ record.resumeUserName }} (复工)
                    </template>
                  </span>
                </div>
                <div class="pause-info-row">
                  <span class="info-label">停工原因:</span>
                  <span class="info-value">{{ record.reason }}</span>
                </div>
                <div class="pause-info-row" v-if="record.remark">
                  <span class="info-label">备注:</span>
                  <span class="info-value">{{ record.remark }}</span>
                </div>
              </div>
            </div>
          </TimelineItem>
        </Timeline>
      </div>
    </template>
  </div>
</template>
@@ -187,4 +229,86 @@
    font-size: 14px;
  }
}
.detail-pause-records {
  margin-top: 24px;
}
.detail-pause-records__title {
  font-size: 16px;
  font-weight: 600;
  color: #1a1a1a;
  margin-bottom: 20px;
}
.pause-timeline {
  padding-top: 6px;
  padding-left: 4px;
}
.pause-timeline-item {
  background: #fafafa;
  border: 1px solid #f0f0f0;
  border-radius: 8px;
  padding: 14px 18px;
  margin-top: -6px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 2%);
  transition: all 0.3s ease;
  &:hover {
    box-shadow: 0 4px 12px rgb(0 0 0 / 5%);
    border-color: #e6d8d8;
  }
}
.pause-timeline-time {
  font-size: 13px;
  color: #595959;
  font-weight: 500;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.time-label {
  background: #e6f7ff;
  color: #1890ff;
  padding: 2px 8px;
  border-radius: 4px;
  font-variant-numeric: tabular-nums;
}
.time-label.is-ongoing {
  background: #fff1f0;
  color: #f5222d;
}
.time-separator {
  color: #bfbfbf;
  font-size: 12px;
}
.pause-timeline-content {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pause-info-row {
  font-size: 13px;
  display: flex;
  line-height: 1.5;
}
.pause-info-row .info-label {
  color: #8c8c8c;
  width: 70px;
  flex-shrink: 0;
}
.pause-info-row .info-value {
  color: #262626;
  flex: 1;
}
</style>
src/views/mes/workbench/index.vue
@@ -215,6 +215,7 @@
          :selected-id="selectedTask?.id"
          :loading="taskLoading"
          @select="handleSelectTask"
          @refresh="loadTaskList"
        />
      </aside>
src/views/wls/arrivalnotice/index.vue
@@ -155,13 +155,14 @@
      <template #toolbar-tools>
        <TableAction
          :actions="[
            {
              label: $t('ui.actionTitle.create', ['到货通知单']),
              type: 'primary',
              icon: ACTION_ICON.ADD,
              auth: ['mes:wm-arrival-notice:create'],
              onClick: handleCreate,
            },
            // 注释:隐藏新增到货通知单按钮
            // {
            //   label: $t('ui.actionTitle.create', ['到货通知单']),
            //   type: 'primary',
            //   icon: ACTION_ICON.ADD,
            //   auth: ['mes:wm-arrival-notice:create'],
            //   onClick: handleCreate,
            // },
            {
              label: '从采购订单生成',
              type: 'primary',
src/views/wls/arrivalnotice/modules/po-select-dialog.vue
@@ -117,7 +117,7 @@
</script>
<template>
  <Modal title="选择采购订单" class="w-2/3">
  <Modal title="选择采购订单" class="w-[1000px]">
    <Grid table-title="采购订单列表" />
  </Modal>
</template>
vite.config.ts
@@ -5,18 +5,24 @@
export default defineConfig(async ({ mode }) => {
  const env = loadEnv(mode, process.cwd(), 'VITE_');
  const companyKey = env.VITE_COMPANY || '';
  // deploy.bat 通过 set "VITE_COMPANY=xx" 传入,是进程环境变量,loadEnv 读不到,需优先取 process.env
  const companyKey = process.env.VITE_COMPANY || env.VITE_COMPANY || '';
  const company = companies[companyKey];
  const appLogo = company?.logo ?? '';
  const appFavicon = company?.favicon ?? '';
  const appApiUrl = company?.apiUrl ?? '';
  const appName = company?.name ?? '';
  const isDev = mode === 'development';
  console.log('[company]', companyKey, '-> name:', company?.name, 'logo:', appLogo, 'api:', appApiUrl || '(from env)');
  if (appName) {
    // 覆盖浏览器标签页标题:Vite 的 resolveEnv 优先读 process.env,从而替换 index.html 里的 %VITE_APP_TITLE%
    process.env.VITE_APP_TITLE = appName;
  }
  console.log('[company]', companyKey, '-> name:', appName, 'logo:', appLogo, 'api:', appApiUrl || '(from env)');
  const defineVars: Record<string, string> = {
    __APP_LOGO__: JSON.stringify(appLogo),
    __APP_FAVICON__: JSON.stringify(appFavicon),
    __APP_NAME__: JSON.stringify(company?.name ?? ''),
    __APP_NAME__: JSON.stringify(appName),
  };
  if (!isDev && appApiUrl) {
    defineVars['import.meta.env.VITE_GLOB_API_URL'] = JSON.stringify(appApiUrl);