1、先上代码data.tsimport type { VbenFormSchema } from #/adapter/form; import type { VxeTableGridOptions } from #/adapter/vxe-table; import { DICT_TYPE } from vben/constants; import { getDictOptions } from vben/hooks; import { useUserStore } from vben/stores; import dayjs from dayjs; import { getSimpleUserList } from #/api/system/user; import { getRangePickerFutureProps } from #/utils; const userStore useUserStore(); /** 列表的搜索表单 */ export function useGridFormSchema(tabType: number): VbenFormSchema[] { return [ { fieldName: sn, label: 设备型号, component: Input, componentProps: { allowClear: true, placeholder: 请输入设备型号, }, }, { fieldName: firmwareStatus, label: 升级状态, component: Select, componentProps: { options: getDictOptions( DICT_TYPE.DJ_SK2_EQUIPMENT_FIRMWARE_STATUS_ENUM, number, ), allowClear: true, mode: multiple, placeholder: 请选择升级状态, }, // 直接在这里控制显隐最稳定 hide: tabType 1, }, { fieldName: deviceOnlineStatus, label: 在线状态, component: Select, componentProps: { options: getDictOptions( DICT_TYPE.DJ_SK2_EQUIPMENT_ONLINE_STATUS_SELECT, string, ), allowClear: true, placeholder: 请选择在线状态, }, // 直接在这里控制显隐最稳定 hide: tabType 0, }, { fieldName: deviceWorkingStatus, label: 工作状态, component: Select, componentProps: { allowClear: true, options: getDictOptions( DICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM, string, ), placeholder: 请选择工作状态, }, // 直接在这里控制显隐最稳定 hide: tabType 1, }, // { // fieldName: deptId, // label: 所属项目, // component: ApiTreeSelect, // componentProps: { // api: async () { // const data await getDeptList(); // return handleTree(data); // }, // labelField: name, // valueField: id, // childrenField: children, // placeholder: 请选择所属项目, // treeDefaultExpandAll: true, // allowClear: true, // }, // }, { fieldName: travelApplication, label: 设备型号, component: Select, componentProps: { allowClear: true, options: getDictOptions(DICT_TYPE.APP_FLY_ZERO_NO_ONE_YES, number), placeholder: 请选择设备型号, }, }, ]; } /** 列表的字段 */ export function useGridColumns( tabType: number, ): VxeTableGridOptions[columns] { return [ { type: seq, title: 序号, minWidth: 50, }, { field: deviceModel, title: 设备型号, minWidth: 150, align: left, // 左对齐 slots: { default: device_model }, }, { field: deviceSn, title: 设备SN, minWidth: 180, align: left, // 左对齐 slots: { default: device_sn }, }, { field: deviceCallsign, title: 设备名称, minWidth: 120, align: left, // 左对齐 slots: { default: device_callsign }, }, { field: deviceFirmwareVersion, title: 固件版本, minWidth: 110, align: left, // 左对齐 slots: { default: device_firmware_version }, }, { field: deviceFirmwareStatus, title: 固件升级, minWidth: 120, align: left, // 左对齐 formatter: ({ cellValue }) { return cellValue 0 ? 要看司空2 : cellValue 1 ? 无需升级 : cellValue 1 ? 升级中 : —; }, }, { field: deviceWorkingStatus, title: 工作状态, minWidth: 90, visible: tabType 0, align: left, // 左对齐 slots: { default: device_working_status }, }, { field: deviceOnlineStatus, title: 在线状态, minWidth: 90, visible: tabType 1, cellRender: { name: CellDict, props: { type: DICT_TYPE.DJ_SK2_EQUIPMENT_ONLINE_STATUS_ENUM }, }, }, { field: deviceActiveProjectName, title: 所属项目, minWidth: 120, }, { field: deviceAddTime, title: 加入组织时间, minWidth: 150, align: left, // 左对齐 slots: { default: device_add_time }, formatter: ({ cellValue }) { return cellValue ? dayjs(Number(cellValue) * 1000).format(YYYY-MM-DD HH:mm:ss) : —; }, }, { field: deviceLastOnlineTime, title: 在线时间, minWidth: 150, align: left, // 左对齐 slots: { default: device_last_online_time }, formatter: ({ cellValue }) { return cellValue ? dayjs(Number(cellValue) * 1000).format(YYYY-MM-DD HH:mm:ss) : —; }, }, // { // title: 操作, // width: 250, // fixed: right, // slots: { default: actions }, // }, ]; } /** 新增/修改的表单 */ export function useFormSchema(automaticAssignment: number): VbenFormSchema[] { return [ { fieldName: id, component: Input, dependencies: { triggerFields: [], show: () false, }, }, { fieldName: userId, label: automaticAssignment 1 ? 处理人员(不选则系统自动指派) : 处理人员, component: ApiSelect, componentProps: { api: getSimpleUserList, labelField: nickname, valueField: id, placeholder: 请选择处理人员, allowClear: true, showSearch: true, disabled: automaticAssignment 0, filterOption: (inputValue: any, option: any) { console.warn(automaticAssignment); // 确保 option 存在且 nickname 字段可被检索 if (!option || !option.label) return false; // 忽略大小写的模糊匹配 return option.label.toLowerCase().includes(inputValue.toLowerCase()); }, }, formItemClass: col-span-12, // 是否属于自动分配任务 0-否1-是不能为空 defaultValue: automaticAssignment 0 ? userStore.userInfo?.id : null, }, { fieldName: type, label: 事务类型, component: Select, componentProps: { options: automaticAssignment 1 ? getDictOptions(DICT_TYPE.TASK_TYPE, string).filter((v: any) { return v.value ! leave_task; }) : getDictOptions(DICT_TYPE.TASK_TYPE, string), placeholder: 请选择事务类型, }, rules: required, formItemClass: col-span-12, }, { fieldName: inspectionTime, label: 事务时间, component: RangePicker, componentProps: { ...getRangePickerFutureProps(), allowClear: true, }, rules: required, formItemClass: col-span-12, }, { fieldName: estimatedTotalTime, label: 预估总用时(只能输入0.5的倍数), component: InputNumber, componentProps: { placeholder: 请输入预估总用时最小值为0.5只能输入0.5的倍数, min: 0.5, // 最小值设为0.5 step: 0.5, // 步长设为0.5控制点击增减按钮的幅度 precision: 1, // 保留1位小数 stringMode: true, defaultValue: 0.5, // 默认值为0.5 // 核心格式化输入值为0.5的倍数 formatter: (value) { // 处理空值/非数字情况 if (!value || isNaN(Number(value))) { return 0.5; } const numValue Number(value); // 核心逻辑计算最近的0.5倍数向下取整 const formattedValue Math.floor(numValue * 2) / 2; // 确保不小于最小值0.5 const finalValue Math.max(formattedValue, 0.5); return finalValue; }, // 解析函数确保提交的值是正确的数值 parser: (value) { if (!value || isNaN(Number(value))) { return 0.5; } const numValue Number(value); const formattedValue Math.floor(numValue * 2) / 2; return Math.max(formattedValue, 0.5); }, // 失焦时再次校验防止手动输入不符合规则的值 onBlur: (e) { const value e.target.value; if (!value || isNaN(Number(value))) { e.target.value 0.5; return; } const numValue Number(value); const formattedValue Math.floor(numValue * 2) / 2; e.target.value Math.max(formattedValue, 0.5); }, }, formItemClass: col-span-12, }, { fieldName: deviceSn, label: 事务内容, component: Textarea, componentProps: { placeholder: 请输入事务内容, rows: 2, maxLength: 100, // 直接限制输入框最大输入长度提升用户体验 showCount: true, }, formItemClass: col-span-12, rules: required, }, { fieldName: travelApplication, label: 是否外出, component: RadioGroup, dependencies: { triggerFields: [type], show: (values) { return ![leave_task].includes(values.type); }, }, componentProps: { options: getDictOptions(DICT_TYPE.APP_FLY_ZERO_NO_ONE_YES, number), buttonStyle: solid, optionType: button, }, rules: required, defaultValue: 0, }, { fieldName: outTime, label: 外出时间, component: RangePicker, componentProps: { ...getRangePickerFutureProps(), allowClear: true, }, dependencies: { triggerFields: [travelApplication], show: (values) { return values.travelApplication 1; }, }, rules: required, formItemClass: col-span-12, }, ]; } /** 处置任务 */ export function dealFormSchema(): VbenFormSchema[] { return [ { component: Input, fieldName: id, dependencies: { triggerFields: [], show: () false, }, }, { fieldName: satisfactionScore, label: 客户评分, component: InputNumber, componentProps: { placeholder: 请输入客户评分1~100分, min: 1, // 组件层面限制最小值 max: 100, // 组件层面限制最大值 precision: 0, // 核心精度0 → 只能输入整数组件原生属性无类型问题 step: 1, // 步长1 → 增减按钮只变整数 // 组件内置的输入格式化避免手动输入小数 formatter: (value) { return value ? Number.parseInt(value.toString(), 10) : ; }, }, formItemClass: col-span-12, }, { component: Textarea, fieldName: remark, label: 处置说明, componentProps: { placeholder: 请输入处置说明, maxLength: 200, // 直接限制输入框最大输入长度提升用户体验 showCount: true, }, formItemClass: col-span-12, }, { fieldName: satisfactionAttachmentUrl, label: 上传图片, component: ImageUpload, componentProps: { maxNumber: 9, maxCount: 9, // 新增主流属性核心修复 multiple: true, maxSize: 30, // 可选手动添加 onChange 校验兜底方案 onChange: (files: any) { if (files.length 9) { // 截断超出的图片强制限制 return files.slice(0, 9); } return files; }, }, }, ]; }2、index.vuescript langts setup import type { VxeTableGridOptions } from #/adapter/vxe-table; import type { FlyTaskApi } from #/api/fly/flyTask; import { computed, ref } from vue; import { useRouter } from vue-router; import { Page, useVbenModal } from vben/common-ui; import { DICT_TYPE } from vben/constants; import { useUserStore } from vben/stores; import { message, Tabs, Tag } from ant-design-vue; import dayjs from dayjs; import { ACTION_ICON, TableAction, useVbenVxeGrid } from #/adapter/vxe-table; // import { deleteFlyTask, getFlyTaskPage } from #/api/fly/flyTasks; import { getFlyDevicesPageManage } from #/api/fly/flyDevices; import { getConfigKey } from #/api/infra/config; import { DictTag } from #/components/dict-tag; import { $t } from #/locales; import { useGridColumns, useGridFormSchema } from ./data; import DealForm from ./modules/deal-form.vue; import Form from ./modules/form.vue; const { push } useRouter(); const userStore useUserStore(); const tabType ref(0); const tabsData ref([ { name: 机场, type: 0, value: airport, }, { name: 飞行器, type: 1, value: drone, }, ]); const [FormModal, formModalApi] useVbenModal({ connectedComponent: Form, destroyOnClose: true, fullscreen: false, fullscreenButton: false, class: w-1/2, }); const [DealFormModal, dealFormModalApi] useVbenModal({ connectedComponent: DealForm, destroyOnClose: true, fullscreen: false, fullscreenButton: false, class: w-1/2, }); // 响应式 schema const formSchema computed(() useGridFormSchema(tabType.value)); /** 刷新表格 */ function handleRefresh() { gridApi.query(); } /** 创建事务管理 */ function handleCreate() { formModalApi.setData({ automaticAssignment: 0 }).open(); } /** 创建事务管理 */ function handleCreateAuto() { formModalApi.setData({ automaticAssignment: 1 }).open(); } /** 编辑事务管理 */ function handleEdit(row: FlyTaskApi.Task) { formModalApi.setData(row).open(); } /** 查看事务管理详情 */ function handleDetail(row: FlyTaskApi.Task) { push({ path: /flytask/${row.id} }); } function onChangeTab(key: any) { tabType.value Number(key); gridApi.setGridOptions({ columns: useGridColumns(tabType.value), }); // gridApi.query(); } /** 事务管理 删除 */ async function handleDelete(row: FlyTaskApi.Task) { const hideLoading message.loading({ content: $t(ui.actionMessage.deleting), duration: 0, }); try { await deleteFlyTask(row.id); message.success($t(ui.actionMessage.deleteSuccess)); handleRefresh(); } finally { hideLoading(); } } /** 变更事务管理状态为已完成 */ async function handleCompleted(row: FlyTaskApi.Task) { dealFormModalApi.setData(row).open(); } const [Grid, gridApi] useVbenVxeGrid({ formOptions: { schema: formSchema, }, gridOptions: { columns: useGridColumns(), cellConfig: { height: 90, }, height: auto, keepSource: true, proxyConfig: { ajax: { query: async ({ page }, formValues) { const data await getConfigKey(org_uuid); const params { pageNo: page.currentPage, pageSize: page.pageSize, deviceModelClass: airport, orgUuid: data, ...formValues, }; if ( formValues.deviceWorkingStatus Number.parseInt(formValues.deviceWorkingStatus) 100 ) { params.deviceOnlineStatus false; delete params.deviceWorkingStatus; } if (tabType.value 1) { params.deviceModelClass drone; } return await getFlyDevicesPageManage(params); }, }, }, rowConfig: { keyField: id, isHover: true, }, toolbarConfig: { refresh: true, search: true, }, } as VxeTableGridOptionsFlyTaskApi.Task, }); /script template Page auto-content-height FormModal successhandleRefresh / DealFormModal successhandleRefresh / Grid :keytabType !-- Grid -- !-- 顶部切换 -- template #toolbar-actions Tabs changeonChangeTab classw-full v-model:active-keytabType Tabs.TabPane v-foritem in tabsData :keyitem.type :tab${item.name} / /Tabs /template !-- 设备型号 -- template #device_model{ row } div classflex div class div style {{ row.deviceModel.name }} /div div classjustify-left mt-3 flex items-center v-if row.associateDroneDeviceInfo row.associateDroneDeviceInfo.deviceModel div classl-xing mr-1/div div {{ row.associateDroneDeviceInfo.deviceModel.name }} /div /div /div /div /template !-- 设备SN -- template #device_sn{ row } div classflex div class div {{ row.deviceSn }} /div div v-ifrow.associateDroneDeviceInfo classmt-3 {{ row.associateDroneDeviceInfo.deviceSn }} /div /div /div /template !-- 设备名称 -- template #device_callsign{ row } div classflex div class div {{ row.deviceCallsign }} /div div classmt-3 v-if row.associateDroneDeviceInfo row.associateDroneDeviceInfo.deviceCallsign {{ (row.associateDroneDeviceInfo row.associateDroneDeviceInfo.deviceCallsign) || - }} /div /div /div /template !-- 固件版本 -- template #device_firmware_version{ row } div classflex div class div {{ row.deviceFirmwareVersion }} /div div v-ifrow.associateDroneDeviceInfo classmt-3 {{ row.associateDroneDeviceInfo.deviceFirmwareVersion }} /div /div /div /template !-- 工作状态 -- template #device_working_status{ row } div classflex div class div DictTag :typeDICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM :valuerow.deviceWorkingStatus v-ifrow.deviceOnlineStatus / DictTag :typeDICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM :value100 v-else / /div div v-ifrow.associateDroneDeviceInfo classmt-3 DictTag :typeDICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM :valuerow.associateDroneDeviceInfo.deviceWorkingStatus v-ifrow.associateDroneDeviceInfo.deviceOnlineStatus / !-- DictTag :typeDICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM :value100 v-else / -- Tag v-else typeinfo 飞行器未连接 /Tag /div /div /div /template !-- 加入组织时间 -- template #device_add_time{ row } div {{ row.deviceAddTime ? dayjs(Number(row.deviceAddTime) * 1000).format( YYYY-MM-DD HH:mm:ss, ) : — }} /div div v-ifrow.associateDroneDeviceInfo classmt-3 {{ row.associateDroneDeviceInfo row.associateDroneDeviceInfo.deviceAddTime ? dayjs( Number(row.associateDroneDeviceInfo.deviceAddTime) * 1000, ).format(YYYY-MM-DD HH:mm:ss) : — }} /div /template !-- 在线时间 -- template #device_last_online_time{ row } div {{ row.deviceLastOnlineTime ? dayjs(Number(row.deviceLastOnlineTime) * 1000).format( YYYY-MM-DD HH:mm:ss, ) : — }} /div div v-ifrow.associateDroneDeviceInfo classmt-3 {{ row.associateDroneDeviceInfo row.associateDroneDeviceInfo.deviceLastOnlineTime ? dayjs( Number(row.associateDroneDeviceInfo.deviceLastOnlineTime) * 1000, ).format(YYYY-MM-DD HH:mm:ss) : — }} /div /template !-- 操作栏 -- template #toolbar-tools TableAction :actions[ // { // label: 如何绑定机场, // type: text, // onClick: handleCreateAuto, // }, { label: $t(ui.actionTitle.create, [事务]), type: primary, icon: ACTION_ICON.ADD, auth: [starpivot:star-pivot-task:create], onClick: handleCreate, }, { label: $t(ui.actionTitle.create, [指派事务]), type: primary, icon: ACTION_ICON.ADD, auth: [starpivot:star-pivot-task:dispatch], onClick: handleCreateAuto, }, { label: $t(ui.actionTitle.create, [日志反馈]), type: primary, icon: ACTION_ICON.ADD, onClick: handleCreateAuto, }, { label: $t(ui.actionTitle.create, [固件管理]), type: primary, icon: ACTION_ICON.ADD, onClick: handleCreateAuto, }, { label: $t(ui.actionTitle.create, [绑定设备码]), type: primary, icon: ACTION_ICON.ADD, onClick: handleCreateAuto, }, ] / /template template #actions{ row, seq } TableAction :keytabType :actions[ { label: $t(common.edit), type: link, icon: ACTION_ICON.EDIT, auth: [starpivot:star-pivot-task:update], onClick: handleEdit.bind(null, row), // 状态 1-待完成2-已完成 // 1-待审核2-已通过3-已驳回 ifShow: [1].includes(row.status) ![2].includes(row.outApprovalResult) (parseInt(row.userId) userStore.userInfo?.id || parseInt(row.creator) userStore.userInfo?.id), }, { label: $t(common.completed), type: link, icon: ACTION_ICON.CIRCLECHECK, auth: [starpivot:star-pivot-task:update], onClick: handleCompleted.bind(null, row), // 状态 1-待完成2-已完成 // 1-待审核2-已通过3-已驳回 ifShow: ![leave_task].includes(row.type) [1].includes(row.status) [0, 2].includes(row.outApprovalResult) parseInt(row.userId) userStore.userInfo?.id, }, { label: $t(common.detail), type: link, icon: ACTION_ICON.VIEW, auth: [starpivot:star-pivot-task:query], onClick: handleDetail.bind(null, row), }, { label: $t(common.delete), type: link, icon: ACTION_ICON.DELETE, auth: [starpivot:star-pivot-task:delete], popConfirm: { title: $t(ui.actionMessage.deleteConfirm), confirm: handleDelete.bind(null, row), }, ifShow: [1].includes(row.status) parseInt(row.userId) userStore.userInfo?.id, }, ] / /template /Grid /Page /template style langcss scoped .l-xing { width: 5px; height: 5px; background: #e9e3e3; clip-path: polygon(0 0, 100% 0, 100% 40%, 40% 40%, 40% 100%, 0 100%); transform: rotate(270deg); /* 旋转90度方向正确 */ transition: 0.3s; } /style3、核心方法// 直接在这里控制显隐最稳定 hide: tabType 0, // 第二处 Grid :keytabType