云
昨天 8a9338f2f43bfe936124feaaa22420b4ca86eb84
src/views/mes/qc/oqc/index.vue
@@ -1,19 +1,95 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcOqcApi } from '#/api/mes/qc/oqc';
import type { OqcAction } from '#/api/mes/qc/oqc';
import { ref } from 'vue';
import { useAccess } from '@vben/access';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesQcStatusEnum } from '@vben/constants';
import { MesBizTypeEnum, 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 { approveOqc, deleteOqc, exportOqc, getOqcActions, getOqcPage, retestOqc } from '#/api/mes/qc/oqc';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const props = withDefaults(defineProps<{ salesMode?: boolean }>(), { salesMode: false });
const { hasAccessByCodes } = useAccess();
const workflowLabels: Record<number, string> = {
  0: '草稿', 10: '待复检', 20: '待销售部审批', 30: '待公司质检审批',
  40: '已放行', 50: '待销售部不合格评审', 60: '待公司不合格审批', 70: '已拒绝出货',
};
const active = ref<MesQcOqcApi.Oqc>();
const retestOpen = ref(false);
const retestBusy = ref(false);
const retestForm = ref({ checkQuantity: 0, qualifiedQuantity: 0, unqualifiedQuantity: 0, opinion: '' });
function openRetest(row: MesQcOqcApi.Oqc) {
  active.value = row;
  retestForm.value = {
    checkQuantity: Number(row.checkQuantity ?? 0), qualifiedQuantity: Number(row.qualifiedQuantity ?? 0),
    unqualifiedQuantity: Number(row.unqualifiedQuantity ?? 0), opinion: '',
  };
  retestOpen.value = true;
}
async function saveRetest() {
  if (!active.value?.id) return;
  const form = retestForm.value;
  if (form.checkQuantity < 0 || form.qualifiedQuantity < 0 || form.unqualifiedQuantity < 0
    || form.checkQuantity !== form.qualifiedQuantity + form.unqualifiedQuantity) {
    message.warning('检测数量必须等于合格与不合格数量之和');
    return;
  }
  if (form.unqualifiedQuantity > 0 && !form.opinion.trim()) {
    message.warning('复检不合格请填写原因');
    return;
  }
  retestBusy.value = true;
  try {
    await retestOqc({ id: active.value.id, ...form, checkResult: form.unqualifiedQuantity === 0 ? 1 : 2 });
    message.success('复检结果已提交,待销售部评审');
    retestOpen.value = false;
    handleRefresh();
  } catch (error) {
    message.error((error as Error).message || '复检失败');
  } finally { retestBusy.value = false; }
}
const reviewOpen = ref(false);
const reviewBusy = ref(false);
const reviewStage = ref<'sales' | 'company'>('sales');
const reviewApproved = ref(true);
const reviewOpinion = ref('');
function openReview(row: MesQcOqcApi.Oqc, stage: 'sales' | 'company') {
  active.value = row; reviewStage.value = stage; reviewApproved.value = true;
  reviewOpinion.value = ''; reviewOpen.value = true;
}
async function saveReview() {
  if (!active.value?.id) return;
  if (!reviewOpinion.value.trim()) { message.warning('请填写审批意见'); return; }
  reviewBusy.value = true;
  try {
    await approveOqc(reviewStage.value, { id: active.value.id, approved: reviewApproved.value, opinion: reviewOpinion.value.trim() });
    message.success('审批完成'); reviewOpen.value = false; handleRefresh();
  } catch (error) { message.error((error as Error).message || '审批失败'); }
  finally { reviewBusy.value = false; }
}
const historyOpen = ref(false);
const historyActions = ref<OqcAction[]>([]);
const actionLabels: Record<string, string> = {
  INITIAL: '初检', RETEST: '复检', SALES_APPROVE: '销售部通过', SALES_REJECT: '销售部驳回',
  COMPANY_APPROVE: '公司质检通过', COMPANY_REJECT: '公司质检驳回',
};
async function openHistory(row: MesQcOqcApi.Oqc) {
  if (!row.id) return;
  active.value = row;
  try { historyActions.value = await getOqcActions(row.id); historyOpen.value = true; }
  catch (error) { message.error((error as Error).message || '读取流程记录失败'); }
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
@@ -58,15 +134,23 @@
/** 导出表格 */
async function handleExport() {
  const data = await exportOqc(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '出货检验单.xls', source: data });
  downloadFileFromBlobPart({ fileName: props.salesMode ? '销售质检单.xls' : '出货检验单.xls', source: data });
}
const columns = useGridColumns();
if (props.salesMode && columns) {
  columns.splice(columns.length - 1, 0, {
    field: 'workflowStatus', title: '销售质检流程', width: 170, slots: { default: 'workflow' },
  });
  const actions = columns.at(-1);
  if (actions) actions.width = 320;
}
const [Grid, gridApi] = useVbenVxeGrid({
  formOptions: {
    schema: useGridFormSchema(),
  },
  gridOptions: {
    columns: useGridColumns(),
    columns,
    height: 'auto',
    keepSource: true,
    proxyConfig: {
@@ -76,6 +160,7 @@
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
            sourceDocType: props.salesMode ? MesBizTypeEnum.WM_PRODUCT_SALES : undefined,
          });
        },
      },
@@ -95,7 +180,7 @@
<template>
  <Page auto-content-height><FormModal @success="handleRefresh" />
    <Grid table-title="出货检验单列表">
    <Grid :table-title="props.salesMode ? '销售质检单列表' : '出货检验单列表'">
      <template #toolbar-tools>
        <TableAction
          :actions="[
@@ -120,6 +205,9 @@
        <Button type="link" @click="handleDetail(row)">
          {{ row.code }}
        </Button>
      </template>
      <template #workflow="{ row }">
        <span>{{ workflowLabels[row.workflowStatus ?? 0] || '未知状态' }}</span>
      </template>
      <template #actions="{ row }">
        <TableAction
@@ -153,7 +241,38 @@
            },
          ]"
        />
        <Button v-if="props.salesMode && row.workflowStatus === 10 && hasAccessByCodes(['mes:qc-oqc:retest'])" type="link" @click="openRetest(row)">复检</Button>
        <Button v-if="props.salesMode && [20, 50].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:sales-approve'])" type="link" @click="openReview(row, 'sales')">销售部审批</Button>
        <Button v-if="props.salesMode && [30, 60].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:company-approve'])" type="link" @click="openReview(row, 'company')">公司质检审批</Button>
        <Button v-if="props.salesMode && hasAccessByCodes(['mes:qc-oqc:query'])" type="link" @click="openHistory(row)">流程记录</Button>
      </template>
    </Grid>
    <a-modal v-if="props.salesMode" v-model:open="retestOpen" title="销售质检复检" :confirm-loading="retestBusy" ok-text="提交复检" @ok="saveRetest">
      <p>检验单:{{ active?.code }}</p>
      <a-form layout="vertical">
        <a-form-item label="本次检测数量"><a-input-number v-model:value="retestForm.checkQuantity" :min="0" style="width: 100%" /></a-form-item>
        <a-form-item label="合格数量"><a-input-number v-model:value="retestForm.qualifiedQuantity" :min="0" style="width: 100%" /></a-form-item>
        <a-form-item label="不合格数量"><a-input-number v-model:value="retestForm.unqualifiedQuantity" :min="0" style="width: 100%" /></a-form-item>
        <a-form-item label="复检意见"><a-textarea v-model:value="retestForm.opinion" :rows="3" :maxlength="500" /></a-form-item>
      </a-form>
    </a-modal>
    <a-modal v-if="props.salesMode" v-model:open="reviewOpen" :title="reviewStage === 'sales' ? '销售部审批' : '公司质检审批'" :confirm-loading="reviewBusy" ok-text="提交审批" @ok="saveReview">
      <p>{{ active?.code }} · {{ workflowLabels[active?.workflowStatus ?? 0] }}</p>
      <a-radio-group v-model:value="reviewApproved" style="display: flex; gap: 16px; margin: 12px 0 18px">
        <a-radio :value="true">同意{{ reviewStage === 'company' ? active?.workflowStatus === 30 ? '出货放行' : '不合格处置' : '上报公司质检' }}</a-radio>
        <a-radio :value="false">驳回重检</a-radio>
      </a-radio-group>
      <a-textarea v-model:value="reviewOpinion" :rows="3" :maxlength="500" placeholder="请填写审批意见" />
    </a-modal>
    <a-modal v-if="props.salesMode" v-model:open="historyOpen" :title="`流程记录 · ${active?.code || ''}`" :footer="null">
      <a-timeline v-if="historyActions.length">
        <a-timeline-item v-for="action in historyActions" :key="action.id">
          <strong>{{ actionLabels[action.action] || action.action }}</strong>
          <span> · {{ action.createTime || '—' }} · 操作人 {{ action.operatorId || '—' }}</span>
          <div v-if="action.opinion">{{ action.opinion }}</div>
        </a-timeline-item>
      </a-timeline>
      <a-empty v-else description="暂无流程记录" />
    </a-modal>
  </Page>
</template>