feat(mes): 迁移“生产报工(pro_feedback)”的 ele 功能
parent
44b62e14ac
commit
5a1100aed4
|
|
@ -1 +1,2 @@
|
|||
export { default as ProTaskSelectDialog } from './pro-task-select-dialog.vue';
|
||||
export { default as ProTaskSelect } from './pro-task-select.vue';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProTaskApi } from '#/api/mes/pro/task';
|
||||
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
|
||||
import { Alert, Button, message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getTaskPage } from '#/api/mes/pro/task';
|
||||
|
||||
import { useTaskSelectGridColumns, useTaskSelectGridFormSchema } from '../data';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
statuses?: number[];
|
||||
}>(),
|
||||
{
|
||||
statuses: undefined,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesProTaskApi.Task[]];
|
||||
}>();
|
||||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(false); // 是否多选;默认按单选选择器使用
|
||||
const selectedRows = ref<MesProTaskApi.Task[]>([]); // 已选任务列表
|
||||
const preSelectedIds = ref<number[]>([]); // 预选任务编号列表
|
||||
const externalWorkOrderId = ref<number>(); // 外部传入的默认工单过滤
|
||||
const externalWorkstationId = ref<number>(); // 外部传入的默认工位过滤
|
||||
|
||||
const statusTip = computed(() => {
|
||||
if (!props.statuses?.length) {
|
||||
return '';
|
||||
}
|
||||
const labels = props.statuses
|
||||
.map((value) => getDictLabel(DICT_TYPE.MES_PRO_TASK_STATUS, value))
|
||||
.filter(Boolean)
|
||||
.join('、');
|
||||
return `仅展示状态为【${labels}】的任务`;
|
||||
});
|
||||
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesProTaskApi.Task>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesProTaskApi.Task[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId !== undefined) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理多选勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选切换 */
|
||||
function handleRadioChange(row: MesProTaskApi.Task) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesProTaskApi.Task) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击:单选直接确认,多选切换勾选 */
|
||||
async function handleCellDblclick({ row }: { row: MesProTaskApi.Task }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 回显预选任务 */
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = gridApi.grid.getData() as MesProTaskApi.Task[];
|
||||
for (const row of rows) {
|
||||
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useTaskSelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useTaskSelectGridColumns(false),
|
||||
height: 520,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getTaskPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
statuses: props.statuses,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesProTaskApi.Task>,
|
||||
gridEvents: {
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesProTaskApi.Task }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 重置查询和选择状态,保留外部传入的工单/工位默认过滤 */
|
||||
async function resetQueryState() {
|
||||
selectedRows.value = [];
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
if (externalWorkOrderId.value) {
|
||||
await gridApi.formApi.setFieldValue('workOrderId', externalWorkOrderId.value);
|
||||
}
|
||||
if (externalWorkstationId.value) {
|
||||
await gridApi.formApi.setFieldValue(
|
||||
'workstationId',
|
||||
externalWorkstationId.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开任务选择弹窗 */
|
||||
async function openModal(
|
||||
selectedIds?: number[],
|
||||
options?: {
|
||||
multiple?: boolean;
|
||||
workOrderId?: number;
|
||||
workstationId?: number;
|
||||
},
|
||||
) {
|
||||
open.value = true;
|
||||
multiple.value = options?.multiple ?? false;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
externalWorkOrderId.value = options?.workOrderId;
|
||||
externalWorkstationId.value = options?.workstationId;
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useTaskSelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭任务选择弹窗 */
|
||||
function closeModal() {
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
/** 确认选择任务 */
|
||||
function handleConfirm() {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open: openModal });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="open"
|
||||
title="生产任务选择"
|
||||
width="80%"
|
||||
:destroy-on-close="true"
|
||||
@ok="handleConfirm"
|
||||
@cancel="closeModal"
|
||||
>
|
||||
<Alert
|
||||
v-if="statusTip"
|
||||
:message="statusTip"
|
||||
type="info"
|
||||
show-icon
|
||||
class="!mb-3"
|
||||
/>
|
||||
<Grid table-title="生产任务列表" />
|
||||
<template #footer>
|
||||
<Button @click="closeModal">取消</Button>
|
||||
<Button type="primary" @click="handleConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -1,21 +1,16 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProTaskApi } from '#/api/mes/pro/task';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { Select, Tag, Tooltip } from 'ant-design-vue';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { getTask, getTaskPage } from '#/api/mes/pro/task';
|
||||
import { Input, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { getTask } from '#/api/mes/pro/task';
|
||||
|
||||
import ProTaskSelectDialog from './pro-task-select-dialog.vue';
|
||||
|
||||
// TODO @AI:直接完整迁移!
|
||||
/**
|
||||
* MES 生产任务选择器(轻量版)
|
||||
*
|
||||
* 当前用于生产报工等只需要单选任务 ID 的业务页面:
|
||||
* - 默认按 `workOrderId` / `workstationId` / `statuses` 过滤拉取首页 100 条任务作为下拉
|
||||
* - 编辑回显走 `getTask(id)`
|
||||
* - 后续 `mes/pro/task` 完整迁移后,可替换为带弹窗的复杂选择器
|
||||
*/
|
||||
defineOptions({ name: 'ProTaskSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
|
|
@ -23,7 +18,6 @@ const props = withDefaults(
|
|||
allowClear?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
pageSize?: number;
|
||||
placeholder?: string;
|
||||
statuses?: number[];
|
||||
workOrderId?: number;
|
||||
|
|
@ -33,131 +27,131 @@ const props = withDefaults(
|
|||
allowClear: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
pageSize: 100,
|
||||
placeholder: '请选择任务',
|
||||
statuses: undefined,
|
||||
workOrderId: undefined,
|
||||
workstationId: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [item: MesProTaskApi.Task | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
const attrs = useAttrs(); // 透传属性
|
||||
const dialogRef = ref<InstanceType<typeof ProTaskSelectDialog>>(); // 任务选择弹窗
|
||||
const hovering = ref(false); // 是否悬停
|
||||
const selectedItem = ref<MesProTaskApi.Task>(); // 选中的任务
|
||||
|
||||
const allList = ref<MesProTaskApi.Task[]>([]);
|
||||
const selectedItem = ref<MesProTaskApi.Task>();
|
||||
const displayLabel = computed(() => selectedItem.value?.code ?? ''); // 选择器展示编号
|
||||
const showClear = computed( // 是否显示清空图标
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== undefined,
|
||||
);
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: number | undefined) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
/** 前端过滤:按任务编号或名称模糊匹配 */
|
||||
function handleFilter(input: string, option: any) {
|
||||
const keyword = input.toLowerCase();
|
||||
const item = option?.item as MesProTaskApi.Task | undefined;
|
||||
return Boolean(
|
||||
item?.code?.toLowerCase().includes(keyword) ||
|
||||
item?.name?.toLowerCase().includes(keyword),
|
||||
);
|
||||
}
|
||||
|
||||
/** 同步选中任务详情,未在列表内时单独拉取 */
|
||||
async function syncSelectedItem(value: number | undefined) {
|
||||
if (value === undefined) {
|
||||
/** 根据任务编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id === undefined) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
const found = allList.value.find((item) => item.id === value);
|
||||
if (found) {
|
||||
selectedItem.value = found;
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getTask(value);
|
||||
selectedItem.value = await getTask(id);
|
||||
} catch (error) {
|
||||
console.error('[ProTaskSelect] resolveItemById failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 除 v-model 外,额外抛出完整任务对象给业务表单使用 */
|
||||
function handleChange(value: any) {
|
||||
const nextValue = value === undefined ? undefined : Number(value);
|
||||
syncSelectedItem(nextValue);
|
||||
emit('change', selectedItem.value);
|
||||
}
|
||||
|
||||
/** 重新拉取候选任务列表 */
|
||||
async function loadList() {
|
||||
const data = await getTaskPage({
|
||||
pageNo: 1,
|
||||
pageSize: props.pageSize,
|
||||
statuses: props.statuses,
|
||||
workOrderId: props.workOrderId,
|
||||
workstationId: props.workstationId,
|
||||
});
|
||||
allList.value = data.list ?? [];
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
syncSelectedItem(value);
|
||||
resolveItemById(value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.workOrderId, props.workstationId],
|
||||
async () => {
|
||||
await loadList();
|
||||
syncSelectedItem(props.modelValue);
|
||||
},
|
||||
);
|
||||
/** 清空已选任务 */
|
||||
function clearSelected() {
|
||||
selectedItem.value = undefined;
|
||||
emit('update:modelValue', undefined);
|
||||
emit('change', undefined);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadList();
|
||||
syncSelectedItem(props.modelValue);
|
||||
});
|
||||
/** 打开任务选择弹窗 */
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
if (showClear.value && target.closest('.ant-input-suffix')) {
|
||||
event.stopPropagation();
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds =
|
||||
props.modelValue === undefined ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, {
|
||||
multiple: false,
|
||||
workOrderId: props.workOrderId,
|
||||
workstationId: props.workstationId,
|
||||
});
|
||||
}
|
||||
|
||||
/** 回填选中的任务 */
|
||||
function handleSelected(rows: MesProTaskApi.Task[]) {
|
||||
const item = rows[0];
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = item;
|
||||
emit('update:modelValue', item.id);
|
||||
emit('change', item);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
|
||||
<template #title>
|
||||
<div v-if="selectedItem" class="leading-6">
|
||||
<div>任务编号:{{ selectedItem.code || '-' }}</div>
|
||||
<div>任务名称:{{ selectedItem.name || '-' }}</div>
|
||||
<div>工序:{{ selectedItem.processName || '-' }}</div>
|
||||
<div>工作站:{{ selectedItem.workstationName || '-' }}</div>
|
||||
<div>物料:{{ selectedItem.itemName || '-' }}</div>
|
||||
<div>规格:{{ selectedItem.itemSpecification || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<Select
|
||||
v-bind="$attrs"
|
||||
v-model:value="selectValue"
|
||||
:allow-clear="allowClear"
|
||||
:disabled="disabled"
|
||||
:filter-option="handleFilter"
|
||||
:placeholder="placeholder"
|
||||
class="w-full"
|
||||
show-search
|
||||
@change="handleChange"
|
||||
>
|
||||
<Select.Option
|
||||
v-for="item in allList"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:value="item.id"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ item.code }}</span>
|
||||
<Tag v-if="item.itemName" color="default">{{ item.itemName }}</Tag>
|
||||
<div
|
||||
v-bind="attrs"
|
||||
class="w-full"
|
||||
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||
@click="handleClick"
|
||||
@mouseenter="hovering = true"
|
||||
@mouseleave="hovering = false"
|
||||
>
|
||||
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
|
||||
<template #title>
|
||||
<div v-if="selectedItem" class="leading-6">
|
||||
<div>任务编号:{{ selectedItem.code || '-' }}</div>
|
||||
<div>任务名称:{{ selectedItem.name || '-' }}</div>
|
||||
<div>工序:{{ selectedItem.processName || '-' }}</div>
|
||||
<div>工作站:{{ selectedItem.workstationName || '-' }}</div>
|
||||
<div>物料:{{ selectedItem.itemName || '-' }}</div>
|
||||
<div>规格:{{ selectedItem.itemSpecification || '-' }}</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</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>
|
||||
<ProTaskSelectDialog
|
||||
ref="dialogRef"
|
||||
:statuses="statuses"
|
||||
@selected="handleSelected"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProTaskApi } from '#/api/mes/pro/task';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
|
||||
import { ProProcessSelect } from '#/views/mes/pro/process/components';
|
||||
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
||||
|
||||
/** 任务选择弹窗的搜索表单 */
|
||||
export function useTaskSelectGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'workOrderId',
|
||||
label: '生产工单',
|
||||
component: markRaw(ProWorkOrderSelect),
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择生产工单',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'processId',
|
||||
label: '所属工序',
|
||||
component: markRaw(ProProcessSelect),
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择工序',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: markRaw(MdWorkstationSelect),
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择工作站',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '任务编号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入任务编号',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '任务名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入任务名称',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 任务选择弹窗的字段 */
|
||||
export function useTaskSelectGridColumns(
|
||||
multiple = false,
|
||||
): VxeTableGridOptions<MesProTaskApi.Task>['columns'] {
|
||||
return [
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{ field: 'code', title: '任务编号', width: 180 },
|
||||
{ field: 'name', title: '任务名称', minWidth: 140 },
|
||||
{ field: 'workstationCode', title: '工作站编码', width: 140 },
|
||||
{ field: 'workstationName', title: '工作站名称', width: 140 },
|
||||
{ field: 'processName', title: '工序', width: 120 },
|
||||
{
|
||||
field: 'checkFlag',
|
||||
title: '是否质检',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||
},
|
||||
},
|
||||
{ field: 'itemCode', title: '物料编码', width: 140 },
|
||||
{ field: 'itemName', title: '物料名称', width: 140 },
|
||||
{ field: 'itemSpecification', title: '规格型号', width: 120 },
|
||||
{ field: 'quantity', title: '排产数量', width: 100 },
|
||||
{ field: 'producedQuantity', title: '已生产数量', width: 110 },
|
||||
{
|
||||
field: 'startTime',
|
||||
title: '开始生产时间',
|
||||
width: 170,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'duration', title: '生产时长', width: 100 },
|
||||
{
|
||||
field: 'endTime',
|
||||
title: '预计完成时间',
|
||||
width: 170,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '任务状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_TASK_STATUS },
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -166,10 +166,10 @@ export const MesProTaskStatusEnum = {
|
|||
/** MES 生产报工状态枚举 */
|
||||
export const MesProFeedbackStatusEnum = {
|
||||
PREPARE: MesOrderStatusConstants.DRAFT,
|
||||
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
|
||||
APPROVING: MesOrderStatusConstants.APPROVING,
|
||||
UNCHECK: MesOrderStatusConstants.APPROVED,
|
||||
FINISHED: MesOrderStatusConstants.FINISHED,
|
||||
CANCELLED: MesOrderStatusConstants.CANCELLED,
|
||||
CANCELED: MesOrderStatusConstants.CANCELLED,
|
||||
} as const;
|
||||
|
||||
/** MES 流转卡状态枚举 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProFeedbackApi {
|
||||
/** MES 生产报工 */
|
||||
export interface Feedback {
|
||||
id?: number;
|
||||
code?: string; // 报工单编号
|
||||
type?: number; // 报工类型
|
||||
channel?: string; // 报工途径
|
||||
feedbackTime?: number; // 报工时间
|
||||
workstationId?: number; // 工作站编号
|
||||
workstationCode?: string; // 工作站编码
|
||||
workstationName?: string; // 工作站名称
|
||||
routeId?: number; // 工艺路线编号
|
||||
routeCode?: string; // 工艺路线编码
|
||||
processId?: number; // 工序编号
|
||||
processCode?: string; // 工序编码
|
||||
processName?: string; // 工序名称
|
||||
checkFlag?: boolean; // 是否需要检验
|
||||
workOrderId?: number; // 生产工单编号
|
||||
workOrderCode?: string; // 工单编码
|
||||
workOrderName?: string; // 工单名称
|
||||
taskId?: number; // 生产任务编号
|
||||
taskCode?: string; // 任务编码
|
||||
itemId?: number; // 产品物料编号
|
||||
itemCode?: string; // 物料编码
|
||||
itemName?: string; // 物料名称
|
||||
itemSpecification?: string; // 规格型号
|
||||
unitMeasureId?: number; // 单位编号
|
||||
unitMeasureName?: string; // 单位名称
|
||||
expireDate?: number; // 过期日期
|
||||
scheduledQuantity?: number; // 排产数量
|
||||
feedbackQuantity?: number; // 本次报工数量
|
||||
qualifiedQuantity?: number; // 合格品数量
|
||||
unqualifiedQuantity?: number; // 不良品数量
|
||||
uncheckQuantity?: number; // 待检测数量
|
||||
laborScrapQuantity?: number; // 工废数量
|
||||
materialScrapQuantity?: number; // 料废数量
|
||||
otherScrapQuantity?: number; // 其他废品数量
|
||||
feedbackUserId?: number; // 报工用户编号
|
||||
feedbackUserNickname?: string; // 报工人昵称
|
||||
approveUserId?: number; // 审核用户编号
|
||||
approveUserNickname?: string; // 审核人昵称
|
||||
status?: number; // 状态
|
||||
remark?: string; // 备注
|
||||
creator?: string; // 创建人
|
||||
createTime?: number; // 创建时间
|
||||
}
|
||||
|
||||
/** MES 生产报工分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
code?: string;
|
||||
type?: number;
|
||||
workOrderId?: number;
|
||||
itemId?: number;
|
||||
feedbackUserId?: number;
|
||||
creator?: string;
|
||||
status?: number;
|
||||
feedbackTime?: string[];
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询生产报工分页 */
|
||||
export function getFeedbackPage(params: MesProFeedbackApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProFeedbackApi.Feedback>>(
|
||||
'/mes/pro/feedback/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询生产报工详情 */
|
||||
export function getFeedback(id: number) {
|
||||
return requestClient.get<MesProFeedbackApi.Feedback>(
|
||||
`/mes/pro/feedback/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增生产报工 */
|
||||
export function createFeedback(data: MesProFeedbackApi.Feedback) {
|
||||
return requestClient.post<number>('/mes/pro/feedback/create', data);
|
||||
}
|
||||
|
||||
/** 修改生产报工 */
|
||||
export function updateFeedback(data: MesProFeedbackApi.Feedback) {
|
||||
return requestClient.put('/mes/pro/feedback/update', data);
|
||||
}
|
||||
|
||||
/** 删除生产报工 */
|
||||
export function deleteFeedback(id: number) {
|
||||
return requestClient.delete(`/mes/pro/feedback/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 导出生产报工 Excel */
|
||||
export function exportFeedback(params: Partial<MesProFeedbackApi.PageParams>) {
|
||||
return requestClient.download('/mes/pro/feedback/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 提交生产报工 */
|
||||
export function submitFeedback(id: number) {
|
||||
return requestClient.put(`/mes/pro/feedback/submit?id=${id}`);
|
||||
}
|
||||
|
||||
/** 驳回生产报工 */
|
||||
export function rejectFeedback(id: number) {
|
||||
return requestClient.put(`/mes/pro/feedback/reject?id=${id}`);
|
||||
}
|
||||
|
||||
/** 审批生产报工(返回是否已审批完成) */
|
||||
export function approveFeedback(id: number) {
|
||||
return requestClient.put<boolean>(`/mes/pro/feedback/approve?id=${id}`);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProTaskApi {
|
||||
/** MES 生产任务 */
|
||||
export interface Task {
|
||||
id?: number;
|
||||
code?: string; // 任务编码
|
||||
name?: string; // 任务名称
|
||||
workOrderId?: number; // 生产工单编号
|
||||
workOrderCode?: string; // 工单编码
|
||||
workOrderName?: string; // 工单名称
|
||||
workstationId?: number; // 工作站编号
|
||||
workstationCode?: string; // 工作站编码
|
||||
workstationName?: string; // 工作站名称
|
||||
routeId?: number; // 工艺路线编号
|
||||
processId?: number; // 工序编号
|
||||
processName?: string; // 工序名称
|
||||
itemId?: number; // 产品物料编号
|
||||
itemCode?: string; // 产品编码
|
||||
itemName?: string; // 产品名称
|
||||
itemSpecification?: string; // 规格型号
|
||||
unitMeasureId?: number; // 单位编号
|
||||
unitMeasureName?: string; // 单位名称
|
||||
quantity?: number; // 排产数量
|
||||
producedQuantity?: number; // 已生产数量
|
||||
qualifyQuantity?: number; // 合格品数量
|
||||
unqualifyQuantity?: number; // 不良品数量
|
||||
changedQuantity?: number; // 调整数量
|
||||
clientId?: number; // 客户编号
|
||||
clientName?: string; // 客户名称
|
||||
startTime?: number; // 开始生产时间
|
||||
endTime?: number; // 结束生产时间
|
||||
duration?: number; // 生产时长(工作日,1=8小时)
|
||||
requestDate?: number; // 需求日期(从工单查)
|
||||
finishDate?: number; // 完成日期
|
||||
cancelDate?: number; // 取消日期
|
||||
colorCode?: string; // 甘特图显示颜色
|
||||
status?: number; // 任务状态
|
||||
checkFlag?: boolean; // 是否质检(派生自工艺路线工序)
|
||||
remark?: string; // 备注
|
||||
}
|
||||
|
||||
/** MES 生产任务分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
code?: string;
|
||||
name?: string;
|
||||
workOrderId?: number;
|
||||
workstationId?: number;
|
||||
itemId?: number;
|
||||
statuses?: number[];
|
||||
status?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询生产任务分页 */
|
||||
export function getTaskPage(params: MesProTaskApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProTaskApi.Task>>(
|
||||
'/mes/pro/task/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询生产任务详情 */
|
||||
export function getTask(id: number) {
|
||||
return requestClient.get<MesProTaskApi.Task>(`/mes/pro/task/get?id=${id}`);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesWmItemConsumeLineApi {
|
||||
/** MES 物料消耗行 */
|
||||
export interface ItemConsumeLine {
|
||||
id?: number;
|
||||
feedbackId?: number; // 报工编号
|
||||
itemId?: number; // 物料编号
|
||||
itemCode?: string; // 物资编码
|
||||
itemName?: string; // 物资名称
|
||||
specification?: string; // 规格型号
|
||||
unitId?: number; // 单位编号
|
||||
unitName?: string; // 单位
|
||||
quantity?: number; // 消耗数量
|
||||
batchCode?: string; // 批次号
|
||||
locationId?: number; // 库位编号
|
||||
locationName?: string; // 库位名称
|
||||
remark?: string; // 备注
|
||||
}
|
||||
|
||||
/** MES 物料消耗行分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
feedbackId?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询物料消耗行分页 */
|
||||
export function getItemConsumeLinePage(
|
||||
params: MesWmItemConsumeLineApi.PageParams,
|
||||
) {
|
||||
return requestClient.get<PageResult<MesWmItemConsumeLineApi.ItemConsumeLine>>(
|
||||
'/mes/wm/item-consume-line/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesWmProductProduceLineApi {
|
||||
/** MES 产品产出行 */
|
||||
export interface ProductProduceLine {
|
||||
id?: number;
|
||||
feedbackId?: number; // 报工编号
|
||||
itemId?: number; // 物料编号
|
||||
itemCode?: string; // 物资编码
|
||||
itemName?: string; // 物资名称
|
||||
specification?: string; // 规格型号
|
||||
unitMeasureId?: number; // 单位编号
|
||||
unitMeasureName?: string; // 单位
|
||||
quantity?: number; // 产出数量
|
||||
batchCode?: string; // 批次号
|
||||
qualityStatus?: number; // 质量状态
|
||||
locationId?: number; // 库位编号
|
||||
locationName?: string; // 库位名称
|
||||
remark?: string; // 备注
|
||||
}
|
||||
|
||||
/** MES 产品产出行分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
feedbackId?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询产品产出行分页 */
|
||||
export function getProductProduceLinePage(
|
||||
params: MesWmProductProduceLineApi.PageParams,
|
||||
) {
|
||||
return requestClient.get<
|
||||
PageResult<MesWmProductProduceLineApi.ProductProduceLine>
|
||||
>('/mes/wm/product-produce-line/page', { params });
|
||||
}
|
||||
|
|
@ -0,0 +1,589 @@
|
|||
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';
|
||||
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
||||
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
||||
import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
|
||||
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';
|
||||
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
||||
import {
|
||||
MesAutoCodeRuleCode,
|
||||
MesProTaskStatusEnum,
|
||||
MesProWorkOrderStatusEnum,
|
||||
} from '#/views/mes/utils/constants';
|
||||
|
||||
/** 生产报工表单类型 */
|
||||
export type FormType = 'approve' | 'create' | 'detail' | 'submit' | 'update';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '报工单号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请输入报工单号',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
label: '报工类型',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_FEEDBACK_TYPE, 'number'),
|
||||
placeholder: '请选择报工类型',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'workOrderId',
|
||||
label: '生产工单',
|
||||
component: markRaw(ProWorkOrderSelect),
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请选择工单',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'itemId',
|
||||
label: '产品物料',
|
||||
component: markRaw(MdItemSelect),
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请选择产品物料',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'feedbackUserId',
|
||||
label: '报工人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择报工人',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'creator',
|
||||
label: '记录人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择记录人',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_FEEDBACK_STATUS, 'number'),
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'feedbackTime',
|
||||
label: '报工时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions<MesProFeedbackApi.Feedback>['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'code',
|
||||
title: '报工单号',
|
||||
width: 160,
|
||||
slots: { default: 'code' },
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
title: '报工类型',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_FEEDBACK_TYPE },
|
||||
},
|
||||
},
|
||||
{ field: 'workstationName', title: '工作站', width: 120 },
|
||||
{ field: 'processName', title: '工序', width: 100 },
|
||||
{ field: 'workOrderCode', title: '生产工单编码', width: 160 },
|
||||
{ field: 'itemCode', title: '产品物料编码', width: 120 },
|
||||
{ field: 'itemName', title: '产品物料名称', minWidth: 140 },
|
||||
{ field: 'itemSpecification', title: '规格型号', width: 120 },
|
||||
{ field: 'unitMeasureName', title: '单位', width: 80 },
|
||||
{ field: 'feedbackQuantity', title: '报工数量', width: 100 },
|
||||
{ field: 'feedbackUserNickname', title: '报工人', width: 100 },
|
||||
{
|
||||
field: 'feedbackTime',
|
||||
title: '报工时间',
|
||||
width: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'approveUserNickname', title: '审核人', width: 100 },
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_FEEDBACK_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 240,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/编辑/提交/审批/详情生产报工的表单
|
||||
*
|
||||
* - create / update:录入报工主体信息和数量
|
||||
* - submit / approve / detail:主体字段只读
|
||||
*
|
||||
* 数量区域根据 `checkFlag` 和 `unqualifiedQuantity` 动态显示:
|
||||
* - 非质检工序:报工数量 = 合格 + 不良;不良 > 0 时再展开工废/料废/其他废品
|
||||
* - 质检工序:只填报工数量(视为待检数量)
|
||||
*/
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
const isHeaderReadonly = ['approve', 'detail', 'submit'].includes(formType);
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
{
|
||||
fieldName: 'checkFlag',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
fieldName: 'routeId',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
{
|
||||
fieldName: 'processId',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
{
|
||||
fieldName: 'itemId',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '报工单号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
disabled: isHeaderReadonly,
|
||||
placeholder: '请输入报工单号',
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
disabled: isHeaderReadonly,
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.PRO_FEEDBACK_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
label: '报工类型',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
disabled: isHeaderReadonly,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_FEEDBACK_TYPE, 'number'),
|
||||
placeholder: '请选择报工类型',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'workOrderId',
|
||||
label: '生产工单',
|
||||
component: markRaw(ProWorkOrderSelect),
|
||||
componentProps: {
|
||||
disabled: isHeaderReadonly,
|
||||
placeholder: '请选择工单',
|
||||
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
||||
// 工单变更:清空任务及任务带出的产品信息、数量区域控制位
|
||||
onChange: async () => {
|
||||
await formApi?.setValues({
|
||||
checkFlag: true,
|
||||
itemCode: undefined,
|
||||
itemId: undefined,
|
||||
itemName: undefined,
|
||||
itemSpecification: undefined,
|
||||
processId: undefined,
|
||||
routeId: undefined,
|
||||
taskId: undefined,
|
||||
unitMeasureName: undefined,
|
||||
workstationId: undefined,
|
||||
});
|
||||
},
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'taskId',
|
||||
label: '生产任务',
|
||||
component: markRaw(ProTaskSelect),
|
||||
dependencies: {
|
||||
triggerFields: ['workOrderId', 'workstationId'],
|
||||
componentProps: (values) => ({
|
||||
disabled: isHeaderReadonly || !values.workOrderId,
|
||||
placeholder: values.workOrderId ? '请选择任务' : '请先选择工单',
|
||||
statuses: [MesProTaskStatusEnum.PREPARE],
|
||||
workOrderId: values.workOrderId,
|
||||
workstationId: values.workstationId,
|
||||
}),
|
||||
},
|
||||
// 任务变更:自动填充关联字段、产品信息、checkFlag
|
||||
componentProps: {
|
||||
onChange: async (task?: MesProTaskApi.Task) => {
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
await formApi?.setValues({
|
||||
itemCode: task.itemCode,
|
||||
itemId: task.itemId,
|
||||
itemName: task.itemName,
|
||||
itemSpecification: task.itemSpecification,
|
||||
processId: task.processId,
|
||||
routeId: task.routeId,
|
||||
unitMeasureName: task.unitMeasureName,
|
||||
workstationId: task.workstationId,
|
||||
});
|
||||
// 工艺路线工序的 checkFlag 决定数量区域展示
|
||||
if (task.routeId && task.processId) {
|
||||
try {
|
||||
const routeProcess = await getRouteProcessByRouteAndProcess(
|
||||
task.routeId,
|
||||
task.processId,
|
||||
);
|
||||
await formApi?.setFieldValue(
|
||||
'checkFlag',
|
||||
routeProcess?.checkFlag ?? false,
|
||||
);
|
||||
} catch {
|
||||
await formApi?.setFieldValue('checkFlag', true);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: markRaw(MdWorkstationSelect),
|
||||
componentProps: {
|
||||
disabled: isHeaderReadonly,
|
||||
placeholder: '请选择工作站',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'itemCode',
|
||||
label: '产品编码',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'itemName',
|
||||
label: '产品名称',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'unitMeasureName',
|
||||
label: '单位',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'itemSpecification',
|
||||
label: '规格',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'feedbackQuantity',
|
||||
label: '报工数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag'],
|
||||
// 非质检工序时,报工数量 = 合格 + 不良,禁用直接编辑
|
||||
componentProps: (values) => ({
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
disabled: !values.checkFlag,
|
||||
min: 0,
|
||||
placeholder: '请输入报工数量',
|
||||
precision: 2,
|
||||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'qualifiedQuantity',
|
||||
label: '合格品数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
// 合格/不良变更,自动累计为报工数量
|
||||
onChange: async () => {
|
||||
const values = await formApi?.getValues();
|
||||
await formApi?.setFieldValue(
|
||||
'feedbackQuantity',
|
||||
(values?.qualifiedQuantity || 0) +
|
||||
(values?.unqualifiedQuantity || 0),
|
||||
);
|
||||
},
|
||||
},
|
||||
defaultValue: 0,
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag'],
|
||||
show: (values) => !values.checkFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'unqualifiedQuantity',
|
||||
label: '不良品数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
// 合格/不良变更,自动累计为报工数量
|
||||
onChange: async () => {
|
||||
const values = await formApi?.getValues();
|
||||
await formApi?.setFieldValue(
|
||||
'feedbackQuantity',
|
||||
(values?.qualifiedQuantity || 0) +
|
||||
(values?.unqualifiedQuantity || 0),
|
||||
);
|
||||
},
|
||||
},
|
||||
defaultValue: 0,
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag'],
|
||||
show: (values) => !values.checkFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'laborScrapQuantity',
|
||||
label: '工废数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
// 废品分类变更,自动累计为不良品数量及报工数量
|
||||
onChange: async () => {
|
||||
const values = await formApi?.getValues();
|
||||
const unqualified =
|
||||
(values?.laborScrapQuantity || 0) +
|
||||
(values?.materialScrapQuantity || 0) +
|
||||
(values?.otherScrapQuantity || 0);
|
||||
await formApi?.setValues({
|
||||
feedbackQuantity:
|
||||
(values?.qualifiedQuantity || 0) + unqualified,
|
||||
unqualifiedQuantity: unqualified,
|
||||
});
|
||||
},
|
||||
},
|
||||
defaultValue: 0,
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag', 'unqualifiedQuantity'],
|
||||
show: (values) =>
|
||||
!values.checkFlag && (values.unqualifiedQuantity || 0) > 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'materialScrapQuantity',
|
||||
label: '料废数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
// 废品分类变更,自动累计为不良品数量及报工数量
|
||||
onChange: async () => {
|
||||
const values = await formApi?.getValues();
|
||||
const unqualified =
|
||||
(values?.laborScrapQuantity || 0) +
|
||||
(values?.materialScrapQuantity || 0) +
|
||||
(values?.otherScrapQuantity || 0);
|
||||
await formApi?.setValues({
|
||||
feedbackQuantity:
|
||||
(values?.qualifiedQuantity || 0) + unqualified,
|
||||
unqualifiedQuantity: unqualified,
|
||||
});
|
||||
},
|
||||
},
|
||||
defaultValue: 0,
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag', 'unqualifiedQuantity'],
|
||||
show: (values) =>
|
||||
!values.checkFlag && (values.unqualifiedQuantity || 0) > 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'otherScrapQuantity',
|
||||
label: '其他废品',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
controlsPosition: 'right',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
// 废品分类变更,自动累计为不良品数量及报工数量
|
||||
onChange: async () => {
|
||||
const values = await formApi?.getValues();
|
||||
const unqualified =
|
||||
(values?.laborScrapQuantity || 0) +
|
||||
(values?.materialScrapQuantity || 0) +
|
||||
(values?.otherScrapQuantity || 0);
|
||||
await formApi?.setValues({
|
||||
feedbackQuantity:
|
||||
(values?.qualifiedQuantity || 0) + unqualified,
|
||||
unqualifiedQuantity: unqualified,
|
||||
});
|
||||
},
|
||||
},
|
||||
defaultValue: 0,
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag', 'unqualifiedQuantity'],
|
||||
show: (values) =>
|
||||
!values.checkFlag && (values.unqualifiedQuantity || 0) > 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'feedbackUserId',
|
||||
label: '报工人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
disabled: isHeaderReadonly,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择报工人',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'feedbackTime',
|
||||
label: '报工时间',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: isHeaderReadonly,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: '请选择报工时间',
|
||||
type: 'datetime',
|
||||
valueFormat: 'x',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'approveUserId',
|
||||
label: '审核人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
disabled: isHeaderReadonly,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择审核人',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
formItemClass: 'col-span-3',
|
||||
componentProps: {
|
||||
autosize: { maxRows: 3, minRows: 2 },
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProFeedbackApi } from '#/api/mes/pro/feedback';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { ElButton, ElLoading, ElMessage } from 'element-plus';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteFeedback,
|
||||
exportFeedback,
|
||||
getFeedbackPage,
|
||||
} from '#/api/mes/pro/feedback';
|
||||
import { $t } from '#/locales';
|
||||
import { MesProFeedbackStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const currentUserId = userStore.userInfo?.id; // 当前登录用户 ID,用于审批人权限判断
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建生产报工 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData({ formType: 'create' }).open();
|
||||
}
|
||||
|
||||
/** 编辑生产报工 */
|
||||
function handleEdit(row: MesProFeedbackApi.Feedback) {
|
||||
formModalApi.setData({ formType: 'update', id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 提交生产报工 */
|
||||
function handleSubmit(row: MesProFeedbackApi.Feedback) {
|
||||
formModalApi.setData({ formType: 'submit', id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 审批生产报工 */
|
||||
function handleApprove(row: MesProFeedbackApi.Feedback) {
|
||||
formModalApi.setData({ formType: 'approve', id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 详情生产报工 */
|
||||
function handleDetail(row: MesProFeedbackApi.Feedback) {
|
||||
formModalApi.setData({ formType: 'detail', id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 删除生产报工 */
|
||||
async function handleDelete(row: MesProFeedbackApi.Feedback) {
|
||||
const hideLoading = ElLoading.service({
|
||||
text: $t('ui.actionMessage.deleting', [row.code]),
|
||||
});
|
||||
try {
|
||||
await deleteFeedback(row.id!);
|
||||
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.code]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出表格 */
|
||||
async function handleExport() {
|
||||
const data = await exportFeedback(await gridApi.formApi.getValues());
|
||||
downloadFileFromBlobPart({ fileName: '生产报工.xls', source: data });
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: { schema: useGridFormSchema() },
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getFeedbackPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true, keyField: 'id' },
|
||||
toolbarConfig: { refresh: true, search: true },
|
||||
} as VxeTableGridOptions<MesProFeedbackApi.Feedback>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<template #doc>
|
||||
<DocAlert
|
||||
title="【生产】生产报工"
|
||||
url="https://doc.iocoder.cn/mes/pro/feedback/"
|
||||
/>
|
||||
</template>
|
||||
<FormModal @success="handleRefresh" />
|
||||
<Grid table-title="生产报工列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['生产报工']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['mes:pro-feedback:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.export'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.DOWNLOAD,
|
||||
auth: ['mes:pro-feedback:export'],
|
||||
onClick: handleExport,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #code="{ row }">
|
||||
<ElButton link type="primary" @click="handleDetail(row)">
|
||||
{{ row.code }}
|
||||
</ElButton>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'primary',
|
||||
link: true,
|
||||
auth: ['mes:pro-feedback:update'],
|
||||
ifShow: () => row.status === MesProFeedbackStatusEnum.PREPARE,
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.submit'),
|
||||
type: 'success',
|
||||
link: true,
|
||||
auth: ['mes:pro-feedback:update'],
|
||||
ifShow: () => row.status === MesProFeedbackStatusEnum.PREPARE,
|
||||
onClick: handleSubmit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.approve'),
|
||||
type: 'primary',
|
||||
link: true,
|
||||
auth: ['mes:pro-feedback:approve'],
|
||||
ifShow: () =>
|
||||
row.status === MesProFeedbackStatusEnum.APPROVING &&
|
||||
row.approveUserId === currentUserId,
|
||||
onClick: handleApprove.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'danger',
|
||||
link: true,
|
||||
auth: ['mes:pro-feedback:delete'],
|
||||
ifShow: () => row.status === MesProFeedbackStatusEnum.PREPARE,
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.code]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
<script lang="ts" setup>
|
||||
import type { FormType } from '../data';
|
||||
|
||||
import type { MesProFeedbackApi } from '#/api/mes/pro/feedback';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElMessage,
|
||||
ElPopconfirm,
|
||||
ElTabPane,
|
||||
ElTabs,
|
||||
} from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
||||
import {
|
||||
approveFeedback,
|
||||
createFeedback,
|
||||
getFeedback,
|
||||
rejectFeedback,
|
||||
submitFeedback,
|
||||
updateFeedback,
|
||||
} from '#/api/mes/pro/feedback';
|
||||
import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
|
||||
import { $t } from '#/locales';
|
||||
import {
|
||||
MesAutoCodeRuleCode,
|
||||
MesProFeedbackStatusEnum,
|
||||
} from '#/views/mes/utils/constants';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
import ItemConsumeList from './item-consume-list.vue';
|
||||
import ProductProduceList from './product-produce-list.vue';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesProFeedbackApi.Feedback>();
|
||||
const userStore = useUserStore();
|
||||
const subTabsName = ref('itemConsume');
|
||||
|
||||
const isEditable = computed(() =>
|
||||
['create', 'submit', 'update'].includes(formType.value),
|
||||
);
|
||||
const canSubmit = computed(
|
||||
() =>
|
||||
isEditable.value &&
|
||||
formData.value?.status === MesProFeedbackStatusEnum.PREPARE,
|
||||
);
|
||||
const canApprove = computed(() => formType.value === 'approve');
|
||||
const showSubTabs = computed(
|
||||
() =>
|
||||
!!formData.value?.id &&
|
||||
formData.value?.status !== MesProFeedbackStatusEnum.PREPARE &&
|
||||
formData.value?.status !== MesProFeedbackStatusEnum.APPROVING,
|
||||
);
|
||||
const getTitle = computed(() => {
|
||||
if (formType.value === 'detail') {
|
||||
return $t('ui.actionTitle.view', ['生产报工']);
|
||||
}
|
||||
if (formType.value === 'approve') {
|
||||
return '审批生产报工';
|
||||
}
|
||||
if (formType.value === 'submit') {
|
||||
return '提交生产报工';
|
||||
}
|
||||
return formType.value === 'update'
|
||||
? $t('ui.actionTitle.edit', ['生产报工'])
|
||||
: $t('ui.actionTitle.create', ['生产报工']);
|
||||
});
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-1',
|
||||
labelWidth: 110,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: [],
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-3',
|
||||
});
|
||||
|
||||
/** 表单 schema 需要 formApi 引用,所以通过 setState 设置 schema */
|
||||
formApi.setState({ schema: useFormSchema(formType.value, formApi) });
|
||||
|
||||
/** 提交前对齐数量:根据 checkFlag 决定 uncheck/合格/不良归零策略 */
|
||||
function alignQuantity(data: MesProFeedbackApi.Feedback) {
|
||||
if (data.checkFlag) {
|
||||
data.uncheckQuantity = data.feedbackQuantity;
|
||||
data.qualifiedQuantity = 0;
|
||||
data.unqualifiedQuantity = 0;
|
||||
data.laborScrapQuantity = 0;
|
||||
data.materialScrapQuantity = 0;
|
||||
data.otherScrapQuantity = 0;
|
||||
} else {
|
||||
data.feedbackQuantity =
|
||||
(data.qualifiedQuantity || 0) + (data.unqualifiedQuantity || 0);
|
||||
data.uncheckQuantity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存:create 后切换为 update 模式 */
|
||||
async function handleSave() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const data = (await formApi.getValues()) as MesProFeedbackApi.Feedback;
|
||||
alignQuantity(data);
|
||||
if (formType.value === 'create') {
|
||||
const id = await createFeedback(data);
|
||||
formData.value = {
|
||||
...data,
|
||||
id,
|
||||
status: MesProFeedbackStatusEnum.PREPARE,
|
||||
};
|
||||
formType.value = 'update';
|
||||
formApi.setState({ schema: useFormSchema(formType.value, formApi) });
|
||||
await formApi.setFieldValue('id', id);
|
||||
ElMessage.success($t('common.createSuccess'));
|
||||
} else {
|
||||
await updateFeedback(data);
|
||||
formData.value = { ...formData.value, ...data };
|
||||
ElMessage.success($t('common.updateSuccess'));
|
||||
}
|
||||
emit('success');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交:保存最新内容后调用提交接口 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const data = (await formApi.getValues()) as MesProFeedbackApi.Feedback;
|
||||
alignQuantity(data);
|
||||
let id = formData.value?.id;
|
||||
if (formType.value === 'create' || !id) {
|
||||
id = await createFeedback(data);
|
||||
} else {
|
||||
await updateFeedback(data);
|
||||
}
|
||||
await submitFeedback(id!);
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success('报工单已提交');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 审批通过 */
|
||||
async function handleApprove() {
|
||||
if (!formData.value?.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const finished = await approveFeedback(formData.value.id);
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success(
|
||||
finished ? '报工单已审批完成' : '报工成功,请等待质量检验完成!',
|
||||
);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 审批不通过 */
|
||||
async function handleReject() {
|
||||
if (!formData.value?.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
await rejectFeedback(formData.value.id);
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success('报工单已驳回');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载工序的 checkFlag 用于回显数量区域 */
|
||||
async function resolveCheckFlag(routeId?: number, processId?: number) {
|
||||
if (!routeId || !processId) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const routeProcess = await getRouteProcessByRouteAndProcess(
|
||||
routeId,
|
||||
processId,
|
||||
);
|
||||
return routeProcess?.checkFlag ?? false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
if (formType.value === 'detail' || formType.value === 'approve') {
|
||||
await modalApi.close();
|
||||
return;
|
||||
}
|
||||
await handleSave();
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
subTabsName.value = 'itemConsume';
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(formType.value, formApi) });
|
||||
// 审批/详情整表禁用,避免审核人误改未提交的字段
|
||||
formApi.setDisabled(
|
||||
formType.value === 'approve' || formType.value === 'detail',
|
||||
);
|
||||
modalApi.setState({
|
||||
showConfirmButton:
|
||||
formType.value !== 'detail' && formType.value !== 'approve',
|
||||
});
|
||||
await formApi.resetForm();
|
||||
if (!data?.id) {
|
||||
// 新增:默认报工人和报工时间,并自动生成报工单号
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.PRO_FEEDBACK_CODE,
|
||||
);
|
||||
await formApi.setValues({
|
||||
code,
|
||||
feedbackTime: Date.now(),
|
||||
feedbackUserId: userStore.userInfo?.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getFeedback(data.id);
|
||||
const checkFlag = await resolveCheckFlag(
|
||||
formData.value.routeId,
|
||||
formData.value.processId,
|
||||
);
|
||||
// 设置到 values
|
||||
await formApi.setValues({ ...formData.value, checkFlag });
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-3/5">
|
||||
<Form class="mx-4" />
|
||||
<ElTabs
|
||||
v-if="showSubTabs"
|
||||
v-model="subTabsName"
|
||||
type="card"
|
||||
class="mx-4 mt-2"
|
||||
>
|
||||
<ElTabPane label="BOM 物资消耗" name="itemConsume">
|
||||
<ItemConsumeList :feedback-id="formData!.id!" />
|
||||
</ElTabPane>
|
||||
<ElTabPane label="产品产出" name="productProduce">
|
||||
<ProductProduceList :feedback-id="formData!.id!" />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center justify-end gap-2">
|
||||
<ElPopconfirm
|
||||
v-if="canSubmit"
|
||||
title="确认提交该报工单?提交后将不能修改。"
|
||||
@confirm="handleSubmit"
|
||||
>
|
||||
<template #reference>
|
||||
<ElButton type="primary">{{ $t('common.submit') }}</ElButton>
|
||||
</template>
|
||||
</ElPopconfirm>
|
||||
<ElPopconfirm
|
||||
v-if="canApprove"
|
||||
title="确认审批通过该报工单?"
|
||||
@confirm="handleApprove"
|
||||
>
|
||||
<template #reference>
|
||||
<ElButton type="success">通过</ElButton>
|
||||
</template>
|
||||
</ElPopconfirm>
|
||||
<ElPopconfirm
|
||||
v-if="canApprove"
|
||||
title="确认驳回该报工单?"
|
||||
@confirm="handleReject"
|
||||
>
|
||||
<template #reference>
|
||||
<ElButton type="danger">不通过</ElButton>
|
||||
</template>
|
||||
</ElPopconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesWmItemConsumeLineApi } from '#/api/mes/wm/itemconsume/line';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getItemConsumeLinePage } from '#/api/mes/wm/itemconsume/line';
|
||||
|
||||
const props = defineProps<{
|
||||
feedbackId: number;
|
||||
}>();
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: [
|
||||
{ field: 'itemCode', title: '物资编码', minWidth: 120 },
|
||||
{ field: 'itemName', title: '物资名称', minWidth: 140 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 120 },
|
||||
{ field: 'quantity', title: '消耗数量', minWidth: 100 },
|
||||
{ field: 'unitName', title: '单位', minWidth: 80 },
|
||||
{ field: 'batchCode', title: '批次号', minWidth: 120 },
|
||||
],
|
||||
height: 320,
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }) => {
|
||||
return await getItemConsumeLinePage({
|
||||
feedbackId: props.feedbackId,
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesWmItemConsumeLineApi.ItemConsumeLine>,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.feedbackId,
|
||||
() => {
|
||||
gridApi.query();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Grid />
|
||||
</template>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesWmProductProduceLineApi } from '#/api/mes/wm/productproduce/line';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProductProduceLinePage } from '#/api/mes/wm/productproduce/line';
|
||||
|
||||
const props = defineProps<{
|
||||
feedbackId: number;
|
||||
}>();
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: [
|
||||
{ field: 'itemCode', title: '物资编码', minWidth: 120 },
|
||||
{ field: 'itemName', title: '物资名称', minWidth: 140 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 120 },
|
||||
{ field: 'quantity', title: '产出数量', minWidth: 100 },
|
||||
{ field: 'unitMeasureName', title: '单位', minWidth: 80 },
|
||||
{ field: 'batchCode', title: '批次号', minWidth: 120 },
|
||||
{
|
||||
field: 'qualityStatus',
|
||||
title: '质量状态',
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
|
||||
},
|
||||
},
|
||||
],
|
||||
height: 320,
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }) => {
|
||||
return await getProductProduceLinePage({
|
||||
feedbackId: props.feedbackId,
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesWmProductProduceLineApi.ProductProduceLine>,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.feedbackId,
|
||||
() => {
|
||||
gridApi.query();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Grid />
|
||||
</template>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as ProTaskSelectDialog } from './pro-task-select-dialog.vue';
|
||||
export { default as ProTaskSelect } from './pro-task-select.vue';
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProTaskApi } from '#/api/mes/pro/task';
|
||||
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
|
||||
import { ElAlert, ElButton, ElDialog, ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getTaskPage } from '#/api/mes/pro/task';
|
||||
|
||||
import { useTaskSelectGridColumns, useTaskSelectGridFormSchema } from '../data';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
statuses?: number[];
|
||||
}>(),
|
||||
{
|
||||
statuses: undefined,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesProTaskApi.Task[]];
|
||||
}>();
|
||||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(false); // 是否多选;默认按单选选择器使用
|
||||
const selectedRows = ref<MesProTaskApi.Task[]>([]); // 已选任务列表
|
||||
const preSelectedIds = ref<number[]>([]); // 预选任务编号列表
|
||||
const externalWorkOrderId = ref<number>(); // 外部传入的默认工单过滤
|
||||
const externalWorkstationId = ref<number>(); // 外部传入的默认工位过滤
|
||||
|
||||
const statusTip = computed(() => {
|
||||
if (!props.statuses?.length) {
|
||||
return '';
|
||||
}
|
||||
const labels = props.statuses
|
||||
.map((value) => getDictLabel(DICT_TYPE.MES_PRO_TASK_STATUS, value))
|
||||
.filter(Boolean)
|
||||
.join('、');
|
||||
return `仅展示状态为【${labels}】的任务`;
|
||||
});
|
||||
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesProTaskApi.Task>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesProTaskApi.Task[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId !== undefined) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理多选勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选切换 */
|
||||
function handleRadioChange(row: MesProTaskApi.Task) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesProTaskApi.Task) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击:单选直接确认,多选切换勾选 */
|
||||
async function handleCellDblclick({ row }: { row: MesProTaskApi.Task }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 回显预选任务 */
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = gridApi.grid.getData() as MesProTaskApi.Task[];
|
||||
for (const row of rows) {
|
||||
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useTaskSelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useTaskSelectGridColumns(false),
|
||||
height: 520,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getTaskPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
statuses: props.statuses,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesProTaskApi.Task>,
|
||||
gridEvents: {
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesProTaskApi.Task }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 重置查询和选择状态,保留外部传入的工单/工位默认过滤 */
|
||||
async function resetQueryState() {
|
||||
selectedRows.value = [];
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
if (externalWorkOrderId.value) {
|
||||
await gridApi.formApi.setFieldValue(
|
||||
'workOrderId',
|
||||
externalWorkOrderId.value,
|
||||
);
|
||||
}
|
||||
if (externalWorkstationId.value) {
|
||||
await gridApi.formApi.setFieldValue(
|
||||
'workstationId',
|
||||
externalWorkstationId.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开任务选择弹窗 */
|
||||
async function openModal(
|
||||
selectedIds?: number[],
|
||||
options?: {
|
||||
multiple?: boolean;
|
||||
workOrderId?: number;
|
||||
workstationId?: number;
|
||||
},
|
||||
) {
|
||||
open.value = true;
|
||||
multiple.value = options?.multiple ?? false;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
externalWorkOrderId.value = options?.workOrderId;
|
||||
externalWorkstationId.value = options?.workstationId;
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useTaskSelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭任务选择弹窗 */
|
||||
function closeModal() {
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
/** 确认选择任务 */
|
||||
function handleConfirm() {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open: openModal });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="open"
|
||||
title="生产任务选择"
|
||||
width="80%"
|
||||
destroy-on-close
|
||||
@close="closeModal"
|
||||
>
|
||||
<ElAlert
|
||||
v-if="statusTip"
|
||||
:title="statusTip"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="!mb-3"
|
||||
/>
|
||||
<Grid table-title="生产任务列表" />
|
||||
<template #footer>
|
||||
<ElButton @click="closeModal">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleConfirm">确定</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProTaskApi } from '#/api/mes/pro/task';
|
||||
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { CircleX, Search } from '@vben/icons';
|
||||
|
||||
import { ElInput, ElTooltip } from 'element-plus';
|
||||
|
||||
import { getTask } from '#/api/mes/pro/task';
|
||||
|
||||
import ProTaskSelectDialog from './pro-task-select-dialog.vue';
|
||||
|
||||
defineOptions({ name: 'ProTaskSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
clearable?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
statuses?: number[];
|
||||
workOrderId?: number;
|
||||
workstationId?: number;
|
||||
}>(),
|
||||
{
|
||||
clearable: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择任务',
|
||||
statuses: undefined,
|
||||
workOrderId: undefined,
|
||||
workstationId: undefined,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
change: [item: MesProTaskApi.Task | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
const attrs = useAttrs(); // 透传属性
|
||||
const dialogRef = ref<InstanceType<typeof ProTaskSelectDialog>>(); // 任务选择弹窗
|
||||
const hovering = ref(false); // 是否悬停
|
||||
const selectedItem = ref<MesProTaskApi.Task>(); // 选中的任务
|
||||
|
||||
const displayLabel = computed(() => selectedItem.value?.code ?? ''); // 选择器展示编号
|
||||
const showClear = computed( // 是否显示清空图标
|
||||
() =>
|
||||
props.clearable &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== undefined,
|
||||
);
|
||||
|
||||
/** 根据任务编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id === undefined) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getTask(id);
|
||||
} catch (error) {
|
||||
console.error('[ProTaskSelect] resolveItemById failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
resolveItemById(value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 清空已选任务 */
|
||||
function clearSelected() {
|
||||
selectedItem.value = undefined;
|
||||
emit('update:modelValue', undefined);
|
||||
emit('change', undefined);
|
||||
}
|
||||
|
||||
/** 打开任务选择弹窗 */
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
if (showClear.value && target.closest('.el-input__suffix')) {
|
||||
event.stopPropagation();
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds =
|
||||
props.modelValue === undefined ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, {
|
||||
multiple: false,
|
||||
workOrderId: props.workOrderId,
|
||||
workstationId: props.workstationId,
|
||||
});
|
||||
}
|
||||
|
||||
/** 回填选中的任务 */
|
||||
function handleSelected(rows: MesProTaskApi.Task[]) {
|
||||
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"
|
||||
>
|
||||
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
|
||||
<template #content>
|
||||
<div v-if="selectedItem" class="leading-6">
|
||||
<div>任务编号:{{ selectedItem.code || '-' }}</div>
|
||||
<div>任务名称:{{ selectedItem.name || '-' }}</div>
|
||||
<div>工序:{{ selectedItem.processName || '-' }}</div>
|
||||
<div>工作站:{{ selectedItem.workstationName || '-' }}</div>
|
||||
<div>物料:{{ selectedItem.itemName || '-' }}</div>
|
||||
<div>规格:{{ selectedItem.itemSpecification || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<ElInput
|
||||
:disabled="disabled"
|
||||
:model-value="displayLabel"
|
||||
:placeholder="placeholder"
|
||||
readonly
|
||||
>
|
||||
<template #suffix>
|
||||
<CircleX v-if="showClear" class="size-4" />
|
||||
<Search v-else class="size-4" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<ProTaskSelectDialog
|
||||
ref="dialogRef"
|
||||
:statuses="statuses"
|
||||
@selected="handleSelected"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProTaskApi } from '#/api/mes/pro/task';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
|
||||
import { ProProcessSelect } from '#/views/mes/pro/process/components';
|
||||
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
||||
|
||||
/** 任务选择弹窗的搜索表单 */
|
||||
export function useTaskSelectGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'workOrderId',
|
||||
label: '生产工单',
|
||||
component: markRaw(ProWorkOrderSelect),
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请选择生产工单',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'processId',
|
||||
label: '所属工序',
|
||||
component: markRaw(ProProcessSelect),
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请选择工序',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: markRaw(MdWorkstationSelect),
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请选择工作站',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '任务编号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请输入任务编号',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '任务名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请输入任务名称',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 任务选择弹窗的字段 */
|
||||
export function useTaskSelectGridColumns(
|
||||
multiple = false,
|
||||
): VxeTableGridOptions<MesProTaskApi.Task>['columns'] {
|
||||
return [
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{ field: 'code', title: '任务编号', width: 180 },
|
||||
{ field: 'name', title: '任务名称', minWidth: 140 },
|
||||
{ field: 'workstationCode', title: '工作站编码', width: 140 },
|
||||
{ field: 'workstationName', title: '工作站名称', width: 140 },
|
||||
{ field: 'processName', title: '工序', width: 120 },
|
||||
{
|
||||
field: 'checkFlag',
|
||||
title: '是否质检',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||
},
|
||||
},
|
||||
{ field: 'itemCode', title: '物料编码', width: 140 },
|
||||
{ field: 'itemName', title: '物料名称', width: 140 },
|
||||
{ field: 'itemSpecification', title: '规格型号', width: 120 },
|
||||
{ field: 'quantity', title: '排产数量', width: 100 },
|
||||
{ field: 'producedQuantity', title: '已生产数量', width: 110 },
|
||||
{
|
||||
field: 'startTime',
|
||||
title: '开始生产时间',
|
||||
width: 170,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'duration', title: '生产时长', width: 100 },
|
||||
{
|
||||
field: 'endTime',
|
||||
title: '预计完成时间',
|
||||
width: 170,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '任务状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_TASK_STATUS },
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -166,10 +166,10 @@ export const MesProTaskStatusEnum = {
|
|||
/** MES 生产报工状态枚举 */
|
||||
export const MesProFeedbackStatusEnum = {
|
||||
PREPARE: MesOrderStatusConstants.DRAFT,
|
||||
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
|
||||
APPROVING: MesOrderStatusConstants.APPROVING,
|
||||
UNCHECK: MesOrderStatusConstants.APPROVED,
|
||||
FINISHED: MesOrderStatusConstants.FINISHED,
|
||||
CANCELLED: MesOrderStatusConstants.CANCELLED,
|
||||
CANCELED: MesOrderStatusConstants.CANCELLED,
|
||||
} as const;
|
||||
|
||||
/** MES 流转卡状态枚举 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue