修复iot产品管理:

1.删除,导出,获取分页 引入地址不正确的问题
2.新增修改页面 点击确定按钮没有调用接口
3.新增修改页面productKey不显示问题,修复新增没有生成按钮问题
4.新增更多设置折叠框标题为更多设置 图标 图片 产品描述放到 更多配置折叠区域默认折叠点击展开
5.卡片列表中的产品图标根据设置的图标显示

Signed-off-by: Administrator <425053404@qq.com>
This commit is contained in:
huppygo
2025-10-13 22:20:35 +08:00
committed by Administrator
parent 5f88a54d60
commit 4261ca0a50
4 changed files with 341 additions and 49 deletions

View File

@@ -1,17 +1,19 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { ref } from 'vue'; import { h, ref } from 'vue';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { Button } from 'ant-design-vue';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { getSimpleProductCategoryList } from '#/api/iot/product/category'; import { getSimpleProductCategoryList } from '#/api/iot/product/category';
import { getProductPage } from '#/api/iot/product/product'; import { getProductPage } from '#/api/iot/product/product';
/** 新增/修改产品的表单 */ /** 新增/修改产品的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(formApi?: any): VbenFormSchema[] {
return [ return [
{ {
component: 'Input', component: 'Input',
@@ -21,6 +23,55 @@ export function useFormSchema(): VbenFormSchema[] {
show: () => false, 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', fieldName: 'name',
label: '产品名称', label: '产品名称',
@@ -67,41 +118,11 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required', rules: 'required',
}, },
{ {
fieldName: 'protocolType', fieldName: 'codecType',
label: '接入协议',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'number'),
placeholder: '请选择接入协议',
},
rules: 'required',
},
{
fieldName: 'dataFormat',
label: '数据格式', label: '数据格式',
component: 'RadioGroup', component: 'RadioGroup',
componentProps: { componentProps: {
options: getDictOptions(DICT_TYPE.IOT_DATA_FORMAT, 'number'), options: getDictOptions(DICT_TYPE.IOT_CODEC_TYPE, 'string'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: 'required',
},
{
fieldName: 'description',
label: '产品描述',
component: 'Textarea',
componentProps: {
placeholder: '请输入产品描述',
rows: 3,
},
},
{
fieldName: 'validateType',
label: '认证方式',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.IOT_VALIDATE_TYPE, 'number'),
buttonStyle: 'solid', buttonStyle: 'solid',
optionType: 'button', optionType: 'button',
}, },
@@ -118,6 +139,196 @@ export function useFormSchema(): VbenFormSchema[] {
}, },
rules: 'required', rules: 'required',
}, },
{
fieldName: 'icon',
label: '产品图标',
component: 'ImageUpload',
},
{
fieldName: 'picUrl',
label: '产品图片',
component: 'ImageUpload',
},
{
fieldName: 'description',
label: '产品描述',
component: 'Textarea',
componentProps: {
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: 'codecType',
label: '数据格式',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.IOT_CODEC_TYPE, 'string'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: 'required',
},
{
fieldName: 'status',
label: '产品状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
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, 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;
}

View File

@@ -15,7 +15,7 @@ import {
deleteProduct, deleteProduct,
exportProduct, exportProduct,
getProductPage, getProductPage,
} from '#/api/crm/product'; } from '#/api/iot/product/product';
import { getSimpleProductCategoryList } from '#/api/iot/product/category'; import { getSimpleProductCategoryList } from '#/api/iot/product/category';
import { $t } from '#/locales'; import { $t } from '#/locales';

View File

@@ -117,7 +117,7 @@ defineExpose({
<div class="mb-4 flex items-start"> <div class="mb-4 flex items-start">
<div class="product-icon"> <div class="product-icon">
<IconifyIcon <IconifyIcon
icon="ant-design:inbox-outlined" :icon="item.icon || 'ant-design:inbox-outlined'"
class="text-[32px]" class="text-[32px]"
/> />
</div> </div>

View File

@@ -5,7 +5,7 @@ import { computed, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui'; import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue'; import { Collapse, message } from 'ant-design-vue';
import { import {
createProduct, createProduct,
@@ -14,16 +14,21 @@ import {
} from '#/api/iot/product/product'; } from '#/api/iot/product/product';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useFormSchema } from '../data'; import { generateProductKey, useBasicFormSchema, useAdvancedFormSchema } from '../data';
defineOptions({ name: 'IoTProductForm' }); defineOptions({ name: 'IoTProductForm' });
const CollapsePanel = Collapse.Panel;
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<any>(); const formData = ref<any>();
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id ? '编辑产品' : '新增产品'; return formData.value?.id ? '编辑产品' : '新增产品';
}); });
// 折叠面板的激活key默认不展开
const activeKey = ref<string[]>([]);
const [Form, formApi] = useVbenForm({ const [Form, formApi] = useVbenForm({
commonConfig: { commonConfig: {
componentProps: { componentProps: {
@@ -32,20 +37,58 @@ const [Form, formApi] = useVbenForm({
}, },
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
layout: 'horizontal', layout: 'horizontal',
schema: useFormSchema(), schema: [],
showDefaultActions: false, 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({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
const { valid } = await formApi.validate(); // 只验证基础表单
if (!valid) { const { valid: basicValid } = await formApi.validate();
if (!basicValid) {
return; return;
} }
modalApi.lock(); modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as IotProductApi.Product;
try { 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 (formData.value?.id ? updateProduct(data) : createProduct(data));
// 关闭并提示 // 关闭并提示
await modalApi.close(); await modalApi.close();
@@ -58,6 +101,8 @@ const [Modal, modalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined; formData.value = undefined;
// 重置折叠面板状态
activeKey.value = [];
return; return;
} }
// 加载数据 // 加载数据
@@ -65,18 +110,34 @@ const [Modal, modalApi] = useVbenModal({
if (!data || !data.id) { if (!data || !data.id) {
// 设置默认值 // 设置默认值
await formApi.setValues({ await formApi.setValues({
deviceType: 0, // 默认直连设备 productKey: generateProductKey(), // 自动生成 ProductKey
dataFormat: 1, // 默认 JSON // deviceType: 0, // 默认直连设备
validateType: 1, // 默认设备密钥 // codecType: 'Alink', // 默认 Alink
// dataFormat: 1, // 默认 JSON
// validateType: 1, // 默认设备密钥
status: 0, // 默认启用 status: 0, // 默认启用
}); });
return; return;
} }
modalApi.lock();
try { try {
formData.value = await getProduct(data.id); formData.value = await getProduct(data.id);
// 设置到 values // 设置基础表单
await formApi.setValues(formData.value); 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 { } finally {
modalApi.unlock(); modalApi.unlock();
} }
@@ -86,6 +147,16 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal :title="getTitle" class="w-2/5"> <Modal :title="getTitle" class="w-2/5">
<Form class="mx-4" /> <div class="mx-4">
<Form />
<Collapse v-model:active-key="activeKey" class="mt-4">
<CollapsePanel key="advanced" header="更多设置">
<template #extra>
<span class="text-gray-500">📷</span>
</template>
<AdvancedForm />
</CollapsePanel>
</Collapse>
</div>
</Modal> </Modal>
</template> </template>