refactor: 重构设备产品详情页面的属性、功能和事件定义

- 将属性、功能和事件定义抽离为单独的组件
- 优化了数据类型、表单类型等选项的管理
- 改进了输出参数的编辑功能
- 统一了保存和取消操作的样式
- 调整了部分UI样式,如描述字段的样式
This commit is contained in:
fhysy 2025-08-15 15:46:41 +08:00
parent fde2ec9bae
commit f8619047c5
7 changed files with 1906 additions and 429 deletions

View File

@ -21,3 +21,93 @@ export const deviceTypeOptions = [
tooltip: '能挂载子设备与平台进行通信的设备', tooltip: '能挂载子设备与平台进行通信的设备',
}, },
]; ];
// 数据类型选项
export const dataTypeOptions = [
{
value: 'int',
label: 'int(整数型)',
},
{
value: 'long',
label: 'long(长整数型)',
},
{
value: 'float',
label: 'float(单精度浮点型)',
},
{
value: 'double',
label: 'double(双精度浮点数)',
},
{
value: 'string',
label: 'text(字符串)',
},
{
value: 'boolean',
label: 'boolean(布尔型)',
},
{
value: 'date',
label: 'date(时间型)',
},
{
value: 'enum',
label: 'enum(枚举)',
},
// {
// value: 'array',
// label: 'array(数组)',
// },
// {
// value: 'object',
// label: 'object(结构体)',
// },
// {
// value: 'file',
// label: 'file(文件)',
// },
// {
// value: 'password',
// label: 'password(密码)',
// },
// {
// value: 'geoPoint',
// label: 'geoPoint(地理位置)',
// }
];
// 表单类型选项
export const formTypeOptions = [
{ label: '文本', value: 'input' },
{ label: '开关', value: 'switch' },
{ label: '数字', value: 'number' },
{ label: '进度条', value: 'progress' },
{ label: '选择器', value: 'select' },
{ label: '时间选择器', value: 'time' },
// { label: '文本域', value: 'textarea' },
];
export const viewTypeOptions = [
{ label: '文本', value: 'input' },
{ label: '开关', value: 'switch' },
{ label: '选择器', value: 'select' },
{ label: '进度条', value: 'progress' },
{ label: '图片', value: 'img' },
{ label: '折线图', value: 'line' },
{ label: '仪表盘', value: 'dashboard' },
];
// 读写类型选项
export const readWriteTypeOptions = [
{ label: '读', value: 'R' },
{ label: '写', value: 'W' },
{ label: '读写', value: 'RW' },
];
export const timeOptions = [
{ label: 'yyyy-MM-dd HH:mm:ss', value: 'yyyy-MM-dd HH:mm:ss' },
{ label: 'yyyy-MM-dd', value: 'yyyy-MM-dd' },
{ label: 'HH:mm:ss', value: 'HH:mm:ss' },
];

View File

@ -129,7 +129,7 @@ const productParams = computed(() =>
<DescriptionsItem label="更新时间"> <DescriptionsItem label="更新时间">
{{ formatTime(productInfo.updateTime) }} {{ formatTime(productInfo.updateTime) }}
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="描述" :span="2"> <DescriptionsItem label="描述">
{{ productInfo.description || '暂无描述' }} {{ productInfo.description || '暂无描述' }}
</DescriptionsItem> </DescriptionsItem>
</Descriptions> </Descriptions>

View File

@ -0,0 +1,412 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import {
Button,
Col,
Drawer,
Form,
FormItem,
Input,
InputNumber,
message,
Popconfirm,
Row,
Space,
Table,
Tag,
Textarea,
} from 'ant-design-vue';
import { dataTypeOptions, formTypeOptions } from '#/constants/dicts';
import ParameterModal from './ParameterModal.vue';
interface EventData {
id: string;
name: string;
sort: number;
description: string;
outputs: any[];
}
interface Props {
open: boolean;
isEdit: boolean;
data?: EventData;
}
interface Emits {
(e: 'update:open', value: boolean): void;
(e: 'save', data: EventData): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const visible = computed({
get: () => props.open,
set: (value) => emit('update:open', value),
});
const formRef = ref();
const saveLoading = ref(false);
const parameterType = ref('add');
const parameterDataIndex = ref(-1);
//
const outputParamModalVisible = ref(false);
const outputParamForm = ref<any>(null);
const outputColumns = ref([
{
title: '标识符',
dataIndex: 'id',
key: 'id',
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '数据类型',
dataIndex: 'dataType',
key: 'dataType',
scopedSlots: { customRender: 'dataType' },
},
{
title: '是否必填',
dataIndex: 'required',
key: 'required',
scopedSlots: { customRender: 'required' },
},
{
title: '表单类型',
dataIndex: 'formType',
key: 'formType',
scopedSlots: { customRender: 'formType' },
},
{
title: '操作',
key: 'action',
width: 100,
fixed: 'right',
},
]);
//
const defaultEventData: EventData = {
id: '',
name: '',
sort: 1,
description: '',
outputs: [],
};
const formData = ref<EventData>({ ...defaultEventData });
//
const formRules = {
id: [
{ required: true, message: '请输入标识符', trigger: 'blur' },
{
pattern: /^[a-z]\w*$/i,
message: '请输入以字母开头,只包含字母、数字和下划线的标识符',
trigger: 'blur',
},
],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
// 'valueParams.dataType': [
// { required: true, message: '', trigger: 'blur' },
// ],
};
//
const drawerTitle = computed(() => {
return props.isEdit ? '编辑事件' : '新增事件';
});
//
const handleAddOutputParam = () => {
parameterType.value = 'add';
parameterDataIndex.value = -1;
outputParamForm.value = null;
outputParamModalVisible.value = true;
};
//
const handleDeleteOutputParam = (index: number) => {
formData.value.outputs.splice(index, 1);
};
//
const handleOutputParamsConfirm = (params: any[]) => {
if (parameterType.value === 'add') {
formData.value.outputs.push(params);
} else if (parameterType.value === 'edit') {
formData.value.outputs[parameterDataIndex.value] = params;
} else {
formData.value.outputs = params;
}
outputParamModalVisible.value = false;
};
//
const handleSave = async () => {
try {
await formRef.value.validate();
saveLoading.value = true;
emit('save', { ...formData.value });
message.success('保存成功');
visible.value = false;
} catch {
message.error('保存失败');
} finally {
saveLoading.value = false;
}
};
//
const handleDrawerClose = () => {
visible.value = false;
resetForm();
};
//
const resetForm = () => {
Object.assign(formData.value, JSON.parse(JSON.stringify(defaultEventData)));
};
//
watch(
() => props.data,
(newVal) => {
if (newVal) {
formData.value = { ...newVal };
} else {
resetForm();
}
},
{ immediate: true },
);
</script>
<template>
<Drawer
v-model:open="visible"
:title="drawerTitle"
width="800px"
@close="handleDrawerClose"
>
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<Row :gutter="24">
<Col :span="12">
<FormItem label="标识符" name="id">
<Input v-model:value="formData.id" placeholder="请输入标识符" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="名称" name="name">
<Input v-model:value="formData.name" placeholder="请输入名称" />
</FormItem>
</Col>
<!-- 事件特有字段 -->
<!-- 输出参数 -->
<Col :span="24">
<FormItem label="输出参数" name="outputs">
<div class="parameter-preview">
<Table
:columns="outputColumns"
:data-source="formData.outputs"
:pagination="false"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'dataType'">
{{
dataTypeOptions.find(
(item) => item.value === record.valueParams.dataType,
)?.label
}}
</template>
<template v-if="column.key === 'required'">
<Tag color="processing">
{{ record.required ? '是' : '否' }}
</Tag>
</template>
<template v-if="column.key === 'formType'">
{{
formTypeOptions.find(
(item) => item.value === record.valueParams.formType,
)?.label
}}
</template>
<template v-if="column.key === 'action'">
<Space>
<Button
type="link"
size="small"
@click="handleEditOutputParam(record, index)"
>
编辑
</Button>
<Popconfirm
placement="left"
title="确认删除?"
@confirm="handleDeleteOutputParam(index)"
>
<Button type="link" size="small" danger> 删除 </Button>
</Popconfirm>
</Space>
</template>
</template>
</Table>
<div class="add-button-container">
<a-button
@click="handleAddOutputParam"
type="primary"
class="add-button"
>
<template #icon>
<PlusOutlined />
</template>
新增输出参数
</a-button>
</div>
</div>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="排序" name="sort">
<InputNumber
style="width: 100%"
v-model:value="formData.sort"
placeholder="请输入排序"
/>
</FormItem>
</Col>
<Col :span="24">
<FormItem label="描述" name="description">
<Textarea
v-model:value="formData.description"
placeholder="请输入描述"
:rows="3"
/>
</FormItem>
</Col>
</Row>
</Form>
<template #footer>
<Space>
<Button @click="handleDrawerClose">取消</Button>
<Button type="primary" @click="handleSave" :loading="saveLoading">
保存
</Button>
</Space>
</template>
<!-- 输入参数编辑弹窗 -->
<ParameterModal
v-model:open="outputParamModalVisible"
:parameter-type="parameterType"
:data="outputParamForm"
@confirm="handleOutputParamsConfirm"
/>
</Drawer>
</template>
<style lang="scss" scoped>
.enum-preview {
.enum-items-preview {
padding: 12px;
margin-top: 12px;
background-color: #fafafa;
border-radius: 4px;
.preview-title {
margin-bottom: 8px;
font-size: 12px;
color: #666;
}
.preview-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
}
}
.parameter-preview {
.parameter-table {
margin-top: 12px;
overflow: hidden;
border: 1px solid #f0f0f0;
border-radius: 4px;
.table-header {
display: flex;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
.header-item {
flex: 1;
padding: 8px 12px;
font-weight: 500;
color: #262626;
text-align: center;
border-right: 1px solid #f0f0f0;
&:first-child {
text-align: left;
}
&:last-child {
border-right: none;
}
}
}
.table-body {
.table-row {
display: flex;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.cell {
flex: 1;
padding: 8px 12px;
text-align: center;
border-right: 1px solid #f0f0f0;
&:first-child {
text-align: left;
}
&:last-child {
border-right: none;
}
}
}
}
}
}
.add-button-container {
padding-top: 12px;
text-align: center;
.add-button {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,504 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import {
Button,
Col,
Drawer,
Form,
FormItem,
Input,
InputNumber,
InputPassword,
message,
Popconfirm,
RadioButton,
RadioGroup,
Row,
Select,
Space,
Table,
Tag,
Textarea,
} from 'ant-design-vue';
import { dataTypeOptions, formTypeOptions } from '#/constants/dicts';
import ParameterModal from './ParameterModal.vue';
interface FunctionData {
id: string;
name: string;
sort: number;
description: string;
async: boolean;
inputs: any[];
expands: {
checkType: string;
preCheck: boolean;
staticPasswordValue: string;
};
outputs: any[];
}
interface Props {
open: boolean;
isEdit: boolean;
data?: FunctionData;
}
interface Emits {
(e: 'update:open', value: boolean): void;
(e: 'save', data: FunctionData): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const visible = computed({
get: () => props.open,
set: (value) => emit('update:open', value),
});
const formRef = ref();
const saveLoading = ref(false);
const parameterType = ref('add');
const parameterDataIndex = ref(-1);
//
const inputParamModalVisible = ref(false);
const inputParamForm = ref<any>(null);
const asyncOptions = ref([
{ label: '是', value: true },
{ label: '否', value: false },
]);
const preCheckOptions = ref([
{ label: '是', value: true },
{ label: '否', value: false },
]);
const preCheckTypeOptions = ref([
{ label: '用户密码', value: 'userPassword' },
{ label: '固定密码', value: 'staticPassword' },
{ label: '用户短信', value: 'userSms' },
]);
const inputColumns = ref([
{
title: '标识符',
dataIndex: 'id',
key: 'id',
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '数据类型',
dataIndex: 'dataType',
key: 'dataType',
scopedSlots: { customRender: 'dataType' },
},
{
title: '是否必填',
dataIndex: 'required',
key: 'required',
scopedSlots: { customRender: 'required' },
},
{
title: '表单类型',
dataIndex: 'formType',
key: 'formType',
scopedSlots: { customRender: 'formType' },
},
{
title: '操作',
key: 'action',
width: 100,
fixed: 'right',
},
]);
//
const defaultFunctionData: FunctionData = {
id: '',
name: '',
sort: 1,
description: '',
async: false,
expands: {
//
preCheck: false,
// userPassword staticPassword , userSms
checkType: 'userPassword',
// md5
staticPasswordValue: '',
},
inputs: [],
outputs: [],
};
const formData = ref<FunctionData>({ ...defaultFunctionData });
//
const formRules = {
id: [
{ required: true, message: '请输入标识符', trigger: 'blur' },
{
pattern: /^[a-z]\w*$/i,
message: '请输入以字母开头,只包含字母、数字和下划线的标识符',
trigger: 'blur',
},
],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
};
//
const drawerTitle = computed(() => {
return props.isEdit ? '编辑功能' : '新增功能';
});
//
const handleAddInputParam = () => {
parameterDataIndex.value = -1;
parameterType.value = 'add';
inputParamForm.value = null;
inputParamModalVisible.value = true;
};
//
const handleEditInputParam = (record: any, index: number) => {
parameterDataIndex.value = index;
parameterType.value = 'edit';
inputParamForm.value = JSON.parse(JSON.stringify(record));
inputParamModalVisible.value = true;
};
//
const handleDeleteInputParam = (index: number) => {
formData.value.inputs.splice(index, 1);
};
//
const handleInputParamsConfirm = (params: any) => {
if (parameterType.value === 'add') {
formData.value.inputs.push(params);
} else if (parameterType.value === 'edit') {
formData.value.inputs[parameterDataIndex.value] = params;
} else {
formData.value.inputs = params;
}
inputParamModalVisible.value = false;
};
//
const handleSave = async () => {
try {
await formRef.value.validate();
saveLoading.value = true;
emit('save', { ...formData.value });
visible.value = false;
} catch {
message.error('保存失败');
} finally {
saveLoading.value = false;
}
};
//
const handleDrawerClose = () => {
visible.value = false;
resetForm();
};
//
const resetForm = () => {
Object.assign(
formData.value,
JSON.parse(JSON.stringify(defaultFunctionData)),
);
};
//
watch(
() => props.data,
(newVal) => {
if (newVal) {
formData.value = { ...newVal };
} else {
resetForm();
}
},
{ immediate: true },
);
</script>
<template>
<Drawer
v-model:open="visible"
:title="drawerTitle"
width="800px"
@close="handleDrawerClose"
>
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<Row :gutter="24">
<Col :span="12">
<FormItem label="标识符" name="id">
<Input v-model:value="formData.id" placeholder="请输入标识符" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="名称" name="name">
<Input v-model:value="formData.name" placeholder="请输入名称" />
</FormItem>
</Col>
<!-- 输入参数 -->
<Col :span="24">
<FormItem label="输入参数" name="inputs">
<div class="parameter-preview">
<Table
:columns="inputColumns"
:data-source="formData.inputs"
:pagination="false"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'dataType'">
{{
dataTypeOptions.find(
(item) => item.value === record.valueParams.dataType,
)?.label
}}
</template>
<template v-if="column.key === 'required'">
<Tag color="processing">
{{ record.required ? '是' : '否' }}
</Tag>
</template>
<template v-if="column.key === 'formType'">
{{
formTypeOptions.find(
(item) => item.value === record.valueParams.formType,
)?.label
}}
</template>
<template v-if="column.key === 'action'">
<Space>
<Button
type="link"
size="small"
@click="handleEditInputParam(record, index)"
>
编辑
</Button>
<Popconfirm
placement="left"
title="确认删除?"
@confirm="handleDeleteInputParam(index)"
>
<Button type="link" size="small" danger> 删除 </Button>
</Popconfirm>
</Space>
</template>
</template>
</Table>
<div class="add-button-container">
<a-button
@click="handleAddInputParam"
type="primary"
class="add-button"
>
<template #icon>
<PlusOutlined />
</template>
新增输入参数
</a-button>
</div>
</div>
</FormItem>
</Col>
<!-- 功能特有字段 -->
<Col :span="12">
<FormItem label="是否异步" name="async">
<RadioGroup v-model:value="formData.async" button-style="solid">
<RadioButton
:value="item.value"
v-for="item in asyncOptions"
:key="item.value"
>
{{ item.label }}
</RadioButton>
</RadioGroup>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="是否开启前置校验" name="expands.preCheck">
<RadioGroup
v-model:value="formData.expands.preCheck"
button-style="solid"
>
<RadioButton
:value="item.value"
v-for="item in preCheckOptions"
:key="item.value"
>
{{ item.label }}
</RadioButton>
</RadioGroup>
</FormItem>
</Col>
<Col :span="12" v-if="formData.expands.preCheck">
<FormItem label="前置校验类型" name="expands.checkType">
<Select
v-model:value="formData.expands.checkType"
placeholder="请选择前置校验类型"
:options="preCheckTypeOptions"
/>
</FormItem>
</Col>
<Col :span="12" v-if="formData.expands.checkType === 'staticPassword'">
<FormItem label="固定密码" name="expands.staticPasswordValue">
<InputPassword
v-model:value="formData.expands.staticPasswordValue"
placeholder="请输入固定密码"
:visibility-toggle="false"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="排序" name="sort">
<InputNumber
style="width: 100%"
v-model:value="formData.sort"
placeholder="请输入排序"
/>
</FormItem>
</Col>
<Col :span="24">
<FormItem label="描述" name="description">
<Textarea
v-model:value="formData.description"
placeholder="请输入描述"
:rows="3"
/>
</FormItem>
</Col>
</Row>
</Form>
<template #footer>
<Space>
<Button @click="handleDrawerClose">取消</Button>
<Button type="primary" @click="handleSave" :loading="saveLoading">
保存
</Button>
</Space>
</template>
<!-- 输入参数编辑弹窗 -->
<ParameterModal
v-model:open="inputParamModalVisible"
:parameter-type="parameterType"
:data="inputParamForm"
@confirm="handleInputParamsConfirm"
/>
</Drawer>
</template>
<style lang="scss" scoped>
.enum-preview {
.enum-items-preview {
padding: 12px;
margin-top: 12px;
background-color: #fafafa;
border-radius: 4px;
.preview-title {
margin-bottom: 8px;
font-size: 12px;
color: #666;
}
.preview-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
}
}
.parameter-preview {
.parameter-table {
margin-top: 12px;
overflow: hidden;
border: 1px solid #f0f0f0;
border-radius: 4px;
.table-header {
display: flex;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
.header-item {
flex: 1;
padding: 8px 12px;
font-weight: 500;
color: #262626;
text-align: center;
border-right: 1px solid #f0f0f0;
&:first-child {
text-align: left;
}
&:last-child {
border-right: none;
}
}
}
.table-body {
.table-row {
display: flex;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.cell {
flex: 1;
padding: 8px 12px;
text-align: center;
border-right: 1px solid #f0f0f0;
&:first-child {
text-align: left;
}
&:last-child {
border-right: none;
}
}
}
}
}
}
.add-button-container {
padding-top: 12px;
text-align: center;
.add-button {
width: 100%;
}
}
</style>

View File

@ -2,28 +2,13 @@
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue'; import { PlusOutlined } from '@ant-design/icons-vue';
import { import { Button, message, Popconfirm, Space, Table, Tag } from 'ant-design-vue';
Button,
Col,
Drawer,
Form,
FormItem,
Input,
InputNumber,
message,
Popconfirm,
RadioButton,
RadioGroup,
Row,
Select,
Space,
Switch,
Table,
Tag,
Textarea,
} from 'ant-design-vue';
import EnumListModal from './EnumListModal.vue'; import { dataTypeOptions, readWriteTypeOptions } from '#/constants/dicts';
import EventDrawer from './EventDrawer.vue';
import FunctionDrawer from './FunctionDrawer.vue';
import PropertyDrawer from './PropertyDrawer.vue';
const props = defineProps<{ const props = defineProps<{
metadata: any[]; metadata: any[];
@ -38,139 +23,14 @@ const emit = defineEmits<{
const loading = ref(false); const loading = ref(false);
const drawerVisible = ref(false); const drawerVisible = ref(false);
const saveLoading = ref(false);
const isEdit = ref(false);
const formRef = ref();
// const isEdit = ref(false);
const enumModalVisible = ref(false);
const editDataIndex = ref(-1);
const tableData = ref<any[]>([]); const tableData = ref<any[]>([]);
const defaultPropertiesFormData = { const formData = ref<any>(null);
id: '',
name: '',
sort: 1,
description: '',
required: false,
expands: {
source: 'device',
type: 'R',
},
valueParams: {
dataType: 'string',
formType: 'input',
length: null,
viewType: 'input',
unit: '',
enumConf: [],
},
};
const formData = ref({});
const formRules = {
id: [
{ required: true, message: '请输入标识符', trigger: 'blur' },
{
pattern: /^[a-z]\w*$/i,
message: '请输入以字母开头,只包含字母、数字和下划线的标识符',
trigger: 'blur',
},
],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
valueParams: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
};
//
const dataTypeOptions = ref([
{
value: 'int',
label: 'int(整数型)',
},
{
value: 'long',
label: 'long(长整数型)',
},
{
value: 'float',
label: 'float(单精度浮点型)',
},
{
value: 'double',
label: 'double(双精度浮点数)',
},
{
value: 'string',
label: 'text(字符串)',
},
{
value: 'boolean',
label: 'boolean(布尔型)',
},
{
value: 'date',
label: 'date(时间型)',
},
{
value: 'enum',
label: 'enum(枚举)',
},
// {
// value: 'array',
// label: 'array()',
// },
// {
// value: 'object',
// label: 'object()',
// },
// {
// value: 'file',
// label: 'file()',
// },
// {
// value: 'password',
// label: 'password()',
// },
// {
// value: 'geoPoint',
// label: 'geoPoint()',
// }
]);
//
const formTypeOptions = ref([
{ label: '文本', value: 'input' },
{ label: '开关', value: 'switch' },
{ label: '数字', value: 'number' },
{ label: '进度条', value: 'progress' },
{ label: '选择器', value: 'select' },
{ label: '时间选择器', value: 'time' },
// { label: '', value: 'textarea' },
]);
const viewTypeOptions = ref([
{ label: '文本', value: 'input' },
{ label: '开关', value: 'switch' },
{ label: '选择器', value: 'select' },
{ label: '进度条', value: 'progress' },
{ label: '图片', value: 'img' },
{ label: '折线图', value: 'line' },
{ label: '仪表盘', value: 'dashboard' },
]);
//
const readWriteTypeOptions = ref([
{ label: '读', value: 'R' },
{ label: '写', value: 'W' },
{ label: '读写', value: 'RW' },
]);
const timeOptions = ref([
{ label: 'yyyy-MM-dd HH:mm:ss', value: 'yyyy-MM-dd HH:mm:ss' },
{ label: 'yyyy-MM-dd', value: 'yyyy-MM-dd' },
{ label: 'HH:mm:ss', value: 'HH:mm:ss' },
]);
// //
const columns = computed(() => { const columns = computed(() => {
@ -273,11 +133,6 @@ const getTypeLabel = () => {
return labels[props.type]; return labels[props.type];
}; };
//
const drawerTitle = computed(() => {
return isEdit.value ? `编辑${getTypeLabel()}` : `新增${getTypeLabel()}`;
});
// //
const loadData = async () => { const loadData = async () => {
loading.value = true; loading.value = true;
@ -299,9 +154,10 @@ const handleAdd = () => {
}; };
// //
const handleEdit = (record: any) => { const handleEdit = (record: any, index: number) => {
editDataIndex.value = index;
isEdit.value = true; isEdit.value = true;
formData.value = structuredClone(record.value); formData.value = JSON.parse(JSON.stringify(record));
drawerVisible.value = true; drawerVisible.value = true;
}; };
@ -321,54 +177,30 @@ const handleDelete = async (record: any) => {
}; };
// //
const handleSave = async () => { const handleSave = async (data: any) => {
try { try {
await formRef.value.validate();
saveLoading.value = true;
if (isEdit.value) { if (isEdit.value) {
// //
const index = tableData.value.findIndex( if (editDataIndex.value === -1) {
(item) => item.id === formData.value.id, tableData.value.push({ ...data });
); } else {
if (index !== -1) { tableData.value[editDataIndex.value] = { ...data };
tableData.value[index] = { ...formData.value };
} }
} else { } else {
// //
tableData.value.push({ ...formData.value }); tableData.value.push({ ...data });
} }
message.success('保存成功');
drawerVisible.value = false; drawerVisible.value = false;
emit('change', tableData.value); emit('change', tableData.value);
} catch { } catch {
message.error('保存失败'); message.error('保存失败');
} finally {
saveLoading.value = false;
} }
}; };
//
const handleDrawerClose = () => {
drawerVisible.value = false;
resetForm();
};
// //
const resetForm = () => { const resetForm = () => {
Object.assign(formData.value, structuredClone(defaultPropertiesFormData)); formData.value = null;
};
//
const handleEditEnum = () => {
enumModalVisible.value = true;
};
//
const handleEnumConfirm = (enumList: any[]) => {
formData.value.valueParams.enumConf = enumList;
enumModalVisible.value = false;
}; };
// metadata // metadata
@ -407,13 +239,13 @@ onMounted(() => {
:pagination="false" :pagination="false"
row-key="id" row-key="id"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<Space> <Space>
<Button <Button
type="link" type="link"
size="small" size="small"
@click="handleEdit(record)" @click="handleEdit(record, index)"
v-access:code="['device:product:edit']" v-access:code="['device:product:edit']"
> >
编辑 编辑
@ -461,247 +293,31 @@ onMounted(() => {
</template> </template>
</Table> </Table>
<!-- 新增/编辑抽屉 --> <!-- 属性定义抽屉 -->
<Drawer <PropertyDrawer
v-if="type === 'properties'"
v-model:open="drawerVisible" v-model:open="drawerVisible"
:title="drawerTitle" :is-edit="isEdit"
width="800px" :data="formData"
@close="handleDrawerClose" @save="handleSave"
> />
<Form
ref="formRef"
:model="formData"
:rules="formRules"
layout="vertical"
>
<Row :gutter="24">
<Col :span="12">
<FormItem label="标识符" name="id">
<Input v-model:value="formData.id" placeholder="请输入标识符" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="名称" name="name">
<Input v-model:value="formData.name" placeholder="请输入名称" />
</FormItem>
</Col>
<!-- 值参数配置 -->
<Col :span="24">
<FormItem label="数据类型" name="dataType">
<Select
v-model:value="formData.valueParams.dataType"
placeholder="请选择数据类型"
:options="dataTypeOptions"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="表单类型" name="valueParams.formType">
<Select
v-model:value="formData.valueParams.formType"
placeholder="请选择表单类型"
:options="formTypeOptions"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="展示类型" name="valueParams.viewType">
<Select
v-model:value="formData.valueParams.viewType"
placeholder="请选择展示类型"
:options="viewTypeOptions"
/>
</FormItem>
</Col>
<Col :span="24" v-if="formData.valueParams.formType === 'switch'">
<Row
:gutter="24"
style="
padding-top: 16px;
margin: 0;
margin-bottom: 16px;
border: 1px solid #f0f0f0;
border-radius: 4px;
"
>
<Col :span="12">
<FormItem label="开关打开名称" name="valueParams.trueText">
<Input
v-model:value="formData.valueParams.trueText"
default-value="是"
placeholder="请输入开关打开名称"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关打开值" name="valueParams.trueValue">
<Input
v-model:value="formData.valueParams.trueValue"
default-value="true"
placeholder="请输入开关打开值"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关关闭名称" name="valueParams.falseText">
<Input
v-model:value="formData.valueParams.falseText"
default-value="否"
placeholder="请输入开关关闭名称"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关关闭值" name="valueParams.falseValue">
<Input
v-model:value="formData.valueParams.falseValue"
default-value="false"
placeholder="请输入开关关闭值"
/>
</FormItem>
</Col>
</Row>
</Col>
<Col :span="24" v-if="formData.valueParams.formType === 'select'"> <!-- 功能定义抽屉 -->
<FormItem label="枚举列表" name="valueParams.enumList"> <FunctionDrawer
<a-button type="primary" @click="handleEditEnum"> v-if="type === 'functions'"
编辑枚举列表 v-model:open="drawerVisible"
</a-button> :is-edit="isEdit"
</FormItem> :data="formData"
</Col> @save="handleSave"
/>
<Col <!-- 事件定义抽屉 -->
:span="12" <EventDrawer
v-if=" v-if="type === 'events'"
formData.valueParams.formType === 'number' || v-model:open="drawerVisible"
formData.valueParams.formType === 'progress' :is-edit="isEdit"
" :data="formData"
> @save="handleSave"
<FormItem label="最小值" name="valueParams.min">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.min"
placeholder="请输入最小值"
/>
</FormItem>
</Col>
<Col
:span="12"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<FormItem label="最大值" name="valueParams.max">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.max"
placeholder="请输入最大值"
/>
</FormItem>
</Col>
<Col :span="12" v-if="formData.valueParams.formType === 'input'">
<FormItem label="长度" name="valueParams.length">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.length"
placeholder="请输入长度"
/>
</FormItem>
</Col>
<Col
:span="12"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<FormItem label="小数位" name="valueParams.scale">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.scale"
placeholder="请输入小数位"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="单位" name="valueParams.unit">
<Input
v-model:value="formData.valueParams.unit"
placeholder="请输入单位"
/>
</FormItem>
</Col>
<Col :span="12" v-if="formData.valueParams.formType === 'time'">
<FormItem label="时间格式" name="valueParams.format">
<Select
v-model:value="formData.valueParams.format"
placeholder="请选择时间格式"
:options="timeOptions"
/>
</FormItem>
</Col>
<Col :span="12">
<!-- 属性特有字段 -->
<FormItem label="是否必填" name="required">
<Switch v-model:checked="formData.required" />
</FormItem>
</Col>
<Col :span="12">
<!-- 属性特有字段 -->
<FormItem label="读写类型" name="type">
<RadioGroup
v-model:value="formData.expands.type"
button-style="solid"
>
<RadioButton
:value="item.value"
v-for="item in readWriteTypeOptions"
:key="item.value"
>
{{ item.label }}
</RadioButton>
</RadioGroup>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="排序" name="sort">
<InputNumber
style="width: 100%"
v-model:value="formData.sort"
placeholder="请输入排序"
/>
</FormItem>
</Col>
<Col :span="24">
<FormItem label="描述" name="description">
<Textarea
v-model:value="formData.description"
placeholder="请输入描述"
:rows="3"
/>
</FormItem>
</Col>
</Row>
</Form>
<template #footer>
<Space>
<Button @click="handleDrawerClose">取消</Button>
<Button type="primary" @click="handleSave" :loading="saveLoading">
保存
</Button>
</Space>
</template>
</Drawer>
<!-- 枚举列表编辑弹窗 -->
<EnumListModal
v-model:open="enumModalVisible"
:enum-conf="formData.valueParams?.enumConf"
@confirm="handleEnumConfirm"
/> />
</div> </div>
</template> </template>

View File

@ -0,0 +1,387 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import {
Col,
Descriptions,
DescriptionsItem,
Form,
FormItem,
Input,
InputNumber,
message,
Modal,
Row,
Select,
SelectOption,
Switch,
} from 'ant-design-vue';
import { dataTypeOptions, formTypeOptions } from '#/constants/dicts';
import EnumListModal from './EnumListModal.vue';
interface ParameterItem {
id: string;
name: string;
dataType: string;
required: boolean;
formType: string;
}
interface Props {
open: boolean;
parameterType: 'add' | 'edit';
data?: ParameterItem[];
}
interface Emits {
(e: 'update:open', value: boolean): void;
(e: 'confirm', value: any): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const visible = computed({
get: () => props.open,
set: (value) => emit('update:open', value),
});
//
const defaultPropertyData: PropertyData = {
id: '',
name: '',
sort: 1,
description: '',
required: false,
expands: {
source: 'device',
type: 'R',
},
valueParams: {
dataType: 'string',
formType: 'input',
length: null,
viewType: 'input',
unit: '',
enumConf: [],
},
};
const formRef = ref();
const loading = ref(false);
const enumModalVisible = ref(false);
const formData = ref<ParameterItem>({ ...defaultPropertyData });
//
const formRules = {
id: [
{ required: true, message: '请输入标识符', trigger: 'blur' },
{
pattern: /^[a-z]\w*$/i,
message: '请输入以字母开头,只包含字母、数字和下划线的标识符',
trigger: 'blur',
},
],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
dataType: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
formType: [{ required: true, message: '请选择表单类型', trigger: 'blur' }],
};
//
const resetForm = () => {
Object.assign(
formData.value,
JSON.parse(JSON.stringify(defaultPropertyData)),
);
};
//
const handleEditEnum = () => {
enumModalVisible.value = true;
};
//
const handleEnumConfirm = (enumList: any[]) => {
formData.value.valueParams.enumConf = enumList;
enumModalVisible.value = false;
};
//
const modalTitle = computed(() => {
return props.parameterType === 'add' ? '新增参数' : '编辑参数';
});
//
const validateData = (): boolean => {
try {
formRef.value.validate();
return true;
} catch {
return false;
}
};
//
const handleOk = async () => {
if (!validateData()) {
return;
}
loading.value = true;
try {
emit('confirm', JSON.parse(JSON.stringify(formData.value)));
visible.value = false;
message.success('保存成功');
resetForm();
} catch {
message.error('保存失败');
} finally {
loading.value = false;
}
};
//
const handleCancel = () => {
visible.value = false;
resetForm();
};
// data
watch(
() => props.data,
(newVal) => {
if (newVal === null) {
//
resetForm();
} else {
//
formData.value = JSON.parse(JSON.stringify(newVal));
}
},
{ immediate: true },
);
</script>
<template>
<Modal
v-model:open="visible"
:title="modalTitle"
width="600px"
@ok="handleOk"
@cancel="handleCancel"
:confirm-loading="loading"
>
<div class="parameter-form">
<Form
ref="formRef"
:model="formData"
:rules="formRules"
layout="vertical"
>
<FormItem label="标识符" name="id">
<Input v-model:value="formData.id" placeholder="请输入标识符" />
</FormItem>
<FormItem label="名称" name="name">
<Input v-model:value="formData.name" placeholder="请输入名称" />
</FormItem>
<FormItem label="数据类型" name="valueParams.dataType">
<Select
v-model:value="formData.valueParams.dataType"
placeholder="请选择数据类型"
style="width: 100%"
>
<SelectOption
v-for="option in dataTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem>
<FormItem label="表单类型" name="valueParams.formType">
<Select
v-model:value="formData.valueParams.formType"
placeholder="请选择表单类型"
style="width: 100%"
>
<SelectOption
v-for="option in formTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem>
<!-- 开关类型配置 -->
<Row
:gutter="24"
style="
padding-top: 16px;
margin: 0;
margin-bottom: 16px;
border: 1px solid #f0f0f0;
border-radius: 4px;
"
v-if="formData.valueParams.formType === 'switch'"
>
<Col :span="12">
<FormItem label="开关打开名称" name="valueParams.trueText">
<Input
v-model:value="formData.valueParams.trueText"
default-value="是"
placeholder="请输入开关打开名称"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关打开值" name="valueParams.trueValue">
<Input
v-model:value="formData.valueParams.trueValue"
default-value="true"
placeholder="请输入开关打开值"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关关闭名称" name="valueParams.falseText">
<Input
v-model:value="formData.valueParams.falseText"
default-value="否"
placeholder="请输入开关关闭名称"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关关闭值" name="valueParams.falseValue">
<Input
v-model:value="formData.valueParams.falseValue"
default-value="false"
placeholder="请输入开关关闭值"
/>
</FormItem>
</Col>
</Row>
<!-- 选择器类型配置 -->
<FormItem
label="枚举列表"
name="valueParams.enumConf"
v-if="formData.valueParams.formType === 'select'"
>
<div class="enum-preview">
<a-button type="primary" @click="handleEditEnum">
编辑枚举列表
</a-button>
<Descriptions
v-if="
formData.valueParams.enumConf &&
formData.valueParams.enumConf.length > 0
"
:column="4"
bordered
title=" "
>
<DescriptionsItem
:label="`${item.text} :`"
v-for="item in formData.valueParams.enumConf"
:key="item.value"
>
{{ item.value }}
</DescriptionsItem>
</Descriptions>
</div>
</FormItem>
<Row
:gutter="24"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<Col :span="12">
<FormItem label="最小值" name="valueParams.min">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.min"
placeholder="请输入最小值"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="最大值" name="valueParams.max">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.max"
placeholder="请输入最大值"
/>
</FormItem>
</Col>
</Row>
<FormItem
label="小数位"
name="valueParams.scale"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.scale"
placeholder="请输入小数位"
/>
</FormItem>
<FormItem
label="长度"
name="valueParams.length"
v-if="formData.valueParams.formType === 'input'"
>
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.length"
placeholder="请输入长度"
/>
</FormItem>
<FormItem
label="时间格式"
name="valueParams.format"
v-if="formData.valueParams.formType === 'time'"
>
<Select
v-model:value="formData.valueParams.format"
placeholder="请选择时间格式"
:options="timeOptions"
/>
</FormItem>
<FormItem label="是否必填" name="required">
<Switch
v-model:checked="formData.required"
checked-children="是"
un-checked-children="否"
/>
</FormItem>
</Form>
</div>
<EnumListModal
v-model:open="enumModalVisible"
:enum-conf="formData.valueParams?.enumConf"
@confirm="handleEnumConfirm"
/>
</Modal>
</template>
<style lang="scss" scoped>
.parameter-form {
padding: 16px 0;
}
</style>

View File

@ -0,0 +1,468 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import {
Button,
Col,
Descriptions,
DescriptionsItem,
Drawer,
Form,
FormItem,
Input,
InputNumber,
message,
RadioButton,
RadioGroup,
Row,
Select,
Space,
Switch,
Textarea,
} from 'ant-design-vue';
import {
dataTypeOptions,
formTypeOptions,
readWriteTypeOptions,
timeOptions,
viewTypeOptions,
} from '#/constants/dicts';
import EnumListModal from './EnumListModal.vue';
interface PropertyData {
id: string;
name: string;
sort: number;
description: string;
required: boolean;
expands: {
source: string;
type: string;
};
valueParams: {
dataType: string;
enumConf: any[];
falseText?: string;
falseValue?: string;
format?: string;
formType: string;
length: any;
max?: number;
min?: number;
scale?: number;
trueText?: string;
trueValue?: string;
unit: string;
viewType: string;
};
}
interface Props {
open: boolean;
isEdit: boolean;
data?: PropertyData;
}
interface Emits {
(e: 'update:open', value: boolean): void;
(e: 'save', data: PropertyData): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const visible = computed({
get: () => props.open,
set: (value) => emit('update:open', value),
});
const formRef = ref();
const saveLoading = ref(false);
const enumModalVisible = ref(false);
//
const defaultPropertyData: PropertyData = {
id: '',
name: '',
sort: 1,
description: '',
required: false,
expands: {
source: 'device',
type: 'R',
},
valueParams: {
dataType: 'string',
formType: 'input',
length: null,
viewType: 'input',
unit: '',
enumConf: [],
},
};
const formData = ref<PropertyData>({ ...defaultPropertyData });
//
const formRules = {
id: [
{ required: true, message: '请输入标识符', trigger: 'blur' },
{
pattern: /^[a-z]\w*$/i,
message: '请输入以字母开头,只包含字母、数字和下划线的标识符',
trigger: 'blur',
},
],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
// 'valueParams.dataType': [
// { required: true, message: '', trigger: 'blur' },
// ],
};
//
const drawerTitle = computed(() => {
return props.isEdit ? '编辑属性' : '新增属性';
});
//
const resetForm = () => {
Object.assign(
formData.value,
JSON.parse(JSON.stringify(defaultPropertyData)),
);
};
//
const handleEditEnum = () => {
enumModalVisible.value = true;
};
//
const handleEnumConfirm = (enumList: any[]) => {
formData.value.valueParams.enumConf = enumList;
enumModalVisible.value = false;
};
//
const handleSave = async () => {
try {
await formRef.value.validate();
saveLoading.value = true;
emit('save', { ...formData.value });
visible.value = false;
} catch {
message.error('保存失败');
} finally {
saveLoading.value = false;
}
};
//
const handleDrawerClose = () => {
visible.value = false;
resetForm();
};
//
watch(
() => props.data,
(newVal) => {
if (newVal === null) {
resetForm();
} else {
formData.value = { ...newVal };
}
},
{ immediate: true },
);
</script>
<template>
<Drawer
v-model:open="visible"
:title="drawerTitle"
width="800px"
@close="handleDrawerClose"
>
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<Row :gutter="24">
<Col :span="12">
<FormItem label="标识符" name="id">
<Input v-model:value="formData.id" placeholder="请输入标识符" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="名称" name="name">
<Input v-model:value="formData.name" placeholder="请输入名称" />
</FormItem>
</Col>
<!-- 值参数配置 -->
<Col :span="24">
<FormItem label="数据类型" name="valueParams.dataType">
<Select
v-model:value="formData.valueParams.dataType"
placeholder="请选择数据类型"
:options="dataTypeOptions"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="表单类型" name="valueParams.formType">
<Select
v-model:value="formData.valueParams.formType"
placeholder="请选择表单类型"
:options="formTypeOptions"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="展示类型" name="valueParams.viewType">
<Select
v-model:value="formData.valueParams.viewType"
placeholder="请选择展示类型"
:options="viewTypeOptions"
/>
</FormItem>
</Col>
<!-- 开关类型配置 -->
<Col :span="24" v-if="formData.valueParams.formType === 'switch'">
<Row
:gutter="24"
style="
padding-top: 16px;
margin: 0;
margin-bottom: 16px;
border: 1px solid #f0f0f0;
border-radius: 4px;
"
>
<Col :span="12">
<FormItem label="开关打开名称" name="valueParams.trueText">
<Input
v-model:value="formData.valueParams.trueText"
default-value="是"
placeholder="请输入开关打开名称"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关打开值" name="valueParams.trueValue">
<Input
v-model:value="formData.valueParams.trueValue"
default-value="true"
placeholder="请输入开关打开值"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关关闭名称" name="valueParams.falseText">
<Input
v-model:value="formData.valueParams.falseText"
default-value="否"
placeholder="请输入开关关闭名称"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="开关关闭值" name="valueParams.falseValue">
<Input
v-model:value="formData.valueParams.falseValue"
default-value="false"
placeholder="请输入开关关闭值"
/>
</FormItem>
</Col>
</Row>
</Col>
<!-- 选择器类型配置 -->
<Col :span="24" v-if="formData.valueParams.formType === 'select'">
<FormItem label="枚举列表" name="valueParams.enumConf">
<div class="enum-preview">
<a-button type="primary" @click="handleEditEnum">
编辑枚举列表
</a-button>
<Descriptions
v-if="
formData.valueParams.enumConf &&
formData.valueParams.enumConf.length > 0
"
:column="4"
bordered
title=" "
>
<DescriptionsItem
:label="`${item.text} :`"
v-for="item in formData.valueParams.enumConf"
:key="item.value"
>
{{ item.value }}
</DescriptionsItem>
</Descriptions>
</div>
</FormItem>
</Col>
<!-- 数字类型配置 -->
<Col
:span="12"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<FormItem label="最小值" name="valueParams.min">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.min"
placeholder="请输入最小值"
/>
</FormItem>
</Col>
<Col
:span="12"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<FormItem label="最大值" name="valueParams.max">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.max"
placeholder="请输入最大值"
/>
</FormItem>
</Col>
<Col :span="12" v-if="formData.valueParams.formType === 'input'">
<FormItem label="长度" name="valueParams.length">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.length"
placeholder="请输入长度"
/>
</FormItem>
</Col>
<Col
:span="12"
v-if="
formData.valueParams.formType === 'number' ||
formData.valueParams.formType === 'progress'
"
>
<FormItem label="小数位" name="valueParams.scale">
<InputNumber
style="width: 100%"
v-model:value="formData.valueParams.scale"
placeholder="请输入小数位"
/>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="单位" name="valueParams.unit">
<Input
v-model:value="formData.valueParams.unit"
placeholder="请输入单位"
/>
</FormItem>
</Col>
<Col :span="12" v-if="formData.valueParams.formType === 'time'">
<FormItem label="时间格式" name="valueParams.format">
<Select
v-model:value="formData.valueParams.format"
placeholder="请选择时间格式"
:options="timeOptions"
/>
</FormItem>
</Col>
<!-- 属性特有字段 -->
<Col :span="12">
<FormItem label="是否必填" name="required">
<Switch v-model:checked="formData.required" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="读写类型" name="type">
<RadioGroup
v-model:value="formData.expands.type"
button-style="solid"
>
<RadioButton
:value="item.value"
v-for="item in readWriteTypeOptions"
:key="item.value"
>
{{ item.label }}
</RadioButton>
</RadioGroup>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="排序" name="sort">
<InputNumber
style="width: 100%"
v-model:value="formData.sort"
placeholder="请输入排序"
/>
</FormItem>
</Col>
<Col :span="24">
<FormItem label="描述" name="description">
<Textarea
v-model:value="formData.description"
placeholder="请输入描述"
:rows="3"
/>
</FormItem>
</Col>
</Row>
</Form>
<template #footer>
<Space>
<Button @click="handleDrawerClose">取消</Button>
<Button type="primary" @click="handleSave" :loading="saveLoading">
保存
</Button>
</Space>
</template>
<!-- 枚举列表编辑弹窗 -->
<EnumListModal
v-model:open="enumModalVisible"
:enum-conf="formData.valueParams?.enumConf"
@confirm="handleEnumConfirm"
/>
</Drawer>
</template>
<style lang="scss" scoped>
.enum-preview {
.enum-items-preview {
padding: 12px;
margin-top: 12px;
background-color: #fafafa;
border-radius: 4px;
.preview-title {
margin-bottom: 8px;
font-size: 12px;
color: #666;
}
.preview-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
}
}
</style>