芋道yudao vban vue3版本表单搜索条件,列表表格数据根据传值变化
·
1、先上代码,data.ts
import 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.vue
<script lang="ts" 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 VxeTableGridOptions<FlyTaskApi.Task>,
});
</script>
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<DealFormModal @success="handleRefresh" />
<Grid :key="tabType">
<!-- <Grid> -->
<!-- 顶部切换 -->
<template #toolbar-actions>
<Tabs @change="onChangeTab" class="w-full" v-model:active-key="tabType">
<Tabs.TabPane
v-for="item in tabsData"
:key="item.type"
:tab="`${item.name}`"
/>
</Tabs>
</template>
<!-- 设备型号 -->
<template #device_model="{ row }">
<div class="flex">
<div class="">
<div style="">
{{ row.deviceModel.name }}
</div>
<div
class="justify-left mt-3 flex items-center"
v-if="
row.associateDroneDeviceInfo &&
row.associateDroneDeviceInfo.deviceModel
"
>
<div class="l-xing mr-1"></div>
<div>
{{ row.associateDroneDeviceInfo.deviceModel.name }}
</div>
</div>
</div>
</div>
</template>
<!-- 设备SN -->
<template #device_sn="{ row }">
<div class="flex">
<div class="">
<div>
{{ row.deviceSn }}
</div>
<div v-if="row.associateDroneDeviceInfo" class="mt-3">
{{ row.associateDroneDeviceInfo.deviceSn }}
</div>
</div>
</div>
</template>
<!-- 设备名称 -->
<template #device_callsign="{ row }">
<div class="flex">
<div class="">
<div>
{{ row.deviceCallsign }}
</div>
<div
class="mt-3"
v-if="
row.associateDroneDeviceInfo &&
row.associateDroneDeviceInfo.deviceCallsign
"
>
{{
(row.associateDroneDeviceInfo &&
row.associateDroneDeviceInfo.deviceCallsign) ||
'-'
}}
</div>
</div>
</div>
</template>
<!-- 固件版本 -->
<template #device_firmware_version="{ row }">
<div class="flex">
<div class="">
<div>
{{ row.deviceFirmwareVersion }}
</div>
<div v-if="row.associateDroneDeviceInfo" class="mt-3">
{{ row.associateDroneDeviceInfo.deviceFirmwareVersion }}
</div>
</div>
</div>
</template>
<!-- 工作状态 -->
<template #device_working_status="{ row }">
<div class="flex">
<div class="">
<div>
<DictTag
:type="DICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM"
:value="row.deviceWorkingStatus"
v-if="row.deviceOnlineStatus"
/>
<DictTag
:type="DICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM"
:value="100"
v-else
/>
</div>
<div v-if="row.associateDroneDeviceInfo" class="mt-3">
<DictTag
:type="DICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM"
:value="row.associateDroneDeviceInfo.deviceWorkingStatus"
v-if="row.associateDroneDeviceInfo.deviceOnlineStatus"
/>
<!-- <DictTag
:type="DICT_TYPE.DJ_SK2_EQUIPMENT_WORK_STATUS_ENUM"
:value="100"
v-else
/> -->
<Tag v-else type="info"> 飞行器未连接 </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-if="row.associateDroneDeviceInfo" class="mt-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-if="row.associateDroneDeviceInfo" class="mt-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
:key="tabType"
: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 lang="css" 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;
}
</style>
3、核心方法
// 👇 直接在这里控制显隐,最稳定
hide: tabType === 0,
// 第二处
<Grid :key="tabType">
更多推荐
所有评论(0)