liu
9 天以前 a7dc7b230fcc6abc6ee5a7bfadb90789b03d2ca8
Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0
已添加5个文件
已修改22个文件
1268 ■■■■■ 文件已修改
ZSJC.bat 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
company.config.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/leave/index.ts 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/qc/template/item/index.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/productissue/line/index.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/returnissue/line/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/leave/data.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/leave/detail/approve/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/leave/index.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/leave/modules/form.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/components/export-range-modal.vue 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/data.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/iqc/data.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/iqc/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/oqc/data.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/oqc/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/rqc/data.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/rqc/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/components/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/components/select-dialog.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/components/select.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/data.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/data.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/form.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/line-form.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/line-list.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ZSJC.bat
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,451 @@
@echo off
setlocal EnableExtensions EnableDelayedExpansion
REM ============================================================
REM Vue Frontend Deploy Script
REM ============================================================
REM =========================
REM Configuration
REM =========================
set "COMPANY=ZSJC"
set "REMOTE_USER=root"
set "REMOTE_HOST=116.172.73.239"
set "REMOTE_PORT=22"
set "PASSWORD=ecs-QGY8ecs-QGY8"
set "ZIP_FILE=dist.zip"
set "REMOTE_DIR=/home/vue/dist"
set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3"
set "RETRY_COUNT=3"
set "SSHPASS_DIR=%~dp0bin"
REM ============================================================
REM Start
REM ============================================================
echo.
echo ============================================================
echo              Vue Dist Auto Deploy
echo ============================================================
echo.
echo Company    : %COMPANY%
echo Server     : %REMOTE_USER%@%REMOTE_HOST%
echo Port       : %REMOTE_PORT%
echo Remote Dir : %REMOTE_DIR%
echo Package    : %ZIP_FILE%
echo.
REM ============================================================
REM Check sshpass
REM ============================================================
echo [1/7] Checking sshpass...
if exist "%SSHPASS_DIR%\sshpass.exe" (
    set "PATH=%SSHPASS_DIR%;%PATH%"
)
where sshpass >nul 2>&1
if not errorlevel 1 (
    echo [OK] sshpass found.
    goto CHECK_PNPM
)
echo [WARN] sshpass not found.
if not exist "%SSHPASS_DIR%" (
    mkdir "%SSHPASS_DIR%" >nul 2>&1
)
if exist "%SSHPASS_DIR%\sshpass.exe" (
    set "PATH=%SSHPASS_DIR%;%PATH%"
    echo [OK] sshpass found.
    goto CHECK_PNPM
)
echo [INFO] Downloading sshpass...
powershell -NoProfile -ExecutionPolicy Bypass -Command "$url='https://github.com/xhcoding/sshpass-win32/releases/download/v1.0.3/sshpass.exe'; $out='%SSHPASS_DIR%\sshpass.exe'; Invoke-WebRequest -Uri $url -OutFile $out"
if errorlevel 1 (
    echo [ERROR] Failed to download sshpass.
    echo [ERROR] Please install sshpass manually.
    goto ERROR_EXIT
)
set "PATH=%SSHPASS_DIR%;%PATH%"
echo [OK] sshpass installed.
REM ============================================================
REM Check pnpm
REM ============================================================
:CHECK_PNPM
echo.
echo [2/7] Checking pnpm...
where pnpm >nul 2>&1
if errorlevel 1 (
    echo [ERROR] pnpm not found.
    echo [HINT] npm install -g pnpm
    goto ERROR_EXIT
)
call pnpm --version
if errorlevel 1 (
    echo [ERROR] pnpm execution failed.
    goto ERROR_EXIT
)
REM ============================================================
REM Check node
REM ============================================================
echo.
echo [3/7] Checking Node.js...
where node >nul 2>&1
if errorlevel 1 (
    echo [ERROR] Node.js not found.
    goto ERROR_EXIT
)
call node --version
if errorlevel 1 (
    echo [ERROR] Node.js execution failed.
    goto ERROR_EXIT
)
REM ============================================================
REM Check node_modules
REM ============================================================
if not exist "node_modules" (
    echo.
    echo [INFO] node_modules not found.
    echo [INFO] Running pnpm install...
    call pnpm install
    if errorlevel 1 (
        echo [ERROR] pnpm install failed.
        goto ERROR_EXIT
    )
    echo [OK] pnpm install completed.
) else (
    echo [OK] node_modules exists.
)
REM ============================================================
REM Build
REM ============================================================
echo.
echo [4/7] Building project...
if exist "%ZIP_FILE%" (
    echo.
    echo [WARN] %ZIP_FILE% already exists.
    set "REBUILD="
    set /p "REBUILD=Rebuild? Y/N (default Y): "
    if /I "!REBUILD!"=="N" (
        echo [INFO] Skip build.
        goto CHECK_ZIP
    )
)
echo [INFO] Company: %COMPANY%
echo [INFO] Command: pnpm run build
echo.
set "VITE_COMPANY=%COMPANY%"
call pnpm run build
if errorlevel 1 (
    echo.
    echo [ERROR] Build failed.
    goto ERROR_EXIT
)
echo.
echo [OK] Build completed.
REM ============================================================
REM Check ZIP
REM ============================================================
:CHECK_ZIP
echo.
echo [5/7] Checking package...
if not exist "%ZIP_FILE%" (
    echo [ERROR] %ZIP_FILE% not found.
    echo [HINT] Please check whether your build generates dist.zip.
    goto ERROR_EXIT
)
for %%F in ("%ZIP_FILE%") do set "ZIP_BYTES=%%~zF"
set /a ZIP_MB=ZIP_BYTES/1024/1024
echo [OK] Package found.
echo [INFO] File size: !ZIP_BYTES! bytes
echo [INFO] File size: !ZIP_MB! MB
REM ============================================================
REM Set sshpass password
REM ============================================================
set "SSHPASS=%PASSWORD%"
REM ============================================================
REM Test SSH
REM ============================================================
echo.
echo [6/7] Testing SSH connection...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "echo SSH_OK"
if errorlevel 1 (
    echo [ERROR] SSH connection failed.
    goto ERROR_EXIT
)
echo [OK] SSH connection successful.
REM ============================================================
REM Create remote directory
REM ============================================================
echo.
echo [INFO] Creating remote directory...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "mkdir -p '%REMOTE_DIR%'"
if errorlevel 1 (
    echo [ERROR] Failed to create remote directory.
    goto ERROR_EXIT
)
echo [OK] Remote directory ready.
REM ============================================================
REM Upload temporary file
REM ============================================================
echo.
echo [INFO] Uploading package...
set "REMOTE_TMP=%REMOTE_DIR%/.dist.zip.uploading"
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "rm -f '%REMOTE_TMP%'"
sshpass -e scp -P %REMOTE_PORT% %SSH_OPTS% "%ZIP_FILE%" %REMOTE_USER%@%REMOTE_HOST%:"%REMOTE_TMP%"
if errorlevel 1 (
    echo [ERROR] Upload failed.
    goto ERROR_EXIT
)
echo [OK] Upload completed.
REM ============================================================
REM Check remote file
REM ============================================================
echo.
echo [INFO] Checking remote package...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "test -f '%REMOTE_TMP%'"
if errorlevel 1 (
    echo [ERROR] Remote package does not exist.
    goto ERROR_EXIT
)
echo [OK] Remote package exists.
REM ============================================================
REM Compare file size
REM ============================================================
echo.
echo [INFO] Checking package size...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "stat -c %%s '%REMOTE_TMP%'"
echo.
REM ============================================================
REM Rename temporary file
REM ============================================================
echo [INFO] Activating package...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "mv -f '%REMOTE_TMP%' '%REMOTE_DIR%/%ZIP_FILE%'"
if errorlevel 1 (
    echo [ERROR] Failed to activate package.
    goto ERROR_EXIT
)
echo [OK] Package activated.
REM ============================================================
REM Check unzip
REM ============================================================
echo.
echo [INFO] Checking unzip...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "command -v unzip"
if errorlevel 1 (
    echo [ERROR] unzip is not installed on server.
    echo.
    echo CentOS/RHEL:
    echo yum install -y unzip
    echo.
    echo Ubuntu/Debian:
    echo apt install -y unzip
    goto ERROR_EXIT
)
echo [OK] unzip found.
REM ============================================================
REM Test ZIP
REM ============================================================
echo.
echo [INFO] Testing remote ZIP...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "cd '%REMOTE_DIR%' && unzip -t '%ZIP_FILE%'"
if errorlevel 1 (
    echo.
    echo [ERROR] ZIP test failed.
    echo [INFO] Remote ZIP has been kept.
    goto ERROR_EXIT
)
echo [OK] ZIP test passed.
REM ============================================================
REM Extract
REM ============================================================
echo.
echo [INFO] Extracting package...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "cd '%REMOTE_DIR%' && unzip -o '%ZIP_FILE%'"
if errorlevel 1 (
    echo.
    echo [ERROR] Remote unzip failed.
    echo [INFO] Remote ZIP has been kept.
    goto ERROR_EXIT
)
echo.
echo [OK] Extract completed.
REM ============================================================
REM Remove ZIP
REM ============================================================
echo.
echo [INFO] Removing remote ZIP...
sshpass -e ssh -p %REMOTE_PORT% %SSH_OPTS% %REMOTE_USER%@%REMOTE_HOST% "rm -f '%REMOTE_DIR%/%ZIP_FILE%'"
if errorlevel 1 (
    echo [WARN] Failed to remove remote ZIP.
) else (
    echo [OK] Remote ZIP removed.
)
REM ============================================================
REM Success
REM ============================================================
echo.
echo ============================================================
echo                 DEPLOY SUCCESS
echo ============================================================
echo.
echo Company    : %COMPANY%
echo Server     : %REMOTE_HOST%:%REMOTE_PORT%
echo Remote Dir : %REMOTE_DIR%
echo Package    : %ZIP_FILE%
echo Size       : !ZIP_MB! MB
echo.
echo Deployment completed successfully.
echo.
goto END
REM ============================================================
REM Error
REM ============================================================
:ERROR_EXIT
echo.
echo ============================================================
echo                  DEPLOY FAILED
echo ============================================================
echo.
echo Please check the error message above.
echo.
goto END
REM ============================================================
REM End
REM ============================================================
:END
pause
endlocal
company.config.ts
@@ -53,4 +53,10 @@
    favicon: '/logos/YZFXLogo.ico',
    apiUrl: 'http://42.63.71.140:9004/admin-api',
  },
  ZSJC: {
    name: '宁夏中盛建材科技有限公司',
    logo: '/logos/YZFXLogo.png',
    favicon: '/logos/YZFXLogo.ico',
    apiUrl: 'http://116.172.73.239:9001/admin-api',
  },
};
src/api/hrm/leave/index.ts
@@ -7,8 +7,8 @@
  /** è¯·å‡ç”³è¯· */
  export interface Leave {
    id?: number;
    employeeId?: number;
    employeeName?: string;
    userId?: number;
    userName?: string;
    deptName?: string;
    leaveType?: number;
    startTime?: string;
@@ -21,6 +21,12 @@
    processInstanceId?: string;
    remark?: string;
    createTime?: string;
  }
  /** å®¡æ‰¹æµç¨‹ */
  export interface ApproveProcess {
    key: string;
    name: string;
  }
}
@@ -53,8 +59,10 @@
}
/** æäº¤è¯·å‡ç”³è¯·ï¼ˆå‘起审批) */
export function submitLeave(id: number) {
  return requestClient.post(`/hrm/leave/submit?id=${id}`);
export function submitLeave(id: number, processDefinitionKey: string) {
  return requestClient.post('/hrm/leave/submit', null, {
    params: { id, processDefinitionKey },
  });
}
/** æ’¤é”€è¯·å‡ç”³è¯· */
@@ -64,5 +72,7 @@
/** èŽ·å–å¯ç”¨æµç¨‹å®šä¹‰åˆ—è¡¨ */
export function getLeaveApproveProcessDefinitionList() {
  return requestClient.get<any[]>('/hrm/leave/approve-process-definition-list');
  return requestClient.get<HrmLeaveApi.ApproveProcess[]>(
    '/hrm/leave/approve-process-definition-list',
  );
}
src/api/mes/qc/template/item/index.ts
@@ -21,6 +21,21 @@
  }
}
/** è´¨æ£€æ–¹æ¡ˆé€‰é¡¹ï¼ˆæ–¹æ¡ˆ-产品关联) */
export namespace MesQcTemplateItemOptionApi {
  export interface Option {
    id?: number; // äº§å“å…³è”编号
    templateId?: number; // è´¨æ£€æ–¹æ¡ˆ ID
    templateCode?: string; // è´¨æ£€æ–¹æ¡ˆç¼–号
    templateName?: string; // è´¨æ£€æ–¹æ¡ˆåç§°
    quantityCheck?: number; // æœ€ä½Žæ£€æµ‹æ•°
    quantityUnqualified?: number; // æœ€å¤§ä¸åˆæ ¼æ•°
    criticalRate?: number; // æœ€å¤§è‡´å‘½ç¼ºé™·çŽ‡ï¼ˆ%)
    majorRate?: number; // æœ€å¤§ä¸¥é‡ç¼ºé™·çŽ‡ï¼ˆ%)
    minorRate?: number; // æœ€å¤§è½»å¾®ç¼ºé™·çŽ‡ï¼ˆ%)
  }
}
/** æŸ¥è¯¢äº§å“å…³è”分页 */
export function getTemplateItemPage(
  params: PageParam & { templateId?: number },
@@ -52,3 +67,16 @@
export function deleteTemplateItem(id: number) {
  return requestClient.delete(`/mes/qc/template/item/delete?id=${id}`);
}
/** æ ¹æ®ç‰©æ–™æˆ–工单/任务和检测种类获取可用质检方案列表 */
export function getTemplateItemOptionList(params: {
  itemId?: number;
  qcType: number;
  taskId?: number;
  workOrderId?: number;
}) {
  return requestClient.get<MesQcTemplateItemOptionApi.Option[]>(
    '/mes/qc/template/item/list-by-item-and-type',
    { params },
  );
}
src/api/mes/wm/productissue/line/index.ts
@@ -69,3 +69,11 @@
    { params: { issueId } },
  );
}
/** æŸ¥è¯¢å¯é€€æ–™çš„领料单行列表(按生产订单过滤,仅返回已完成领料的记录) */
export function getReturnableIssueLines(workOrderId: number) {
  return requestClient.get<MesWmProductIssueLineApi.ProductIssueLine[]>(
      '/mes/wm/product-issue-line/returnable-list',
      { params: { workOrderId } },
  );
}
src/api/mes/wm/returnissue/line/index.ts
@@ -7,6 +7,7 @@
  export interface ReturnIssueLine {
    id?: number; // è¡Œç¼–号
    issueId?: number; // é€€æ–™å•编号
    issueLineId?: number; // é¢†æ–™å•行编号(关联来源领料记录)
    itemId?: number; // ç‰©æ–™ç¼–号
    itemCode?: string; // ç‰©æ–™ç¼–码
    itemName?: string; // ç‰©æ–™åç§°
src/views/hrm/leave/data.ts
@@ -2,8 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmLeaveApi } from '#/api/hrm/leave';
import { markRaw } from 'vue';
import { DICT_TYPE } from '#/packages/constants/src';
import { getDictOptions } from '#/packages/effects/hooks/src';
import { UserSelect } from '#/views/system/user/components';
/** è¡¨å•类型 */
export type FormType = 'create' | 'detail' | 'update';
@@ -18,6 +22,15 @@
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'userId',
      label: '申请人',
      component: markRaw(UserSelect),
      componentProps: {
        placeholder: '请选择申请人',
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'leaveType',
@@ -134,7 +147,7 @@
export function useGridColumns(): VxeTableGridOptions<HrmLeaveApi.Leave>['columns'] {
  return [
    { field: 'id', title: '申请编号', width: 80 },
    { field: 'employeeName', title: '申请人', minWidth: 100 },
    { field: 'userName', title: '申请人', minWidth: 100 },
    { field: 'deptName', title: '部门', minWidth: 120 },
    {
      field: 'leaveType',
src/views/hrm/leave/detail/approve/index.vue
@@ -30,7 +30,7 @@
  bordered: true,
  column: 2,
  schema: [
    { label: '员工姓名', field: 'employeeName', render: textRender },
    { label: '申请人', field: 'userName', render: textRender },
    { label: '所属部门', field: 'deptName', render: textRender },
    {
      label: '请假类型',
src/views/hrm/leave/index.vue
@@ -2,9 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmLeaveApi } from '#/api/hrm/leave';
import { ref } from 'vue';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { message, Tag } from 'ant-design-vue';
import { message, Modal, Select, Tag } from 'ant-design-vue';
import { DICT_TYPE } from '#/packages/constants/src';
import { getDictLabel } from '#/packages/effects/hooks/src';
@@ -13,6 +15,7 @@
import {
  cancelLeave,
  deleteLeave,
  getLeaveApproveProcessDefinitionList,
  getLeavePage,
  submitLeave,
} from '#/api/hrm/leave';
@@ -61,11 +64,34 @@
  }
}
/** æäº¤è¯·å‡ç”³è¯· */
const submitModalVisible = ref(false);
const currentRow = ref<HrmLeaveApi.Leave>();
const processList = ref<HrmLeaveApi.ApproveProcess[]>([]);
const selectedProcessKey = ref<string>();
const submitLoading = ref(false);
/** æäº¤è¯·å‡ç”³è¯·ï¼ˆå…ˆé€‰æ‹©å®¡æ‰¹æµç¨‹ï¼‰ */
async function handleSubmit(row: HrmLeaveApi.Leave) {
  await submitLeave(row.id!);
  message.success('提交成功');
  handleRefresh();
  processList.value = await getLeaveApproveProcessDefinitionList();
  currentRow.value = row;
  selectedProcessKey.value = undefined;
  submitModalVisible.value = true;
}
async function confirmSubmit() {
  if (!selectedProcessKey.value) {
    message.warning('请选择请假审批流程');
    return;
  }
  submitLoading.value = true;
  try {
    await submitLeave(currentRow.value!.id!, selectedProcessKey.value);
    message.success('提交成功');
    submitModalVisible.value = false;
    handleRefresh();
  } finally {
    submitLoading.value = false;
  }
}
/** æ’¤é”€è¯·å‡ç”³è¯· */
@@ -187,5 +213,19 @@
        />
      </template>
    </Grid>
    <Modal
      v-model:open="submitModalVisible"
      title="提交至协同办公"
      width="420px"
      :confirm-loading="submitLoading"
      @ok="confirmSubmit"
    >
      <Select
        v-model:value="selectedProcessKey"
        class="w-full"
        placeholder="请选择请假审批流程"
        :options="processList.map((item) => ({ label: item.name, value: item.key }))"
      />
    </Modal>
  </Page>
</template>
src/views/hrm/leave/modules/form.vue
@@ -4,6 +4,8 @@
import { computed, ref } from 'vue';
import { useUserStore } from '@vben/stores';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -16,6 +18,8 @@
const emit = defineEmits(['success']);
const formType = ref<FormType>('create');
const userStore = useUserStore();
const isDetail = computed(() => formType.value === 'detail');
const getTitle = computed(() => {
@@ -74,6 +78,8 @@
    formApi.setDisabled(data.formType === 'detail');
    modalApi.setState({ showConfirmButton: data.formType !== 'detail' });
    if (!data?.id) {
      // æ–°å¢žæ—¶é»˜è®¤ç”³è¯·äººä¸ºå½“前登录用户,可修改
      await formApi.setFieldValue('userId', userStore.userInfo?.id);
      return;
    }
    modalApi.lock();
src/views/mes/qc/components/export-range-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { DatePicker, message } from 'ant-design-vue';
import { getRangePickerDefaultProps } from '#/utils';
/** å¯¼å‡ºå‚数(后端 PageReqVO ä»¥æ•°ç»„解析 inspectDate èµ·æ­¢ï¼‰ */
interface ExportParams {
  inspectDate?: [string, string];
}
interface ExportModalData {
  /** å¼¹çª—标题 */
  title?: string;
  /** æ¨¡å—导出函数:requestClient.download(...) è¿”回 Blob */
  exportApi: (params: ExportParams) => Promise<Blob>;
  /** å¯¼å‡ºæ–‡ä»¶å */
  fileName: string;
}
defineOptions({ name: 'QcExportRangeModal' });
const rangePickerProps = getRangePickerDefaultProps();
const modalData = ref<ExportModalData>();
const range = ref<[string, string] | undefined>(undefined);
const exporting = ref(false);
const title = ref('导出');
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    if (exporting.value) {
      return;
    }
    const data = modalData.value;
    if (!data) {
      return;
    }
    exporting.value = true;
    modalApi.lock();
    try {
      const params: ExportParams =
        range.value && range.value.length === 2
          ? { inspectDate: range.value }
          : {};
      const blob = await data.exportApi(params);
      downloadFileFromBlobPart({ fileName: data.fileName, source: blob });
      message.success('导出成功');
      await modalApi.close();
    } finally {
      exporting.value = false;
      modalApi.unlock();
    }
  },
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      modalData.value = undefined;
      range.value = undefined;
      return;
    }
    const data = modalApi.getData<ExportModalData>();
    modalData.value = data;
    range.value = undefined;
    title.value = data?.title ?? '导出';
    modalApi.setState({ confirmText: '导出' });
  },
});
</script>
<template>
  <Modal :title="title" class="w-2/5" :mask-closable="false">
    <div class="px-1 py-3">
      <div class="mb-2 text-sm font-medium text-gray-700">检验时间</div>
      <DatePicker.RangePicker
        v-model:value="range"
        v-bind="rangePickerProps"
        allow-clear
        class="w-full"
      />
      <div class="mt-2 text-xs text-gray-400">
        ä¸é€‰æ—¶é—´åˆ™å¯¼å‡ºå…¨éƒ¨æ•°æ®ï¼›å¯¼å‡ºæŒ‰ã€Œæ£€æµ‹æ—¥æœŸã€è¿‡æ»¤
      </div>
    </div>
  </Modal>
</template>
src/views/mes/qc/ipqc/data.ts
@@ -16,7 +16,9 @@
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
import { ProTaskSelect } from '#/views/mes/pro/task/components';
@@ -202,6 +204,39 @@
          statuses: [MesProTaskStatusEnum.PREPARE],
          workOrderId: values.workOrderId,
          workstationId: values.workstationId,
        }),
      },
    },
    {
      fieldName: 'templateId',
      label: '质检方案',
      component: 'ApiSelect',
      componentProps: {
        api: getTemplateItemOptionList,
        autoSelect: 'first',
        labelField: 'templateName',
        valueField: 'templateId',
        placeholder: '请选择质检方案',
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['workOrderId', 'taskId'],
        componentProps: (values) => ({
          api: getTemplateItemOptionList,
          autoSelect: 'first',
          labelField: 'templateName',
          valueField: 'templateId',
          disabled: formType !== 'create' || !values.workOrderId,
          params: values.workOrderId
            ? {
                qcType: 2,
                workOrderId: values.workOrderId,
                taskId: values.taskId,
              }
            : {},
          placeholder: values.workOrderId
            ? '请选择质检方案'
            : '请先选择生产订单',
        }),
      },
    },
@@ -404,6 +439,14 @@
        placeholder: '请选择检测结果',
      },
    },
    {
      fieldName: 'inspectDate',
      label: '检测日期',
      component: 'RangePicker',
      componentProps: {
        ...getRangePickerDefaultProps(),
      },
    },
  ];
}
src/views/mes/qc/ipqc/index.vue
@@ -4,19 +4,24 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteIpqc, exportIpqc, getIpqcPage } from '#/api/mes/qc/ipqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -55,10 +60,15 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ */
async function handleExport() {
  const data = await exportIpqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '过程检验单.xls', source: data });
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导出过程检验单',
      exportApi: exportIpqc,
      fileName: '过程检验单.xls',
    })
    .open();
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -93,8 +103,9 @@
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <Grid table-title="过程检验单列表">
      <template #toolbar-tools>
        <TableAction
src/views/mes/qc/iqc/data.ts
@@ -11,6 +11,7 @@
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -135,6 +136,33 @@
      },
    },
    {
      fieldName: 'templateId',
      label: '质检方案',
      component: 'ApiSelect',
      componentProps: {
        api: getTemplateItemOptionList,
        autoSelect: 'first',
        labelField: 'templateName',
        valueField: 'templateId',
        placeholder: '请选择质检方案',
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['itemId'],
        componentProps: (values) => ({
          api: getTemplateItemOptionList,
          autoSelect: 'first',
          labelField: 'templateName',
          valueField: 'templateId',
          disabled: formType !== 'create' || !values.itemId,
          params: values.itemId
            ? { itemId: values.itemId, qcType: 1 }
            : {},
          placeholder: values.itemId ? '请选择质检方案' : '请先选择产品物料',
        }),
      },
    },
    {
      fieldName: 'vendorId',
      label: '供应商',
      component: 'ApiSelect',
src/views/mes/qc/iqc/index.vue
@@ -7,13 +7,13 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteIqc, exportIqc, getIqcPage } from '#/api/mes/qc/iqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -24,6 +24,11 @@
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -62,10 +67,15 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ */
async function handleExport() {
  const data = await exportIqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '来料检验单.xls', source: data });
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导出来料检验单',
      exportApi: exportIqc,
      fileName: '来料检验单.xls',
    })
    .open();
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -107,8 +117,9 @@
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <Grid table-title="来料检验单列表">
      <template #toolbar-tools>
        <TableAction
src/views/mes/qc/oqc/data.ts
@@ -11,7 +11,9 @@
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
import CrmCustomerSelect from '#/components/crm-customer-select.vue';
import { MdmItemSelect } from '#/views/basicData/mdm/components';
@@ -129,6 +131,33 @@
        componentProps: (values) => ({
          disabled: !!values.sourceDocId,
          placeholder: '请选择产品物料',
        }),
      },
    },
    {
      fieldName: 'templateId',
      label: '质检方案',
      component: 'ApiSelect',
      componentProps: {
        api: getTemplateItemOptionList,
        autoSelect: 'first',
        labelField: 'templateName',
        valueField: 'templateId',
        placeholder: '请选择质检方案',
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['itemId'],
        componentProps: (values) => ({
          api: getTemplateItemOptionList,
          autoSelect: 'first',
          labelField: 'templateName',
          valueField: 'templateId',
          disabled: formType !== 'create' || !values.itemId,
          params: values.itemId
            ? { itemId: values.itemId, qcType: 3 }
            : {},
          placeholder: values.itemId ? '请选择质检方案' : '请先选择产品物料',
        }),
      },
    },
@@ -340,6 +369,14 @@
        placeholder: '请选择检测结果',
      },
    },
    {
      fieldName: 'inspectDate',
      label: '检测日期',
      component: 'RangePicker',
      componentProps: {
        ...getRangePickerDefaultProps(),
      },
    },
  ];
}
src/views/mes/qc/oqc/index.vue
@@ -4,19 +4,24 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteOqc, exportOqc, getOqcPage } from '#/api/mes/qc/oqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -55,10 +60,15 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ */
async function handleExport() {
  const data = await exportOqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '出货检验单.xls', source: data });
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导出货检验单',
      exportApi: exportOqc,
      fileName: '出货检验单.xls',
    })
    .open();
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -93,8 +103,9 @@
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <Grid table-title="出货检验单列表">
      <template #toolbar-tools>
        <TableAction
src/views/mes/qc/rqc/data.ts
@@ -15,7 +15,9 @@
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getTemplateItemOptionList } from '#/api/mes/qc/template/item';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
import { MdmItemSelect } from '#/views/basicData/mdm/components';
/** è¡¨å•类型 */
@@ -154,6 +156,33 @@
        componentProps: (values) => ({
          disabled: !!values.sourceDocId,
          placeholder: '请选择产品物料',
        }),
      },
    },
    {
      fieldName: 'templateId',
      label: '质检方案',
      component: 'ApiSelect',
      componentProps: {
        api: getTemplateItemOptionList,
        autoSelect: 'first',
        labelField: 'templateName',
        valueField: 'templateId',
        placeholder: '请选择质检方案',
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['itemId'],
        componentProps: (values) => ({
          api: getTemplateItemOptionList,
          autoSelect: 'first',
          labelField: 'templateName',
          valueField: 'templateId',
          disabled: formType !== 'create' || !values.itemId,
          params: values.itemId
            ? { itemId: values.itemId, qcType: 4 }
            : {},
          placeholder: values.itemId ? '请选择质检方案' : '请先选择产品物料',
        }),
      },
    },
@@ -330,6 +359,14 @@
        valueField: 'id',
      },
    },
    {
      fieldName: 'inspectDate',
      label: '检测日期',
      component: 'RangePicker',
      componentProps: {
        ...getRangePickerDefaultProps(),
      },
    },
  ];
}
src/views/mes/qc/rqc/index.vue
@@ -4,19 +4,24 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteRqc, exportRqc, getRqcPage } from '#/api/mes/qc/rqc';
import { $t } from '#/locales';
import QcExportRangeModal from '#/views/mes/qc/components/export-range-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
const [ExportRangeModal, exportRangeModalApi] = useVbenModal({
  connectedComponent: QcExportRangeModal,
  destroyOnClose: true,
});
@@ -55,10 +60,15 @@
  }
}
/** å¯¼å‡ºè¡¨æ ¼ */
async function handleExport() {
  const data = await exportRqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '退货检验单.xls', source: data });
/** å¯¼å‡ºè¡¨æ ¼ï¼ˆå¼¹çª—选检验时间范围) */
function handleExport() {
  exportRangeModalApi
    .setData({
      title: '按检验时间导退货检验单',
      exportApi: exportRqc,
      fileName: '退货检验单.xls',
    })
    .open();
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -93,8 +103,9 @@
</script>
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <ExportRangeModal />
    <Grid table-title="退货检验单列表">
      <template #toolbar-tools>
        <TableAction
src/views/wls/productissue/components/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
export { default as WmProductIssueLineSelect } from './select.vue';
export { default as WmProductIssueLineSelectDialog } from './select-dialog.vue';
src/views/wls/productissue/components/select-dialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
import { nextTick, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getReturnableIssueLines } from '#/api/mes/wm/productissue/line';
const props = defineProps<{
  workOrderId?: number;
}>();
const emit = defineEmits<{
  selected: [rows: MesWmProductIssueLineApi.ProductIssueLine[]];
}>();
const open = ref(false);
const selectedRows = ref<MesWmProductIssueLineApi.ProductIssueLine[]>([]);
const columns: VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>['columns'] = [
  { type: 'radio', width: 60 },
  { field: 'itemCode', title: '物料编码', minWidth: 120 },
  { field: 'itemName', title: '物料名称', minWidth: 140 },
  { field: 'specification', title: '规格型号', minWidth: 120 },
  { field: 'unitMeasureName', title: '单位', width: 80 },
  { field: 'quantity', title: '可退数量', width: 100 },
  { field: 'batchCode', title: '批次号', minWidth: 120 },
];
const [Grid, gridApi] = useVbenVxeGrid({
  gridOptions: {
    columns,
    height: 480,
    keepSource: true,
    radioConfig: { highlight: true, trigger: 'row' },
    proxyConfig: {
      ajax: {
        query: async () => {
          if (!props.workOrderId) {
            return { list: [], total: 0 };
          }
          const list = await getReturnableIssueLines(props.workOrderId);
          return { list, total: list.length };
        },
      },
    },
    rowConfig: {
      keyField: 'id',
      isHover: true,
    },
    toolbarConfig: {
      refresh: true,
    },
  } as VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>,
  gridEvents: {
    radioChange: ({ row }: { row: MesWmProductIssueLineApi.ProductIssueLine }) => {
      selectedRows.value = [row];
    },
    cellDblclick: async ({ row }: { row: MesWmProductIssueLineApi.ProductIssueLine }) => {
      selectedRows.value = [row];
      await gridApi.grid.setRadioRow(row);
      handleConfirm();
    },
  },
});
async function openModal() {
  open.value = true;
  selectedRows.value = [];
  await nextTick();
  await gridApi.grid.clearRadioRow();
  await gridApi.query();
}
function closeModal() {
  open.value = false;
  selectedRows.value = [];
}
function handleConfirm() {
  if (selectedRows.value.length === 0) {
    message.warning('请选择一条领料记录');
    return;
  }
  emit('selected', selectedRows.value);
  open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
  <Modal
    v-model:open="open"
    :destroy-on-close="true"
    title="选择领料记录"
    width="70%"
    @cancel="closeModal"
    @ok="handleConfirm"
  >
    <Grid table-title="可退料的领料记录" />
  </Modal>
</template>
src/views/wls/productissue/components/select.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
<script lang="ts" setup>
import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
import { computed, ref, useAttrs, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Input, Tooltip } from 'ant-design-vue';
import { getProductIssueLine } from '#/api/mes/wm/productissue/line';
import ProductIssueLineSelectDialog from './select-dialog.vue';
defineOptions({ name: 'WmProductIssueLineSelect', inheritAttrs: false });
const props = withDefaults(
  defineProps<{
    allowClear?: boolean;
    disabled?: boolean;
    modelValue?: number;
    placeholder?: string;
    workOrderId?: number;
  }>(),
  {
    allowClear: true,
    disabled: false,
    modelValue: undefined,
    placeholder: '请选择领料记录',
    workOrderId: undefined,
  },
);
const emit = defineEmits<{
  change: [item: MesWmProductIssueLineApi.ProductIssueLine | undefined];
  'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof ProductIssueLineSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<MesWmProductIssueLineApi.ProductIssueLine>();
const displayLabel = computed(() => {
  const item = selectedItem.value;
  if (!item) {
    return '';
  }
  return `${item.itemCode || '-'} | ${item.itemName || '-'} | æ•°é‡:${item.quantity}`;
});
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 getProductIssueLine(id);
}
watch(() => props.modelValue, resolveItemById, { 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;
  }
  dialogRef.value?.open();
}
function handleSelected(rows: MesWmProductIssueLineApi.ProductIssueLine[]) {
  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.itemCode || '-' }}</div>
          <div>物料名称:{{ selectedItem.itemName || '-' }}</div>
          <div>规格型号:{{ selectedItem.specification || '-' }}</div>
          <div>可退数量:{{ selectedItem.quantity ?? '-' }}</div>
          <div>批次号:{{ selectedItem.batchCode || '-' }}</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>
  <ProductIssueLineSelectDialog
    ref="dialogRef"
    :work-order-id="workOrderId"
    @selected="handleSelected"
  />
</template>
src/views/wls/productissue/data.ts
@@ -1,5 +1,6 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProTaskApi } from '#/api/mes/pro/task';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
@@ -20,6 +21,7 @@
import { getRangePickerDefaultProps } from '#/utils';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
import { ProTaskSelect } from '#/views/mes/pro/task/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
@@ -110,7 +112,33 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        placeholder: '请选择生产订单',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        // è®¢å•变更:清空已选生产任务,避免任务与订单不一致
        onChange: async () => {
          await formApi?.setFieldValue('taskId', undefined);
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'taskId',
      label: '生产任务',
      component: markRaw(ProTaskSelect),
      componentProps: {
        placeholder: '请选择生产任务',
        // é€‰ä¸­ä»»åŠ¡åŽè‡ªåŠ¨å¸¦å‡ºå…¶å·¥ä½œç«™
        onChange: async (task?: MesProTaskApi.Task) => {
          await formApi?.setFieldValue('workstationId', task?.workstationId);
        },
      },
      dependencies: {
        triggerFields: ['workOrderId'],
        componentProps: (values) => ({
          disabled: !values.workOrderId,
          placeholder: values.workOrderId ? '请选择生产任务' : '请先选择生产订单',
          workOrderId: values.workOrderId,
        }),
      },
      rules: 'selectRequired',
    },
@@ -198,6 +226,11 @@
    {
      field: 'workOrderCode',
      title: '生产订单',
      minWidth: 140,
    },
    {
      field: 'taskCode',
      title: '生产任务',
      minWidth: 140,
    },
    {
@@ -370,6 +403,14 @@
      },
    },
    {
      fieldName: 'batchId',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'itemId',
      label: '物料',
      component: markRaw(MdItemSelect),
@@ -382,12 +423,13 @@
      label: '库存记录',
      component: markRaw(WmMaterialStockSelect),
      componentProps: {
        // é€‰æ‹©åº“存记录后,自动回填仓库/库区/库位/批次/数量
        // é€‰æ‹©åº“存记录后,自动回填物料/仓库/库区/库位/批次/数量,全部以库存记录为准
        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
          await formApi?.setValues({
            areaId: stock?.areaId,
            batchCode: stock?.batchCode,
            batchId: stock?.batchId,
            itemId: stock?.itemId,
            locationId: stock?.locationId,
            quantity: stock?.quantity,
            quantityMax: stock?.quantity,
@@ -399,6 +441,7 @@
      dependencies: {
        triggerFields: ['itemId'],
        componentProps: (values) => ({
          // åº“存选择弹窗只识别 mdmItemId å…¥å‚,并用它作为 itemId è¿‡æ»¤æ¡ä»¶
          mdmItemId: values.itemId,
        }),
      },
src/views/wls/returnissue/data.ts
@@ -1,6 +1,7 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
@@ -20,6 +21,7 @@
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
import { WmProductIssueLineSelect } from '#/views/wls/productissue/components';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import {
  WmWarehouseAreaSelect,
@@ -316,27 +318,29 @@
}
/** é€€æ–™å•行新增/修改的表单 */
export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
export function useLineFormSchema(
  formApi?: VbenFormApi,
  workOrderId?: number,
): VbenFormSchema[] {
  return [
    {
      fieldName: 'materialStockId',
      label: '库存记录',
      component: markRaw(WmMaterialStockSelect),
      fieldName: 'issueLineId',
      label: '领料记录',
      component: markRaw(WmProductIssueLineSelect),
      componentProps: {
        // é€‰æ‹©åº“存记录后,自动回填物料/批次/数量
        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
        workOrderId,
        onChange: async (line?: MesWmProductIssueLineApi.ProductIssueLine) => {
          await formApi?.setValues({
            batchCode: stock?.batchCode,
            batchId: stock?.batchId,
            itemId: stock?.itemId,
            materialDisplayName: stock
              ? [stock.itemName, stock.specification].filter(Boolean).join(' | ')
            batchCode: line?.batchCode,
            batchId: line?.batchId,
            itemId: line?.itemId,
            materialDisplayName: line
              ? [line.itemName, line.specification].filter(Boolean).join(' | ')
              : undefined,
            quantity: stock?.quantity,
            quantityMax: stock?.quantity,
            quantity: line?.quantity,
            quantityMax: line?.quantity,
          });
        },
        virtualFilter: 'only',
      },
      rules: 'selectRequired',
    },
@@ -390,7 +394,7 @@
      component: 'Input',
      componentProps: {
        disabled: true,
        placeholder: '选择库存后自动带出',
        placeholder: '选择领料记录后自动带出',
      },
    },
    {
@@ -399,7 +403,7 @@
      component: 'Input',
      componentProps: {
        disabled: true,
        placeholder: '选择库存后自动带出',
        placeholder: '选择领料记录后自动带出',
      },
    },
    {
src/views/wls/returnissue/modules/form.vue
@@ -198,7 +198,7 @@
    <template v-if="formData?.id">
      <Divider>物料信息</Divider>
      <div class="mx-4">
        <LineList :form-type="formType" :issue-id="formData.id" />
        <LineList :form-type="formType" :issue-id="formData.id" :work-order-id="formData.workOrderId" />
      </div>
    </template>
    <template #prepend-footer>
src/views/wls/returnissue/modules/line-form.vue
@@ -19,7 +19,8 @@
const emit = defineEmits(['success']);
const formData = ref<MesWmReturnIssueLineApi.ReturnIssueLine>();
const issueId = ref<number>(); // æ‰€å±žé€€æ–™å•编号
const issueId = ref<number>();
const workOrderId = ref<number>();
const getTitle = computed(() => {
  return formData.value?.id
@@ -69,17 +70,16 @@
      formData.value = undefined;
      return;
    }
    formApi.setState({ schema: useLineFormSchema(formApi) });
    // åŠ è½½æ•°æ®
    const data = modalApi.getData<{ id?: number; issueId: number }>();
    const data = modalApi.getData<{ id?: number; issueId: number; workOrderId?: number }>();
    issueId.value = data.issueId;
    workOrderId.value = data.workOrderId;
    formApi.setState({ schema: useLineFormSchema(formApi, workOrderId.value) });
    if (!data.id) {
      return;
    }
    modalApi.lock();
    try {
      formData.value = await getReturnIssueLine(data.id);
      // è®¾ç½®åˆ° values,并构建物料展示名
      await formApi.setValues({
        ...formData.value,
        materialDisplayName: [formData.value.itemName, formData.value.specification]
src/views/wls/returnissue/modules/line-list.vue
@@ -29,6 +29,7 @@
const props = defineProps<{
  formType: FormType;
  issueId: number;
  workOrderId?: number;
}>();
const isEditable = computed(() =>
@@ -61,12 +62,12 @@
/** æ·»åŠ ç‰©æ–™ */
function handleCreate() {
  lineFormModalApi.setData({ issueId: props.issueId }).open();
  lineFormModalApi.setData({ issueId: props.issueId, workOrderId: props.workOrderId }).open();
}
/** ç¼–辑物料 */
function handleEdit(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
  lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
  lineFormModalApi.setData({ id: row.id, issueId: props.issueId, workOrderId: props.workOrderId }).open();
}
/** åˆ é™¤ç‰©æ–™ */