diff --git a/apps/web-antd/src/views/iot/product/product/data.ts b/apps/web-antd/src/views/iot/product/product/data.ts index 68fd51b46e98c868a13e7eb9add4c76e7faf365b..8811e2c64f1614365267eead4d709615dfe4d253 100644 --- a/apps/web-antd/src/views/iot/product/product/data.ts +++ b/apps/web-antd/src/views/iot/product/product/data.ts @@ -1,17 +1,19 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import { ref } from 'vue'; +import { h, ref } 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 { getSimpleProductCategoryList } from '#/api/iot/product/category'; import { getProductPage } from '#/api/iot/product/product'; /** 新增/修改产品的表单 */ -export function useFormSchema(): VbenFormSchema[] { +export function useFormSchema(formApi?: any): VbenFormSchema[] { return [ { component: 'Input', @@ -21,6 +23,55 @@ export function useFormSchema(): VbenFormSchema[] { show: () => false, }, }, + // 创建时的 ProductKey 字段(带生成按钮) + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入 ProductKey', + }, + dependencies: { + triggerFields: ['id'], + if(values) { + // 仅在创建时显示(没有 id) + return !values.id; + }, + }, + rules: z + .string() + .min(1, 'ProductKey 不能为空') + .max(32, 'ProductKey 长度不能超过 32 个字符'), + suffix: () => { + return h(Button, { + type: 'default', + onClick: () => { + formApi?.setFieldValue('productKey', generateProductKey()); + }, + }, { default: () => '重新生成' }); + }, + }, + // 编辑时的 ProductKey 字段(禁用,无按钮) + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入 ProductKey', + disabled: true, + }, + dependencies: { + triggerFields: ['id'], + if(values) { + // 仅在编辑时显示(有 id) + return !!values.id; + }, + }, + rules: z + .string() + .min(1, 'ProductKey 不能为空') + .max(32, 'ProductKey 长度不能超过 32 个字符'), + }, { fieldName: 'name', label: '产品名称', @@ -67,26 +118,37 @@ export function useFormSchema(): VbenFormSchema[] { rules: 'required', }, { - fieldName: 'protocolType', - label: '接入协议', - component: 'Select', + fieldName: 'codecType', + label: '数据格式', + component: 'RadioGroup', componentProps: { - options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'number'), - placeholder: '请选择接入协议', + options: getDictOptions(DICT_TYPE.IOT_CODEC_TYPE, 'string'), + buttonStyle: 'solid', + optionType: 'button', }, rules: 'required', }, { - fieldName: 'dataFormat', - label: '数据格式', + fieldName: 'status', + label: '产品状态', component: 'RadioGroup', componentProps: { - options: getDictOptions(DICT_TYPE.IOT_DATA_FORMAT, 'number'), + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), buttonStyle: 'solid', optionType: 'button', }, rules: 'required', }, + { + fieldName: 'icon', + label: '产品图标', + component: 'ImageUpload', + }, + { + fieldName: 'picUrl', + label: '产品图片', + component: 'ImageUpload', + }, { fieldName: 'description', label: '产品描述', @@ -95,13 +157,121 @@ export function useFormSchema(): VbenFormSchema[] { placeholder: '请输入产品描述', rows: 3, }, + } + ]; +} + +/** 基础表单字段(不含图标、图片、描述) */ +export function useBasicFormSchema(formApi?: any): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + // 创建时的 ProductKey 字段(带生成按钮) + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入 ProductKey', + }, + dependencies: { + triggerFields: ['id'], + if(values) { + // 仅在创建时显示(没有 id) + return !values.id; + }, + }, + rules: z + .string() + .min(1, 'ProductKey 不能为空') + .max(32, 'ProductKey 长度不能超过 32 个字符'), + suffix: () => { + return h(Button, { + type: 'default', + onClick: () => { + formApi?.setFieldValue('productKey', generateProductKey()); + }, + }, { default: () => '重新生成' }); + }, + }, + // 编辑时的 ProductKey 字段(禁用,无按钮) + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入 ProductKey', + disabled: true, + }, + dependencies: { + triggerFields: ['id'], + if(values) { + // 仅在编辑时显示(有 id) + return !!values.id; + }, + }, + rules: z + .string() + .min(1, 'ProductKey 不能为空') + .max(32, 'ProductKey 长度不能超过 32 个字符'), + }, + { + fieldName: 'name', + label: '产品名称', + component: 'Input', + componentProps: { + placeholder: '请输入产品名称', + }, + rules: z + .string() + .min(1, '产品名称不能为空') + .max(64, '产品名称长度不能超过 64 个字符'), + }, + { + fieldName: 'categoryId', + label: '产品分类', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductCategoryList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品分类', + }, + rules: 'required', + }, + { + fieldName: 'deviceType', + label: '设备类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'netType', + label: '联网方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_NET_TYPE, 'number'), + placeholder: '请选择联网方式', + }, + rules: 'required', }, { - fieldName: 'validateType', - label: '认证方式', + fieldName: 'codecType', + label: '数据格式', component: 'RadioGroup', componentProps: { - options: getDictOptions(DICT_TYPE.IOT_VALIDATE_TYPE, 'number'), + options: getDictOptions(DICT_TYPE.IOT_CODEC_TYPE, 'string'), buttonStyle: 'solid', optionType: 'button', }, @@ -118,6 +288,47 @@ export function useFormSchema(): VbenFormSchema[] { }, rules: 'required', }, + { + fieldName: 'locationType', + label: '定位类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_LOCATION_TYPE, 'number'), + placeholder: '请选择定位类型', + }, + rules: 'required', + } + ]; +} + +/** 高级设置表单字段(图标、图片、产品描述) */ +export function useAdvancedFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'icon', + label: '产品图标', + component: 'IconPicker',//用这个组件 产品卡片列表 可以根据这个显示 否则就显示默认的 + componentProps: { + placeholder: '请选择产品图标', + prefix: 'carbon', + autoFetchApi: false, + }, + }, + { + fieldName: 'picUrl', + label: '产品图片', + component: 'ImageUpload', + }, + { + fieldName: 'description', + label: '产品描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入产品描述', + rows: 3, + }, + formItemClass: 'col-span-2', // 让描述占满两列 + }, ]; } @@ -226,3 +437,13 @@ export function useImagePreview() { handlePreviewImage, }; } + +/** 生成 ProductKey(包含大小写字母和数字) */ +export function generateProductKey(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < 16; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} diff --git a/apps/web-antd/src/views/iot/product/product/index.vue b/apps/web-antd/src/views/iot/product/product/index.vue index ad056a811810c290a4cc53356d798bd879cfa92c..190da4a1830c3b872b4708257888575f1dd7a650 100644 --- a/apps/web-antd/src/views/iot/product/product/index.vue +++ b/apps/web-antd/src/views/iot/product/product/index.vue @@ -15,7 +15,7 @@ import { deleteProduct, exportProduct, getProductPage, -} from '#/api/crm/product'; +} from '#/api/iot/product/product'; import { getSimpleProductCategoryList } from '#/api/iot/product/category'; import { $t } from '#/locales'; diff --git a/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue b/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue index 124428fb94b3ebb4686489e9b1522235e3c8d143..e28090822ec73acfddfa6a41b1715b4858cf38c4 100644 --- a/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue +++ b/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue @@ -117,7 +117,7 @@ defineExpose({
diff --git a/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue b/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue index e21d6f85c016177afdacf9992ab1bb9923f42fce..7fad6cd197e55a4931a59db2c990382bda096db5 100644 --- a/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue +++ b/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue @@ -5,7 +5,7 @@ import { computed, ref } from 'vue'; import { useVbenForm, useVbenModal } from '@vben/common-ui'; -import { message } from 'ant-design-vue'; +import { Collapse, message } from 'ant-design-vue'; import { createProduct, @@ -14,16 +14,21 @@ import { } from '#/api/iot/product/product'; import { $t } from '#/locales'; -import { useFormSchema } from '../data'; +import { generateProductKey, useBasicFormSchema, useAdvancedFormSchema } from '../data'; defineOptions({ name: 'IoTProductForm' }); +const CollapsePanel = Collapse.Panel; + const emit = defineEmits(['success']); const formData = ref(); const getTitle = computed(() => { return formData.value?.id ? '编辑产品' : '新增产品'; }); +// 折叠面板的激活key,默认不展开 +const activeKey = ref([]); + const [Form, formApi] = useVbenForm({ commonConfig: { componentProps: { @@ -32,20 +37,58 @@ const [Form, formApi] = useVbenForm({ }, wrapperClass: 'grid-cols-2', layout: 'horizontal', - schema: useFormSchema(), + schema: [], showDefaultActions: false, }); +// 创建高级设置表单 +const [AdvancedForm, advancedFormApi] = useVbenForm({ + commonConfig: { + componentProps: { + class: 'w-full', + }, + }, + wrapperClass: 'grid-cols-2', + layout: 'horizontal', + schema: [], + showDefaultActions: false, +}); + +// 在 formApi 创建后设置 schema +formApi.setState({ schema: useBasicFormSchema(formApi) }); +advancedFormApi.setState({ schema: useAdvancedFormSchema() }); + const [Modal, modalApi] = useVbenModal({ async onConfirm() { - const { valid } = await formApi.validate(); - if (!valid) { + // 只验证基础表单 + const { valid: basicValid } = await formApi.validate(); + if (!basicValid) { return; } + modalApi.lock(); - // 提交表单 - const data = (await formApi.getValues()) as IotProductApi.Product; try { + // 提交表单 - 合并两个表单的值 + const basicValues = await formApi.getValues(); + + // 如果折叠面板展开,则获取高级表单的值,否则保留原有值(编辑时)或使用空值(新增时) + let advancedValues: any = {}; + if (activeKey.value.includes('advanced')) { + advancedValues = await advancedFormApi.getValues(); + } else if (formData.value?.id) { + // 编辑时保留原有的高级字段值 + advancedValues = { + icon: formData.value.icon, + picUrl: formData.value.picUrl, + description: formData.value.description, + }; + } + + const values = { ...basicValues, ...advancedValues } as IotProductApi.Product; + const data = formData.value?.id + ? { ...values, id: formData.value.id } + : values; + await (formData.value?.id ? updateProduct(data) : createProduct(data)); // 关闭并提示 await modalApi.close(); @@ -58,6 +101,8 @@ const [Modal, modalApi] = useVbenModal({ async onOpenChange(isOpen: boolean) { if (!isOpen) { formData.value = undefined; + // 重置折叠面板状态 + activeKey.value = []; return; } // 加载数据 @@ -65,18 +110,34 @@ const [Modal, modalApi] = useVbenModal({ if (!data || !data.id) { // 设置默认值 await formApi.setValues({ - deviceType: 0, // 默认直连设备 - dataFormat: 1, // 默认 JSON - validateType: 1, // 默认设备密钥 + productKey: generateProductKey(), // 自动生成 ProductKey + // deviceType: 0, // 默认直连设备 + // codecType: 'Alink', // 默认 Alink + // dataFormat: 1, // 默认 JSON + // validateType: 1, // 默认设备密钥 status: 0, // 默认启用 }); return; } - modalApi.lock(); try { formData.value = await getProduct(data.id); - // 设置到 values + // 设置基础表单 await formApi.setValues(formData.value); + + // 先设置高级表单的值(不等待) + advancedFormApi.setValues({ + icon: formData.value.icon, + picUrl: formData.value.picUrl, + description: formData.value.description, + }); + + // 如果有图标、图片或描述,自动展开折叠面板以便显示 + if (formData.value.icon || formData.value.picUrl || formData.value.description) { + activeKey.value = ['advanced']; + } + } catch (error) { + console.error('加载产品数据失败:', error); + message.error('加载产品数据失败,请重试'); } finally { modalApi.unlock(); } @@ -86,6 +147,16 @@ const [Modal, modalApi] = useVbenModal({