@@ -2,7 +2,7 @@ import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
namespace ErpFinanceReceiptApi {
|
||||
export namespace ErpFinanceReceiptApi {
|
||||
/** 收款单项 */
|
||||
export interface FinanceReceiptItem {
|
||||
id?: number;
|
||||
|
||||
@@ -101,6 +101,7 @@ export interface Action {
|
||||
identifier?: string;
|
||||
value?: any;
|
||||
alertConfigId?: number;
|
||||
params?: string;
|
||||
}
|
||||
|
||||
/** 查询场景联动规则分页 */
|
||||
|
||||
@@ -175,17 +175,27 @@ export function deleteThingModelList(ids: number[]) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 导入物模型 TSL */
|
||||
/** 获取物模型 TSL */
|
||||
export function getThingModelTSL(productId: number) {
|
||||
return requestClient.get<ThingModelApi.ThingModel[]>(
|
||||
'/iot/thing-model/get-tsl',
|
||||
{ params: { productId } },
|
||||
);
|
||||
}
|
||||
|
||||
/** 导入物模型 TSL
|
||||
export function importThingModelTSL(productId: number, tslData: any) {
|
||||
return requestClient.post('/iot/thing-model/import-tsl', {
|
||||
productId,
|
||||
tslData,
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
/** 导出物模型 TSL */
|
||||
/** 导出物模型 TSL
|
||||
export function exportThingModelTSL(productId: number) {
|
||||
return requestClient.get<any>('/iot/thing-model/export-tsl', {
|
||||
params: { productId },
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -740,7 +740,10 @@
|
||||
"name": "FailedJobRetryTimeCycle",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["activiti:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"]
|
||||
"allowedIn": [
|
||||
"activiti:AsyncCapable",
|
||||
"bpmn:MultiInstanceLoopCharacteristics"
|
||||
]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
|
||||
@@ -727,7 +727,10 @@
|
||||
"name": "FailedJobRetryTimeCycle",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["camunda:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"]
|
||||
"allowedIn": [
|
||||
"camunda:AsyncCapable",
|
||||
"bpmn:MultiInstanceLoopCharacteristics"
|
||||
]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
|
||||
@@ -910,7 +910,10 @@
|
||||
"name": "FailedJobRetryTimeCycle",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["flowable:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"]
|
||||
"allowedIn": [
|
||||
"flowable:AsyncCapable",
|
||||
"bpmn:MultiInstanceLoopCharacteristics"
|
||||
]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
@@ -1254,11 +1257,11 @@
|
||||
"allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -238,7 +238,7 @@ watch(
|
||||
<IconifyIcon
|
||||
icon="ant-design:check-circle-filled"
|
||||
v-if="valid"
|
||||
style=" margin-left: 8px;color: green"
|
||||
style="margin-left: 8px; color: green"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; margin-top: 10px">
|
||||
|
||||
@@ -18,7 +18,8 @@ const routes: RouteRecordRaw[] = [
|
||||
title: '产品详情',
|
||||
activePath: '/iot/device/product',
|
||||
},
|
||||
component: () => import('#/views/iot/product/product/modules/detail/index.vue'),
|
||||
component: () =>
|
||||
import('#/views/iot/product/product/modules/detail/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'device/detail/:id',
|
||||
@@ -27,11 +28,11 @@ const routes: RouteRecordRaw[] = [
|
||||
title: '设备详情',
|
||||
activePath: '/iot/device/device',
|
||||
},
|
||||
component: () => import('#/views/iot/device/device/modules/detail/index.vue'),
|
||||
component: () =>
|
||||
import('#/views/iot/device/device/modules/detail/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ export function useGridFormSchemaMessage(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -93,7 +93,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择引用知识库',
|
||||
api: getSimpleKnowledgeList,
|
||||
api: () => getSimpleKnowledgeList(),
|
||||
labelField: 'name',
|
||||
mode: 'multiple',
|
||||
valueField: 'id',
|
||||
@@ -106,7 +106,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择引用工具',
|
||||
api: getToolSimpleList,
|
||||
api: () => getToolSimpleList(),
|
||||
mode: 'multiple',
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
|
||||
@@ -48,7 +48,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择API 秘钥',
|
||||
api: getApiKeySimpleList,
|
||||
api: () => getApiKeySimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
allowClear: true,
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择成员',
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
mode: 'tags',
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
// componentProps: {
|
||||
// placeholder: '请选择发起人',
|
||||
// allowClear: true,
|
||||
// api: getSimpleUserList,
|
||||
// api: () => getSimpleUserList(),
|
||||
// labelField: 'nickname',
|
||||
// valueField: 'id',
|
||||
// },
|
||||
@@ -48,7 +48,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
placeholder: '请输入流程分类',
|
||||
allowClear: true,
|
||||
api: getCategorySimpleList,
|
||||
api: () => getCategorySimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
placeholder: '请选择发起人',
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -54,7 +54,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
placeholder: '请输入流程分类',
|
||||
allowClear: true,
|
||||
api: getCategorySimpleList,
|
||||
api: () => getCategorySimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
placeholder: '请输入流程分类',
|
||||
allowClear: true,
|
||||
api: getCategorySimpleList,
|
||||
api: () => getCategorySimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
placeholder: '请输入流程分类',
|
||||
allowClear: true,
|
||||
api: getCategorySimpleList,
|
||||
api: () => getCategorySimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
},
|
||||
|
||||
@@ -41,10 +41,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
},
|
||||
@@ -57,10 +55,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
},
|
||||
@@ -85,10 +81,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getBusinessStatusTypeSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择商机状态组',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||
import type { DescriptionItemSchema } from '#/components/description';
|
||||
|
||||
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
|
||||
|
||||
import { DEFAULT_STATUSES, getBusinessStatusSimpleList } from '#/api/crm/business/status';
|
||||
import {
|
||||
DEFAULT_STATUSES,
|
||||
getBusinessStatusSimpleList,
|
||||
} from '#/api/crm/business/status';
|
||||
|
||||
/** 详情页的字段 */
|
||||
export function useDetailSchema(): DescriptionItemSchema[] {
|
||||
|
||||
@@ -53,7 +53,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '负责人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
allowClear: true,
|
||||
|
||||
@@ -42,10 +42,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
},
|
||||
defaultValue: userStore.userInfo?.id,
|
||||
@@ -57,10 +55,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
},
|
||||
},
|
||||
@@ -139,10 +135,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleContactList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择直属上级',
|
||||
},
|
||||
},
|
||||
@@ -195,10 +189,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -47,10 +47,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
@@ -66,10 +64,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
},
|
||||
},
|
||||
@@ -142,10 +138,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
defaultValue: userStore.userInfo?.id,
|
||||
},
|
||||
@@ -270,10 +264,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -61,10 +61,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
},
|
||||
@@ -223,10 +221,8 @@ export function useImportFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
class: 'w-full',
|
||||
|
||||
@@ -28,10 +28,8 @@ export function useDistributeFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
defaultValue: userStore.userInfo?.id,
|
||||
rules: 'required',
|
||||
|
||||
@@ -34,11 +34,9 @@ export function useFormSchema(confType: LimitConfType): VbenFormSchema[] {
|
||||
label: '规则适用人群',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
allowClear: true,
|
||||
placeholder: '请选择规则适用人群',
|
||||
|
||||
@@ -85,8 +85,9 @@ export function useFormSchema(
|
||||
});
|
||||
return res.list;
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
fieldNames: { label: 'name', value: 'id' },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -105,8 +106,9 @@ export function useFormSchema(
|
||||
});
|
||||
return res.list;
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
fieldNames: { label: 'name', value: 'id' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -23,7 +23,7 @@ export function useTransferFormSchema(): VbenFormSchema[] {
|
||||
label: '选择新负责人',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -116,7 +116,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '选择人员',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -43,10 +43,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -70,4 +70,3 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择负责人',
|
||||
allowClear: true,
|
||||
},
|
||||
@@ -61,10 +59,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
},
|
||||
dependencies: {
|
||||
@@ -193,10 +189,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -29,10 +29,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
@@ -48,11 +46,10 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -63,6 +60,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
options: [],
|
||||
placeholder: '请选择合同',
|
||||
allowClear: true,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['customerId'],
|
||||
@@ -156,10 +154,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getCustomerSimpleList(),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -31,8 +31,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
||||
{
|
||||
field: 'receivable',
|
||||
label: '实际回款金额',
|
||||
content: (data) =>
|
||||
erpPriceInputFormatter(data?.receivable?.price ?? 0),
|
||||
content: (data) => erpPriceInputFormatter(data?.receivable?.price ?? 0),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -78,8 +77,7 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] {
|
||||
{
|
||||
field: 'receivable',
|
||||
label: '实际回款金额',
|
||||
content: (data) =>
|
||||
erpPriceInputFormatter(data?.receivable?.price ?? 0),
|
||||
content: (data) => erpPriceInputFormatter(data?.receivable?.price ?? 0),
|
||||
},
|
||||
{
|
||||
field: 'receivableRemain',
|
||||
@@ -92,8 +90,7 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] {
|
||||
{
|
||||
field: 'receivable.returnTime',
|
||||
label: '实际回款日期',
|
||||
content: (data) =>
|
||||
formatDateTime(data?.receivable?.returnTime) as string,
|
||||
content: (data) => formatDateTime(data?.receivable?.returnTime) as string,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
|
||||
@@ -99,7 +99,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '员工',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择员工',
|
||||
|
||||
@@ -73,7 +73,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '员工',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
allowClear: true,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
|
||||
@@ -64,7 +64,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '员工',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择员工',
|
||||
|
||||
@@ -68,7 +68,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '员工',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择员工',
|
||||
|
||||
@@ -38,8 +38,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
ajax: {
|
||||
query: async (_, formValues) => {
|
||||
const res = await getDatas(activeTabName.value, formValues);
|
||||
await renderLeftEcharts(getChartOptions(activeTabName.value, res).left);
|
||||
await renderRightEcharts(getChartOptions(activeTabName.value, res).right);
|
||||
await renderLeftEcharts(
|
||||
getChartOptions(activeTabName.value, res).left,
|
||||
);
|
||||
await renderRightEcharts(
|
||||
getChartOptions(activeTabName.value, res).right,
|
||||
);
|
||||
return res;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,11 +52,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -68,11 +66,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择财务人员',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -123,11 +119,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择付款账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -247,11 +241,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -262,11 +254,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -277,11 +267,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择财务人员',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -292,11 +280,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择付款账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,11 +52,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getCustomerSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getCustomerSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -68,11 +66,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择财务人员',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -123,11 +119,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择收款账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -247,11 +241,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getCustomerSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getCustomerSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -262,11 +254,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -277,11 +267,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择财务人员',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -292,11 +280,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择收款账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -26,17 +26,18 @@ const formData = ref<
|
||||
}
|
||||
>({
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
customerId: undefined,
|
||||
accountId: undefined,
|
||||
financeUserId: undefined,
|
||||
receiptTime: undefined,
|
||||
remark: undefined,
|
||||
no: '',
|
||||
customerId: 0,
|
||||
accountId: 0,
|
||||
financeUserId: 0,
|
||||
receiptTime: new Date(),
|
||||
remark: '',
|
||||
fileUrl: undefined,
|
||||
totalPrice: 0,
|
||||
discountPrice: 0,
|
||||
receiptPrice: 0,
|
||||
items: [],
|
||||
status: 0,
|
||||
});
|
||||
|
||||
const formType = ref(''); // 表单类型:'create' | 'edit' | 'detail'
|
||||
@@ -141,7 +142,21 @@ const [Modal, modalApi] = useVbenModal({
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
no: '',
|
||||
customerId: 0,
|
||||
accountId: 0,
|
||||
financeUserId: 0,
|
||||
receiptTime: new Date(),
|
||||
remark: '',
|
||||
totalPrice: 0,
|
||||
discountPrice: 0,
|
||||
receiptPrice: 0,
|
||||
status: 0,
|
||||
items: [],
|
||||
bizNo: '',
|
||||
};
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
@@ -191,4 +206,4 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</template>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getProductCategorySimpleList } from '#/api/erp/product/category';
|
||||
@@ -60,7 +61,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '单位',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getProductUnitSimpleList,
|
||||
api: () => getProductUnitSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择单位',
|
||||
|
||||
@@ -66,11 +66,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -176,11 +174,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择结算账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -323,11 +319,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -347,11 +341,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -362,7 +354,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -375,11 +367,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -399,11 +389,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择结算账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -542,11 +530,9 @@ export function useOrderGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,11 +52,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -142,11 +140,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择结算账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -265,11 +261,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -289,11 +283,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -304,11 +296,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -66,11 +66,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -175,11 +173,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择结算账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -323,11 +319,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -347,11 +341,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -362,7 +354,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -375,11 +367,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -526,11 +516,9 @@ export function useOrderGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,11 +52,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getCustomerSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getCustomerSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -68,11 +66,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择销售人员',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -157,11 +153,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择结算账户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -281,11 +275,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -305,11 +297,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getCustomerSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getCustomerSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -320,11 +310,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -544,11 +544,9 @@ export function useOrderGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -66,11 +66,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getCustomerSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getCustomerSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -82,11 +80,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择销售人员',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -191,11 +187,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
disabled: true,
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getAccountSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getAccountSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -339,11 +333,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -363,11 +355,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择客户',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getCustomerSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getCustomerSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -378,7 +368,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -391,11 +381,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -543,11 +531,9 @@ export function useOrderGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -114,4 +114,4 @@ const handleOk = () => {
|
||||
>
|
||||
<Grid class="max-h-[600px]" table-title="销售订单列表(仅展示可退货)" />
|
||||
</Modal>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -180,11 +180,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -204,11 +202,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -219,11 +215,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -305,4 +305,4 @@ onMounted(async () => {
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -50,11 +50,9 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -189,11 +187,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -213,11 +209,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择供应商',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSupplierSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSupplierSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -228,11 +222,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -243,11 +235,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -178,11 +178,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -202,11 +200,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择调出仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -217,11 +213,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择调入仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -232,11 +226,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择创建人',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getSimpleUserList,
|
||||
fieldNames: {
|
||||
label: 'nickname',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -32,7 +32,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择产品',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getProductSimpleList,
|
||||
api: () => getProductSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
@@ -28,7 +28,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
placeholder: '请选择仓库',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
api: getWarehouseSimpleList,
|
||||
api: () => getWarehouseSimpleList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
|
||||
@@ -25,13 +25,9 @@ export function useImportTableFormSchema(): VbenFormSchema[] {
|
||||
label: '数据源',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: async () => {
|
||||
const data = await getDataSourceConfigList();
|
||||
return data.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
},
|
||||
api: () => getDataSourceConfigList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
autoSelect: 'first',
|
||||
placeholder: '请选择数据源',
|
||||
},
|
||||
|
||||
@@ -63,7 +63,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '关联场景联动规则',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleRuleSceneList,
|
||||
api: () => getSimpleRuleSceneList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
@@ -76,7 +76,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '接收的用户',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '告警配置',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleAlertConfigList,
|
||||
api: () => getSimpleAlertConfigList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择告警配置',
|
||||
@@ -40,7 +40,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -53,7 +53,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '设备',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceList,
|
||||
api: () => getSimpleDeviceList(),
|
||||
labelField: 'deviceName',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择设备',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getSimpleDeviceList } from '#/api/iot/device/device';
|
||||
import { getSimpleDeviceGroupList } from '#/api/iot/device/group';
|
||||
import {
|
||||
DeviceTypeEnum,
|
||||
@@ -27,7 +28,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -55,12 +56,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '网关设备',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: async () => {
|
||||
const { getSimpleDeviceList } = await import(
|
||||
'#/api/iot/device/device'
|
||||
);
|
||||
return getSimpleDeviceList(DeviceTypeEnum.GATEWAY);
|
||||
},
|
||||
api: () => getSimpleDeviceList(DeviceTypeEnum.GATEWAY),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '子设备可选择父设备',
|
||||
@@ -93,7 +89,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '设备分组',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
@@ -113,16 +109,16 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
.optional()
|
||||
.or(z.literal('')),
|
||||
},
|
||||
// {
|
||||
// fieldName: 'locationType',
|
||||
// label: '定位类型',
|
||||
// component: 'RadioGroup',
|
||||
// componentProps: {
|
||||
// options: getDictOptions(DICT_TYPE.IOT_LOCATION_TYPE, 'number'),
|
||||
// buttonStyle: 'solid',
|
||||
// optionType: 'button',
|
||||
// },
|
||||
// },
|
||||
{
|
||||
fieldName: 'locationType',
|
||||
label: '定位类型',
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.IOT_LOCATION_TYPE, 'number'),
|
||||
buttonStyle: 'solid',
|
||||
optionType: 'button',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'longitude',
|
||||
label: '设备经度',
|
||||
@@ -160,7 +156,7 @@ export function useGroupFormSchema(): VbenFormSchema[] {
|
||||
label: '设备分组',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
@@ -203,7 +199,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -253,7 +249,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '设备分组',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择设备分组',
|
||||
|
||||
@@ -32,7 +32,6 @@ import { getSimpleProductList } from '#/api/iot/product/product';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns } from './data';
|
||||
// @ts-ignore
|
||||
import DeviceCardView from './modules/DeviceCardView.vue';
|
||||
import DeviceForm from './modules/DeviceForm.vue';
|
||||
import DeviceGroupForm from './modules/DeviceGroupForm.vue';
|
||||
|
||||
@@ -44,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('common.updateSuccess'));
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
|
||||
@@ -111,14 +111,14 @@ const [Modal, modalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
/** 下载模板 */
|
||||
const handleDownloadTemplate = async () => {
|
||||
async function handleDownloadTemplate() {
|
||||
try {
|
||||
const res = await importDeviceTemplate();
|
||||
downloadFileFromBlobPart({ fileName: '设备导入模版.xls', source: res });
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '下载失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -139,7 +139,7 @@ const rowSelection = computed(() => ({
|
||||
}));
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
async function getList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (props.productId) {
|
||||
@@ -151,22 +151,22 @@ const getList = async () => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
function handleQuery() {
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
function resetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
async function open() {
|
||||
dialogVisible.value = true;
|
||||
// 重置选择状态
|
||||
selectedDevices.value = [];
|
||||
@@ -178,7 +178,7 @@ const open = async () => {
|
||||
}
|
||||
// 获取设备列表
|
||||
await getList();
|
||||
};
|
||||
}
|
||||
defineExpose({ open });
|
||||
|
||||
/** 处理行点击事件 */
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { computed, ref, watchEffect } from 'vue';
|
||||
|
||||
import { Alert, Button, message } from 'ant-design-vue';
|
||||
import { Alert, Button, message, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device';
|
||||
import { IotDeviceMessageMethodEnum } from '#/views/iot/utils/constants';
|
||||
@@ -22,41 +22,69 @@ const emit = defineEmits<{
|
||||
const loading = ref(false); // 加载中
|
||||
const pushLoading = ref(false); // 推送加载中
|
||||
const config = ref<any>({}); // 只存储 config 字段
|
||||
const hasJsonError = ref(false); // 是否有 JSON 格式错误
|
||||
const configString = ref(''); // 用于编辑器的字符串格式
|
||||
|
||||
/** 监听 props.device 的变化,只更新 config 字段 */
|
||||
watchEffect(() => {
|
||||
try {
|
||||
config.value = props.device.config ? JSON.parse(props.device.config) : {};
|
||||
// 将对象转换为格式化的 JSON 字符串
|
||||
configString.value = JSON.stringify(config.value, null, 2);
|
||||
} catch {
|
||||
config.value = {};
|
||||
configString.value = '{}';
|
||||
}
|
||||
});
|
||||
|
||||
const isEditing = ref(false); // 编辑状态
|
||||
|
||||
/** 格式化的配置用于只读展示 */
|
||||
const formattedConfig = computed(() => {
|
||||
try {
|
||||
if (typeof config.value === 'string') {
|
||||
return JSON.stringify(JSON.parse(config.value), null, 2);
|
||||
}
|
||||
return JSON.stringify(config.value, null, 2);
|
||||
} catch {
|
||||
return JSON.stringify(config.value, null, 2);
|
||||
}
|
||||
});
|
||||
|
||||
/** 判断配置是否有数据 */
|
||||
const hasConfigData = computed(() => {
|
||||
return config.value && Object.keys(config.value).length > 0;
|
||||
});
|
||||
|
||||
/** 启用编辑模式的函数 */
|
||||
function enableEdit() {
|
||||
isEditing.value = true;
|
||||
hasJsonError.value = false; // 重置错误状态
|
||||
// 重新同步编辑器内容
|
||||
configString.value = JSON.stringify(config.value, null, 2);
|
||||
}
|
||||
|
||||
/** 取消编辑的函数 */
|
||||
function cancelEdit() {
|
||||
try {
|
||||
config.value = props.device.config ? JSON.parse(props.device.config) : {};
|
||||
configString.value = JSON.stringify(config.value, null, 2);
|
||||
} catch {
|
||||
config.value = {};
|
||||
configString.value = '{}';
|
||||
}
|
||||
isEditing.value = false;
|
||||
hasJsonError.value = false; // 重置错误状态
|
||||
}
|
||||
|
||||
/** 保存配置的函数 */
|
||||
async function saveConfig() {
|
||||
if (hasJsonError.value) {
|
||||
// 验证 JSON 格式
|
||||
try {
|
||||
config.value = JSON.parse(configString.value);
|
||||
} catch (error) {
|
||||
console.error('JSON格式错误:', error);
|
||||
message.error({ content: 'JSON格式错误,请修正后再提交!' });
|
||||
return;
|
||||
}
|
||||
|
||||
await updateDeviceConfig();
|
||||
isEditing.value = false;
|
||||
}
|
||||
@@ -102,39 +130,26 @@ async function updateDeviceConfig() {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理 JSON 编辑器错误的函数 */
|
||||
function onError(errors: any) {
|
||||
if (!errors || (Array.isArray(errors) && errors.length === 0)) {
|
||||
hasJsonError.value = false;
|
||||
return;
|
||||
}
|
||||
hasJsonError.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 只在没有配置数据时显示提示 -->
|
||||
<Alert
|
||||
v-if="!hasConfigData"
|
||||
message="支持远程更新设备的配置文件(JSON 格式),可以在下方编辑配置模板,对设备的系统参数、网络参数等进行远程配置。配置完成后,需点击「下发」按钮,设备即可进行远程配置。"
|
||||
type="info"
|
||||
show-icon
|
||||
class="my-4"
|
||||
description="如需编辑文件,请点击下方编辑按钮"
|
||||
/>
|
||||
<JsonEditor
|
||||
v-model="config"
|
||||
:mode="isEditing ? 'code' : 'view'"
|
||||
height="600px"
|
||||
@error="onError"
|
||||
/>
|
||||
<div class="mt-5 text-center">
|
||||
<Button v-if="isEditing" @click="cancelEdit">取消</Button>
|
||||
<Button
|
||||
v-if="isEditing"
|
||||
type="primary"
|
||||
@click="saveConfig"
|
||||
:disabled="hasJsonError"
|
||||
:loading="loading"
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
@@ -148,5 +163,45 @@ function onError(errors: any) {
|
||||
配置推送
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 代码视图 - 只读展示 -->
|
||||
<div v-if="!isEditing" class="json-viewer-container">
|
||||
<pre class="json-code"><code>{{ formattedConfig }}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- 编辑器视图 - 可编辑 -->
|
||||
<Textarea
|
||||
v-else
|
||||
v-model:value="configString"
|
||||
:rows="20"
|
||||
placeholder="请输入 JSON 格式的配置信息"
|
||||
class="json-editor"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
max-height: 600px;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.json-code {
|
||||
margin: 0;
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -85,7 +85,7 @@ function handleAuthInfoDialogClose() {
|
||||
<span>设备信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<Descriptions :column="1" bordered>
|
||||
<Descriptions :column="1" bordered size="small">
|
||||
<Descriptions.Item label="产品名称">
|
||||
{{ product.name }}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- 设备消息列表 -->
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
computed,
|
||||
@@ -62,36 +61,42 @@ const columns = [
|
||||
title: '时间',
|
||||
dataIndex: 'ts',
|
||||
key: 'ts',
|
||||
align: 'center' as const,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '上行/下行',
|
||||
dataIndex: 'upstream',
|
||||
key: 'upstream',
|
||||
align: 'center' as const,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '是否回复',
|
||||
dataIndex: 'reply',
|
||||
key: 'reply',
|
||||
align: 'center' as const,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '请求编号',
|
||||
dataIndex: 'requestId',
|
||||
key: 'requestId',
|
||||
align: 'center' as const,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '请求方法',
|
||||
dataIndex: 'method',
|
||||
key: 'method',
|
||||
align: 'center' as const,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '请求/响应数据',
|
||||
dataIndex: 'params',
|
||||
key: 'params',
|
||||
align: 'center' as const,
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
@@ -220,7 +225,6 @@ defineExpose({
|
||||
:data-source="list"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
align="center"
|
||||
class="whitespace-nowrap"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
|
||||
@@ -8,19 +8,21 @@ import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { ContentWrap } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Input,
|
||||
message,
|
||||
Row,
|
||||
Table,
|
||||
Tabs,
|
||||
Textarea,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device';
|
||||
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
|
||||
import {
|
||||
IotDeviceMessageMethodEnum,
|
||||
IoTThingModelTypeEnum,
|
||||
@@ -41,6 +43,10 @@ const downstreamTab = ref(IotDeviceMessageMethodEnum.PROPERTY_SET.method); //
|
||||
const deviceMessageRef = ref(); // 设备消息组件引用
|
||||
const deviceMessageRefreshDelay = 2000; // 延迟 N 秒,保证模拟上行的消息被处理
|
||||
|
||||
// 折叠状态
|
||||
const debugCollapsed = ref(false); // 指令调试区域折叠状态
|
||||
const messageCollapsed = ref(false); // 设备消息区域折叠状态
|
||||
|
||||
// 表单数据:存储用户输入的模拟值
|
||||
const formData = ref<Record<string, string>>({});
|
||||
|
||||
@@ -90,12 +96,12 @@ const propertyColumns: TableColumnType[] = [
|
||||
{
|
||||
title: '数据定义',
|
||||
key: 'dataDefinition',
|
||||
minWidth: 200,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
key: 'value',
|
||||
width: 150,
|
||||
width: 300,
|
||||
fixed: 'right' as any,
|
||||
},
|
||||
];
|
||||
@@ -332,232 +338,266 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Row :gutter="20">
|
||||
<!-- 左侧指令调试区域 -->
|
||||
<Col :span="12">
|
||||
<Card>
|
||||
<Tabs v-model:active-key="activeTab">
|
||||
<!-- 上行指令调试 -->
|
||||
<Tabs.Pane key="upstream" tab="上行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'upstream'"
|
||||
v-model:active-key="upstreamTab"
|
||||
<!-- 上方:指令调试区域 -->
|
||||
<Card class="simulator-tabs mb-4">
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>指令调试</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="debugCollapsed = !debugCollapsed"
|
||||
>
|
||||
<IconifyIcon icon="lucide:chevron-up" v-if="!debugCollapsed" />
|
||||
<IconifyIcon icon="lucide:chevron-down" v-if="debugCollapsed" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!debugCollapsed">
|
||||
<Tabs v-model:active-key="activeTab" size="small">
|
||||
<!-- 上行指令调试 -->
|
||||
<Tabs.TabPane key="upstream" tab="上行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'upstream'"
|
||||
v-model:active-key="upstreamTab"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
|
||||
tab="属性上报"
|
||||
>
|
||||
<!-- 属性上报 -->
|
||||
<Tabs.Pane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
|
||||
tab="属性上报"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性上报」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertyPost">
|
||||
发送属性上报
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
|
||||
<!-- 事件上报 -->
|
||||
<Tabs.Pane
|
||||
:key="IotDeviceMessageMethodEnum.EVENT_POST.method"
|
||||
tab="事件上报"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="eventList"
|
||||
align="center"
|
||||
:columns="eventColumns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.event?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入事件参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEventPost(record)"
|
||||
>
|
||||
上报事件
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性上报」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertyPost">
|
||||
发送属性上报
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 状态变更 -->
|
||||
<Tabs.Pane
|
||||
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
|
||||
tab="状态变更"
|
||||
>
|
||||
<ContentWrap>
|
||||
<div class="flex gap-4">
|
||||
<Button
|
||||
type="primary"
|
||||
@click="handleDeviceState(DeviceStateEnum.ONLINE)"
|
||||
>
|
||||
设备上线
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
@click="handleDeviceState(DeviceStateEnum.OFFLINE)"
|
||||
>
|
||||
设备下线
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs>
|
||||
</Tabs.Pane>
|
||||
|
||||
<!-- 下行指令调试 -->
|
||||
<Tabs.Pane key="downstream" tab="下行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'downstream'"
|
||||
v-model:active-key="downstreamTab"
|
||||
<!-- 事件上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.EVENT_POST.method"
|
||||
tab="事件上报"
|
||||
>
|
||||
<!-- 属性调试 -->
|
||||
<Tabs.Pane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
|
||||
tab="属性设置"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="eventList"
|
||||
align="center"
|
||||
:columns="eventColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.event?.dataType ?? '-' }}
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性设置」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertySet">
|
||||
发送属性设置
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
|
||||
<!-- 服务调用 -->
|
||||
<Tabs.Pane
|
||||
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
|
||||
tab="设备服务调用"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="serviceList"
|
||||
align="center"
|
||||
:columns="serviceColumns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入服务参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleServiceInvoke(record)"
|
||||
>
|
||||
服务调用
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs>
|
||||
</Tabs.Pane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Col>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入事件参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEventPost(record)"
|
||||
>
|
||||
上报事件
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 右侧设备日志区域 -->
|
||||
<Col :span="12">
|
||||
<ContentWrap title="设备消息">
|
||||
<DeviceDetailsMessage
|
||||
v-if="device.id"
|
||||
ref="deviceMessageRef"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</Col>
|
||||
</Row>
|
||||
<!-- 状态变更 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
|
||||
tab="状态变更"
|
||||
>
|
||||
<ContentWrap>
|
||||
<div class="flex gap-4">
|
||||
<Button
|
||||
type="primary"
|
||||
@click="handleDeviceState(DeviceStateEnum.ONLINE)"
|
||||
>
|
||||
设备上线
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
@click="handleDeviceState(DeviceStateEnum.OFFLINE)"
|
||||
>
|
||||
设备下线
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 下行指令调试 -->
|
||||
<Tabs.TabPane key="downstream" tab="下行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'downstream'"
|
||||
v-model:active-key="downstreamTab"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性调试 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
|
||||
tab="属性设置"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性设置」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertySet">
|
||||
发送属性设置
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 服务调用 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
|
||||
tab="设备服务调用"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="serviceList"
|
||||
align="center"
|
||||
:columns="serviceColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入服务参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleServiceInvoke(record)"
|
||||
>
|
||||
服务调用
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 下方:设备消息区域 -->
|
||||
<Card>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>设备消息</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="messageCollapsed = !messageCollapsed"
|
||||
>
|
||||
<IconifyIcon icon="lucide:chevron-down" v-if="!messageCollapsed" />
|
||||
<IconifyIcon icon="lucide:chevron-down" v-if="messageCollapsed" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!messageCollapsed">
|
||||
<DeviceDetailsMessage
|
||||
v-if="device.id"
|
||||
ref="deviceMessageRef"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Card, Empty } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
deviceId: number;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const loading = ref(false);
|
||||
const subDevices = ref<any[]>([]);
|
||||
|
||||
/** 获取子设备列表 */
|
||||
async function getSubDeviceList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
// TODO: 实现获取子设备列表的API调用
|
||||
// const data = await getSubDevicesByGatewayId(deviceId);
|
||||
// subDevices.value = data || [];
|
||||
subDevices.value = [];
|
||||
} catch (error) {
|
||||
console.error('获取子设备列表失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSubDeviceList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card :loading="loading" title="子设备管理">
|
||||
<Empty
|
||||
description="暂无子设备数据,此功能待实现"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
/>
|
||||
<!-- TODO: 实现子设备列表展示和管理功能 -->
|
||||
</Card>
|
||||
</template>
|
||||
@@ -22,21 +22,26 @@ const activeTab = ref('property'); // 默认选中设备属性
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Tabs v-model:active-key="activeTab" class="!h-auto !p-0">
|
||||
<Tabs.Pane key="property" tab="设备属性(运行状态)">
|
||||
<DeviceDetailsThingModelProperty :device-id="deviceId" />
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="event" tab="设备事件上报">
|
||||
<Tabs.TabPane key="property" tab="设备属性(运行状态)">
|
||||
<DeviceDetailsThingModelProperty
|
||||
v-if="activeTab === 'property'"
|
||||
:device-id="deviceId"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="event" tab="设备事件上报">
|
||||
<DeviceDetailsThingModelEvent
|
||||
v-if="activeTab === 'event'"
|
||||
:device-id="props.deviceId"
|
||||
:thing-model-list="props.thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="service" tab="设备服务调用">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="service" tab="设备服务调用">
|
||||
<DeviceDetailsThingModelService
|
||||
v-if="activeTab === 'service'"
|
||||
:device-id="deviceId"
|
||||
:thing-model-list="props.thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
@@ -148,6 +148,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.times"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!-- 设备物模型 -> 运行状态 -> 查看数据(设备的属性值历史)-->
|
||||
// 重新关闭打开图表,图表不显示可能图例注销失败等大佬修复
|
||||
<script setup lang="ts">
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
@@ -10,7 +11,7 @@ import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import { beginOfDay, endOfDay, formatDate } from '@vben/utils';
|
||||
import { beginOfDay, endOfDay, formatDate, formatDateTime } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
RangePicker,
|
||||
Space,
|
||||
Spin,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -49,8 +51,8 @@ const queryParams = reactive({
|
||||
deviceId: -1,
|
||||
identifier: '',
|
||||
times: [
|
||||
formatDate(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
|
||||
formatDate(endOfDay(new Date())),
|
||||
formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
|
||||
formatDateTime(endOfDay(new Date())),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -100,7 +102,7 @@ const tableColumns = computed(() => [
|
||||
title: '序号',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
align: 'center' as const,
|
||||
customRender: ({ index }: { index: number }) => index + 1,
|
||||
},
|
||||
{
|
||||
@@ -108,20 +110,20 @@ const tableColumns = computed(() => [
|
||||
key: 'updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
align: 'center' as const,
|
||||
},
|
||||
{
|
||||
title: '属性值',
|
||||
key: 'value',
|
||||
dataIndex: 'value',
|
||||
align: 'center',
|
||||
align: 'center' as const,
|
||||
},
|
||||
]);
|
||||
|
||||
// 分页配置
|
||||
const paginationConfig = computed(() => ({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
total: total.value,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
@@ -134,7 +136,10 @@ async function getList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getHistoryDevicePropertyList(queryParams);
|
||||
list.value = (data?.list as IotDeviceApi.DevicePropertyDetail[]) || [];
|
||||
// 后端直接返回数组,不是 { list: [] } 格式
|
||||
list.value = (
|
||||
Array.isArray(data) ? data : data?.list || []
|
||||
) as IotDeviceApi.DevicePropertyDetail[];
|
||||
total.value = list.value.length;
|
||||
|
||||
// 如果是图表模式且不是复杂数据类型,渲染图表
|
||||
@@ -143,7 +148,9 @@ async function getList() {
|
||||
!isComplexDataType.value &&
|
||||
list.value.length > 0
|
||||
) {
|
||||
// 等待 DOM 更新完成后再渲染图表
|
||||
await nextTick();
|
||||
await nextTick(); // 双重 nextTick 确保 DOM 完全准备好
|
||||
renderChart();
|
||||
}
|
||||
} catch {
|
||||
@@ -157,34 +164,43 @@ async function getList() {
|
||||
|
||||
/** 渲染图表 */
|
||||
function renderChart() {
|
||||
if (!list.value || list.value.length === 0) return;
|
||||
if (!list.value || list.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chartData = list.value.map((item) => [item.updateTime, item.value]);
|
||||
|
||||
renderEcharts({
|
||||
title: {
|
||||
text: '属性值趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
// 使用 setTimeout 延迟渲染,避免 ECharts 主进程冲突
|
||||
setTimeout(() => {
|
||||
// 检查 chartRef 是否存在且已挂载
|
||||
if (!chartRef.value || !chartRef.value.$el) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderEcharts({
|
||||
title: {
|
||||
text: '属性值趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: 60,
|
||||
right: 60,
|
||||
bottom: 100,
|
||||
top: 80,
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
grid: {
|
||||
left: 60,
|
||||
right: 60,
|
||||
bottom: 100,
|
||||
top: 80,
|
||||
containLabel: true,
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const param = params[0];
|
||||
return `
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const param = params[0];
|
||||
return `
|
||||
<div style="padding: 8px;">
|
||||
<div style="margin-bottom: 4px; font-weight: bold;">
|
||||
${formatDate(new Date(param.value[0]), 'YYYY-MM-DD HH:mm:ss')}
|
||||
@@ -195,76 +211,77 @@ function renderChart() {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
name: '时间',
|
||||
nameTextStyle: {
|
||||
padding: [10, 0, 0, 0],
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
return String(formatDate(new Date(value), 'MM-DD HH:mm') || '');
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '属性值',
|
||||
nameTextStyle: {
|
||||
padding: [0, 0, 10, 0],
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '属性值',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#1890FF',
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
name: '时间',
|
||||
nameTextStyle: {
|
||||
padding: [10, 0, 0, 0],
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890FF',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(24, 144, 255, 0.3)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(24, 144, 255, 0.05)',
|
||||
},
|
||||
],
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
return String(formatDate(new Date(value), 'MM-DD HH:mm') || '');
|
||||
},
|
||||
},
|
||||
data: chartData,
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '属性值',
|
||||
nameTextStyle: {
|
||||
padding: [0, 0, 10, 0],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
height: 30,
|
||||
bottom: 20,
|
||||
},
|
||||
],
|
||||
});
|
||||
series: [
|
||||
{
|
||||
name: '属性值',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#1890FF',
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890FF',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(24, 144, 255, 0.3)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(24, 144, 255, 0.05)',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
data: chartData,
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
height: 30,
|
||||
bottom: 20,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, 300); // 延迟300ms渲染,确保 DOM 完全准备好
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
@@ -275,12 +292,31 @@ async function open(deviceId: number, identifier: string, dataType: string) {
|
||||
propertyIdentifier.value = identifier;
|
||||
thingModelDataType.value = dataType;
|
||||
|
||||
// 重置时间范围为最近7天
|
||||
dateRange.value = [
|
||||
dayjs().subtract(7, 'day').startOf('day'),
|
||||
dayjs().endOf('day'),
|
||||
];
|
||||
|
||||
// 更新查询参数的时间
|
||||
queryParams.times = [
|
||||
formatDateTime(dateRange.value[0].toDate()),
|
||||
formatDateTime(dateRange.value[1].toDate()),
|
||||
];
|
||||
|
||||
// 如果物模型是 struct、array,需要默认使用 list 模式
|
||||
viewMode.value = isComplexDataType.value ? 'list' : 'chart';
|
||||
|
||||
// 等待弹窗完全渲染后再获取数据
|
||||
await nextTick();
|
||||
await getList();
|
||||
|
||||
// 如果是图表模式,延迟渲染图表
|
||||
if (viewMode.value === 'chart' && !isComplexDataType.value) {
|
||||
setTimeout(() => {
|
||||
renderChart();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/** 时间变化处理 */
|
||||
@@ -290,8 +326,8 @@ function handleTimeChange() {
|
||||
}
|
||||
|
||||
queryParams.times = [
|
||||
formatDate(dateRange.value[0].toDate()),
|
||||
formatDate(dateRange.value[1].toDate()),
|
||||
formatDateTime(dateRange.value[0].toDate()),
|
||||
formatDateTime(dateRange.value[1].toDate()),
|
||||
];
|
||||
|
||||
getList();
|
||||
@@ -372,8 +408,14 @@ watch(viewMode, async (newMode) => {
|
||||
!isComplexDataType.value &&
|
||||
list.value.length > 0
|
||||
) {
|
||||
// 等待 DOM 显示完成
|
||||
await nextTick();
|
||||
renderChart();
|
||||
await nextTick();
|
||||
|
||||
// 延迟渲染图表
|
||||
setTimeout(() => {
|
||||
renderChart();
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -398,7 +440,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
class="!w-[400px]"
|
||||
@press-enter="handleTimeChange"
|
||||
@change="handleTimeChange"
|
||||
/>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
@@ -460,18 +502,20 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
<!-- 数据展示区域 -->
|
||||
<Spin :spinning="loading" :delay="200">
|
||||
<!-- 图表模式 -->
|
||||
<div v-if="viewMode === 'chart'" class="chart-container">
|
||||
<div v-show="viewMode === 'chart'" class="chart-container">
|
||||
<Empty
|
||||
v-if="list.length === 0"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
description="暂无数据"
|
||||
class="py-20"
|
||||
/>
|
||||
<EchartsUI v-else ref="chartRef" height="500px" />
|
||||
<div v-else>
|
||||
<EchartsUI ref="chartRef" height="500px" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格模式 -->
|
||||
<div v-else class="table-container">
|
||||
<div v-show="viewMode === 'list'" class="table-container">
|
||||
<Table
|
||||
:data-source="list"
|
||||
:columns="tableColumns"
|
||||
|
||||
@@ -52,7 +52,7 @@ const serviceThingModels = computed(() => {
|
||||
});
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
async function getList() {
|
||||
if (!props.deviceId) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -62,43 +62,43 @@ const getList = async () => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
function handleQuery() {
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
function resetQuery() {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.identifier = '';
|
||||
queryParams.times = [];
|
||||
handleQuery();
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取服务名称 */
|
||||
const getServiceName = (identifier: string | undefined) => {
|
||||
function getServiceName(identifier: string | undefined) {
|
||||
if (!identifier) return '-';
|
||||
const service = serviceThingModels.value.find(
|
||||
(item: ThingModelData) => item.identifier === identifier,
|
||||
);
|
||||
return service?.name || identifier;
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取调用方式 */
|
||||
const getCallType = (identifier: string | undefined) => {
|
||||
function getCallType(identifier: string | undefined) {
|
||||
if (!identifier) return '-';
|
||||
const service = serviceThingModels.value.find(
|
||||
(item: ThingModelData) => item.identifier === identifier,
|
||||
);
|
||||
if (!service?.service?.callType) return '-';
|
||||
return getThingModelServiceCallTypeLabel(service.service.callType) || '-';
|
||||
};
|
||||
}
|
||||
|
||||
/** 解析参数 */
|
||||
const parseParams = (params: string) => {
|
||||
function parseParams(params: string) {
|
||||
if (!params) return '-';
|
||||
try {
|
||||
const parsed = JSON.parse(params);
|
||||
@@ -109,7 +109,7 @@ const parseParams = (params: string) => {
|
||||
} catch {
|
||||
return params;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
@@ -148,6 +148,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.times"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { onMounted, ref, unref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
|
||||
import { message, Tabs } from 'ant-design-vue';
|
||||
|
||||
@@ -20,33 +19,42 @@ import DeviceDetailsHeader from './DeviceDetailsHeader.vue';
|
||||
import DeviceDetailsInfo from './DeviceDetailsInfo.vue';
|
||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
|
||||
import DeviceDetailsSimulator from './DeviceDetailsSimulator.vue';
|
||||
import DeviceDetailsSubDevice from './DeviceDetailsSubDevice.vue';
|
||||
import DeviceDetailsThingModel from './DeviceDetailsThingModel.vue';
|
||||
|
||||
defineOptions({ name: 'IoTDeviceDetail' });
|
||||
|
||||
const route = useRoute();
|
||||
const id = Number(route.params.id); // 将字符串转换为数字
|
||||
const loading = ref(true); // 加载中
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product); // 产品详情
|
||||
const device = ref<IotDeviceApi.Device>({} as IotDeviceApi.Device); // 设备详情
|
||||
const activeTab = ref('info'); // 默认激活的标签页
|
||||
const thingModelList = ref<ThingModelData[]>([]); // 物模型列表数据
|
||||
const router = useRouter();
|
||||
|
||||
const id = Number(route.params.id);
|
||||
const loading = ref(true);
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||
const device = ref<IotDeviceApi.Device>({} as IotDeviceApi.Device);
|
||||
const activeTab = ref('info');
|
||||
const thingModelList = ref<ThingModelData[]>([]);
|
||||
|
||||
/** 获取设备详情 */
|
||||
async function getDeviceData() {
|
||||
async function getDeviceData(deviceId: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
device.value = await getDevice(id);
|
||||
device.value = await getDevice(deviceId);
|
||||
await getProductData(device.value.productId);
|
||||
await getThingModelList(device.value.productId);
|
||||
} catch {
|
||||
message.error('获取设备详情失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取产品详情 */
|
||||
async function getProductData(id: number) {
|
||||
product.value = await getProduct(id);
|
||||
async function getProductData(productId: number) {
|
||||
try {
|
||||
product.value = await getProduct(productId);
|
||||
} catch (error) {
|
||||
console.error('获取产品详情失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取物模型列表 */
|
||||
@@ -61,17 +69,20 @@ async function getThingModelList(productId: number) {
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const tabbarStore = useTabbarStore(); // 视图操作
|
||||
const router = useRouter(); // 路由
|
||||
const { currentRoute } = router;
|
||||
onMounted(async () => {
|
||||
if (!id) {
|
||||
message.warning({ content: '参数错误,产品不能为空!' });
|
||||
await tabbarStore.closeTab(unref(currentRoute), router);
|
||||
message.warning('参数错误,设备不能为空!');
|
||||
router.back();
|
||||
return;
|
||||
}
|
||||
await getDeviceData();
|
||||
activeTab.value = (route.query.tab as string) || 'info';
|
||||
|
||||
await getDeviceData(id);
|
||||
|
||||
// 处理 tab 参数
|
||||
const { tab } = route.query;
|
||||
if (tab) {
|
||||
activeTab.value = tab as string;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
@@ -80,50 +91,55 @@ onMounted(async () => {
|
||||
:loading="loading"
|
||||
:product="product"
|
||||
:device="device"
|
||||
@refresh="getDeviceData"
|
||||
@refresh="() => getDeviceData(id)"
|
||||
/>
|
||||
|
||||
<Tabs v-model:active-key="activeTab" class="device-detail-tabs mt-4">
|
||||
<Tabs.Pane key="info" tab="设备信息">
|
||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||
<Tabs.TabPane key="info" tab="设备信息">
|
||||
<DeviceDetailsInfo
|
||||
v-if="activeTab === 'info'"
|
||||
:product="product"
|
||||
:device="device"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="model" tab="物模型数据">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="model" tab="物模型数据">
|
||||
<DeviceDetailsThingModel
|
||||
v-if="activeTab === 'model' && device.id"
|
||||
:device-id="device.id"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
v-if="product.deviceType === DeviceTypeEnum.GATEWAY"
|
||||
key="sub-device"
|
||||
tab="子设备管理"
|
||||
/>
|
||||
<Tabs.Pane key="log" tab="设备消息">
|
||||
>
|
||||
<DeviceDetailsSubDevice
|
||||
v-if="activeTab === 'sub-device' && device.id"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="log" tab="设备消息">
|
||||
<DeviceDetailsMessage
|
||||
v-if="activeTab === 'log' && device.id"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="simulator" tab="模拟设备">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="simulator" tab="模拟设备">
|
||||
<DeviceDetailsSimulator
|
||||
v-if="activeTab === 'simulator'"
|
||||
:product="product"
|
||||
:device="device"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="config" tab="设备配置">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="config" tab="设备配置">
|
||||
<DeviceDetailConfig
|
||||
v-if="activeTab === 'config'"
|
||||
:device="device"
|
||||
@success="getDeviceData"
|
||||
@success="() => getDeviceData(id)"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -34,11 +34,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '父级分组',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择父级分组',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '所属产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -85,7 +85,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '所属产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -86,7 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
|
||||
@@ -34,11 +34,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '父级分类',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductCategoryList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleProductCategoryList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择父级分类',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -93,7 +93,7 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
||||
label: '产品分类',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductCategoryList,
|
||||
api: () => getSimpleProductCategoryList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品分类',
|
||||
@@ -246,7 +246,7 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
||||
label: '产品分类',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductCategoryList,
|
||||
api: () => getSimpleProductCategoryList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品分类',
|
||||
|
||||
@@ -11,16 +11,15 @@ import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
import { Button, Card, Image, Input, message, Space } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
import {
|
||||
deleteProduct,
|
||||
exportProduct,
|
||||
getProductPage,
|
||||
} from '#/api/iot/product/product';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useImagePreview } from './data';
|
||||
// @ts-ignore
|
||||
import ProductCardView from './modules/ProductCardView.vue';
|
||||
import ProductForm from './modules/ProductForm.vue';
|
||||
|
||||
|
||||
@@ -237,12 +237,10 @@ defineExpose({
|
||||
.product-card {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #d9d9d9;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
@@ -273,7 +271,6 @@ defineExpose({
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
color: #1f2937;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -292,14 +289,13 @@ defineExpose({
|
||||
.info-label {
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
color: #6b7280;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
white-space: nowrap;
|
||||
|
||||
&.text-primary {
|
||||
@@ -315,9 +311,9 @@ defineExpose({
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
color: #374151;
|
||||
white-space: nowrap;
|
||||
cursor: help;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.info-tag {
|
||||
@@ -337,6 +333,7 @@ defineExpose({
|
||||
color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
|
||||
border-radius: 8px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
// 按钮组
|
||||
@@ -345,7 +342,7 @@ defineExpose({
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
margin-top: auto;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-top: 1px solid var(--ant-color-split);
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
@@ -392,4 +389,38 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 夜间模式适配
|
||||
html.dark {
|
||||
.product-card-view {
|
||||
.product-card {
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 30%);
|
||||
}
|
||||
|
||||
.product-title {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
.info-list {
|
||||
.info-label {
|
||||
color: rgb(255 255 255 / 65%);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
.product-key {
|
||||
color: rgb(255 255 255 / 75%);
|
||||
}
|
||||
}
|
||||
|
||||
.product-3d-icon {
|
||||
color: #8b9cff;
|
||||
background: linear-gradient(135deg, #667eea25 0%, #764ba225 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,13 +14,18 @@ import {
|
||||
} from '#/api/iot/product/product';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { generateProductKey, useBasicFormSchema, useAdvancedFormSchema } from '../data';
|
||||
import {
|
||||
generateProductKey,
|
||||
useAdvancedFormSchema,
|
||||
useBasicFormSchema,
|
||||
} from '../data';
|
||||
|
||||
defineOptions({ name: 'IoTProductForm' });
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const CollapsePanel = Collapse.Panel;
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<any>();
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id ? '编辑产品' : '新增产品';
|
||||
@@ -65,12 +70,12 @@ const [Modal, modalApi] = useVbenModal({
|
||||
if (!basicValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
modalApi.lock();
|
||||
try {
|
||||
// 提交表单 - 合并两个表单的值
|
||||
const basicValues = await formApi.getValues();
|
||||
|
||||
|
||||
// 如果折叠面板展开,则获取高级表单的值,否则保留原有值(编辑时)或使用空值(新增时)
|
||||
let advancedValues: any = {};
|
||||
if (activeKey.value.includes('advanced')) {
|
||||
@@ -83,12 +88,15 @@ const [Modal, modalApi] = useVbenModal({
|
||||
description: formData.value.description,
|
||||
};
|
||||
}
|
||||
|
||||
const values = { ...basicValues, ...advancedValues } as IotProductApi.Product;
|
||||
|
||||
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();
|
||||
@@ -123,16 +131,20 @@ const [Modal, modalApi] = useVbenModal({
|
||||
formData.value = await getProduct(data.id);
|
||||
// 设置基础表单
|
||||
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) {
|
||||
if (
|
||||
formData.value.icon ||
|
||||
formData.value.picUrl ||
|
||||
formData.value.description
|
||||
) {
|
||||
activeKey.value = ['advanced'];
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -151,9 +163,6 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<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>
|
||||
|
||||
@@ -15,15 +15,15 @@ interface Props {
|
||||
defineProps<Props>();
|
||||
|
||||
/** 格式化日期 */
|
||||
const formatDate = (date?: Date | string) => {
|
||||
function formatDate(date?: Date | string) {
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleString('zh-CN');
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card title="产品信息">
|
||||
<Descriptions bordered :column="3">
|
||||
<Descriptions bordered :column="3" size="small">
|
||||
<Descriptions.Item label="产品名称">
|
||||
{{ product.name }}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
|
||||
@@ -210,7 +210,8 @@ function handleOperatorChange() {
|
||||
<!-- 设备状态条件配置 -->
|
||||
<div
|
||||
v-if="
|
||||
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
|
||||
condition.type ===
|
||||
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS.toString()
|
||||
"
|
||||
class="gap-16px flex flex-col"
|
||||
>
|
||||
@@ -222,7 +223,7 @@ function handleOperatorChange() {
|
||||
<Select
|
||||
:model-value="condition.operator"
|
||||
@update:model-value="
|
||||
(value) => updateConditionField('operator', value)
|
||||
(value: any) => updateConditionField('operator', value)
|
||||
"
|
||||
placeholder="请选择操作符"
|
||||
class="w-full"
|
||||
|
||||
@@ -120,7 +120,7 @@ const timeValue2 = computed(() => {
|
||||
* @param value 字段值
|
||||
*/
|
||||
function updateConditionField(field: any, value: any) {
|
||||
condition.value[field] = value;
|
||||
(condition.value as any)[field] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,7 @@ const subGroup = useVModel(props, 'modelValue', emit);
|
||||
const maxConditions = computed(() => props.maxConditions || 3); // 最大条件数量
|
||||
|
||||
/** 添加条件 */
|
||||
const addCondition = async () => {
|
||||
async function addCondition() {
|
||||
// 确保 subGroup.value 是一个数组
|
||||
if (!subGroup.value) {
|
||||
subGroup.value = [];
|
||||
@@ -58,7 +58,7 @@ const addCondition = async () => {
|
||||
if (subGroup.value) {
|
||||
subGroup.value.push(newCondition);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除条件
|
||||
|
||||
@@ -27,7 +27,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
field: 'type',
|
||||
title: '功能类型',
|
||||
minWidth: 100,
|
||||
minWidth: 20,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.IOT_THING_MODEL_TYPE },
|
||||
@@ -41,17 +41,17 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
field: 'identifier',
|
||||
title: '标识符',
|
||||
minWidth: 150,
|
||||
minWidth: 20,
|
||||
},
|
||||
{
|
||||
field: 'dataType',
|
||||
title: '数据类型',
|
||||
minWidth: 120,
|
||||
minWidth: 50,
|
||||
slots: { default: 'dataType' },
|
||||
},
|
||||
{
|
||||
field: 'dataDefinition',
|
||||
title: '数据定义',
|
||||
field: 'property',
|
||||
title: '属性',
|
||||
minWidth: 200,
|
||||
slots: { default: 'dataDefinition' },
|
||||
},
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { onMounted, provide, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProduct } from '#/api/iot/product/product';
|
||||
import { deleteThingModel, getThingModelPage } from '#/api/iot/thingmodel';
|
||||
|
||||
import { getDataTypeOptionsLabel } from '../utils/constants';
|
||||
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '../utils/constants';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import { DataDefinition } from './modules/components';
|
||||
import ThingModelForm from './modules/ThingModelForm.vue';
|
||||
import ThingModelTSL from './modules/ThingModelTSL.vue';
|
||||
|
||||
defineOptions({ name: 'IoTThingModel' });
|
||||
|
||||
@@ -19,6 +23,16 @@ const props = defineProps<{
|
||||
productId: number;
|
||||
}>();
|
||||
|
||||
// 产品信息
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||
|
||||
// 提供产品信息给子组件
|
||||
provide(IOT_PROVIDE_KEY.PRODUCT, product);
|
||||
|
||||
// 组件引用
|
||||
const thingModelFormRef = ref();
|
||||
const thingModelTSLRef = ref();
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
@@ -26,12 +40,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page, form }) => {
|
||||
query: async ({ page }: any, formValues: any) => {
|
||||
return await getThingModelPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
productId: props.productId,
|
||||
...form,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -44,26 +58,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ThingModelApi.ThingModel>,
|
||||
},
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
});
|
||||
|
||||
// 新增功能
|
||||
const handleCreate = () => {
|
||||
// TODO: 打开物模型表单
|
||||
console.error('新增功能');
|
||||
};
|
||||
function handleCreate() {
|
||||
thingModelFormRef.value?.open('create');
|
||||
}
|
||||
|
||||
// 编辑功能
|
||||
const handleEdit = (row: any) => {
|
||||
// TODO: 打开物模型表单
|
||||
console.error('编辑功能:', row);
|
||||
};
|
||||
function handleEdit(row: any) {
|
||||
thingModelFormRef.value?.open('update', row.id);
|
||||
}
|
||||
|
||||
// 删除功能
|
||||
const handleDelete = async (row: any) => {
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await deleteThingModel(row.id);
|
||||
message.success('删除成功');
|
||||
@@ -71,32 +83,62 @@ const handleDelete = async (row: any) => {
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 打开 TSL
|
||||
const handleOpenTSL = () => {
|
||||
// TODO: 打开 TSL 弹窗
|
||||
console.error('打开 TSL');
|
||||
};
|
||||
function handleOpenTSL() {
|
||||
thingModelTSLRef.value?.open();
|
||||
}
|
||||
|
||||
// 获取数据类型标签
|
||||
const getDataTypeLabel = (row: any) => {
|
||||
function getDataTypeLabel(row: any) {
|
||||
return getDataTypeOptionsLabel(row.property?.dataType) || '-';
|
||||
};
|
||||
}
|
||||
|
||||
// 刷新表格
|
||||
function handleRefresh() {
|
||||
gridApi.reload();
|
||||
}
|
||||
|
||||
// 获取产品信息
|
||||
async function getProductData() {
|
||||
try {
|
||||
product.value = await getProduct(props.productId);
|
||||
} catch (error) {
|
||||
console.error('获取产品信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await getProductData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page
|
||||
auto-content-height
|
||||
description="管理产品的物模型定义,包括属性、服务和事件"
|
||||
title="物模型管理"
|
||||
>
|
||||
<Grid>
|
||||
<template #toolbar-tools>
|
||||
<Button @click="handleCreate">
|
||||
<IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
|
||||
添加功能
|
||||
</Button>
|
||||
<Button type="primary" @click="handleOpenTSL"> TSL </Button>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '添加功能',
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: 'TSL',
|
||||
type: 'default',
|
||||
color: 'success',
|
||||
onClick: handleOpenTSL,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 数据类型列 -->
|
||||
@@ -106,17 +148,38 @@ const getDataTypeLabel = (row: any) => {
|
||||
|
||||
<!-- 数据定义列 -->
|
||||
<template #dataDefinition="{ row }">
|
||||
<!-- TODO: 实现数据定义组件 -->
|
||||
<span class="text-gray-400">{{ row }}</span>
|
||||
<DataDefinition :data="row" />
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #actions="{ row }">
|
||||
<Button size="small" type="primary" @click="handleEdit(row)">
|
||||
编辑
|
||||
</Button>
|
||||
<Button size="small" danger @click="handleDelete(row)"> 删除 </Button>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '编辑',
|
||||
type: 'link',
|
||||
icon: ACTION_ICON.EDIT,
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: ACTION_ICON.DELETE,
|
||||
popConfirm: {
|
||||
title: '确认删除该功能吗?',
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
|
||||
<!-- 物模型表单 -->
|
||||
<ThingModelForm ref="thingModelFormRef" @success="handleRefresh" />
|
||||
|
||||
<!-- TSL 弹窗 -->
|
||||
<ThingModelTSL ref="thingModelTSLRef" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { ThingModelEvent } from '#/api/iot/thingmodel';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
@@ -23,11 +21,7 @@ defineOptions({ name: 'ThingModelEvent' });
|
||||
|
||||
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const thingModelEvent = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<ThingModelEvent>;
|
||||
const thingModelEvent = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
// 默认选中,INFO 信息
|
||||
watch(
|
||||
@@ -43,9 +37,9 @@ watch(
|
||||
<Form.Item
|
||||
:rules="[{ required: true, message: '请选择事件类型', trigger: 'change' }]"
|
||||
label="事件类型"
|
||||
prop="event.type"
|
||||
name="event.type"
|
||||
>
|
||||
<Radio.Group v-model="thingModelEvent.type">
|
||||
<Radio.Group v-model:value="thingModelEvent.type">
|
||||
<Radio
|
||||
v-for="eventType in Object.values(IoTThingModelEventTypeEnum)"
|
||||
:key="eventType.value"
|
||||
@@ -57,7 +51,7 @@ watch(
|
||||
</Form.Item>
|
||||
<Form.Item label="输出参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="thingModelEvent.outputData"
|
||||
v-model="thingModelEvent.outputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getDictOptions } from '@vben/hooks';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Button, Form, Input, message, Radio } from 'ant-design-vue';
|
||||
import { Form, Input, message, Modal, Radio } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
createThingModel,
|
||||
@@ -40,8 +40,8 @@ const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref(''); // 弹窗的标题
|
||||
const formLoading = ref(false); // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref(''); // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<ThingModelData>({
|
||||
type: IoTThingModelTypeEnum.PROPERTY.toString(),
|
||||
const formData = ref<any>({
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
@@ -49,22 +49,34 @@ const formData = ref<ThingModelData>({
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {},
|
||||
event: {},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
});
|
||||
|
||||
const formRef = ref(); // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
async function open(type: string, id?: number) {
|
||||
dialogVisible.value = true;
|
||||
dialogTitle.value = $t(`action.${type}`);
|
||||
// 设置标题:create -> 新增,update -> 编辑
|
||||
dialogTitle.value =
|
||||
type === 'create' ? $t('page.action.add') : $t('page.action.edit');
|
||||
formType.value = type;
|
||||
resetForm();
|
||||
if (id) {
|
||||
formLoading.value = true;
|
||||
try {
|
||||
formData.value = await getThingModel(id);
|
||||
const result = await getThingModel(id);
|
||||
// 转换类型为数字
|
||||
formData.value = {
|
||||
...result,
|
||||
type: Number(result.type),
|
||||
};
|
||||
// 情况一:属性初始化
|
||||
if (
|
||||
!formData.value.property ||
|
||||
@@ -77,26 +89,56 @@ const open = async (type: string, id?: number) => {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// 确保 dataSpecs 和 dataSpecsList 存在
|
||||
if (!formData.value.property.dataSpecs) {
|
||||
formData.value.property.dataSpecs = {};
|
||||
}
|
||||
if (!formData.value.property.dataSpecsList) {
|
||||
formData.value.property.dataSpecsList = [];
|
||||
}
|
||||
// 如果 property.dataType 不存在,设置为默认值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
// 情况二:服务初始化
|
||||
if (
|
||||
!formData.value.service ||
|
||||
Object.keys(formData.value.service).length === 0
|
||||
) {
|
||||
formData.value.service = {};
|
||||
formData.value.service = {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
};
|
||||
} else {
|
||||
// 确保参数数组存在
|
||||
if (!formData.value.service.inputParams) {
|
||||
formData.value.service.inputParams = [];
|
||||
}
|
||||
if (!formData.value.service.outputParams) {
|
||||
formData.value.service.outputParams = [];
|
||||
}
|
||||
}
|
||||
// 情况三:事件初始化
|
||||
if (
|
||||
!formData.value.event ||
|
||||
Object.keys(formData.value.event).length === 0
|
||||
) {
|
||||
formData.value.event = {};
|
||||
formData.value.event = {
|
||||
outputParams: [],
|
||||
};
|
||||
} else {
|
||||
// 确保参数数组存在
|
||||
if (!formData.value.event.outputParams) {
|
||||
formData.value.event.outputParams = [];
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
defineExpose({ open, close: () => (dialogVisible.value = false) });
|
||||
|
||||
async function submitForm() {
|
||||
@@ -137,6 +179,13 @@ function fillExtraAttributes(data: any) {
|
||||
data.dataType = data.service.dataType;
|
||||
data.service.identifier = data.identifier;
|
||||
data.service.name = data.name;
|
||||
// 保留输入输出参数,但如果为空数组则删除
|
||||
if (!data.service.inputParams || data.service.inputParams.length === 0) {
|
||||
delete data.service.inputParams;
|
||||
}
|
||||
if (!data.service.outputParams || data.service.outputParams.length === 0) {
|
||||
delete data.service.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.event;
|
||||
}
|
||||
@@ -146,6 +195,10 @@ function fillExtraAttributes(data: any) {
|
||||
data.dataType = data.event.dataType;
|
||||
data.event.identifier = data.identifier;
|
||||
data.event.name = data.name;
|
||||
// 保留输出参数,但如果为空数组则删除
|
||||
if (!data.event.outputParams || data.event.outputParams.length === 0) {
|
||||
delete data.event.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.service;
|
||||
}
|
||||
@@ -164,7 +217,7 @@ function removeDataSpecs(val: any) {
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
formData.value = {
|
||||
type: IoTThingModelTypeEnum.PROPERTY.toString(),
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
@@ -172,18 +225,27 @@ function resetForm() {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {},
|
||||
event: {},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
};
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model="dialogVisible" :title="dialogTitle">
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="formRef"
|
||||
:loading="formLoading"
|
||||
:model="formData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
@@ -191,12 +253,9 @@ function resetForm() {
|
||||
<Form.Item label="功能类型" name="type">
|
||||
<Radio.Group v-model:value="formData.type">
|
||||
<Radio.Button
|
||||
v-for="(dict, index) in getDictOptions(
|
||||
DICT_TYPE.IOT_THING_MODEL_TYPE,
|
||||
'number',
|
||||
)"
|
||||
:key="index"
|
||||
:value="dict.value"
|
||||
v-for="dict in getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
|
||||
:key="String(dict.value)"
|
||||
:value="Number(dict.value)"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Radio.Button>
|
||||
@@ -210,17 +269,17 @@ function resetForm() {
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY.toString()"
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY"
|
||||
v-model="formData.property"
|
||||
/>
|
||||
<!-- 服务配置 -->
|
||||
<ThingModelService
|
||||
v-if="formData.type === IoTThingModelTypeEnum.SERVICE.toString()"
|
||||
v-if="formData.type === IoTThingModelTypeEnum.SERVICE"
|
||||
v-model="formData.service"
|
||||
/>
|
||||
<!-- 事件配置 -->
|
||||
<ThingModelEvent
|
||||
v-if="formData.type === IoTThingModelTypeEnum.EVENT.toString()"
|
||||
v-if="formData.type === IoTThingModelTypeEnum.EVENT"
|
||||
v-model="formData.event"
|
||||
/>
|
||||
<Form.Item label="描述" name="desc">
|
||||
@@ -232,12 +291,5 @@ function resetForm() {
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<template #footer>
|
||||
<Button :disabled="formLoading" type="primary" @click="submitForm">
|
||||
确 定
|
||||
</Button>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -29,6 +29,7 @@ const formData = ref<any>({
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,16 +41,22 @@ function openParamForm(val: any) {
|
||||
return;
|
||||
}
|
||||
// 编辑时回显数据
|
||||
const valData = val as any;
|
||||
formData.value = {
|
||||
identifier: val.identifier,
|
||||
name: val.name,
|
||||
description: val.description,
|
||||
identifier: valData?.identifier || '',
|
||||
name: valData?.name || '',
|
||||
description: valData?.description || '',
|
||||
property: {
|
||||
dataType: val.dataType,
|
||||
dataSpecs: val.dataSpecs,
|
||||
dataSpecsList: val.dataSpecsList,
|
||||
dataType: valData?.dataType || IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: valData?.dataSpecs ?? {},
|
||||
dataSpecsList: valData?.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
// 确保 property.dataType 有值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 param 项 */
|
||||
@@ -108,6 +115,7 @@ function resetForm() {
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
paramFormRef.value?.resetFields();
|
||||
@@ -122,35 +130,34 @@ function resetForm() {
|
||||
>
|
||||
<span>参数名称:{{ item.name }}</span>
|
||||
<div class="btn">
|
||||
<Button link type="primary" @click="openParamForm(item)"> 编辑 </Button>
|
||||
<Divider direction="vertical" />
|
||||
<Button link danger @click="deleteParamItem(index)"> 删除 </Button>
|
||||
<Button type="link" @click="openParamForm(item)">编辑</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" danger @click="deleteParamItem(index)">删除</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button link type="primary" @click="openParamForm(null)"> +新增参数 </Button>
|
||||
<Button type="link" @click="openParamForm(null)">+新增参数</Button>
|
||||
|
||||
<!-- param 表单 -->
|
||||
<Modal v-model="dialogVisible" title="新增参数" append-to-body>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
title="新增参数"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="paramFormRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
label-width="100px"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="参数名称" prop="name">
|
||||
<Input v-model="formData.name" placeholder="请输入功能名称" />
|
||||
<Form.Item label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标识符" prop="identifier">
|
||||
<Input v-model="formData.identifier" placeholder="请输入标识符" />
|
||||
<Form.Item label="标识符" name="identifier">
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-params />
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button :disabled="formLoading" type="primary" @click="submitForm">
|
||||
确 定
|
||||
</Button>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,6 @@ import { isEmpty } from '@vben/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Radio, Select } from 'ant-design-vue';
|
||||
|
||||
import { validateBoolName } from '#/api/iot/thingmodel';
|
||||
import {
|
||||
getDataTypeOptions,
|
||||
IoTDataSpecsDataTypeEnum,
|
||||
@@ -82,6 +81,7 @@ function handleChange(dataType: any) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// useVModel 会自动同步数据到父组件,不需要手动 emit
|
||||
}
|
||||
|
||||
/** 默认选中读写 */
|
||||
@@ -91,22 +91,18 @@ watch(
|
||||
if (props.isStructDataSpecs || props.isParams) {
|
||||
return;
|
||||
}
|
||||
isEmpty(val) &&
|
||||
(property.value.accessMode =
|
||||
IoTThingModelAccessModeEnum.READ_WRITE.value);
|
||||
if (isEmpty(val)) {
|
||||
property.value.accessMode = IoTThingModelAccessModeEnum.READ_WRITE.value;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
|
||||
label="数据类型"
|
||||
prop="property.dataType"
|
||||
>
|
||||
<Form.Item label="数据类型">
|
||||
<Select
|
||||
v-model="property.dataType"
|
||||
v-model:value="property.dataType"
|
||||
placeholder="请选择数据类型"
|
||||
@change="handleChange"
|
||||
>
|
||||
@@ -114,9 +110,10 @@ watch(
|
||||
<Select.Option
|
||||
v-for="option in getDataTypeOptions2"
|
||||
:key="option.value"
|
||||
:label="`${option.value}(${option.label})`"
|
||||
:value="option.value"
|
||||
/>
|
||||
>
|
||||
{{ `${option.value}(${option.label})` }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<!-- 数值型配置 -->
|
||||
@@ -140,24 +137,17 @@ watch(
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.BOOL"
|
||||
label="布尔值"
|
||||
>
|
||||
<template v-for="(item, index) in property.dataSpecsList" :key="item.value">
|
||||
<template v-for="item in property.dataSpecsList" :key="item.value">
|
||||
<div class="w-1/1 mb-5px flex items-center justify-start">
|
||||
<span>{{ item.value }}</span>
|
||||
<span class="mx-2">-</span>
|
||||
<Form.Item
|
||||
:prop="`property.dataSpecsList[${index}].name`"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举描述不能为空' },
|
||||
{ validator: validateBoolName, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<Input
|
||||
v-model="item.name"
|
||||
v-model:value="item.name"
|
||||
:placeholder="`如:${item.value === 0 ? '关' : '开'}`"
|
||||
class="w-255px!"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Form.Item>
|
||||
@@ -165,21 +155,21 @@ watch(
|
||||
<Form.Item
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.TEXT"
|
||||
label="数据长度"
|
||||
prop="property.dataSpecs.length"
|
||||
name="property.dataSpecs.length"
|
||||
>
|
||||
<Input
|
||||
v-model="property.dataSpecs.length"
|
||||
v-model:value="property.dataSpecs.length"
|
||||
class="w-255px!"
|
||||
placeholder="请输入文本字节长度"
|
||||
>
|
||||
<template #append>字节</template>
|
||||
<template #addonAfter>字节</template>
|
||||
</Input>
|
||||
</Form.Item>
|
||||
<!-- 时间型配置 -->
|
||||
<Form.Item
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.DATE"
|
||||
label="时间格式"
|
||||
prop="date"
|
||||
name="date"
|
||||
>
|
||||
<Input
|
||||
class="w-255px!"
|
||||
@@ -200,13 +190,13 @@ watch(
|
||||
<Form.Item
|
||||
v-if="!isStructDataSpecs && !isParams"
|
||||
label="读写类型"
|
||||
prop="property.accessMode"
|
||||
name="property.accessMode"
|
||||
>
|
||||
<Radio.Group v-model="property.accessMode">
|
||||
<Radio.Group v-model:value="property.accessMode">
|
||||
<Radio
|
||||
v-for="accessMode in Object.values(IoTThingModelAccessModeEnum)"
|
||||
:key="accessMode.value"
|
||||
:label="accessMode.value"
|
||||
:value="accessMode.value"
|
||||
>
|
||||
{{ accessMode.label }}
|
||||
</Radio>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { ThingModelService } from '#/api/iot/thingmodel';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
@@ -23,7 +21,7 @@ defineOptions({ name: 'ThingModelService' });
|
||||
|
||||
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const service = useVModel(props, 'modelValue', emits) as Ref<ThingModelService>;
|
||||
const service = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
/** 默认选中,ASYNC 异步 */
|
||||
watch(
|
||||
@@ -39,9 +37,9 @@ watch(
|
||||
<Form.Item
|
||||
:rules="[{ required: true, message: '请选择调用方式', trigger: 'change' }]"
|
||||
label="调用方式"
|
||||
prop="service.callType"
|
||||
name="service.callType"
|
||||
>
|
||||
<Radio.Group v-model="service.callType">
|
||||
<Radio.Group v-model:value="service.callType">
|
||||
<Radio
|
||||
v-for="callType in Object.values(IoTThingModelServiceCallTypeEnum)"
|
||||
:key="callType.value"
|
||||
@@ -53,13 +51,13 @@ watch(
|
||||
</Form.Item>
|
||||
<Form.Item label="输入参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="service.inputData"
|
||||
v-model="service.inputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.INPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="输出参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="service.outputData"
|
||||
v-model="service.outputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -3,58 +3,115 @@ import type { Ref } from 'vue';
|
||||
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { inject, onMounted, ref } from 'vue';
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
|
||||
import { Modal, Radio } from 'ant-design-vue';
|
||||
import hljs from 'highlight.js'; // 导入代码高亮文件
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
import { Modal, Radio, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { getThingModelListByProductId } from '#/api/iot/thingmodel';
|
||||
import { getThingModelTSL } from '#/api/iot/thingmodel';
|
||||
import { IOT_PROVIDE_KEY } from '#/views/iot/utils/constants';
|
||||
|
||||
import 'highlight.js/styles/github.css'; // 导入代码高亮样式
|
||||
|
||||
defineOptions({ name: 'ThingModelTSL' });
|
||||
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref('物模型 TSL'); // 弹窗的标题
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT); // 注入产品信息
|
||||
const viewMode = ref('code'); // 查看模式:code-代码视图,editor-编辑器视图
|
||||
const viewMode = ref('view'); // 查看模式:view-代码视图,editor-编辑器视图
|
||||
|
||||
/** 打开弹窗 */
|
||||
function open() {
|
||||
async function open() {
|
||||
dialogVisible.value = true;
|
||||
await getTsl();
|
||||
}
|
||||
defineExpose({ open });
|
||||
|
||||
/** 获取 TSL */
|
||||
const thingModelTSL = ref({});
|
||||
const thingModelTSL = ref<any>({});
|
||||
const tslString = ref(''); // 用于编辑器的字符串格式
|
||||
|
||||
async function getTsl() {
|
||||
thingModelTSL.value = await getThingModelListByProductId(
|
||||
product?.value?.id || 0,
|
||||
);
|
||||
try {
|
||||
thingModelTSL.value = await getThingModelTSL(product?.value?.id || 0);
|
||||
// 将对象转换为格式化的 JSON 字符串
|
||||
tslString.value = JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} catch (error) {
|
||||
console.error('获取 TSL 失败:', error);
|
||||
thingModelTSL.value = {};
|
||||
tslString.value = '{}';
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 注册代码高亮的各种语言
|
||||
hljs.registerLanguage('json', json);
|
||||
await getTsl();
|
||||
/** 格式化的 TSL 用于只读展示 */
|
||||
const formattedTSL = computed(() => {
|
||||
try {
|
||||
if (typeof thingModelTSL.value === 'string') {
|
||||
return JSON.stringify(JSON.parse(thingModelTSL.value), null, 2);
|
||||
}
|
||||
return JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} catch {
|
||||
return JSON.stringify(thingModelTSL.value, null, 2);
|
||||
}
|
||||
});
|
||||
|
||||
/** 监听编辑器内容变化,实时更新数据 */
|
||||
watch(tslString, (newValue) => {
|
||||
try {
|
||||
thingModelTSL.value = JSON.parse(newValue);
|
||||
} catch {
|
||||
// JSON 解析失败时保持原值
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model="dialogVisible" :title="dialogTitle">
|
||||
<JsonEditor
|
||||
v-model="thingModelTSL"
|
||||
:mode="viewMode === 'editor' ? 'code' : 'view'"
|
||||
height="600px"
|
||||
/>
|
||||
<template #footer>
|
||||
<Radio.Group v-model="viewMode" size="small">
|
||||
<Radio.Button label="code">代码视图</Radio.Button>
|
||||
<Radio.Button label="editor">编辑器视图</Radio.Button>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:footer="null"
|
||||
width="800px"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<Radio.Group v-model:value="viewMode" size="small">
|
||||
<Radio.Button value="view">代码视图</Radio.Button>
|
||||
<Radio.Button value="editor">编辑器视图</Radio.Button>
|
||||
</Radio.Group>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 代码视图 - 只读展示 -->
|
||||
<div v-if="viewMode === 'view'" class="json-viewer-container">
|
||||
<pre class="json-code"><code>{{ formattedTSL }}</code></pre>
|
||||
</div>
|
||||
<!-- 编辑器视图 - 可编辑 -->
|
||||
<Textarea
|
||||
v-else
|
||||
v-model:value="tslString"
|
||||
:rows="20"
|
||||
placeholder="请输入 JSON 格式的物模型 TSL"
|
||||
class="json-editor"
|
||||
/>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
max-height: 600px;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.json-code {
|
||||
margin: 0;
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getEventTypeLabel,
|
||||
getThingModelServiceCallTypeLabel,
|
||||
@@ -11,12 +15,40 @@ import {
|
||||
/** 数据定义展示组件 */
|
||||
defineOptions({ name: 'DataDefinition' });
|
||||
|
||||
defineProps<{ data: ThingModelData }>();
|
||||
const props = defineProps<{ data: ThingModelData }>();
|
||||
|
||||
// 格式化布尔值和枚举值列表为字符串
|
||||
const formattedDataSpecsList = computed(() => {
|
||||
if (
|
||||
!props.data.property?.dataSpecsList ||
|
||||
props.data.property.dataSpecsList.length === 0
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return props.data.property.dataSpecsList
|
||||
.map((item) => `${item.value}-${item.name}`)
|
||||
.join('、');
|
||||
});
|
||||
|
||||
// 显示的简短文本(第一个值)
|
||||
const shortText = computed(() => {
|
||||
if (
|
||||
!props.data.property?.dataSpecsList ||
|
||||
props.data.property.dataSpecsList.length === 0
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
const first = props.data.property.dataSpecsList[0];
|
||||
const count = props.data.property.dataSpecsList.length;
|
||||
return count > 1
|
||||
? `${first.value}-${first.name} 等${count}项`
|
||||
: `${first.value}-${first.name}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 属性 -->
|
||||
<template v-if="data.type === IoTThingModelTypeEnum.PROPERTY.toString()">
|
||||
<template v-if="Number(data.type) === IoTThingModelTypeEnum.PROPERTY">
|
||||
<!-- 非列表型:数值 -->
|
||||
<div
|
||||
v-if="
|
||||
@@ -28,12 +60,12 @@ defineProps<{ data: ThingModelData }>();
|
||||
"
|
||||
>
|
||||
取值范围:{{
|
||||
`${data.property?.dataSpecs.min}~${data.property?.dataSpecs.max}`
|
||||
`${data.property?.dataSpecs?.min}~${data.property?.dataSpecs?.max}`
|
||||
}}
|
||||
</div>
|
||||
<!-- 非列表型:文本 -->
|
||||
<div v-if="IoTDataSpecsDataTypeEnum.TEXT === data.property?.dataType">
|
||||
数据长度:{{ data.property?.dataSpecs.length }}
|
||||
数据长度:{{ data.property?.dataSpecs?.length }}
|
||||
</div>
|
||||
<!-- 列表型: 数组、结构、时间(特殊) -->
|
||||
<div
|
||||
@@ -55,28 +87,37 @@ defineProps<{ data: ThingModelData }>();
|
||||
)
|
||||
"
|
||||
>
|
||||
<div>
|
||||
{{
|
||||
IoTDataSpecsDataTypeEnum.BOOL === data.property?.dataType
|
||||
? '布尔值'
|
||||
: '枚举值'
|
||||
}}:
|
||||
</div>
|
||||
<div v-for="item in data.property?.dataSpecsList" :key="item.value">
|
||||
{{ `${item.name}-${item.value}` }}
|
||||
</div>
|
||||
<Tooltip :title="formattedDataSpecsList" placement="topLeft">
|
||||
<span class="data-specs-text">
|
||||
{{
|
||||
IoTDataSpecsDataTypeEnum.BOOL === data.property?.dataType
|
||||
? '布尔值'
|
||||
: '枚举值'
|
||||
}}:{{ shortText }}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 服务 -->
|
||||
<div v-if="data.type === IoTThingModelTypeEnum.SERVICE.toString()">
|
||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.SERVICE">
|
||||
调用方式:{{
|
||||
getThingModelServiceCallTypeLabel(data.service?.callType as any)
|
||||
}}
|
||||
</div>
|
||||
<!-- 事件 -->
|
||||
<div v-if="data.type === IoTThingModelTypeEnum.EVENT.toString()">
|
||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.EVENT">
|
||||
事件类型:{{ getEventTypeLabel(data.event?.type as any) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.data-specs-text {
|
||||
cursor: help;
|
||||
border-bottom: 1px dashed #d9d9d9;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,8 +29,8 @@ function handleChange(val: any) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item label="元素类型" prop="property.dataSpecs.childDataType">
|
||||
<Radio.Group v-model="dataSpecs.childDataType" @change="handleChange">
|
||||
<Form.Item label="元素类型" name="property.dataSpecs.childDataType">
|
||||
<Radio.Group v-model:value="dataSpecs.childDataType" @change="handleChange">
|
||||
<template v-for="item in getDataTypeOptions()" :key="item.value">
|
||||
<Radio
|
||||
v-if="
|
||||
@@ -50,8 +50,11 @@ function handleChange(val: any) {
|
||||
</template>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="元素个数" prop="property.dataSpecs.size">
|
||||
<Input v-model="dataSpecs.size" placeholder="请输入数组中的元素个数" />
|
||||
<Form.Item label="元素个数" name="property.dataSpecs.size">
|
||||
<Input
|
||||
v-model:value="dataSpecs.size"
|
||||
placeholder="请输入数组中的元素个数"
|
||||
/>
|
||||
</Form.Item>
|
||||
<!-- Struct 型配置-->
|
||||
<ThingModelStructDataSpecs
|
||||
|
||||
@@ -2,31 +2,22 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { DataSpecsEnumOrBoolData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
|
||||
import { IoTDataSpecsDataTypeEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
/** 枚举型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelEnumDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<
|
||||
DataSpecsEnumOrBoolData[]
|
||||
>;
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
|
||||
/** 添加枚举项 */
|
||||
function addEnum() {
|
||||
dataSpecsList.value.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: '', // 枚举值
|
||||
});
|
||||
} as any);
|
||||
}
|
||||
|
||||
/** 删除枚举项 */
|
||||
@@ -37,93 +28,10 @@ function deleteEnum(index: number) {
|
||||
}
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 校验枚举值 */
|
||||
function validateEnumValue(_: any, value: any, callback: any) {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error('枚举值不能为空'));
|
||||
return;
|
||||
}
|
||||
if (Number.isNaN(Number(value))) {
|
||||
callback(new Error('枚举值必须是数字'));
|
||||
return;
|
||||
}
|
||||
// 检查枚举值是否重复
|
||||
const values = dataSpecsList.value.map((item) => item.value);
|
||||
if (values.filter((v) => v === value).length > 1) {
|
||||
callback(new Error('枚举值不能重复'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 校验枚举描述 */
|
||||
function validateEnumName(_: any, value: string, callback: any) {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error('枚举描述不能为空'));
|
||||
return;
|
||||
}
|
||||
// 检查开头字符
|
||||
if (!/^[\u4E00-\u9FA5a-z0-9]/i.test(value)) {
|
||||
callback(new Error('枚举描述必须以中文、英文字母或数字开头'));
|
||||
return;
|
||||
}
|
||||
// 检查整体格式
|
||||
if (!/^[\u4E00-\u9FA5a-z0-9][\w\u4E00-\u9FA5-]*$/i.test(value)) {
|
||||
callback(new Error('枚举描述只能包含中文、英文字母、数字、下划线和短划线'));
|
||||
return;
|
||||
}
|
||||
// 检查长度(一个中文算一个字符)
|
||||
if (value.length > 20) {
|
||||
callback(new Error('枚举描述长度不能超过20个字符'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 校验整个枚举列表 */
|
||||
function validateEnumList(_: any, __: any, callback: any) {
|
||||
if (isEmpty(dataSpecsList.value)) {
|
||||
callback(new Error('请至少添加一个枚举项'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否存在空值
|
||||
const hasEmptyValue = dataSpecsList.value.some(
|
||||
(item) => isEmpty(item.value) || isEmpty(item.name),
|
||||
);
|
||||
if (hasEmptyValue) {
|
||||
callback(new Error('存在未填写的枚举值或描述'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查枚举值是否都是数字
|
||||
const hasInvalidNumber = dataSpecsList.value.some((item) =>
|
||||
Number.isNaN(Number(item.value)),
|
||||
);
|
||||
if (hasInvalidNumber) {
|
||||
callback(new Error('存在非数字的枚举值'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有重复的枚举值
|
||||
const values = dataSpecsList.value.map((item) => item.value);
|
||||
const uniqueValues = new Set(values);
|
||||
if (values.length !== uniqueValues.size) {
|
||||
callback(new Error('存在重复的枚举值'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
:rules="[
|
||||
{ required: true, validator: validateEnumList, trigger: 'change' },
|
||||
]"
|
||||
label="枚举项"
|
||||
>
|
||||
<Form.Item label="枚举项">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="flex-1"> 参数值 </span>
|
||||
@@ -134,39 +42,25 @@ function validateEnumList(_: any, __: any, callback: any) {
|
||||
:key="index"
|
||||
class="mb-5px flex items-center justify-between"
|
||||
>
|
||||
<Form.Item
|
||||
:prop="`property.dataSpecsList[${index}].value`"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举值不能为空' },
|
||||
{ validator: validateEnumValue, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model="item.value" placeholder="请输入枚举值,如'0'" />
|
||||
</Form.Item>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.value" placeholder="请输入枚举值,如'0'" />
|
||||
</div>
|
||||
<span class="mx-2">~</span>
|
||||
<Form.Item
|
||||
:prop="`property.dataSpecsList[${index}].name`"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举描述不能为空' },
|
||||
{ validator: validateEnumName, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model="item.name" placeholder="对该枚举项的描述" />
|
||||
</Form.Item>
|
||||
<Button class="ml-10px" link type="primary" @click="deleteEnum(index)">
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.name" placeholder="对该枚举项的描述" />
|
||||
</div>
|
||||
<Button class="ml-10px" type="link" @click="deleteEnum(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Button link type="primary" @click="addEnum">+添加枚举项</Button>
|
||||
<Button type="link" @click="addEnum">+添加枚举项</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-form-item) {
|
||||
.el-form-item {
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
/** 数值型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelNumberDataSpecs' });
|
||||
@@ -21,132 +22,56 @@ const dataSpecs = useVModel(
|
||||
) as Ref<DataSpecsNumberData>;
|
||||
|
||||
/** 单位发生变化时触发 */
|
||||
const unitChange = (UnitSpecs: string) => {
|
||||
const [unitName, unit] = UnitSpecs.split('-');
|
||||
const unitChange = (UnitSpecs: any) => {
|
||||
if (!UnitSpecs) return;
|
||||
const [unitName, unit] = String(UnitSpecs).split('-');
|
||||
dataSpecs.value.unitName = unitName;
|
||||
dataSpecs.value.unit = unit;
|
||||
};
|
||||
|
||||
/** 校验最小值 */
|
||||
const validateMin = (_: any, __: any, callback: any) => {
|
||||
const min = Number(dataSpecs.value.min);
|
||||
const max = Number(dataSpecs.value.max);
|
||||
if (Number.isNaN(min)) {
|
||||
callback(new Error('请输入有效的数值'));
|
||||
return;
|
||||
}
|
||||
if (max !== undefined && !Number.isNaN(max) && min >= max) {
|
||||
callback(new Error('最小值必须小于最大值'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
/** 校验最大值 */
|
||||
const validateMax = (_: any, __: any, callback: any) => {
|
||||
const min = Number(dataSpecs.value.min);
|
||||
const max = Number(dataSpecs.value.max);
|
||||
if (Number.isNaN(max)) {
|
||||
callback(new Error('请输入有效的数值'));
|
||||
return;
|
||||
}
|
||||
if (min !== undefined && !Number.isNaN(min) && max <= min) {
|
||||
callback(new Error('最大值必须大于最小值'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
/** 校验步长 */
|
||||
const validateStep = (_: any, __: any, callback: any) => {
|
||||
const step = Number(dataSpecs.value.step);
|
||||
if (Number.isNaN(step)) {
|
||||
callback(new Error('请输入有效的数值'));
|
||||
return;
|
||||
}
|
||||
if (step <= 0) {
|
||||
callback(new Error('步长必须大于0'));
|
||||
return;
|
||||
}
|
||||
const min = Number(dataSpecs.value.min);
|
||||
const max = Number(dataSpecs.value.max);
|
||||
if (!Number.isNaN(min) && !Number.isNaN(max) && step > max - min) {
|
||||
callback(new Error('步长不能大于最大值和最小值的差值'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form-item label="取值范围">
|
||||
<Form.Item label="取值范围">
|
||||
<div class="flex items-center justify-between">
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{ required: true, message: '最小值不能为空' },
|
||||
{ validator: validateMin, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0"
|
||||
prop="property.dataSpecs.min"
|
||||
>
|
||||
<el-input v-model="dataSpecs.min" placeholder="请输入最小值" />
|
||||
</el-form-item>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="dataSpecs.min" placeholder="请输入最小值" />
|
||||
</div>
|
||||
<span class="mx-2">~</span>
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{ required: true, message: '最大值不能为空' },
|
||||
{ validator: validateMax, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0"
|
||||
prop="property.dataSpecs.max"
|
||||
>
|
||||
<el-input v-model="dataSpecs.max" placeholder="请输入最大值" />
|
||||
</el-form-item>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="dataSpecs.max" placeholder="请输入最大值" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{ required: true, message: '步长不能为空' },
|
||||
{ validator: validateStep, trigger: 'blur' },
|
||||
]"
|
||||
label="步长"
|
||||
prop="property.dataSpecs.step"
|
||||
>
|
||||
<el-input v-model="dataSpecs.step" placeholder="请输入步长" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:rules="[{ required: true, message: '请选择单位' }]"
|
||||
label="单位"
|
||||
prop="property.dataSpecs.unit"
|
||||
>
|
||||
<el-select
|
||||
</Form.Item>
|
||||
<Form.Item label="步长">
|
||||
<Input v-model:value="dataSpecs.step" placeholder="请输入步长" />
|
||||
</Form.Item>
|
||||
<Form.Item label="单位">
|
||||
<Select
|
||||
:model-value="
|
||||
dataSpecs.unit ? `${dataSpecs.unitName}-${dataSpecs.unit}` : ''
|
||||
"
|
||||
filterable
|
||||
show-search
|
||||
placeholder="请选择单位"
|
||||
class="w-1/1"
|
||||
@change="unitChange"
|
||||
>
|
||||
<el-option
|
||||
<Select.Option
|
||||
v-for="(item, index) in getDictOptions(
|
||||
DICT_TYPE.IOT_THING_MODEL_UNIT,
|
||||
'string',
|
||||
)"
|
||||
:key="index"
|
||||
:label="`${item.label}-${item.value}`"
|
||||
:value="`${item.label}-${item.value}`"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
>
|
||||
{{ `${item.label}-${item.value}` }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-form-item) {
|
||||
.el-form-item {
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user