<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 { 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 { 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,
|
destroyOnClose: true,
|
});
|
|
/** 刷新表格 */
|
function handleRefresh() {
|
gridApi.query();
|
}
|
|
/** 创建出货检验单 */
|
function handleCreate() {
|
formModalApi.setData({ formType: 'create' }).open();
|
}
|
|
/** 查看出货检验单 */
|
function handleDetail(row: MesQcOqcApi.Oqc) {
|
formModalApi.setData({ formType: 'detail', id: row.id }).open();
|
}
|
|
/** 编辑出货检验单 */
|
function handleEdit(row: MesQcOqcApi.Oqc) {
|
formModalApi.setData({ formType: 'update', id: row.id }).open();
|
}
|
|
/** 删除出货检验单 */
|
async function handleDelete(row: MesQcOqcApi.Oqc) {
|
const hideLoading = message.loading({
|
content: $t('ui.actionMessage.deleting', [row.code]),
|
duration: 0,
|
});
|
try {
|
await deleteOqc(row.id!);
|
message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
|
handleRefresh();
|
} finally {
|
hideLoading();
|
}
|
}
|
|
/** 导出表格 */
|
async function handleExport() {
|
const data = await exportOqc(await gridApi.formApi.getValues());
|
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,
|
height: 'auto',
|
keepSource: true,
|
proxyConfig: {
|
ajax: {
|
query: async ({ page }, formValues) => {
|
return await getOqcPage({
|
pageNo: page.currentPage,
|
pageSize: page.pageSize,
|
...formValues,
|
sourceDocType: props.salesMode ? MesBizTypeEnum.WM_PRODUCT_SALES : undefined,
|
});
|
},
|
},
|
},
|
rowConfig: {
|
keyField: 'id',
|
isHover: true,
|
},
|
toolbarConfig: {
|
refresh: true,
|
search: true,
|
},
|
} as VxeTableGridOptions<MesQcOqcApi.Oqc>,
|
});
|
</script>
|
|
<template>
|
<Page auto-content-height><FormModal @success="handleRefresh" />
|
|
<Grid :table-title="props.salesMode ? '销售质检单列表' : '出货检验单列表'">
|
<template #toolbar-tools>
|
<TableAction
|
:actions="[
|
{
|
label: $t('ui.actionTitle.create', ['出货检验单']),
|
type: 'primary',
|
icon: ACTION_ICON.ADD,
|
auth: ['mes:qc-oqc:create'],
|
onClick: handleCreate,
|
},
|
{
|
label: $t('ui.actionTitle.export'),
|
type: 'primary',
|
icon: ACTION_ICON.DOWNLOAD,
|
auth: ['mes:qc-oqc:export'],
|
onClick: handleExport,
|
},
|
]"
|
/>
|
</template>
|
<template #code="{ row }">
|
<Button type="link" @click="handleDetail(row)">
|
{{ row.code }}
|
</Button>
|
</template>
|
<template #workflow="{ row }">
|
<span>{{ workflowLabels[row.workflowStatus ?? 0] || '未知状态' }}</span>
|
</template>
|
<template #actions="{ row }">
|
<TableAction
|
:actions="[
|
{
|
label: $t('common.edit'),
|
type: 'link',
|
icon: ACTION_ICON.EDIT,
|
auth: ['mes:qc-oqc:update'],
|
ifShow: row.status === MesQcStatusEnum.DRAFT,
|
onClick: handleEdit.bind(null, row),
|
},
|
{
|
label: $t('common.delete'),
|
type: 'link',
|
danger: true,
|
icon: ACTION_ICON.DELETE,
|
auth: ['mes:qc-oqc:delete'],
|
ifShow: row.status === MesQcStatusEnum.DRAFT,
|
popConfirm: {
|
title: $t('ui.actionMessage.deleteConfirm', [row.code]),
|
confirm: handleDelete.bind(null, row),
|
},
|
},
|
{
|
label: $t('common.detail'),
|
type: 'link',
|
icon: ACTION_ICON.VIEW,
|
auth: ['mes:qc-oqc:query'],
|
onClick: handleDetail.bind(null, row),
|
},
|
]"
|
/>
|
<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>
|