feat(mes):完成 cal team 【班组】的迁移

pull/348/head
YunaiV 2026-05-24 20:12:16 +08:00
parent 858011bfab
commit 2bcd81dc94
24 changed files with 1810 additions and 23 deletions

View File

@ -386,6 +386,9 @@ watch(
// 使 nextTick tick
await nextTick();
if ((newValue || '') === paramsJson.value) {
return;
}
handleDataDisplay(newValue || '');
},
{ immediate: true },

View File

@ -12,6 +12,7 @@ import {
IotRuleSceneTriggerTypeEnum,
isDeviceTrigger,
} from '@vben/constants';
import { CronUtils } from '@vben/utils';
import { Form, message } from 'ant-design-vue';
@ -158,12 +159,15 @@ function validateTriggers(_rule: any, value: any, callback: any) {
}
}
}
if (
trigger.type === IotRuleSceneTriggerTypeEnum.TIMER &&
!trigger.cronExpression
) {
callback(new Error(`触发器 ${i + 1}CRON 表达式不能为空`));
return;
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
if (!trigger.cronExpression) {
callback(new Error(`触发器 ${i + 1}CRON 表达式不能为空`));
return;
}
if (!CronUtils.validate(trigger.cronExpression)) {
callback(new Error(`触发器 ${i + 1}CRON 表达式格式不正确`));
return;
}
}
// conditionGroups
if (trigger.conditionGroups?.length) {

View File

@ -28,13 +28,11 @@ const childDataTypeOptions = getDataTypeOptions().filter(
const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<any>;
/** 元素类型切到 struct 时,初始化 dataSpecsList 占位 */
/** 元素类型切换时,清理旧子类型的结构体属性配置 */
function handleChange(e: any) {
const val = e?.target?.value ?? e;
if (val !== IoTDataSpecsDataTypeEnum.STRUCT) {
return;
}
dataSpecs.value.dataSpecsList = [];
dataSpecs.value.childDataType = val;
}
</script>

View File

@ -8,13 +8,17 @@ import { IoTDataSpecsDataTypeEnum } from '@vben/constants';
import { cloneDeep, isEmpty } from '@vben/utils';
import { useVModel } from '@vueuse/core';
import { Button, Divider, Form, Input } from 'ant-design-vue';
import { Button, Divider, Form, Input, message } from 'ant-design-vue';
import { ThingModelFormRules } from '#/api/iot/thingmodel';
import ThingModelProperty from './property.vue';
const props = defineProps<{ direction: string; modelValue: any }>();
const props = defineProps<{
direction: string;
existingIdentifiers?: string[];
modelValue: any;
}>();
const emits = defineEmits(['update:modelValue']);
const thingModelParams = useVModel(props, 'modelValue', emits) as Ref<any[]>;
@ -33,6 +37,13 @@ const [Modal, modalApi] = useVbenModal({
}
//
const data = formData.value;
if (
data.identifier &&
props.existingIdentifiers?.includes(data.identifier)
) {
message.warning('输入参数和输出参数标识符不能重复');
return;
}
const item = {
identifier: data.identifier,
name: data.name,

View File

@ -29,6 +29,17 @@ watch(
(service.value.callType = IoTThingModelServiceCallTypeEnum.ASYNC.value),
{ immediate: true },
);
/** 提取参数标识符列表,用于输入 / 输出参数跨表去重 */
function getParamIdentifiers(params?: any[]) {
const identifiers: string[] = [];
for (const item of params || []) {
if (item.identifier) {
identifiers.push(item.identifier);
}
}
return identifiers;
}
</script>
<template>
@ -51,12 +62,14 @@ watch(
<ThingModelInputOutputParam
v-model="service.inputParams"
:direction="IoTThingModelParamDirectionEnum.INPUT"
:existing-identifiers="getParamIdentifiers(service.outputParams)"
/>
</Form.Item>
<Form.Item label="输出参数">
<ThingModelInputOutputParam
v-model="service.outputParams"
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
:existing-identifiers="getParamIdentifiers(service.inputParams)"
/>
</Form.Item>
</template>

View File

@ -0,0 +1,141 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { nextTick, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTeamPage } from '#/api/mes/cal/team';
import { useTeamSelectGridColumns, useTeamSelectGridFormSchema } from '../data';
const emit = defineEmits<{ selected: [rows: MesCalTeamApi.Team[]] }>();
const open = ref(false); //
const multiple = ref(true); //
const selectedRows = ref<MesCalTeamApi.Team[]>([]); //
const preSelectedIds = ref<number[]>([]); //
/** 处理勾选变化 */
function handleCheckboxChange({ records }: { records: MesCalTeamApi.Team[] }) {
selectedRows.value = records;
}
/** 处理全选变化 */
function handleCheckboxAll({ records }: { records: MesCalTeamApi.Team[] }) {
selectedRows.value = records;
}
/** 双击行:多选切换勾选,单选直接确认 */
function handleCellDblclick({ row }: { row: MesCalTeamApi.Team }) {
if (multiple.value) {
const records = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
const checked = records.some((item) => item.id === row.id);
gridApi.grid.setCheckboxRow(row, !checked);
selectedRows.value = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
return;
}
selectedRows.value = [row];
handleConfirm();
}
/** 回显预选班组 */
function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as MesCalTeamApi.Team[];
for (const row of rows) {
if (row.id && preSelectedIds.value.includes(row.id)) {
gridApi.grid.setCheckboxRow(row, true);
}
}
selectedRows.value = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useTeamSelectGridFormSchema(),
},
gridOptions: {
columns: useTeamSelectGridColumns(),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) =>
await getTeamPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues }),
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesCalTeamApi.Team>,
gridEvents: {
checkboxAll: handleCheckboxAll,
checkboxChange: handleCheckboxChange,
cellDblclick: handleCellDblclick,
},
});
/** 重置查询和选择状态 */
async function resetQueryState() {
selectedRows.value = [];
await gridApi.grid.clearCheckboxRow();
await gridApi.formApi.resetForm();
}
/** 打开班组选择弹窗 */
async function openModal(selectedIds?: number[], options?: { multiple?: boolean }) {
open.value = true;
multiple.value = options?.multiple ?? true;
preSelectedIds.value = selectedIds || [];
await nextTick();
await resetQueryState();
await gridApi.query();
await nextTick();
applyPreSelection();
}
/** 关闭班组选择弹窗 */
async function closeModal() {
open.value = false;
await resetQueryState();
}
/** 确认选择班组 */
function handleConfirm() {
if (selectedRows.value.length === 0) {
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? selectedRows.value : [selectedRows.value[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<Modal
v-model:open="open"
title="班组选择"
width="720px"
:destroy-on-close="true"
@ok="handleConfirm"
@cancel="closeModal"
>
<Grid table-title="" />
</Modal>
</template>

View File

@ -0,0 +1,84 @@
<script lang="ts" setup>
import type { SelectValue } from 'ant-design-vue/es/select';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { onMounted, ref } from 'vue';
import { Button, Select } from 'ant-design-vue';
import { getTeamList } from '#/api/mes/cal/team';
import CalTeamSelectDialog from './cal-team-select-dialog.vue';
const props = withDefaults(
defineProps<{
allowClear?: boolean;
disabled?: boolean;
modelValue?: number;
placeholder?: string;
}>(),
{
allowClear: true,
disabled: false,
modelValue: undefined,
placeholder: '请选择班组',
},
);
const emit = defineEmits<{
change: [row?: MesCalTeamApi.Team];
'update:modelValue': [value?: number];
}>();
const teamList = ref<MesCalTeamApi.Team[]>([]); //
const dialogRef = ref<InstanceType<typeof CalTeamSelectDialog>>(); //
/** 加载班组选项 */
async function loadTeamList() {
teamList.value = await getTeamList();
}
/** 处理下拉选择变化 */
function handleChange(value: SelectValue) {
const teamId = typeof value === 'number' ? value : undefined;
emit('update:modelValue', teamId);
emit(
'change',
teamList.value.find((item) => item.id === teamId),
);
}
/** 打开班组选择弹窗 */
function openDialog() {
if (props.disabled) {
return;
}
dialogRef.value?.open(props.modelValue ? [props.modelValue] : [], { multiple: false });
}
/** 处理弹窗选择 */
function handleSelected(rows: MesCalTeamApi.Team[]) {
const row = rows[0];
emit('update:modelValue', row?.id);
emit('change', row);
}
onMounted(loadTeamList);
</script>
<template>
<div class="flex w-full gap-2">
<Select
:allow-clear="allowClear"
:disabled="disabled"
:field-names="{ label: 'name', value: 'id' }"
:options="teamList"
:placeholder="placeholder"
:value="modelValue"
class="flex-1"
option-filter-prop="name"
@change="handleChange"
/>
<Button :disabled="disabled" @click="openDialog"></Button>
<CalTeamSelectDialog ref="dialogRef" :multiple="false" @selected="handleSelected" />
</div>
</template>

View File

@ -0,0 +1,2 @@
export { default as CalTeamSelectDialog } from './cal-team-select-dialog.vue';
export { default as CalTeamSelect } from './cal-team-select.vue';

View File

@ -0,0 +1,188 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { h } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button } from 'ant-design-vue';
import { z } from '#/adapter/form';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
/** 新增/修改班组的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'code',
label: '班组编码',
component: 'Input',
componentProps: {
maxLength: 64,
placeholder: '请输入班组编码',
},
rules: z.string().min(1, '班组编码不能为空').max(64),
suffix: () =>
h(
Button,
{
type: 'default',
onClick: async () => {
try {
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_TEAM_CODE);
await formApi?.setFieldValue('code', code);
} catch (error) {
console.error(error);
}
},
},
{ default: () => '生成' },
),
},
{
fieldName: 'name',
label: '班组名称',
component: 'Input',
componentProps: {
maxLength: 100,
placeholder: '请输入班组名称',
},
rules: z.string().min(1, '班组名称不能为空').max(100),
},
{
fieldName: 'calendarType',
label: '班组类型',
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
optionType: 'button',
options: getDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE, 'number'),
},
rules: 'selectRequired',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
maxLength: 250,
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '班组编码',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入班组编码',
},
},
{
fieldName: 'name',
label: '班组名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入班组名称',
},
},
{
fieldName: 'calendarType',
label: '班组类型',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE, 'number'),
placeholder: '请选择班组类型',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesCalTeamApi.Team>['columns'] {
return [
{
field: 'code',
title: '班组编码',
minWidth: 150,
slots: {
default: 'code',
},
},
{ field: 'name', title: '班组名称', minWidth: 150 },
{
field: 'calendarType',
title: '班组类型',
width: 140,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_CAL_CALENDAR_TYPE },
},
},
{ field: 'remark', title: '备注', minWidth: 180 },
{ field: 'createTime', title: '创建时间', width: 180, formatter: 'formatDateTime' },
{
title: '操作',
width: 180,
fixed: 'right',
slots: {
default: 'actions',
},
},
];
}
/** 班组选择弹窗搜索表单 */
export function useTeamSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '班组编码',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入班组编码',
},
},
{
fieldName: 'name',
label: '班组名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入班组名称',
},
},
];
}
/** 班组选择弹窗字段 */
export function useTeamSelectGridColumns(): VxeTableGridOptions<MesCalTeamApi.Team>['columns'] {
return [
{ type: 'checkbox', width: 50 },
{ field: 'code', title: '班组编码', minWidth: 140 },
{ field: 'name', title: '班组名称', minWidth: 140 },
{ field: 'remark', title: '备注', minWidth: 160 },
];
}

View File

@ -0,0 +1,147 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteTeam, exportTeam, getTeamPage } from '#/api/mes/cal/team';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建班组 */
function handleCreate() {
formModalApi.setData({ type: 'create' }).open();
}
/** 查看班组 */
function handleDetail(row: MesCalTeamApi.Team) {
formModalApi.setData({ id: row.id, type: 'detail' }).open();
}
/** 编辑班组 */
function handleEdit(row: MesCalTeamApi.Team) {
formModalApi.setData({ id: row.id, type: 'update' }).open();
}
/** 删除班组 */
async function handleDelete(row: MesCalTeamApi.Team) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
});
try {
await deleteTeam(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
hideLoading();
}
}
/** 导出班组 */
async function handleExport() {
const data = await exportTeam(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '班组.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getTeamPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesCalTeamApi.Team>,
});
</script>
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['班组']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:cal-team:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['mes:cal-team:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #code="{ row }">
<Button type="link" @click="handleDetail(row)">{{ row.code }}</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['mes:cal-team:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:cal-team:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,116 @@
<script lang="ts" setup>
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Tabs } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createTeam, getTeam, updateTeam } from '#/api/mes/cal/team';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
import MemberList from './member-list.vue';
const emit = defineEmits(['success']);
const formMode = ref<'create' | 'detail' | 'update'>('create'); //
const subTabsName = ref('member'); //
const formData = ref<MesCalTeamApi.Team>();
const isDetail = computed(() => formMode.value === 'detail'); //
const getTitle = computed(() => {
if (formMode.value === 'detail') {
return $t('ui.actionTitle.view', ['班组']);
}
return formMode.value === 'update'
? $t('ui.actionTitle.edit', ['班组'])
: $t('ui.actionTitle.create', ['班组']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 100,
},
wrapperClass: 'grid-cols-3',
layout: 'horizontal',
schema: [],
showDefaultActions: false,
});
/** 表单 schema 需要 formApi 引用,所以通过 setState 设置 schema */
formApi.setState({ schema: useFormSchema(formApi) });
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (isDetail.value) {
await modalApi.close();
return;
}
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesCalTeamApi.Team;
try {
if (formMode.value === 'create') {
const id = await createTeam(data);
formData.value = { ...data, id: id as number };
await formApi.setFieldValue('id', id);
formMode.value = 'update';
} else {
await updateTeam(data);
formData.value = { ...formData.value, ...data };
}
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
subTabsName.value = 'member';
const data = modalApi.getData<{ id?: number; type?: 'create' | 'detail' | 'update' }>();
formMode.value = data?.type || 'create';
formApi.setDisabled(formMode.value === 'detail');
modalApi.setState({ showConfirmButton: formMode.value !== 'detail' });
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTeam(data.id);
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-4/5">
<Form class="mx-4" />
<Tabs
v-if="formMode !== 'create' && formData?.id"
v-model:active-key="subTabsName"
class="mx-4 mt-4"
>
<Tabs.TabPane key="member" tab="班组成员">
<MemberList :form-type="formMode" :team-id="formData.id" />
</Tabs.TabPane>
</Tabs>
</Modal>
</template>

View File

@ -0,0 +1,194 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamMemberApi } from '#/api/mes/cal/team/member';
import { computed, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
createTeamMember,
deleteTeamMember,
getTeamMemberListByTeam,
} from '#/api/mes/cal/team/member';
import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales';
const props = withDefaults(defineProps<{ formType?: string; teamId: number }>(), {
formType: 'update',
});
const isEditable = computed(() => ['create', 'update'].includes(props.formType)); //
const formOpen = ref(false); //
const formLoading = ref(false); //
const list = ref<MesCalTeamMemberApi.TeamMember[]>([]); //
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: [
{
fieldName: 'teamId',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'userId',
label: '用户',
component: 'ApiSelect',
componentProps: {
allowClear: true,
api: getSimpleUserList,
labelField: 'nickname',
placeholder: '请选择用户',
showSearch: true,
valueField: 'id',
},
rules: 'selectRequired',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
],
showDefaultActions: false,
});
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
autoResize: true,
border: true,
columns: [
{ field: 'userId', title: '用户编号', width: 100 },
{ field: 'nickname', title: '用户昵称', minWidth: 120 },
{ field: 'telephone', title: '手机号', minWidth: 120 },
{ field: 'remark', title: '备注', minWidth: 160 },
{
title: '操作',
width: 90,
fixed: 'right',
slots: {
default: 'actions',
},
visible: isEditable.value,
},
],
data: list.value,
minHeight: 240,
pagerConfig: {
enabled: false,
},
rowConfig: {
isHover: true,
keyField: 'id',
},
showOverflow: true,
toolbarConfig: {
enabled: false,
},
} as VxeTableGridOptions<MesCalTeamMemberApi.TeamMember>,
});
/** 加载成员列表 */
async function getList() {
gridApi.setLoading(true);
try {
list.value = await getTeamMemberListByTeam(props.teamId);
gridApi.setGridOptions({ data: list.value });
} finally {
gridApi.setLoading(false);
}
}
/** 打开成员表单 */
async function openForm() {
formOpen.value = true;
await formApi.resetForm();
await formApi.setValues({ teamId: props.teamId });
}
/** 提交成员表单 */
async function submitForm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
formLoading.value = true;
try {
const data = (await formApi.getValues()) as MesCalTeamMemberApi.TeamMember;
await createTeamMember(data);
formOpen.value = false;
message.success($t('ui.actionMessage.operationSuccess'));
await getList();
} finally {
formLoading.value = false;
}
}
/** 删除成员 */
async function handleDelete(id: number) {
await deleteTeamMember(id);
message.success($t('ui.actionMessage.deleteSuccess', ['成员']));
await getList();
}
watch(
() => props.teamId,
(value) => {
if (value) {
getList();
}
},
{ immediate: true },
);
</script>
<template>
<div>
<div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction :actions="[{ label: '添加成员', type: 'primary', onClick: openForm }]" />
</div>
<Grid class="w-full">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '删除',
type: 'link',
danger: true,
popConfirm: {
title: '确认删除该成员吗?',
confirm: handleDelete.bind(null, row.id!),
},
},
]"
/>
</template>
</Grid>
<Modal
v-model:open="formOpen"
title="添加成员"
width="520px"
:confirm-loading="formLoading"
@ok="submitForm"
>
<Form class="mx-4" />
</Modal>
</div>
</template>

View File

@ -386,6 +386,9 @@ watch(
// 使 nextTick tick
await nextTick();
if ((newValue || '') === paramsJson.value) {
return;
}
handleDataDisplay(newValue || '');
},
{ immediate: true },

View File

@ -12,6 +12,7 @@ import {
IotRuleSceneTriggerTypeEnum,
isDeviceTrigger,
} from '@vben/constants';
import { CronUtils } from '@vben/utils';
import { ElForm, ElMessage } from 'element-plus';
@ -158,12 +159,15 @@ function validateTriggers(_rule: any, value: any, callback: any) {
}
}
}
if (
trigger.type === IotRuleSceneTriggerTypeEnum.TIMER &&
!trigger.cronExpression
) {
callback(new Error(`触发器 ${i + 1}CRON 表达式不能为空`));
return;
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
if (!trigger.cronExpression) {
callback(new Error(`触发器 ${i + 1}CRON 表达式不能为空`));
return;
}
if (!CronUtils.validate(trigger.cronExpression)) {
callback(new Error(`触发器 ${i + 1}CRON 表达式格式不正确`));
return;
}
}
// conditionGroups
if (trigger.conditionGroups?.length) {

View File

@ -28,12 +28,10 @@ const childDataTypeOptions = getDataTypeOptions().filter(
const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<any>;
/** 元素类型切到 struct 时,初始化 dataSpecsList 占位 */
/** 元素类型切换时,清理旧子类型的结构体属性配置 */
function handleChange(val: any) {
if (val !== IoTDataSpecsDataTypeEnum.STRUCT) {
return;
}
dataSpecs.value.dataSpecsList = [];
dataSpecs.value.childDataType = val;
}
</script>

View File

@ -14,13 +14,18 @@ import {
ElForm,
ElFormItem,
ElInput,
ElMessage,
} from 'element-plus';
import { ThingModelFormRules } from '#/api/iot/thingmodel';
import ThingModelProperty from './property.vue';
const props = defineProps<{ direction: string; modelValue: any }>();
const props = defineProps<{
direction: string;
existingIdentifiers?: string[];
modelValue: any;
}>();
const emits = defineEmits(['update:modelValue']);
const thingModelParams = useVModel(props, 'modelValue', emits) as Ref<any[]>;
@ -39,6 +44,13 @@ const [Modal, modalApi] = useVbenModal({
}
//
const data = formData.value;
if (
data.identifier &&
props.existingIdentifiers?.includes(data.identifier)
) {
ElMessage.warning('输入参数和输出参数标识符不能重复');
return;
}
const item = {
identifier: data.identifier,
name: data.name,

View File

@ -29,6 +29,17 @@ watch(
(service.value.callType = IoTThingModelServiceCallTypeEnum.ASYNC.value),
{ immediate: true },
);
/** 提取参数标识符列表,用于输入 / 输出参数跨表去重 */
function getParamIdentifiers(params?: any[]) {
const identifiers: string[] = [];
for (const item of params || []) {
if (item.identifier) {
identifiers.push(item.identifier);
}
}
return identifiers;
}
</script>
<template>
@ -51,12 +62,14 @@ watch(
<ThingModelInputOutputParam
v-model="service.inputParams"
:direction="IoTThingModelParamDirectionEnum.INPUT"
:existing-identifiers="getParamIdentifiers(service.outputParams)"
/>
</ElFormItem>
<ElFormItem label="输出参数">
<ThingModelInputOutputParam
v-model="service.outputParams"
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
:existing-identifiers="getParamIdentifiers(service.inputParams)"
/>
</ElFormItem>
</template>

View File

@ -0,0 +1,138 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { nextTick, ref } from 'vue';
import { ElButton, ElDialog, ElMessage } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTeamPage } from '#/api/mes/cal/team';
import { useTeamSelectGridColumns, useTeamSelectGridFormSchema } from '../data';
const emit = defineEmits<{ selected: [rows: MesCalTeamApi.Team[]] }>();
const open = ref(false); //
const multiple = ref(true); //
const selectedRows = ref<MesCalTeamApi.Team[]>([]); //
const preSelectedIds = ref<number[]>([]); //
/** 处理勾选变化 */
function handleCheckboxChange({ records }: { records: MesCalTeamApi.Team[] }) {
selectedRows.value = records;
}
/** 处理全选变化 */
function handleCheckboxAll({ records }: { records: MesCalTeamApi.Team[] }) {
selectedRows.value = records;
}
/** 双击行:多选切换勾选,单选直接确认 */
function handleCellDblclick({ row }: { row: MesCalTeamApi.Team }) {
if (multiple.value) {
const records = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
const checked = records.some((item) => item.id === row.id);
gridApi.grid.setCheckboxRow(row, !checked);
selectedRows.value = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
return;
}
selectedRows.value = [row];
handleConfirm();
}
/** 回显预选班组 */
function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as MesCalTeamApi.Team[];
for (const row of rows) {
if (row.id && preSelectedIds.value.includes(row.id)) {
gridApi.grid.setCheckboxRow(row, true);
}
}
selectedRows.value = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useTeamSelectGridFormSchema(),
},
gridOptions: {
columns: useTeamSelectGridColumns(),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) =>
await getTeamPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues }),
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesCalTeamApi.Team>,
gridEvents: {
checkboxAll: handleCheckboxAll,
checkboxChange: handleCheckboxChange,
cellDblclick: handleCellDblclick,
},
});
/** 重置查询和选择状态 */
async function resetQueryState() {
selectedRows.value = [];
await gridApi.grid.clearCheckboxRow();
await gridApi.formApi.resetForm();
}
/** 打开班组选择弹窗 */
async function openModal(selectedIds?: number[], options?: { multiple?: boolean }) {
open.value = true;
multiple.value = options?.multiple ?? true;
preSelectedIds.value = selectedIds || [];
await nextTick();
await resetQueryState();
await gridApi.query();
await nextTick();
applyPreSelection();
}
/** 关闭班组选择弹窗 */
async function closeModal() {
open.value = false;
await resetQueryState();
}
/** 确认选择班组 */
function handleConfirm() {
if (selectedRows.value.length === 0) {
ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? selectedRows.value : [selectedRows.value[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<ElDialog v-model="open" title="班组选择" width="720px" destroy-on-close @close="closeModal">
<Grid table-title="" />
<template #footer>
<ElButton @click="closeModal"></ElButton>
<ElButton type="primary" @click="handleConfirm"></ElButton>
</template>
</ElDialog>
</template>

View File

@ -0,0 +1,81 @@
<script lang="ts" setup>
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { onMounted, ref } from 'vue';
import { ElButton, ElOption, ElSelect } from 'element-plus';
import { getTeamList } from '#/api/mes/cal/team';
import CalTeamSelectDialog from './cal-team-select-dialog.vue';
const props = withDefaults(
defineProps<{
clearable?: boolean;
disabled?: boolean;
modelValue?: number;
placeholder?: string;
}>(),
{
clearable: true,
disabled: false,
modelValue: undefined,
placeholder: '请选择班组',
},
);
const emit = defineEmits<{
change: [row?: MesCalTeamApi.Team];
'update:modelValue': [value?: number];
}>();
const teamList = ref<MesCalTeamApi.Team[]>([]); //
const dialogRef = ref<InstanceType<typeof CalTeamSelectDialog>>(); //
/** 加载班组选项 */
async function loadTeamList() {
teamList.value = await getTeamList();
}
/** 处理下拉选择变化 */
function handleChange(value: number | string | undefined) {
const teamId = typeof value === 'number' ? value : undefined;
emit('update:modelValue', teamId);
emit(
'change',
teamList.value.find((item) => item.id === teamId),
);
}
/** 打开班组选择弹窗 */
function openDialog() {
if (props.disabled) {
return;
}
dialogRef.value?.open(props.modelValue ? [props.modelValue] : [], { multiple: false });
}
/** 处理弹窗选择 */
function handleSelected(rows: MesCalTeamApi.Team[]) {
const row = rows[0];
emit('update:modelValue', row?.id);
emit('change', row);
}
onMounted(loadTeamList);
</script>
<template>
<div class="flex w-full gap-2">
<ElSelect
:clearable="clearable"
:disabled="disabled"
:model-value="modelValue"
:placeholder="placeholder"
class="flex-1"
@change="handleChange"
>
<ElOption v-for="item in teamList" :key="item.id" :label="item.name" :value="item.id!" />
</ElSelect>
<ElButton :disabled="disabled" @click="openDialog"></ElButton>
<CalTeamSelectDialog ref="dialogRef" :multiple="false" @selected="handleSelected" />
</div>
</template>

View File

@ -0,0 +1,2 @@
export { default as CalTeamSelectDialog } from './cal-team-select-dialog.vue';
export { default as CalTeamSelect } from './cal-team-select.vue';

View File

@ -0,0 +1,186 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { h } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { ElButton } from 'element-plus';
import { z } from '#/adapter/form';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
/** 新增/修改班组的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'code',
label: '班组编码',
component: 'Input',
componentProps: {
maxLength: 64,
placeholder: '请输入班组编码',
},
rules: z.string().min(1, '班组编码不能为空').max(64),
suffix: () =>
h(
ElButton,
{
type: 'default',
onClick: async () => {
try {
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_TEAM_CODE);
await formApi?.setFieldValue('code', code);
} catch (error) {
console.error(error);
}
},
},
{ default: () => '生成' },
),
},
{
fieldName: 'name',
label: '班组名称',
component: 'Input',
componentProps: {
maxLength: 100,
placeholder: '请输入班组名称',
},
rules: z.string().min(1, '班组名称不能为空').max(100),
},
{
fieldName: 'calendarType',
label: '班组类型',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE, 'number'),
},
rules: 'selectRequired',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
maxLength: 250,
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '班组编码',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入班组编码',
},
},
{
fieldName: 'name',
label: '班组名称',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入班组名称',
},
},
{
fieldName: 'calendarType',
label: '班组类型',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE, 'number'),
placeholder: '请选择班组类型',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesCalTeamApi.Team>['columns'] {
return [
{
field: 'code',
title: '班组编码',
minWidth: 150,
slots: {
default: 'code',
},
},
{ field: 'name', title: '班组名称', minWidth: 150 },
{
field: 'calendarType',
title: '班组类型',
width: 140,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_CAL_CALENDAR_TYPE },
},
},
{ field: 'remark', title: '备注', minWidth: 180 },
{ field: 'createTime', title: '创建时间', width: 180, formatter: 'formatDateTime' },
{
title: '操作',
width: 180,
fixed: 'right',
slots: {
default: 'actions',
},
},
];
}
/** 班组选择弹窗搜索表单 */
export function useTeamSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '班组编码',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入班组编码',
},
},
{
fieldName: 'name',
label: '班组名称',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入班组名称',
},
},
];
}
/** 班组选择弹窗字段 */
export function useTeamSelectGridColumns(): VxeTableGridOptions<MesCalTeamApi.Team>['columns'] {
return [
{ type: 'checkbox', width: 50 },
{ field: 'code', title: '班组编码', minWidth: 140 },
{ field: 'name', title: '班组名称', minWidth: 140 },
{ field: 'remark', title: '备注', minWidth: 160 },
];
}

View File

@ -0,0 +1,145 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { ElButton, ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteTeam, exportTeam, getTeamPage } from '#/api/mes/cal/team';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建班组 */
function handleCreate() {
formModalApi.setData({ type: 'create' }).open();
}
/** 查看班组 */
function handleDetail(row: MesCalTeamApi.Team) {
formModalApi.setData({ id: row.id, type: 'detail' }).open();
}
/** 编辑班组 */
function handleEdit(row: MesCalTeamApi.Team) {
formModalApi.setData({ id: row.id, type: 'update' }).open();
}
/** 删除班组 */
async function handleDelete(row: MesCalTeamApi.Team) {
const hideLoading = ElLoading.service({ text: $t('ui.actionMessage.deleting', [row.name]) });
try {
await deleteTeam(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
hideLoading.close();
}
}
/** 导出班组 */
async function handleExport() {
const data = await exportTeam(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '班组.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getTeamPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesCalTeamApi.Team>,
});
</script>
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['班组']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:cal-team:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['mes:cal-team:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #code="{ row }">
<ElButton link type="primary" @click="handleDetail(row)">{{ row.code }}</ElButton>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['mes:cal-team:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:cal-team:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,112 @@
<script lang="ts" setup>
import type { MesCalTeamApi } from '#/api/mes/cal/team';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage, ElTabPane, ElTabs } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { createTeam, getTeam, updateTeam } from '#/api/mes/cal/team';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
import MemberList from './member-list.vue';
const emit = defineEmits(['success']);
const formMode = ref<'create' | 'detail' | 'update'>('create'); //
const subTabsName = ref('member'); //
const formData = ref<MesCalTeamApi.Team>();
const isDetail = computed(() => formMode.value === 'detail'); //
const getTitle = computed(() => {
if (formMode.value === 'detail') {
return $t('ui.actionTitle.view', ['班组']);
}
return formMode.value === 'update'
? $t('ui.actionTitle.edit', ['班组'])
: $t('ui.actionTitle.create', ['班组']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 100,
},
wrapperClass: 'grid-cols-3',
layout: 'horizontal',
schema: [],
showDefaultActions: false,
});
/** 表单 schema 需要 formApi 引用,所以通过 setState 设置 schema */
formApi.setState({ schema: useFormSchema(formApi) });
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (isDetail.value) {
await modalApi.close();
return;
}
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesCalTeamApi.Team;
try {
if (formMode.value === 'create') {
const id = await createTeam(data);
formData.value = { ...data, id: id as number };
await formApi.setFieldValue('id', id);
formMode.value = 'update';
} else {
await updateTeam(data);
formData.value = { ...formData.value, ...data };
}
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
subTabsName.value = 'member';
const data = modalApi.getData<{ id?: number; type?: 'create' | 'detail' | 'update' }>();
formMode.value = data?.type || 'create';
formApi.setDisabled(formMode.value === 'detail');
modalApi.setState({ showConfirmButton: formMode.value !== 'detail' });
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTeam(data.id);
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-4/5">
<Form class="mx-4" />
<ElTabs v-if="formMode !== 'create' && formData?.id" v-model="subTabsName" class="mx-4 mt-4">
<ElTabPane label="班组成员" name="member">
<MemberList :form-type="formMode" :team-id="formData.id" />
</ElTabPane>
</ElTabs>
</Modal>
</template>

View File

@ -0,0 +1,192 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamMemberApi } from '#/api/mes/cal/team/member';
import { computed, ref, watch } from 'vue';
import { ElButton, ElDialog, ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
createTeamMember,
deleteTeamMember,
getTeamMemberListByTeam,
} from '#/api/mes/cal/team/member';
import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales';
const props = withDefaults(defineProps<{ formType?: string; teamId: number }>(), {
formType: 'update',
});
const isEditable = computed(() => ['create', 'update'].includes(props.formType)); //
const formOpen = ref(false); //
const formLoading = ref(false); //
const list = ref<MesCalTeamMemberApi.TeamMember[]>([]); //
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: [
{
fieldName: 'teamId',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'userId',
label: '用户',
component: 'ApiSelect',
componentProps: {
clearable: true,
api: getSimpleUserList,
labelField: 'nickname',
placeholder: '请选择用户',
showSearch: true,
valueField: 'id',
},
rules: 'selectRequired',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
],
showDefaultActions: false,
});
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
autoResize: true,
border: true,
columns: [
{ field: 'userId', title: '用户编号', width: 100 },
{ field: 'nickname', title: '用户昵称', minWidth: 120 },
{ field: 'telephone', title: '手机号', minWidth: 120 },
{ field: 'remark', title: '备注', minWidth: 160 },
{
title: '操作',
width: 90,
fixed: 'right',
slots: {
default: 'actions',
},
visible: isEditable.value,
},
],
data: list.value,
minHeight: 240,
pagerConfig: {
enabled: false,
},
rowConfig: {
isHover: true,
keyField: 'id',
},
showOverflow: true,
toolbarConfig: {
enabled: false,
},
} as VxeTableGridOptions<MesCalTeamMemberApi.TeamMember>,
});
/** 加载成员列表 */
async function getList() {
gridApi.setLoading(true);
try {
list.value = await getTeamMemberListByTeam(props.teamId);
gridApi.setGridOptions({ data: list.value });
} finally {
gridApi.setLoading(false);
}
}
/** 打开成员表单 */
async function openForm() {
formOpen.value = true;
await formApi.resetForm();
await formApi.setValues({ teamId: props.teamId });
}
/** 提交成员表单 */
async function submitForm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
formLoading.value = true;
try {
const data = (await formApi.getValues()) as MesCalTeamMemberApi.TeamMember;
await createTeamMember(data);
formOpen.value = false;
ElMessage.success($t('ui.actionMessage.operationSuccess'));
await getList();
} finally {
formLoading.value = false;
}
}
/** 删除成员 */
async function handleDelete(id: number) {
await deleteTeamMember(id);
ElMessage.success($t('ui.actionMessage.deleteSuccess', ['成员']));
await getList();
}
watch(
() => props.teamId,
(value) => {
if (value) {
getList();
}
},
{ immediate: true },
);
</script>
<template>
<div>
<div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction :actions="[{ label: '添加成员', type: 'primary', onClick: openForm }]" />
</div>
<Grid class="w-full">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '删除',
type: 'danger',
link: true,
popConfirm: {
title: '确认删除该成员吗?',
confirm: handleDelete.bind(null, row.id!),
},
},
]"
/>
</template>
</Grid>
<ElDialog v-model="formOpen" title="添加成员" width="520px">
<Form class="mx-4" />
<template #footer>
<ElButton @click="formOpen = false">取消</ElButton>
<ElButton type="primary" :loading="formLoading" @click="submitForm"></ElButton>
</template>
</ElDialog>
</div>
</template>