feat(mes):新增“安灯(pro_andon)”的迁移
parent
be3d9eaed7
commit
6b6228bc9c
|
|
@ -0,0 +1,48 @@
|
|||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProAndonConfigApi {
|
||||
/** MES 安灯配置 */
|
||||
export interface AndonConfig {
|
||||
id?: number;
|
||||
reason?: string; // 呼叫原因
|
||||
level?: number; // 级别
|
||||
handlerRoleId?: number; // 处置角色编号
|
||||
handlerUserId?: number; // 处置人编号
|
||||
handlerUserNickname?: string; // 处置人姓名(详情回显)
|
||||
remark?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询安灯配置分页 */
|
||||
export function getAndonConfigPage(params: any) {
|
||||
return requestClient.get('/mes/pro/andon-config/page', { params });
|
||||
}
|
||||
|
||||
/** 查询安灯配置列表 */
|
||||
export function getAndonConfigList() {
|
||||
return requestClient.get<MesProAndonConfigApi.AndonConfig[]>(
|
||||
'/mes/pro/andon-config/list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询安灯配置详情 */
|
||||
export function getAndonConfig(id: number) {
|
||||
return requestClient.get<MesProAndonConfigApi.AndonConfig>(
|
||||
`/mes/pro/andon-config/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增安灯配置 */
|
||||
export function createAndonConfig(data: MesProAndonConfigApi.AndonConfig) {
|
||||
return requestClient.post('/mes/pro/andon-config/create', data);
|
||||
}
|
||||
|
||||
/** 修改安灯配置 */
|
||||
export function updateAndonConfig(data: MesProAndonConfigApi.AndonConfig) {
|
||||
return requestClient.put('/mes/pro/andon-config/update', data);
|
||||
}
|
||||
|
||||
/** 删除安灯配置 */
|
||||
export function deleteAndonConfig(id: number) {
|
||||
return requestClient.delete(`/mes/pro/andon-config/delete?id=${id}`);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProAndonRecordApi {
|
||||
/** MES 安灯记录 */
|
||||
export interface AndonRecord {
|
||||
id?: number;
|
||||
configId?: number; // 安灯配置编号
|
||||
workstationId?: number; // 工作站编号
|
||||
workstationCode?: string; // 工作站编码
|
||||
workstationName?: string; // 工作站名称
|
||||
workOrderId?: number; // 生产工单编号
|
||||
workOrderCode?: string; // 工单编码
|
||||
processId?: number; // 工序编号
|
||||
processName?: string; // 工序名称
|
||||
userId?: number; // 发起用户编号
|
||||
userNickname?: string; // 发起人昵称
|
||||
reason?: string; // 呼叫原因
|
||||
level?: number; // 级别
|
||||
status?: number; // 处置状态
|
||||
handleTime?: number; // 处置时间(毫秒时间戳)
|
||||
handlerUserId?: number; // 处置人编号
|
||||
handlerUserNickname?: string; // 处置人昵称
|
||||
remark?: string; // 备注
|
||||
createTime?: number; // 发起时间
|
||||
}
|
||||
|
||||
/** MES 安灯记录分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
workstationId?: number; // 工作站编号
|
||||
userId?: number; // 发起用户编号
|
||||
handlerUserId?: number; // 处置人编号
|
||||
status?: number; // 处置状态
|
||||
createTime?: string[]; // 发起时间区间
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询安灯记录分页 */
|
||||
export function getAndonRecordPage(params: MesProAndonRecordApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProAndonRecordApi.AndonRecord>>(
|
||||
'/mes/pro/andon-record/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询安灯记录详情 */
|
||||
export function getAndonRecord(id: number) {
|
||||
return requestClient.get<MesProAndonRecordApi.AndonRecord>(
|
||||
`/mes/pro/andon-record/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增安灯记录 */
|
||||
export function createAndonRecord(data: MesProAndonRecordApi.AndonRecord) {
|
||||
return requestClient.post('/mes/pro/andon-record/create', data);
|
||||
}
|
||||
|
||||
/** 删除安灯记录 */
|
||||
export function deleteAndonRecord(id: number) {
|
||||
return requestClient.delete(`/mes/pro/andon-record/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 更新安灯记录(保存/已处置) */
|
||||
export function updateAndonRecord(data: MesProAndonRecordApi.AndonRecord) {
|
||||
return requestClient.put('/mes/pro/andon-record/update', data);
|
||||
}
|
||||
|
||||
/** 导出安灯记录 Excel */
|
||||
export function exportAndonRecord(
|
||||
params: Partial<MesProAndonRecordApi.PageParams>,
|
||||
) {
|
||||
return requestClient.download('/mes/pro/andon-record/export-excel', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProWorkOrderApi {
|
||||
/** MES 生产工单 */
|
||||
export interface WorkOrder {
|
||||
id?: number;
|
||||
code?: string; // 工单编码
|
||||
name?: string; // 工单名称
|
||||
type?: number; // 工单类型
|
||||
status?: number; // 工单状态
|
||||
sourceType?: number;
|
||||
productId?: number; // 产品物料编号
|
||||
productCode?: string;
|
||||
productName?: string;
|
||||
productSpecification?: string;
|
||||
quantity?: number;
|
||||
unitName?: string;
|
||||
routeId?: number;
|
||||
routeName?: string;
|
||||
clientId?: number;
|
||||
clientName?: string;
|
||||
planStartTime?: number | string;
|
||||
planEndTime?: number | string;
|
||||
actualStartTime?: number | string;
|
||||
actualEndTime?: number | string;
|
||||
remark?: string;
|
||||
createTime?: number | string;
|
||||
}
|
||||
|
||||
export interface PageParams extends PageParam {
|
||||
code?: string;
|
||||
name?: string;
|
||||
status?: number;
|
||||
type?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询生产工单分页 */
|
||||
export function getWorkOrderPage(params: MesProWorkOrderApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProWorkOrderApi.WorkOrder>>(
|
||||
'/mes/pro/work-order/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询生产工单详情 */
|
||||
export function getWorkOrder(id: number) {
|
||||
return requestClient.get<MesProWorkOrderApi.WorkOrder>(
|
||||
`/mes/pro/work-order/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Select } from 'ant-design-vue';
|
||||
|
||||
import { getAndonConfigList } from '#/api/mes/pro/andon/config';
|
||||
import DictTag from '#/components/dict-tag/dict-tag.vue';
|
||||
|
||||
/** MES 安灯配置选择器:纯下拉,前端按 reason 过滤 */
|
||||
defineOptions({ name: 'AndonConfigSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
allowClear?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
allowClear: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择呼叫原因',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [item: MesProAndonConfigApi.AndonConfig | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
|
||||
const allList = ref<MesProAndonConfigApi.AndonConfig[]>([]);
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: number | undefined) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
/** 前端过滤:按 reason 模糊匹配 */
|
||||
function handleFilter(input: string, option: any) {
|
||||
const keyword = input.toLowerCase();
|
||||
const item = option?.item as MesProAndonConfigApi.AndonConfig | undefined;
|
||||
return Boolean(item?.reason?.toLowerCase().includes(keyword));
|
||||
}
|
||||
|
||||
/** 选中变化 */
|
||||
function handleChange(value: any) {
|
||||
const nextValue = value === undefined ? undefined : Number(value);
|
||||
const item = allList.value.find((o) => o.id === nextValue);
|
||||
emit('change', item);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
allList.value = (await getAndonConfigList()) || [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<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.reason }}</span>
|
||||
<DictTag :type="DICT_TYPE.MES_PRO_ANDON_LEVEL" :value="item.level" />
|
||||
</div>
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as AndonConfigSelect } from './andon-config-select.vue';
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createAndonConfig,
|
||||
getAndonConfig,
|
||||
updateAndonConfig,
|
||||
} from '#/api/mes/pro/andon/config';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useConfigFormSchema } from '../../record/data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<MesProAndonConfigApi.AndonConfig>();
|
||||
|
||||
const getTitle = computed(() =>
|
||||
formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['安灯配置'])
|
||||
: $t('ui.actionTitle.create', ['安灯配置']),
|
||||
);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: { class: 'w-full' },
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 100,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useConfigFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
// TODO @AI:注释缺少
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
const data =
|
||||
(await formApi.getValues()) as MesProAndonConfigApi.AndonConfig;
|
||||
if (!data.handlerRoleId && !data.handlerUserId) {
|
||||
message.warning('处置角色和处置人至少填一个');
|
||||
modalApi.unlock();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await (formData.value?.id
|
||||
? updateAndonConfig(data)
|
||||
: createAndonConfig(data));
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
await formApi.resetForm();
|
||||
const data = modalApi.getData<MesProAndonConfigApi.AndonConfig>();
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getAndonConfig(data.id);
|
||||
await formApi.setValues(formData.value);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteAndonConfig,
|
||||
getAndonConfigList,
|
||||
} from '#/api/mes/pro/andon/config';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useConfigGridColumns } from '../../record/data';
|
||||
import ConfigForm from './config-form.vue';
|
||||
|
||||
const list = ref<MesProAndonConfigApi.AndonConfig[]>([]);
|
||||
|
||||
const [ConfigFormModal, configFormModalApi] = useVbenModal({
|
||||
connectedComponent: ConfigForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
autoResize: true,
|
||||
border: true,
|
||||
columns: useConfigGridColumns(),
|
||||
data: list.value,
|
||||
minHeight: 320,
|
||||
pagerConfig: { enabled: false },
|
||||
rowConfig: { isHover: true, keyField: 'id' },
|
||||
showOverflow: true,
|
||||
toolbarConfig: { enabled: false },
|
||||
} as VxeTableGridOptions<MesProAndonConfigApi.AndonConfig>,
|
||||
});
|
||||
|
||||
/** 加载安灯配置列表 */
|
||||
async function getList() {
|
||||
gridApi.setLoading(true);
|
||||
try {
|
||||
list.value = (await getAndonConfigList()) || [];
|
||||
gridApi.setGridOptions({ data: list.value });
|
||||
} finally {
|
||||
gridApi.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 新增配置 */
|
||||
function handleCreate() {
|
||||
configFormModalApi.setData({}).open();
|
||||
}
|
||||
|
||||
/** 编辑配置 */
|
||||
function handleEdit(row: MesProAndonConfigApi.AndonConfig) {
|
||||
configFormModalApi.setData({ id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 删除配置 */
|
||||
async function handleDelete(row: MesProAndonConfigApi.AndonConfig) {
|
||||
await deleteAndonConfig(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', ['安灯配置']));
|
||||
await getList();
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
await getList();
|
||||
},
|
||||
});
|
||||
|
||||
defineExpose({ open: () => modalApi.open() });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :show-cancel-button="false" :show-confirm-button="false" class="w-3/5" title="安灯设置">
|
||||
<ConfigFormModal @success="getList" />
|
||||
<div class="mb-3 flex items-center justify-start">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['安灯配置']),
|
||||
type: 'primary',
|
||||
auth: ['mes:pro-andon-config:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<Grid class="w-full" table-title="安灯配置">
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
auth: ['mes:pro-andon-config:update'],
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
auth: ['mes:pro-andon-config:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', ['安灯配置']),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
|
||||
import { ProProcessSelect } from '#/views/mes/pro/process/components';
|
||||
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
||||
import { MesProWorkOrderStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import { AndonConfigSelect } from '../config/components';
|
||||
|
||||
/** 列表搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: MdWorkstationSelect as any,
|
||||
componentProps: { allowClear: true, placeholder: '请选择工作站' },
|
||||
},
|
||||
{
|
||||
fieldName: 'userId',
|
||||
label: '发起人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择发起人',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'handlerUserId',
|
||||
label: '处置人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择处置人',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '处理状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_STATUS, 'number'),
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '发起时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
showTime: true,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions<MesProAndonRecordApi.AndonRecord>['columns'] {
|
||||
return [
|
||||
{ field: 'workstationCode', title: '工作站编码', width: 140 },
|
||||
{ field: 'workstationName', title: '工作站名称', minWidth: 140 },
|
||||
{ field: 'workOrderCode', title: '工单编码', width: 140 },
|
||||
{ field: 'processName', title: '工序名称', width: 140 },
|
||||
{ field: 'userNickname', title: '发起人', width: 110 },
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '发起时间',
|
||||
width: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'reason', title: '呼叫原因', minWidth: 160 },
|
||||
{
|
||||
field: 'level',
|
||||
title: '级别',
|
||||
width: 90,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_ANDON_LEVEL },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'handleTime',
|
||||
title: '处理时间',
|
||||
width: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'handlerUserNickname', title: '处理人', width: 110 },
|
||||
{
|
||||
field: 'status',
|
||||
title: '处置状态',
|
||||
width: 110,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_ANDON_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 安灯记录表单(按表单类型动态切换字段) */
|
||||
export function useFormSchema(
|
||||
formType: 'create' | 'detail' | 'update',
|
||||
onConfigChange?: (config: MesProAndonConfigApi.AndonConfig | undefined) => void,
|
||||
): VbenFormSchema[] {
|
||||
const isCreate = formType === 'create';
|
||||
const isUpdate = formType === 'update';
|
||||
const isDetail = formType === 'detail';
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: MdWorkstationSelect as any,
|
||||
componentProps: { placeholder: '请选择工作站' },
|
||||
rules: 'selectRequired',
|
||||
}
|
||||
: {
|
||||
fieldName: 'workstationName',
|
||||
label: '工作站',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'userId',
|
||||
label: '发起人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择发起人',
|
||||
valueField: 'id',
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'userNickname',
|
||||
label: '发起人',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'workOrderId',
|
||||
label: '生产工单',
|
||||
component: ProWorkOrderSelect as any,
|
||||
componentProps: {
|
||||
placeholder: '请选择工单(可选)',
|
||||
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'workOrderCode',
|
||||
label: '生产工单',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'processId',
|
||||
label: '工序',
|
||||
component: ProProcessSelect as any,
|
||||
componentProps: { placeholder: '请选择工序(可选)' },
|
||||
}
|
||||
: {
|
||||
fieldName: 'processName',
|
||||
label: '工序',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'configId',
|
||||
label: '呼叫原因',
|
||||
component: AndonConfigSelect as any,
|
||||
componentProps: { onChange: onConfigChange },
|
||||
rules: 'selectRequired',
|
||||
}
|
||||
: {
|
||||
fieldName: 'reason',
|
||||
label: '呼叫原因',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
{
|
||||
fieldName: 'level',
|
||||
label: '级别',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_LEVEL, 'number'),
|
||||
placeholder: '由呼叫原因自动带出',
|
||||
},
|
||||
},
|
||||
// 处置信息:update / detail 才展示
|
||||
...(isCreate
|
||||
? []
|
||||
: ([
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'handleTime',
|
||||
label: '处置时间',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
disabled: !isUpdate,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: isUpdate ? '请选择处置时间' : undefined,
|
||||
showTime: true,
|
||||
valueFormat: 'x',
|
||||
},
|
||||
},
|
||||
isUpdate
|
||||
? {
|
||||
fieldName: 'handlerUserId',
|
||||
label: '处置人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择处置人',
|
||||
valueField: 'id',
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'handlerUserNickname',
|
||||
label: '处置人',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
] as VbenFormSchema[])),
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
disabled: isDetail,
|
||||
maxLength: 250,
|
||||
placeholder: '请输入备注',
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 安灯配置表格列(弹窗内嵌网格) */
|
||||
export function useConfigGridColumns(): VxeTableGridOptions<MesProAndonConfigApi.AndonConfig>['columns'] {
|
||||
return [
|
||||
{ field: 'reason', title: '呼叫原因', minWidth: 200 },
|
||||
{
|
||||
field: 'level',
|
||||
title: '级别',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_ANDON_LEVEL },
|
||||
},
|
||||
},
|
||||
{ field: 'handlerUserNickname', title: '处置人', width: 140 },
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
title: '操作',
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 安灯配置表单(弹窗内的新增/编辑表单) */
|
||||
export function useConfigFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
{
|
||||
fieldName: 'reason',
|
||||
label: '呼叫原因',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
autoSize: { maxRows: 3, minRows: 1 },
|
||||
maxLength: 200,
|
||||
placeholder: '请输入呼叫原因',
|
||||
},
|
||||
rules: z.string().min(1, '呼叫原因不能为空').max(200),
|
||||
},
|
||||
{
|
||||
fieldName: 'level',
|
||||
label: '级别',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_LEVEL, 'number'),
|
||||
placeholder: '请选择级别',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'handlerRoleId',
|
||||
label: '处置角色',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: () =>
|
||||
import('#/api/system/role').then((m) => m.getSimpleRoleList()),
|
||||
labelField: 'name',
|
||||
placeholder: '请选择角色(与处置人至少填一个)',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'handlerUserId',
|
||||
label: '处置人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择处置人(与角色至少填一个)',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Input',
|
||||
componentProps: { maxLength: 100, placeholder: '请输入备注' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteAndonRecord,
|
||||
exportAndonRecord,
|
||||
getAndonRecordPage,
|
||||
} from '#/api/mes/pro/andon/record';
|
||||
import { $t } from '#/locales';
|
||||
import { MesProAndonStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import ConfigModal from '../config/modules/config-modal.vue';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const configModalRef = ref<InstanceType<typeof ConfigModal>>();
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 新增记录 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData({ type: 'create' }).open();
|
||||
}
|
||||
|
||||
/** 处置记录 */
|
||||
function handleHandle(row: MesProAndonRecordApi.AndonRecord) {
|
||||
formModalApi.setData({ id: row.id, type: 'update' }).open();
|
||||
}
|
||||
|
||||
/** 详情 */
|
||||
function handleDetail(row: MesProAndonRecordApi.AndonRecord) {
|
||||
formModalApi.setData({ id: row.id, type: 'detail' }).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
async function handleDelete(row: MesProAndonRecordApi.AndonRecord) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.workstationName]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteAndonRecord(row.id!);
|
||||
message.success(
|
||||
$t('ui.actionMessage.deleteSuccess', [row.workstationName]),
|
||||
);
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出 */
|
||||
async function handleExport() {
|
||||
const data = await exportAndonRecord(await gridApi.formApi.getValues());
|
||||
downloadFileFromBlobPart({ fileName: '安灯呼叫记录.xls', source: data });
|
||||
}
|
||||
|
||||
/** 打开安灯设置弹窗 */
|
||||
function handleOpenConfig() {
|
||||
configModalRef.value?.open();
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: { schema: useGridFormSchema() },
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getAndonRecordPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true, keyField: 'id' },
|
||||
toolbarConfig: { refresh: true, search: true },
|
||||
} as VxeTableGridOptions<MesProAndonRecordApi.AndonRecord>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<template #doc>
|
||||
<DocAlert
|
||||
title="【生产】安灯配置、安灯呼叫"
|
||||
url="https://doc.iocoder.cn/mes/pro/andon/"
|
||||
/>
|
||||
</template>
|
||||
<FormModal @success="handleRefresh" />
|
||||
<ConfigModal ref="configModalRef" />
|
||||
<Grid table-title="安灯呼叫记录">
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['安灯呼叫']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['mes:pro-andon-record:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: '安灯设置',
|
||||
type: 'primary',
|
||||
auth: ['mes:pro-andon-config:query'],
|
||||
onClick: handleOpenConfig,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.export'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.DOWNLOAD,
|
||||
auth: ['mes:pro-andon-record:export'],
|
||||
onClick: handleExport,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '处置',
|
||||
type: 'link',
|
||||
auth: ['mes:pro-andon-record:update'],
|
||||
ifShow: () => row.status === MesProAndonStatusEnum.ACTIVE,
|
||||
onClick: handleHandle.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
auth: ['mes:pro-andon-record:query'],
|
||||
onClick: handleDetail.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
auth: ['mes:pro-andon-record:delete'],
|
||||
ifShow: () => row.status === MesProAndonStatusEnum.ACTIVE,
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [
|
||||
row.workstationName,
|
||||
]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createAndonRecord,
|
||||
getAndonRecord,
|
||||
updateAndonRecord,
|
||||
} from '#/api/mes/pro/andon/record';
|
||||
import { $t } from '#/locales';
|
||||
import { MesProAndonStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formType = ref<'create' | 'detail' | 'update'>('create'); // 表单类型
|
||||
const formData = ref<MesProAndonRecordApi.AndonRecord>({}); // 表单数据
|
||||
const userStore = useUserStore();
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
switch (formType.value) {
|
||||
case 'create': {
|
||||
return '新增安灯呼叫';
|
||||
}
|
||||
case 'detail': {
|
||||
return '安灯呼叫详情';
|
||||
}
|
||||
case 'update': {
|
||||
return '处置安灯呼叫';
|
||||
}
|
||||
default: {
|
||||
return '安灯呼叫';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/** 选择呼叫原因后自动填充级别 */
|
||||
function handleConfigChange(config: MesProAndonConfigApi.AndonConfig | undefined) {
|
||||
if (!config) {
|
||||
formApi.setValues({ level: undefined, reason: undefined });
|
||||
return;
|
||||
}
|
||||
formApi.setValues({ level: config.level, reason: config.reason });
|
||||
}
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: { class: 'w-full' },
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 100,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema('create', handleConfigChange),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
/** 提交:新增 */
|
||||
async function handleCreate() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const data =
|
||||
(await formApi.getValues()) as MesProAndonRecordApi.AndonRecord;
|
||||
await createAndonRecord(data);
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 处置:保存(保持 ACTIVE) */
|
||||
async function handleSave() {
|
||||
modalApi.lock();
|
||||
try {
|
||||
const values =
|
||||
(await formApi.getValues()) as MesProAndonRecordApi.AndonRecord;
|
||||
await updateAndonRecord({
|
||||
handlerUserId: values.handlerUserId,
|
||||
handleTime: values.handleTime,
|
||||
id: formData.value.id,
|
||||
remark: values.remark,
|
||||
status: MesProAndonStatusEnum.ACTIVE,
|
||||
});
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success('保存成功');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 处置:标记已处置 */
|
||||
async function handleFinish() {
|
||||
const values =
|
||||
(await formApi.getValues()) as MesProAndonRecordApi.AndonRecord;
|
||||
if (!values.handleTime) {
|
||||
message.warning('标记已处置时,处置时间不能为空');
|
||||
return;
|
||||
}
|
||||
if (!values.handlerUserId) {
|
||||
message.warning('标记已处置时,处置人不能为空');
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
await updateAndonRecord({
|
||||
handlerUserId: values.handlerUserId,
|
||||
handleTime: values.handleTime,
|
||||
id: formData.value.id,
|
||||
remark: values.remark,
|
||||
status: MesProAndonStatusEnum.HANDLED,
|
||||
});
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success('处置成功');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @AI:注释缺少
|
||||
// TODO @AI:代码风格,貌似和别的模块不同;
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = {};
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<{
|
||||
id?: number;
|
||||
type: 'create' | 'detail' | 'update';
|
||||
}>();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
formType.value = data.type;
|
||||
// 重新挂载 schema,以便按 type 切换字段表现
|
||||
formApi.setState({ schema: useFormSchema(data.type, handleConfigChange) });
|
||||
await formApi.resetForm();
|
||||
|
||||
if (data.type === 'create') {
|
||||
// 新增时默认填发起人为当前用户
|
||||
const currentUserId = userStore.userInfo?.id;
|
||||
formData.value = { userId: currentUserId };
|
||||
await formApi.setValues({ userId: currentUserId });
|
||||
return;
|
||||
}
|
||||
// 处置 / 详情:加载详情
|
||||
if (!data.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getAndonRecord(data.id);
|
||||
const initial: MesProAndonRecordApi.AndonRecord = { ...formData.value };
|
||||
// 处置模式下,默认填充处置时间和处置人
|
||||
if (data.type === 'update') {
|
||||
if (!initial.handleTime) {
|
||||
initial.handleTime = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
if (!initial.handlerUserId) {
|
||||
initial.handlerUserId = userStore.userInfo?.id;
|
||||
}
|
||||
}
|
||||
await formApi.setValues(initial);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="dialogTitle" class="w-1/2">
|
||||
<Form />
|
||||
<template #footer>
|
||||
<template v-if="formType === 'create'">
|
||||
<Button @click="modalApi.close()">取消</Button>
|
||||
<Button type="primary" @click="handleCreate">确定</Button>
|
||||
</template>
|
||||
<template v-else-if="formType === 'update'">
|
||||
<Button @click="modalApi.close()">关闭</Button>
|
||||
<Button type="primary" @click="handleSave">保存</Button>
|
||||
<Button type="primary" danger @click="handleFinish">已处置</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button @click="modalApi.close()">关闭</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
/** 甘特图颜色选择器(ant-design-vue 4.x 暂未提供 ColorPicker,封装原生 input[type=color]) */
|
||||
defineOptions({ name: 'RouteColorPicker' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled?: boolean;
|
||||
modelValue?: string;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
modelValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
'update:modelValue': [value: string];
|
||||
}>();
|
||||
|
||||
/** 用于 input[type=color] 的展示值,必须是合法 hex;非法时回退到 #000000 但不修改 modelValue */
|
||||
const swatchValue = computed(() =>
|
||||
/^#[0-9a-f]{6}$/i.test(props.modelValue ?? '')
|
||||
? (props.modelValue as string)
|
||||
: '#000000',
|
||||
);
|
||||
|
||||
/** 颜色变化时同步 modelValue */
|
||||
function handleColorChange(event: Event) {
|
||||
const value = (event.target as HTMLInputElement).value;
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
class="route-color-picker__swatch"
|
||||
:disabled="disabled"
|
||||
type="color"
|
||||
:value="swatchValue"
|
||||
@change="handleColorChange"
|
||||
@input="handleColorChange"
|
||||
/>
|
||||
<span v-if="modelValue">{{ modelValue }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.route-color-picker__swatch {
|
||||
block-size: 28px;
|
||||
border: 1px solid var(--ant-color-border, #d9d9d9);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
inline-size: 36px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.route-color-picker__swatch:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as RouteColorPicker } from './color-picker.vue';
|
||||
|
|
@ -7,7 +7,7 @@ import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom';
|
|||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -20,6 +20,8 @@ import {
|
|||
} from '#/views/mes/md/item/components';
|
||||
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
|
||||
|
||||
import { RouteColorPicker } from './components';
|
||||
|
||||
/** 工艺路线表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
return [
|
||||
|
|
@ -120,7 +122,13 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
}
|
||||
|
||||
/** 列表字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions<MesProRouteApi.Route>['columns'] {
|
||||
export function useGridColumns(
|
||||
onStatusChange?: (
|
||||
newStatus: number,
|
||||
row: MesProRouteApi.Route,
|
||||
) => PromiseLike<boolean | undefined>,
|
||||
statusEditable = true,
|
||||
): VxeTableGridOptions<MesProRouteApi.Route>['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'code',
|
||||
|
|
@ -134,7 +142,16 @@ export function useGridColumns(): VxeTableGridOptions<MesProRouteApi.Route>['col
|
|||
field: 'status',
|
||||
title: '状态',
|
||||
width: 110,
|
||||
slots: { default: 'status' },
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: { beforeChange: onStatusChange },
|
||||
name: 'CellSwitch',
|
||||
props: {
|
||||
checkedValue: CommonStatusEnum.ENABLE,
|
||||
disabled: !statusEditable,
|
||||
unCheckedValue: CommonStatusEnum.DISABLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
|
|
@ -200,11 +217,7 @@ export function useRouteProcessFormSchema(
|
|||
{
|
||||
fieldName: 'colorCode',
|
||||
label: '甘特图颜色',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
maxLength: 16,
|
||||
placeholder: '请输入颜色 hex,例如 #00AEF3',
|
||||
},
|
||||
component: RouteColorPicker,
|
||||
},
|
||||
{
|
||||
fieldName: 'keyFlag',
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProRouteApi } from '#/api/mes/pro/route';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { Button, message, Modal, Switch, Tooltip } from 'ant-design-vue';
|
||||
import { Button, message, Modal, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
|
|
@ -20,6 +22,9 @@ import { $t } from '#/locales';
|
|||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
const statusEditable = hasAccessByCodes(['mes:pro-route:update']); // 是否可切换状态
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
|
|
@ -46,20 +51,22 @@ function handleDetail(row: MesProRouteApi.Route) {
|
|||
}
|
||||
|
||||
/** 切换状态 */
|
||||
async function handleStatusChange(row: MesProRouteApi.Route, value: number) {
|
||||
const text = value === CommonStatusEnum.ENABLE ? '启用' : '停用';
|
||||
const previousStatus = row.status;
|
||||
Modal.confirm({
|
||||
title: `确认要"${text}""${row.name}"工艺路线吗?`,
|
||||
onOk: async () => {
|
||||
await updateRouteStatus(row.id!, value);
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
handleRefresh();
|
||||
},
|
||||
onCancel: () => {
|
||||
// 用户取消时回滚开关状态
|
||||
row.status = previousStatus;
|
||||
},
|
||||
async function handleStatusChange(
|
||||
newStatus: number,
|
||||
row: MesProRouteApi.Route,
|
||||
): Promise<boolean | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Modal.confirm({
|
||||
content: `确认要将"${row.name}"工艺路线切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`,
|
||||
async onOk() {
|
||||
await updateRouteStatus(row.id!, newStatus);
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
resolve(true);
|
||||
},
|
||||
onCancel() {
|
||||
reject(new Error('取消操作'));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +96,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
columns: useGridColumns(handleStatusChange, statusEditable),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
|
|
@ -146,20 +153,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
<template #code="{ row }">
|
||||
<Button type="link" @click="handleDetail(row)">{{ row.code }}</Button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Switch
|
||||
:checked="row.status === CommonStatusEnum.ENABLE"
|
||||
checked-children="启用"
|
||||
un-checked-children="停用"
|
||||
@change="
|
||||
(checked: boolean | number | string) =>
|
||||
handleStatusChange(
|
||||
row,
|
||||
checked ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<Tooltip
|
||||
:open="row.status === CommonStatusEnum.DISABLE ? false : undefined"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { message } from 'ant-design-vue';
|
|||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createRouteProductBom,
|
||||
getRouteProductBom,
|
||||
updateRouteProductBom,
|
||||
} from '#/api/mes/pro/route/productbom';
|
||||
import { $t } from '#/locales';
|
||||
|
|
@ -80,27 +81,33 @@ const [Modal, modalApi] = useVbenModal({
|
|||
await formApi.resetForm();
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{
|
||||
id?: number;
|
||||
processId: number;
|
||||
productId: number;
|
||||
routeId: number;
|
||||
row?: MesProRouteProductBomApi.RouteProductBom;
|
||||
}>();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
productId.value = data.productId;
|
||||
if (data.row) {
|
||||
formData.value = data.row;
|
||||
// 设置到 values
|
||||
await formApi.setValues(data.row);
|
||||
if (!data.id) {
|
||||
// 新增时,给 routeId/processId/productId 兜底默认值
|
||||
await formApi.setValues({
|
||||
processId: data.processId,
|
||||
productId: data.productId,
|
||||
quantity: 1,
|
||||
routeId: data.routeId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await formApi.setValues({
|
||||
processId: data.processId,
|
||||
productId: data.productId,
|
||||
quantity: 1,
|
||||
routeId: data.routeId,
|
||||
});
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getRouteProductBom(data.id);
|
||||
// 设置到 values
|
||||
await formApi.setValues(formData.value);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { default as ProWorkOrderSelect } from './pro-work-order-select.vue';
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Select, Tag, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { getWorkOrder, getWorkOrderPage } from '#/api/mes/pro/workorder';
|
||||
|
||||
/**
|
||||
* MES 生产工单选择器(轻量版)
|
||||
*
|
||||
* 当前用于安灯记录等只需要单选工单 ID 的业务页面:
|
||||
* - 默认按 `status` 过滤拉取首页 100 条工单作为下拉
|
||||
* - 编辑回显走 `getWorkOrder(id)`
|
||||
* - 后续 `mes/pro/workorder` 完整迁移后,可替换为带弹窗的复杂选择器
|
||||
*/
|
||||
defineOptions({ name: 'ProWorkOrderSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
allowClear?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
pageSize?: number;
|
||||
placeholder?: string;
|
||||
status?: number;
|
||||
type?: number;
|
||||
}>(),
|
||||
{
|
||||
allowClear: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
pageSize: 100,
|
||||
placeholder: '请选择工单',
|
||||
status: undefined,
|
||||
type: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [item: MesProWorkOrderApi.WorkOrder | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
|
||||
const allList = ref<MesProWorkOrderApi.WorkOrder[]>([]);
|
||||
const selectedItem = ref<MesProWorkOrderApi.WorkOrder>();
|
||||
|
||||
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 MesProWorkOrderApi.WorkOrder | undefined;
|
||||
return Boolean(
|
||||
item?.code?.toLowerCase().includes(keyword) ||
|
||||
item?.name?.toLowerCase().includes(keyword),
|
||||
);
|
||||
}
|
||||
|
||||
/** 同步选中工单详情,未在列表内时单独拉取 */
|
||||
async function syncSelectedItem(value: number | undefined) {
|
||||
if (value === undefined) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
const found = allList.value.find((item) => item.id === value);
|
||||
if (found) {
|
||||
selectedItem.value = found;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getWorkOrder(value);
|
||||
} catch (error) {
|
||||
console.error('[ProWorkOrderSelect] resolveItemById failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 除 v-model 外,额外抛出完整工单对象给业务表单使用 */
|
||||
function handleChange(value: any) {
|
||||
const nextValue = value === undefined ? undefined : Number(value);
|
||||
syncSelectedItem(nextValue);
|
||||
emit('change', selectedItem.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
syncSelectedItem(value);
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await getWorkOrderPage({
|
||||
pageNo: 1,
|
||||
pageSize: props.pageSize,
|
||||
status: props.status,
|
||||
type: props.type,
|
||||
});
|
||||
allList.value = data.list ?? [];
|
||||
syncSelectedItem(props.modelValue);
|
||||
});
|
||||
</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.productName || '-' }}</div>
|
||||
<div>数量:{{ selectedItem.quantity ?? '-' }}</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.name" color="default">{{ item.name }}</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
|
@ -181,20 +181,17 @@ export const MesProCardStatusEnum = {
|
|||
CANCELLED: MesOrderStatusConstants.CANCELLED,
|
||||
} as const;
|
||||
|
||||
/** MES 安灯类型枚举 */
|
||||
export const MesProAndonTypeEnum = {
|
||||
QUALITY: 1,
|
||||
EQUIPMENT: 2,
|
||||
MATERIAL: 3,
|
||||
PROCESS: 4,
|
||||
OTHER: 9,
|
||||
/** MES 安灯处置状态枚举 */
|
||||
export const MesProAndonStatusEnum = {
|
||||
ACTIVE: 0, // 未处置
|
||||
HANDLED: 1, // 已处置
|
||||
} as const;
|
||||
|
||||
/** MES 安灯状态枚举 */
|
||||
export const MesProAndonStatusEnum = {
|
||||
TRIGGERED: 1,
|
||||
HANDLING: 2,
|
||||
CLOSED: 3,
|
||||
/** MES 安灯级别枚举 */
|
||||
export const MesProAndonLevelEnum = {
|
||||
LEVEL1: 1,
|
||||
LEVEL2: 2,
|
||||
LEVEL3: 3,
|
||||
} as const;
|
||||
|
||||
/** MES 编码规则分段类型枚举 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProAndonConfigApi {
|
||||
/** MES 安灯配置 */
|
||||
export interface AndonConfig {
|
||||
id?: number;
|
||||
reason?: string; // 呼叫原因
|
||||
level?: number; // 级别
|
||||
handlerRoleId?: number; // 处置角色编号
|
||||
handlerUserId?: number; // 处置人编号
|
||||
handlerUserNickname?: string; // 处置人姓名(详情回显)
|
||||
remark?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询安灯配置分页 */
|
||||
export function getAndonConfigPage(params: any) {
|
||||
return requestClient.get('/mes/pro/andon-config/page', { params });
|
||||
}
|
||||
|
||||
/** 查询安灯配置列表 */
|
||||
export function getAndonConfigList() {
|
||||
return requestClient.get<MesProAndonConfigApi.AndonConfig[]>(
|
||||
'/mes/pro/andon-config/list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询安灯配置详情 */
|
||||
export function getAndonConfig(id: number) {
|
||||
return requestClient.get<MesProAndonConfigApi.AndonConfig>(
|
||||
`/mes/pro/andon-config/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增安灯配置 */
|
||||
export function createAndonConfig(data: MesProAndonConfigApi.AndonConfig) {
|
||||
return requestClient.post('/mes/pro/andon-config/create', data);
|
||||
}
|
||||
|
||||
/** 修改安灯配置 */
|
||||
export function updateAndonConfig(data: MesProAndonConfigApi.AndonConfig) {
|
||||
return requestClient.put('/mes/pro/andon-config/update', data);
|
||||
}
|
||||
|
||||
/** 删除安灯配置 */
|
||||
export function deleteAndonConfig(id: number) {
|
||||
return requestClient.delete(`/mes/pro/andon-config/delete?id=${id}`);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProAndonRecordApi {
|
||||
/** MES 安灯记录 */
|
||||
export interface AndonRecord {
|
||||
id?: number;
|
||||
configId?: number; // 安灯配置编号
|
||||
workstationId?: number; // 工作站编号
|
||||
workstationCode?: string; // 工作站编码
|
||||
workstationName?: string; // 工作站名称
|
||||
workOrderId?: number; // 生产工单编号
|
||||
workOrderCode?: string; // 工单编码
|
||||
processId?: number; // 工序编号
|
||||
processName?: string; // 工序名称
|
||||
userId?: number; // 发起用户编号
|
||||
userNickname?: string; // 发起人昵称
|
||||
reason?: string; // 呼叫原因
|
||||
level?: number; // 级别
|
||||
status?: number; // 处置状态
|
||||
handleTime?: number; // 处置时间(毫秒时间戳)
|
||||
handlerUserId?: number; // 处置人编号
|
||||
handlerUserNickname?: string; // 处置人昵称
|
||||
remark?: string; // 备注
|
||||
createTime?: number; // 发起时间
|
||||
}
|
||||
|
||||
/** MES 安灯记录分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
workstationId?: number; // 工作站编号
|
||||
userId?: number; // 发起用户编号
|
||||
handlerUserId?: number; // 处置人编号
|
||||
status?: number; // 处置状态
|
||||
createTime?: string[]; // 发起时间区间
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询安灯记录分页 */
|
||||
export function getAndonRecordPage(params: MesProAndonRecordApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProAndonRecordApi.AndonRecord>>(
|
||||
'/mes/pro/andon-record/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询安灯记录详情 */
|
||||
export function getAndonRecord(id: number) {
|
||||
return requestClient.get<MesProAndonRecordApi.AndonRecord>(
|
||||
`/mes/pro/andon-record/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增安灯记录 */
|
||||
export function createAndonRecord(data: MesProAndonRecordApi.AndonRecord) {
|
||||
return requestClient.post('/mes/pro/andon-record/create', data);
|
||||
}
|
||||
|
||||
/** 删除安灯记录 */
|
||||
export function deleteAndonRecord(id: number) {
|
||||
return requestClient.delete(`/mes/pro/andon-record/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 更新安灯记录(保存/已处置) */
|
||||
export function updateAndonRecord(data: MesProAndonRecordApi.AndonRecord) {
|
||||
return requestClient.put('/mes/pro/andon-record/update', data);
|
||||
}
|
||||
|
||||
/** 导出安灯记录 Excel */
|
||||
export function exportAndonRecord(
|
||||
params: Partial<MesProAndonRecordApi.PageParams>,
|
||||
) {
|
||||
return requestClient.download('/mes/pro/andon-record/export-excel', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProWorkOrderApi {
|
||||
/** MES 生产工单 */
|
||||
export interface WorkOrder {
|
||||
id?: number;
|
||||
code?: string; // 工单编码
|
||||
name?: string; // 工单名称
|
||||
type?: number; // 工单类型
|
||||
status?: number; // 工单状态
|
||||
sourceType?: number;
|
||||
productId?: number; // 产品物料编号
|
||||
productCode?: string;
|
||||
productName?: string;
|
||||
productSpecification?: string;
|
||||
quantity?: number;
|
||||
unitName?: string;
|
||||
routeId?: number;
|
||||
routeName?: string;
|
||||
clientId?: number;
|
||||
clientName?: string;
|
||||
planStartTime?: number | string;
|
||||
planEndTime?: number | string;
|
||||
actualStartTime?: number | string;
|
||||
actualEndTime?: number | string;
|
||||
remark?: string;
|
||||
createTime?: number | string;
|
||||
}
|
||||
|
||||
export interface PageParams extends PageParam {
|
||||
code?: string;
|
||||
name?: string;
|
||||
status?: number;
|
||||
type?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询生产工单分页 */
|
||||
export function getWorkOrderPage(params: MesProWorkOrderApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProWorkOrderApi.WorkOrder>>(
|
||||
'/mes/pro/work-order/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询生产工单详情 */
|
||||
export function getWorkOrder(id: number) {
|
||||
return requestClient.get<MesProWorkOrderApi.WorkOrder>(
|
||||
`/mes/pro/work-order/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { ElOption, ElSelect } from 'element-plus';
|
||||
|
||||
import { getAndonConfigList } from '#/api/mes/pro/andon/config';
|
||||
import DictTag from '#/components/dict-tag/dict-tag.vue';
|
||||
|
||||
/** MES 安灯配置选择器:纯下拉,前端按 reason 过滤 */
|
||||
defineOptions({ name: 'AndonConfigSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
clearable?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
clearable: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择呼叫原因',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [item: MesProAndonConfigApi.AndonConfig | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
|
||||
const allList = ref<MesProAndonConfigApi.AndonConfig[]>([]);
|
||||
const filteredList = ref<MesProAndonConfigApi.AndonConfig[]>([]);
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: number | undefined) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
/** 前端过滤:按 reason 模糊匹配 */
|
||||
function handleFilter(query: string) {
|
||||
if (!query) {
|
||||
filteredList.value = allList.value;
|
||||
return;
|
||||
}
|
||||
const keyword = query.toLowerCase();
|
||||
filteredList.value = allList.value.filter((item) =>
|
||||
item.reason?.toLowerCase().includes(keyword),
|
||||
);
|
||||
}
|
||||
|
||||
/** 选中变化 */
|
||||
function handleChange(value: number | undefined) {
|
||||
const item = allList.value.find((o) => o.id === value);
|
||||
emit('change', item);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
allList.value = (await getAndonConfigList()) || [];
|
||||
filteredList.value = allList.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElSelect
|
||||
v-bind="$attrs"
|
||||
v-model="selectValue"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:filter-method="handleFilter"
|
||||
:placeholder="placeholder"
|
||||
class="w-full"
|
||||
filterable
|
||||
@change="handleChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in filteredList"
|
||||
:key="item.id"
|
||||
:label="item.reason"
|
||||
:value="item.id!"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ item.reason }}</span>
|
||||
<DictTag :type="DICT_TYPE.MES_PRO_ANDON_LEVEL" :value="item.level" />
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as AndonConfigSelect } from './andon-config-select.vue';
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createAndonConfig,
|
||||
getAndonConfig,
|
||||
updateAndonConfig,
|
||||
} from '#/api/mes/pro/andon/config';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useConfigFormSchema } from '../../record/data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<MesProAndonConfigApi.AndonConfig>();
|
||||
|
||||
const getTitle = computed(() =>
|
||||
formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['安灯配置'])
|
||||
: $t('ui.actionTitle.create', ['安灯配置']),
|
||||
);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: { class: 'w-full' },
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 100,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useConfigFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
const data =
|
||||
(await formApi.getValues()) as MesProAndonConfigApi.AndonConfig;
|
||||
if (!data.handlerRoleId && !data.handlerUserId) {
|
||||
ElMessage.warning('处置角色和处置人至少填一个');
|
||||
modalApi.unlock();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await (formData.value?.id
|
||||
? updateAndonConfig(data)
|
||||
: createAndonConfig(data));
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
await formApi.resetForm();
|
||||
const data = modalApi.getData<MesProAndonConfigApi.AndonConfig>();
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getAndonConfig(data.id);
|
||||
await formApi.setValues(formData.value);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteAndonConfig,
|
||||
getAndonConfigList,
|
||||
} from '#/api/mes/pro/andon/config';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useConfigGridColumns } from '../../record/data';
|
||||
import ConfigForm from './config-form.vue';
|
||||
|
||||
const list = ref<MesProAndonConfigApi.AndonConfig[]>([]);
|
||||
|
||||
const [ConfigFormModal, configFormModalApi] = useVbenModal({
|
||||
connectedComponent: ConfigForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
autoResize: true,
|
||||
border: true,
|
||||
columns: useConfigGridColumns(),
|
||||
data: list.value,
|
||||
minHeight: 320,
|
||||
pagerConfig: { enabled: false },
|
||||
rowConfig: { isHover: true, keyField: 'id' },
|
||||
showOverflow: true,
|
||||
toolbarConfig: { enabled: false },
|
||||
} as VxeTableGridOptions<MesProAndonConfigApi.AndonConfig>,
|
||||
});
|
||||
|
||||
/** 加载安灯配置列表 */
|
||||
async function getList() {
|
||||
gridApi.setLoading(true);
|
||||
try {
|
||||
list.value = (await getAndonConfigList()) || [];
|
||||
gridApi.setGridOptions({ data: list.value });
|
||||
} finally {
|
||||
gridApi.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 新增配置 */
|
||||
function handleCreate() {
|
||||
configFormModalApi.setData({}).open();
|
||||
}
|
||||
|
||||
/** 编辑配置 */
|
||||
function handleEdit(row: MesProAndonConfigApi.AndonConfig) {
|
||||
configFormModalApi.setData({ id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 删除配置 */
|
||||
async function handleDelete(row: MesProAndonConfigApi.AndonConfig) {
|
||||
await deleteAndonConfig(row.id!);
|
||||
ElMessage.success($t('ui.actionMessage.deleteSuccess', ['安灯配置']));
|
||||
await getList();
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
await getList();
|
||||
},
|
||||
});
|
||||
|
||||
defineExpose({ open: () => modalApi.open() });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:show-cancel-button="false"
|
||||
:show-confirm-button="false"
|
||||
class="w-3/5"
|
||||
title="安灯设置"
|
||||
>
|
||||
<ConfigFormModal @success="getList" />
|
||||
<div class="mb-3 flex items-center justify-start">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['安灯配置']),
|
||||
type: 'primary',
|
||||
auth: ['mes:pro-andon-config:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<Grid class="w-full" table-title="安灯配置">
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'primary',
|
||||
link: true,
|
||||
auth: ['mes:pro-andon-config:update'],
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'danger',
|
||||
link: true,
|
||||
auth: ['mes:pro-andon-config:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', ['安灯配置']),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
|
||||
import { ProProcessSelect } from '#/views/mes/pro/process/components';
|
||||
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
|
||||
import { MesProWorkOrderStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import { AndonConfigSelect } from '../config/components';
|
||||
|
||||
/** 列表搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: MdWorkstationSelect as any,
|
||||
componentProps: { clearable: true, placeholder: '请选择工作站' },
|
||||
},
|
||||
{
|
||||
fieldName: 'userId',
|
||||
label: '发起人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择发起人',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'handlerUserId',
|
||||
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_ANDON_STATUS, 'number'),
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '发起时间',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'datetimerange',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions<MesProAndonRecordApi.AndonRecord>['columns'] {
|
||||
return [
|
||||
{ field: 'workstationCode', title: '工作站编码', width: 140 },
|
||||
{ field: 'workstationName', title: '工作站名称', minWidth: 140 },
|
||||
{ field: 'workOrderCode', title: '工单编码', width: 140 },
|
||||
{ field: 'processName', title: '工序名称', width: 140 },
|
||||
{ field: 'userNickname', title: '发起人', width: 110 },
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '发起时间',
|
||||
width: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'reason', title: '呼叫原因', minWidth: 160 },
|
||||
{
|
||||
field: 'level',
|
||||
title: '级别',
|
||||
width: 90,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_ANDON_LEVEL },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'handleTime',
|
||||
title: '处理时间',
|
||||
width: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{ field: 'handlerUserNickname', title: '处理人', width: 110 },
|
||||
{
|
||||
field: 'status',
|
||||
title: '处置状态',
|
||||
width: 110,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_ANDON_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 安灯记录表单(按表单类型动态切换字段) */
|
||||
export function useFormSchema(
|
||||
formType: 'create' | 'detail' | 'update',
|
||||
onConfigChange?: (config: MesProAndonConfigApi.AndonConfig | undefined) => void,
|
||||
): VbenFormSchema[] {
|
||||
const isCreate = formType === 'create';
|
||||
const isUpdate = formType === 'update';
|
||||
const isDetail = formType === 'detail';
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'workstationId',
|
||||
label: '工作站',
|
||||
component: MdWorkstationSelect as any,
|
||||
componentProps: { placeholder: '请选择工作站' },
|
||||
rules: 'selectRequired',
|
||||
}
|
||||
: {
|
||||
fieldName: 'workstationName',
|
||||
label: '工作站',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'userId',
|
||||
label: '发起人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择发起人',
|
||||
valueField: 'id',
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'userNickname',
|
||||
label: '发起人',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'workOrderId',
|
||||
label: '生产工单',
|
||||
component: ProWorkOrderSelect as any,
|
||||
componentProps: {
|
||||
placeholder: '请选择工单(可选)',
|
||||
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'workOrderCode',
|
||||
label: '生产工单',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'processId',
|
||||
label: '工序',
|
||||
component: ProProcessSelect as any,
|
||||
componentProps: { placeholder: '请选择工序(可选)' },
|
||||
}
|
||||
: {
|
||||
fieldName: 'processName',
|
||||
label: '工序',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
isCreate
|
||||
? {
|
||||
fieldName: 'configId',
|
||||
label: '呼叫原因',
|
||||
component: AndonConfigSelect as any,
|
||||
componentProps: { onChange: onConfigChange },
|
||||
rules: 'selectRequired',
|
||||
}
|
||||
: {
|
||||
fieldName: 'reason',
|
||||
label: '呼叫原因',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
{
|
||||
fieldName: 'level',
|
||||
label: '级别',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_LEVEL, 'number'),
|
||||
placeholder: '由呼叫原因自动带出',
|
||||
},
|
||||
},
|
||||
// 处置信息:update / detail 才展示
|
||||
...(isCreate
|
||||
? []
|
||||
: ([
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'handleTime',
|
||||
label: '处置时间',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: !isUpdate,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: isUpdate ? '请选择处置时间' : undefined,
|
||||
type: 'datetime',
|
||||
valueFormat: 'x',
|
||||
},
|
||||
},
|
||||
isUpdate
|
||||
? {
|
||||
fieldName: 'handlerUserId',
|
||||
label: '处置人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择处置人',
|
||||
valueField: 'id',
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'handlerUserNickname',
|
||||
label: '处置人',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
] as VbenFormSchema[])),
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
autosize: { maxRows: 3, minRows: 2 },
|
||||
disabled: isDetail,
|
||||
maxLength: 250,
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 安灯配置表格列(弹窗内嵌网格) */
|
||||
export function useConfigGridColumns(): VxeTableGridOptions<MesProAndonConfigApi.AndonConfig>['columns'] {
|
||||
return [
|
||||
{ field: 'reason', title: '呼叫原因', minWidth: 200 },
|
||||
{
|
||||
field: 'level',
|
||||
title: '级别',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_PRO_ANDON_LEVEL },
|
||||
},
|
||||
},
|
||||
{ field: 'handlerUserNickname', title: '处置人', width: 140 },
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
title: '操作',
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 安灯配置表单(弹窗内的新增/编辑表单) */
|
||||
export function useConfigFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: { triggerFields: [''], show: () => false },
|
||||
},
|
||||
{
|
||||
fieldName: 'reason',
|
||||
label: '呼叫原因',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
autosize: { maxRows: 3, minRows: 1 },
|
||||
maxLength: 200,
|
||||
placeholder: '请输入呼叫原因',
|
||||
},
|
||||
rules: z.string().min(1, '呼叫原因不能为空').max(200),
|
||||
},
|
||||
{
|
||||
fieldName: 'level',
|
||||
label: '级别',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_LEVEL, 'number'),
|
||||
placeholder: '请选择级别',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'handlerRoleId',
|
||||
label: '处置角色',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () =>
|
||||
import('#/api/system/role').then((m) => m.getSimpleRoleList()),
|
||||
clearable: true,
|
||||
labelField: 'name',
|
||||
placeholder: '请选择角色(与处置人至少填一个)',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'handlerUserId',
|
||||
label: '处置人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
clearable: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择处置人(与角色至少填一个)',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Input',
|
||||
componentProps: { maxLength: 100, placeholder: '请输入备注' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { ElLoading, ElMessage } from 'element-plus';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
deleteAndonRecord,
|
||||
exportAndonRecord,
|
||||
getAndonRecordPage,
|
||||
} from '#/api/mes/pro/andon/record';
|
||||
import { $t } from '#/locales';
|
||||
import { MesProAndonStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import ConfigModal from '../config/modules/config-modal.vue';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const configModalRef = ref<InstanceType<typeof ConfigModal>>();
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 新增记录 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData({ type: 'create' }).open();
|
||||
}
|
||||
|
||||
/** 处置记录 */
|
||||
function handleHandle(row: MesProAndonRecordApi.AndonRecord) {
|
||||
formModalApi.setData({ id: row.id, type: 'update' }).open();
|
||||
}
|
||||
|
||||
/** 详情 */
|
||||
function handleDetail(row: MesProAndonRecordApi.AndonRecord) {
|
||||
formModalApi.setData({ id: row.id, type: 'detail' }).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
async function handleDelete(row: MesProAndonRecordApi.AndonRecord) {
|
||||
const hideLoading = ElLoading.service({
|
||||
text: $t('ui.actionMessage.deleting', [row.workstationName]),
|
||||
});
|
||||
try {
|
||||
await deleteAndonRecord(row.id!);
|
||||
ElMessage.success(
|
||||
$t('ui.actionMessage.deleteSuccess', [row.workstationName]),
|
||||
);
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出 */
|
||||
async function handleExport() {
|
||||
const data = await exportAndonRecord(await gridApi.formApi.getValues());
|
||||
downloadFileFromBlobPart({ fileName: '安灯呼叫记录.xls', source: data });
|
||||
}
|
||||
|
||||
/** 打开安灯设置弹窗 */
|
||||
function handleOpenConfig() {
|
||||
configModalRef.value?.open();
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: { schema: useGridFormSchema() },
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getAndonRecordPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true, keyField: 'id' },
|
||||
toolbarConfig: { refresh: true, search: true },
|
||||
} as VxeTableGridOptions<MesProAndonRecordApi.AndonRecord>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<template #doc>
|
||||
<DocAlert
|
||||
title="【生产】安灯配置、安灯呼叫"
|
||||
url="https://doc.iocoder.cn/mes/pro/andon/"
|
||||
/>
|
||||
</template>
|
||||
<FormModal @success="handleRefresh" />
|
||||
<ConfigModal ref="configModalRef" />
|
||||
<Grid table-title="安灯呼叫记录">
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['安灯呼叫']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['mes:pro-andon-record:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: '安灯设置',
|
||||
type: 'primary',
|
||||
auth: ['mes:pro-andon-config:query'],
|
||||
onClick: handleOpenConfig,
|
||||
},
|
||||
{
|
||||
label: $t('ui.actionTitle.export'),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.DOWNLOAD,
|
||||
auth: ['mes:pro-andon-record:export'],
|
||||
onClick: handleExport,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '处置',
|
||||
type: 'primary',
|
||||
link: true,
|
||||
auth: ['mes:pro-andon-record:update'],
|
||||
ifShow: () => row.status === MesProAndonStatusEnum.ACTIVE,
|
||||
onClick: handleHandle.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
type: 'primary',
|
||||
link: true,
|
||||
auth: ['mes:pro-andon-record:query'],
|
||||
onClick: handleDetail.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'danger',
|
||||
link: true,
|
||||
auth: ['mes:pro-andon-record:delete'],
|
||||
ifShow: () => row.status === MesProAndonStatusEnum.ACTIVE,
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [
|
||||
row.workstationName,
|
||||
]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config';
|
||||
import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
import { ElButton, ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createAndonRecord,
|
||||
getAndonRecord,
|
||||
updateAndonRecord,
|
||||
} from '#/api/mes/pro/andon/record';
|
||||
import { $t } from '#/locales';
|
||||
import { MesProAndonStatusEnum } from '#/views/mes/utils/constants';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formType = ref<'create' | 'detail' | 'update'>('create');
|
||||
const formData = ref<MesProAndonRecordApi.AndonRecord>({});
|
||||
const userStore = useUserStore();
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
switch (formType.value) {
|
||||
case 'create': {
|
||||
return '新增安灯呼叫';
|
||||
}
|
||||
case 'detail': {
|
||||
return '安灯呼叫详情';
|
||||
}
|
||||
case 'update': {
|
||||
return '处置安灯呼叫';
|
||||
}
|
||||
default: {
|
||||
return '安灯呼叫';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/** 选择呼叫原因后自动填充级别 */
|
||||
function handleConfigChange(config: MesProAndonConfigApi.AndonConfig | undefined) {
|
||||
if (!config) {
|
||||
formApi.setValues({ level: undefined, reason: undefined });
|
||||
return;
|
||||
}
|
||||
formApi.setValues({ level: config.level, reason: config.reason });
|
||||
}
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: { class: 'w-full' },
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 100,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema('create', handleConfigChange),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
/** 提交:新增 */
|
||||
async function handleCreate() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const data =
|
||||
(await formApi.getValues()) as MesProAndonRecordApi.AndonRecord;
|
||||
await createAndonRecord(data);
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 处置:保存(保持 ACTIVE) */
|
||||
async function handleSave() {
|
||||
modalApi.lock();
|
||||
try {
|
||||
const values =
|
||||
(await formApi.getValues()) as MesProAndonRecordApi.AndonRecord;
|
||||
await updateAndonRecord({
|
||||
handlerUserId: values.handlerUserId,
|
||||
handleTime: values.handleTime,
|
||||
id: formData.value.id,
|
||||
remark: values.remark,
|
||||
status: MesProAndonStatusEnum.ACTIVE,
|
||||
});
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success('保存成功');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** 处置:标记已处置 */
|
||||
async function handleFinish() {
|
||||
const values =
|
||||
(await formApi.getValues()) as MesProAndonRecordApi.AndonRecord;
|
||||
if (!values.handleTime) {
|
||||
ElMessage.warning('标记已处置时,处置时间不能为空');
|
||||
return;
|
||||
}
|
||||
if (!values.handlerUserId) {
|
||||
ElMessage.warning('标记已处置时,处置人不能为空');
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
await updateAndonRecord({
|
||||
handlerUserId: values.handlerUserId,
|
||||
handleTime: values.handleTime,
|
||||
id: formData.value.id,
|
||||
remark: values.remark,
|
||||
status: MesProAndonStatusEnum.HANDLED,
|
||||
});
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success('处置成功');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = {};
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<{
|
||||
id?: number;
|
||||
type: 'create' | 'detail' | 'update';
|
||||
}>();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
formType.value = data.type;
|
||||
formApi.setState({ schema: useFormSchema(data.type, handleConfigChange) });
|
||||
await formApi.resetForm();
|
||||
|
||||
if (data.type === 'create') {
|
||||
const currentUserId = userStore.userInfo?.id;
|
||||
formData.value = { userId: currentUserId };
|
||||
await formApi.setValues({ userId: currentUserId });
|
||||
return;
|
||||
}
|
||||
if (!data.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getAndonRecord(data.id);
|
||||
const initial: MesProAndonRecordApi.AndonRecord = { ...formData.value };
|
||||
if (data.type === 'update') {
|
||||
if (!initial.handleTime) {
|
||||
initial.handleTime = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
if (!initial.handlerUserId) {
|
||||
initial.handlerUserId = userStore.userInfo?.id;
|
||||
}
|
||||
}
|
||||
await formApi.setValues(initial);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="dialogTitle" class="w-1/2">
|
||||
<Form />
|
||||
<template #footer>
|
||||
<template v-if="formType === 'create'">
|
||||
<ElButton @click="modalApi.close()">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleCreate">确定</ElButton>
|
||||
</template>
|
||||
<template v-else-if="formType === 'update'">
|
||||
<ElButton @click="modalApi.close()">关闭</ElButton>
|
||||
<ElButton type="primary" @click="handleSave">保存</ElButton>
|
||||
<ElButton type="success" @click="handleFinish">已处置</ElButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ElButton @click="modalApi.close()">关闭</ElButton>
|
||||
</template>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { ElColorPicker } from 'element-plus';
|
||||
|
||||
/** 甘特图颜色选择器(封装 ElColorPicker,并在右侧展示 hex 文本) */
|
||||
defineOptions({ name: 'RouteColorPicker' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled?: boolean;
|
||||
modelValue?: string;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
modelValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
'update:modelValue': [value: string];
|
||||
}>();
|
||||
|
||||
/** 内部 v-model 适配 ElColorPicker,避免对外抛出 null */
|
||||
const color = computed({
|
||||
get: () => props.modelValue || '',
|
||||
set: (value: null | string | undefined) => {
|
||||
emit('update:modelValue', value ?? '');
|
||||
emit('change', value ?? '');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElColorPicker v-model="color" :disabled="disabled" />
|
||||
<span v-if="color">{{ color }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as RouteColorPicker } from './color-picker.vue';
|
||||
|
|
@ -7,7 +7,7 @@ import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom';
|
|||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
|
@ -20,6 +20,8 @@ import {
|
|||
} from '#/views/mes/md/item/components';
|
||||
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
|
||||
|
||||
import { RouteColorPicker } from './components';
|
||||
|
||||
/** 工艺路线表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
return [
|
||||
|
|
@ -120,7 +122,13 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
}
|
||||
|
||||
/** 列表字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions<MesProRouteApi.Route>['columns'] {
|
||||
export function useGridColumns(
|
||||
onStatusChange?: (
|
||||
newStatus: number,
|
||||
row: MesProRouteApi.Route,
|
||||
) => PromiseLike<boolean | undefined>,
|
||||
statusEditable = true,
|
||||
): VxeTableGridOptions<MesProRouteApi.Route>['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'code',
|
||||
|
|
@ -134,7 +142,16 @@ export function useGridColumns(): VxeTableGridOptions<MesProRouteApi.Route>['col
|
|||
field: 'status',
|
||||
title: '状态',
|
||||
width: 110,
|
||||
slots: { default: 'status' },
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: { beforeChange: onStatusChange },
|
||||
name: 'CellSwitch',
|
||||
props: {
|
||||
activeValue: CommonStatusEnum.ENABLE,
|
||||
disabled: !statusEditable,
|
||||
inactiveValue: CommonStatusEnum.DISABLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
|
|
@ -205,11 +222,7 @@ export function useRouteProcessFormSchema(
|
|||
{
|
||||
fieldName: 'colorCode',
|
||||
label: '甘特图颜色',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
maxLength: 16,
|
||||
placeholder: '请输入颜色 hex,例如 #00AEF3',
|
||||
},
|
||||
component: RouteColorPicker,
|
||||
},
|
||||
{
|
||||
fieldName: 'keyFlag',
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesProRouteApi } from '#/api/mes/pro/route';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import {
|
||||
|
|
@ -11,7 +13,6 @@ import {
|
|||
ElLoading,
|
||||
ElMessage,
|
||||
ElMessageBox,
|
||||
ElSwitch,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
|
|
@ -27,44 +28,54 @@ import { $t } from '#/locales';
|
|||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
const statusEditable = hasAccessByCodes(['mes:pro-route:update']); // 是否可切换状态
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建工艺路线 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData({ type: 'create' }).open();
|
||||
}
|
||||
|
||||
/** 编辑工艺路线(仅停用状态可编辑) */
|
||||
function handleEdit(row: MesProRouteApi.Route) {
|
||||
formModalApi.setData({ id: row.id, type: 'update' }).open();
|
||||
}
|
||||
|
||||
/** 详情查看 */
|
||||
function handleDetail(row: MesProRouteApi.Route) {
|
||||
formModalApi.setData({ id: row.id, type: 'detail' }).open();
|
||||
}
|
||||
|
||||
async function handleStatusChange(row: MesProRouteApi.Route, value: number) {
|
||||
const text = value === CommonStatusEnum.ENABLE ? '启用' : '停用';
|
||||
const previousStatus = row.status;
|
||||
/** 切换状态 */
|
||||
async function handleStatusChange(
|
||||
newStatus: number,
|
||||
row: MesProRouteApi.Route,
|
||||
): Promise<boolean | undefined> {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认要"${text}""${row.name}"工艺路线吗?`,
|
||||
`确认要将"${row.name}"工艺路线切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`,
|
||||
'提示',
|
||||
{ type: 'warning' },
|
||||
);
|
||||
await updateRouteStatus(row.id!, value);
|
||||
await updateRouteStatus(row.id!, newStatus);
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
handleRefresh();
|
||||
return true;
|
||||
} catch {
|
||||
row.status = previousStatus;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除(仅停用状态可删除) */
|
||||
async function handleDelete(row: MesProRouteApi.Route) {
|
||||
const hideLoading = ElLoading.service({
|
||||
text: $t('ui.actionMessage.deleting', [row.name]),
|
||||
|
|
@ -78,6 +89,7 @@ async function handleDelete(row: MesProRouteApi.Route) {
|
|||
}
|
||||
}
|
||||
|
||||
/** 导出 */
|
||||
async function handleExport() {
|
||||
const data = await exportRoute(await gridApi.formApi.getValues());
|
||||
downloadFileFromBlobPart({ fileName: '工艺路线.xls', source: data });
|
||||
|
|
@ -86,7 +98,7 @@ async function handleExport() {
|
|||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: { schema: useGridFormSchema() },
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
columns: useGridColumns(handleStatusChange, statusEditable),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
|
|
@ -141,20 +153,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
{{ row.code }}
|
||||
</ElButton>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<ElSwitch
|
||||
:model-value="row.status === CommonStatusEnum.ENABLE"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
@update:model-value="
|
||||
(value: boolean | number | string) =>
|
||||
handleStatusChange(
|
||||
row,
|
||||
value ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<ElTooltip
|
||||
:disabled="row.status === CommonStatusEnum.DISABLE"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { ElMessage } from 'element-plus';
|
|||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createRouteProductBom,
|
||||
getRouteProductBom,
|
||||
updateRouteProductBom,
|
||||
} from '#/api/mes/pro/route/productbom';
|
||||
import { $t } from '#/locales';
|
||||
|
|
@ -80,27 +81,33 @@ const [Modal, modalApi] = useVbenModal({
|
|||
await formApi.resetForm();
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{
|
||||
id?: number;
|
||||
processId: number;
|
||||
productId: number;
|
||||
routeId: number;
|
||||
row?: MesProRouteProductBomApi.RouteProductBom;
|
||||
}>();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
productId.value = data.productId;
|
||||
if (data.row) {
|
||||
formData.value = data.row;
|
||||
// 设置到 values
|
||||
await formApi.setValues(data.row);
|
||||
if (!data.id) {
|
||||
// 新增时,给 routeId/processId/productId 兜底默认值
|
||||
await formApi.setValues({
|
||||
processId: data.processId,
|
||||
productId: data.productId,
|
||||
quantity: 1,
|
||||
routeId: data.routeId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await formApi.setValues({
|
||||
processId: data.processId,
|
||||
productId: data.productId,
|
||||
quantity: 1,
|
||||
routeId: data.routeId,
|
||||
});
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getRouteProductBom(data.id);
|
||||
// 设置到 values
|
||||
await formApi.setValues(formData.value);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { default as ProWorkOrderSelect } from './pro-work-order-select.vue';
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
<script lang="ts" setup>
|
||||
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { ElOption, ElSelect, ElTag, ElTooltip } from 'element-plus';
|
||||
|
||||
import { getWorkOrder, getWorkOrderPage } from '#/api/mes/pro/workorder';
|
||||
|
||||
/**
|
||||
* MES 生产工单选择器(轻量版)
|
||||
*
|
||||
* 当前用于安灯记录等只需要单选工单 ID 的业务页面:
|
||||
* - 默认按 `status` 过滤拉取首页 100 条工单作为下拉
|
||||
* - 编辑回显走 `getWorkOrder(id)`
|
||||
* - 后续 `mes/pro/workorder` 完整迁移后,可替换为带弹窗的复杂选择器
|
||||
*/
|
||||
defineOptions({ name: 'ProWorkOrderSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
clearable?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
pageSize?: number;
|
||||
placeholder?: string;
|
||||
status?: number;
|
||||
type?: number;
|
||||
}>(),
|
||||
{
|
||||
clearable: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
pageSize: 100,
|
||||
placeholder: '请选择工单',
|
||||
status: undefined,
|
||||
type: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [item: MesProWorkOrderApi.WorkOrder | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
|
||||
const allList = ref<MesProWorkOrderApi.WorkOrder[]>([]);
|
||||
const filteredList = ref<MesProWorkOrderApi.WorkOrder[]>([]);
|
||||
const selectedItem = ref<MesProWorkOrderApi.WorkOrder>();
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: number | undefined) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
function handleFilter(query: string) {
|
||||
if (!query) {
|
||||
filteredList.value = allList.value;
|
||||
return;
|
||||
}
|
||||
const keyword = query.toLowerCase();
|
||||
filteredList.value = allList.value.filter(
|
||||
(item) =>
|
||||
item.code?.toLowerCase().includes(keyword) ||
|
||||
item.name?.toLowerCase().includes(keyword),
|
||||
);
|
||||
}
|
||||
|
||||
/** 同步选中工单详情,未在列表内时单独拉取 */
|
||||
async function syncSelectedItem(value: number | undefined) {
|
||||
if (value === undefined) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
const found = allList.value.find((item) => item.id === value);
|
||||
if (found) {
|
||||
selectedItem.value = found;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getWorkOrder(value);
|
||||
} catch (error) {
|
||||
console.error('[ProWorkOrderSelect] resolveItemById failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 除 v-model 外,额外抛出完整工单对象给业务表单使用 */
|
||||
function handleChange(value: number | undefined) {
|
||||
syncSelectedItem(value);
|
||||
emit('change', selectedItem.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
syncSelectedItem(value);
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await getWorkOrderPage({
|
||||
pageNo: 1,
|
||||
pageSize: props.pageSize,
|
||||
status: props.status,
|
||||
type: props.type,
|
||||
});
|
||||
allList.value = data.list ?? [];
|
||||
filteredList.value = allList.value;
|
||||
syncSelectedItem(props.modelValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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.productName || '-' }}</div>
|
||||
<div>数量:{{ selectedItem.quantity ?? '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<ElSelect
|
||||
v-bind="$attrs"
|
||||
v-model="selectValue"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:filter-method="handleFilter"
|
||||
:placeholder="placeholder"
|
||||
class="w-full"
|
||||
filterable
|
||||
@change="handleChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in filteredList"
|
||||
:key="item.id"
|
||||
:label="item.code"
|
||||
:value="item.id!"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ item.code }}</span>
|
||||
<ElTag v-if="item.name" size="small" type="info">
|
||||
{{ item.name }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElTooltip>
|
||||
</template>
|
||||
|
|
@ -181,20 +181,17 @@ export const MesProCardStatusEnum = {
|
|||
CANCELLED: MesOrderStatusConstants.CANCELLED,
|
||||
} as const;
|
||||
|
||||
/** MES 安灯类型枚举 */
|
||||
export const MesProAndonTypeEnum = {
|
||||
QUALITY: 1,
|
||||
EQUIPMENT: 2,
|
||||
MATERIAL: 3,
|
||||
PROCESS: 4,
|
||||
OTHER: 9,
|
||||
/** MES 安灯处置状态枚举 */
|
||||
export const MesProAndonStatusEnum = {
|
||||
ACTIVE: 0, // 未处置
|
||||
HANDLED: 1, // 已处置
|
||||
} as const;
|
||||
|
||||
/** MES 安灯状态枚举 */
|
||||
export const MesProAndonStatusEnum = {
|
||||
TRIGGERED: 1,
|
||||
HANDLING: 2,
|
||||
CLOSED: 3,
|
||||
/** MES 安灯级别枚举 */
|
||||
export const MesProAndonLevelEnum = {
|
||||
LEVEL1: 1,
|
||||
LEVEL2: 2,
|
||||
LEVEL3: 3,
|
||||
} as const;
|
||||
|
||||
/** MES 编码规则分段类型枚举 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue