Commit e947951a2319b28e322d7495b458f3c278b12152

Authored by fengwotao
2 parents 0d634f6b 43058d19

Merge branch 'main_dev' into ft

Showing 129 changed files with 5651 additions and 3388 deletions
@@ -52,7 +52,7 @@ VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 500000 @@ -52,7 +52,7 @@ VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 500000
52 VITE_GLOB_ALARM_NOTIFY_DURATION = 5 52 VITE_GLOB_ALARM_NOTIFY_DURATION = 5
53 53
54 # Should Disabled Task Center Execute Interval Unit (Second) 54 # Should Disabled Task Center Execute Interval Unit (Second)
55 -VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = true 55 +VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = false
56 56
57 # Software version number 57 # Software version number
58 VITE_GLOB_SOFTWARE_VERSION_NUMBER = ThingsKit v1.2.0_release 58 VITE_GLOB_SOFTWARE_VERSION_NUMBER = ThingsKit v1.2.0_release
@@ -25,7 +25,7 @@ module.exports = defineConfig({ @@ -25,7 +25,7 @@ module.exports = defineConfig({
25 'plugin:jest/recommended', 25 'plugin:jest/recommended',
26 ], 26 ],
27 rules: { 27 rules: {
28 - 'no-console': 'off', 28 + 'no-console': 'error',
29 'vue/script-setup-uses-vars': 'error', 29 'vue/script-setup-uses-vars': 'error',
30 '@typescript-eslint/ban-ts-ignore': 'off', 30 '@typescript-eslint/ban-ts-ignore': 'off',
31 '@typescript-eslint/explicit-function-return-type': 'off', 31 '@typescript-eslint/explicit-function-return-type': 'off',
1 -版本:v1.1.2_release 1 +版本:v1.2.0_release
2 2
3 ## 准备 3 ## 准备
4 4
1 import { DataType } from '../../device/model/modelOfMatterModel'; 1 import { DataType } from '../../device/model/modelOfMatterModel';
2 -import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config'; 2 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
3 import { DataSource } from '/@/views/visual/palette/types'; 3 import { DataSource } from '/@/views/visual/palette/types';
4 4
5 export interface AddDataBoardParams { 5 export interface AddDataBoardParams {
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +// import { PaginationResult } from '/#/axios';
  3 +
  4 +enum Api {
  5 + DEVICE_PROFILE_CATEGORY = '/device_profile/category',
  6 +}
  7 +
  8 +/**
  9 + * 新增产品分类
  10 + */
  11 +export const deviceProfileCategory = (params) => {
  12 + return defHttp.post<any>({
  13 + url: `${Api.DEVICE_PROFILE_CATEGORY}`,
  14 + params,
  15 + });
  16 +};
  17 +
  18 +/**
  19 + * 获取列表
  20 + */
  21 +export const getDeviceClassList = (params) => {
  22 + return defHttp.get<any>({
  23 + url: Api.DEVICE_PROFILE_CATEGORY,
  24 + params,
  25 + });
  26 +};
  27 +
  28 +/**
  29 + * 删除产品分类
  30 + */
  31 +export const deleteDeviceClass = (params) => {
  32 + return defHttp.delete<any>({
  33 + url: Api.DEVICE_PROFILE_CATEGORY,
  34 + params,
  35 + });
  36 +};
@@ -194,6 +194,7 @@ export interface DeviceRecord { @@ -194,6 +194,7 @@ export interface DeviceRecord {
194 customerAdditionalInfo?: { 194 customerAdditionalInfo?: {
195 isPublic?: boolean; 195 isPublic?: boolean;
196 }; 196 };
  197 + ifShowClass?: Boolean;
197 } 198 }
198 199
199 export interface DeviceModelOfMatterAttrs { 200 export interface DeviceModelOfMatterAttrs {
1 -import {  
2 - DataTypeEnum,  
3 - FunctionType,  
4 -} from '/@/views/device/profiles/step/cpns/physical/cpns/config'; 1 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  2 +import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
5 3
6 export interface Specs { 4 export interface Specs {
7 min: string; 5 min: string;
@@ -26,7 +24,7 @@ export interface DataType { @@ -26,7 +24,7 @@ export interface DataType {
26 24
27 export interface StructJSON { 25 export interface StructJSON {
28 functionName?: string; 26 functionName?: string;
29 - identifier?: string; 27 + identifier: string;
30 remark?: string; 28 remark?: string;
31 dataType?: DataType; 29 dataType?: DataType;
32 serviceCommand?: string; 30 serviceCommand?: string;
@@ -41,13 +39,14 @@ export interface FunctionJson { @@ -41,13 +39,14 @@ export interface FunctionJson {
41 } 39 }
42 40
43 export interface ModelOfMatterParams { 41 export interface ModelOfMatterParams {
44 - deviceProfileId: string; 42 + deviceProfileId?: string;
45 functionJson: FunctionJson; 43 functionJson: FunctionJson;
46 functionName: string; 44 functionName: string;
47 functionType: FunctionType; 45 functionType: FunctionType;
48 identifier: string; 46 identifier: string;
49 remark: string; 47 remark: string;
50 id?: string; 48 id?: string;
  49 + categoryId?: string;
51 callType?: string; 50 callType?: string;
52 eventType?: string; 51 eventType?: string;
53 accessMode?: string; 52 accessMode?: string;
@@ -57,10 +56,12 @@ export interface ModelOfMatterParams { @@ -57,10 +56,12 @@ export interface ModelOfMatterParams {
57 export interface GetModelTslParams { 56 export interface GetModelTslParams {
58 functionType: FunctionType; 57 functionType: FunctionType;
59 deviceProfileId: string; 58 deviceProfileId: string;
  59 + ifShowClass?: string | Boolean;
60 } 60 }
61 61
62 export interface ImportModelOfMatterType { 62 export interface ImportModelOfMatterType {
63 data: Recordable; 63 data: Recordable;
64 functionType: string; 64 functionType: string;
65 - tkDeviceProfileId: string; 65 + tkDeviceProfileId?: string;
  66 + categoryId?: string;
66 } 67 }
@@ -18,13 +18,21 @@ enum ModelOfMatter { @@ -18,13 +18,21 @@ enum ModelOfMatter {
18 IMPORT = '/things_model/import', 18 IMPORT = '/things_model/import',
19 19
20 GET_MODEL_SERVICE = '/things_model/get_services', 20 GET_MODEL_SERVICE = '/things_model/get_services',
  21 + CATEGORY = '/things_model/category',
  22 + CATEGORY_IMPORT = '/things_model/categoryImport',
  23 +
  24 + CATEGORY_EXPORT = '/things_model/categoryGetExport',
  25 +
  26 + IMPORT_CSV = '/things_model/csvImport',
21 } 27 }
22 28
23 export const getModelList = ( 29 export const getModelList = (
24 params: BasicPageParams & { 30 params: BasicPageParams & {
25 - deviceProfileId: string; 31 + deviceProfileId?: string;
26 functionTyp?: FunctionType; 32 functionTyp?: FunctionType;
27 nameOrIdentifier?: string; 33 nameOrIdentifier?: string;
  34 + selectType?: string | undefined;
  35 + id?: string;
28 } 36 }
29 ) => { 37 ) => {
30 return defHttp.get({ 38 return defHttp.get({
@@ -82,3 +90,82 @@ export const importModelOfMatter = (data: ImportModelOfMatterType) => { @@ -82,3 +90,82 @@ export const importModelOfMatter = (data: ImportModelOfMatterType) => {
82 data, 90 data,
83 }); 91 });
84 }; 92 };
  93 +
  94 +// 产品品类接口
  95 +
  96 +// 新增
  97 +export const createModelCategory = (params: Partial<ModelOfMatterParams>) => {
  98 + return defHttp.post({
  99 + url: ModelOfMatter.CATEGORY,
  100 + params,
  101 + });
  102 +};
  103 +
  104 +// 修改
  105 +export const updateModelCategory = (params: Partial<ModelOfMatterParams>) => {
  106 + return defHttp.put({
  107 + url: ModelOfMatter.CATEGORY,
  108 + params,
  109 + });
  110 +};
  111 +
  112 +// 删除
  113 +export const deleteModelCategory = (params: string[]) => {
  114 + return defHttp.delete({
  115 + url: ModelOfMatter.CATEGORY,
  116 + params: {
  117 + ids: params,
  118 + },
  119 + });
  120 +};
  121 +
  122 +// 导入
  123 +export const importModelCategory = (data: ImportModelOfMatterType) => {
  124 + return defHttp.post({
  125 + url: ModelOfMatter.CATEGORY_IMPORT,
  126 + data,
  127 + });
  128 +};
  129 +
  130 +// 导出
  131 +export const ExportModelCategory = (params: any) => {
  132 + const { functionType, deviceProfileId } = params;
  133 + return defHttp.get({
  134 + url: `${ModelOfMatter.CATEGORY_EXPORT}/${functionType}/${deviceProfileId}`,
  135 + params,
  136 + });
  137 +};
  138 +
  139 +/**
  140 + * 物模型产品品类界面excel导入
  141 + */
  142 +
  143 +export const importCsvCategory = (params: {
  144 + categoryId?: string;
  145 + deviceProfileId?: string;
  146 + file: FormData;
  147 +}) => {
  148 + const { categoryId } = params || {};
  149 +
  150 + return defHttp.post<any>({
  151 + url: `${ModelOfMatter.IMPORT_CSV}?categoryId=${categoryId}`,
  152 + params: params.file,
  153 + });
  154 +};
  155 +
  156 +/**
  157 + * 物模型产品物模型界面excel导入
  158 + */
  159 +
  160 +export const importCsvDeviceProfileId = (params: {
  161 + categoryId?: string;
  162 + deviceProfileId?: string;
  163 + file: FormData;
  164 +}) => {
  165 + const { deviceProfileId } = params || {};
  166 +
  167 + return defHttp.post<any>({
  168 + url: `${ModelOfMatter.IMPORT_CSV}?deviceProfileId=${deviceProfileId}`,
  169 + params: params.file,
  170 + });
  171 +};
1 import { BasicPageParams } from '/@/api/model/baseModel'; 1 import { BasicPageParams } from '/@/api/model/baseModel';
  2 +import { SceneLinkageDataType } from '/@/views/rule/linkedge/components/SceneLinkageDrawer/type';
2 3
3 export type ScreenLinkPageQueryParam = BasicPageParams & ScreenParams; 4 export type ScreenLinkPageQueryParam = BasicPageParams & ScreenParams;
4 5
@@ -14,97 +15,7 @@ export type ScreenByDeptIdParams = { @@ -14,97 +15,7 @@ export type ScreenByDeptIdParams = {
14 // organizationId: '2f5c8f2a-196c-4941-8771-290f9da76219'; 15 // organizationId: '2f5c8f2a-196c-4941-8771-290f9da76219';
15 }; 16 };
16 17
17 -export interface ScreenAddModel {  
18 - createTime?: string;  
19 - creator?: string;  
20 - defaultConfig?: string;  
21 - description?: string;  
22 - doAction?: [  
23 - {  
24 - command: string;  
25 - createTime: string;  
26 - creator: string;  
27 - defaultConfig: string;  
28 - description: string;  
29 - deviceId: string;  
30 - enabled: true;  
31 - icon: string;  
32 - id: string;  
33 - name: string;  
34 - outPut: string;  
35 - outTarget: string;  
36 - roleIds: [];  
37 - tenantCode: string;  
38 - tenantExpireTime: string;  
39 - tenantId: string;  
40 - tenantStatus: 'DISABLED';  
41 - updateTime: string;  
42 - updater: string;  
43 - }  
44 - ];  
45 - doCondition?: [  
46 - {  
47 - compare: 0;  
48 - createTime: string;  
49 - creator: string;  
50 - defaultConfig: string;  
51 - description: string;  
52 - deviceId: string;  
53 - enabled: true;  
54 - icon: string;  
55 - id: string;  
56 - name: string;  
57 - property: string;  
58 - roleIds: [];  
59 - status: string;  
60 - tenantCode: string;  
61 - tenantExpireTime: string;  
62 - tenantId: string;  
63 - tenantStatus: 'DISABLED';  
64 - updateTime: string;  
65 - updater: string;  
66 - value: string;  
67 - }  
68 - ];  
69 - enabled?: true;  
70 - icon?: string;  
71 - id?: string;  
72 - name?: string;  
73 - organizationId?: string;  
74 - roleIds?: [string];  
75 - status?: string;  
76 - tenantCode?: string;  
77 - tenantExpireTime?: string;  
78 - tenantId?: string;  
79 - tenantStatus?: 'DISABLED';  
80 - triggers?: [  
81 - {  
82 - attributeChoose?: string;  
83 - compare?: 0;  
84 - createTime?: string;  
85 - creator?: string;  
86 - defaultConfig?: string;  
87 - description?: string;  
88 - deviceId?: string;  
89 - enabled?: true;  
90 - icon?: string;  
91 - id?: string;  
92 - name?: string;  
93 - roleIds?: [];  
94 - tenantCode?: string;  
95 - tenantExpireTime?: string;  
96 - tenantId?: string;  
97 - tenantStatus?: 'DISABLED';  
98 - tiggerEvent?: string;  
99 - touchWay?: string;  
100 - updateTime?: string;  
101 - updater?: string;  
102 - value?: string;  
103 - }  
104 - ];  
105 - updateTime?: string;  
106 - updater?: string;  
107 -} 18 +export type ScreenAddModel = SceneLinkageDataType;
108 19
109 export interface IChangeStatus { 20 export interface IChangeStatus {
110 status?: number; 21 status?: number;
@@ -6,6 +6,7 @@ import { @@ -6,6 +6,7 @@ import {
6 ScreenByDeptIdParams, 6 ScreenByDeptIdParams,
7 IChangeStatus, 7 IChangeStatus,
8 } from '/@/api/ruleengine/model/ruleengineModel'; 8 } from '/@/api/ruleengine/model/ruleengineModel';
  9 +import { DeviceModel } from '../device/model/deviceModel';
9 10
10 enum ScreenManagerApi { 11 enum ScreenManagerApi {
11 /** 12 /**
@@ -126,7 +127,7 @@ export const byOrganizationIdGetMasterDevice = (params: { @@ -126,7 +127,7 @@ export const byOrganizationIdGetMasterDevice = (params: {
126 deviceProfileId?: string; 127 deviceProfileId?: string;
127 }) => { 128 }) => {
128 const { organizationId, deviceProfileId } = params; 129 const { organizationId, deviceProfileId } = params;
129 - return defHttp.get({ 130 + return defHttp.get<DeviceModel[]>({
130 url: `${ScreenManagerApi.MASTER_GET_DEVICE}`, 131 url: `${ScreenManagerApi.MASTER_GET_DEVICE}`,
131 params: { deviceProfileId, organizationId }, 132 params: { deviceProfileId, organizationId },
132 }); 133 });
@@ -37,10 +37,11 @@ export interface AccountListItem { @@ -37,10 +37,11 @@ export interface AccountListItem {
37 } 37 }
38 38
39 export interface OrganizationListItem { 39 export interface OrganizationListItem {
40 - id: string;  
41 - name: string; 40 + id?: string;
  41 + name?: string;
42 parentId?: string; 42 parentId?: string;
43 - remark: string; 43 + remark?: string;
  44 + organizationId?: string;
44 } 45 }
45 46
46 export interface MenuListItem { 47 export interface MenuListItem {
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699588251601" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1411" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.621959 0c-44.79888 0-79.998 36.81188-79.998 81.61076v860.77848c0 44.79888 35.19912 81.61076 79.998 81.61076h732.781681a81.969151 81.969151 0 0 0 81.61076-81.61076V324.80468L657.60916 0h-511.987201z" fill="#F17F53" p-id="1412"></path><path d="M657.60916 0v233.59416c0 25.59936 17.61236 92.79768 97.61036 92.79768h204.79488L657.60916 0z" fill="#FFFFFF" p-id="1413"></path><path d="M109.117272 659.874703c1.61276 0 2.867128 0.614385 3.814305 1.868753s1.663958 2.764731 2.175945 4.582286 0.81918 3.737507 0.972776 5.862253 0.230394 4.044699 0.230394 5.862254v5.708657c2.099148 3.302317 4.78708 6.886228 8.0126 10.726132s6.963026 7.449414 11.18692 10.80293 8.78058 6.143846 13.721257 8.39659 10.214145 3.379116 15.820405 3.379115c5.99025 0 11.622109-1.407965 16.869978-4.198295s10.137347-6.527837 14.617235-11.18692 8.652584-9.98375 12.441288-15.974001 7.21902-12.236494 10.265344-18.661933 5.759856-12.876478 8.089398-19.276318 4.377491-12.287693 6.067048-17.689158 3.046324-10.086148 4.044699-14.028449 1.689558-6.681433 2.099147-8.166196c2.611135-10.598135 4.81268-20.761081 6.681433-30.437639s2.764731-19.250719 2.764731-28.645684c0-2.303942-0.691183-3.60951-2.099147-3.891103l-37.19587 10.495738a19.916302 19.916302 0 0 1-5.094273 0.895978c-3.788705 0-6.963026-1.20317-9.522962-3.60951s-3.814305-5.862253-3.814305-10.342142 0.435189-8.114997 1.279968-10.879728 2.201545-4.81268 4.044699-6.220644 4.275093-2.329542 7.270218-2.764731 6.655834-0.665583 10.956527-0.665583h2.099147c2.40634-0.511987 5.478263-1.20317 9.21577-2.099148s7.935802-1.919952 12.518087-3.071923l14.694032-3.737507 15.743607-4.044699c5.299068-1.356766 10.572536-2.662333 15.820404-3.9679a1637.283868 1637.283868 0 0 1 23.474613-5.63186l5.990251-1.279968a22.01545 22.01545 0 0 1 3.737506-0.588785c1.20317 0 2.303942 0.358391 3.302318 1.049574s1.817555 1.58716 2.483138 2.636734a14.514837 14.514837 0 0 1 2.175945 7.423814c0 2.995125-1.151971 5.60626-3.455913 7.807805s-5.19667 4.121497-8.703783 5.785456-7.321417 3.097523-11.468513 4.351891l-11.62211 3.532711c-3.60951 1.100772-6.681433 2.175946-9.215769 3.22552s-3.967901 2.227144-4.275093 3.532711c-0.511987 4.40309-0.998375 9.292568-1.510363 14.694033s-1.049574 10.546936-1.638359 15.462014c-0.204795 1.20317-0.614385 4.095898-1.279968 8.703782s-1.561561 10.188545-2.687932 16.79318-2.559936 13.823654-4.198295 21.682658-3.532712 15.564411-5.63186 23.167421c-2.303942 8.294193-5.324667 17.151571-9.062173 26.546536s-8.191795 18.559536-13.337267 27.442514-10.982125 17.228369-17.484363 24.984976-13.542061 14.130847-21.145071 19.122721a58.929727 58.929727 0 0 1-23.19302 8.089398 61.336067 61.336067 0 0 1-9.369366 0.742382c-5.19667 0-10.828529-0.691183-16.869978-2.099148s-12.134097-3.404715-18.226744-5.99025-11.9805-5.734257-17.61236-9.369366-10.674933-7.705407-15.078023-12.159696-7.910202-9.21577-10.495738-14.335641-3.891103-10.444539-3.891102-16.050799c0-4.300692 0.435189-8.0126 1.279968-11.110122s2.227144-5.60626 4.121496-7.500613 4.428689-3.302317 7.577411-4.198295 7.014225-1.484763 11.519712-1.484763zM305.61796 693.333067c3.404715 1.20317 6.886228 2.611135 10.418939 4.198295s7.21902 3.123122 11.033324 4.582285 7.782205 2.662333 11.929302 3.686308 8.575786 1.510362 13.286068 1.510362c5.503862 0 11.058924-0.639984 16.639584-1.945551s11.084523-3.225519 16.434789-5.785455 10.470138-5.657459 15.385215-9.292568 9.394965-7.884603 13.490863-12.671683c4.505487-5.094273 8.242994-9.727757 11.238119-13.874854s5.427064-7.859004 7.270218-11.110122 3.174321-6.041449 3.967901-8.39659 1.20317-4.377491 1.20317-6.067048c0-2.508737-0.844779-4.684683-2.559936-6.527837s-3.967901-3.379116-6.835029-4.582285-6.067048-2.073548-9.676558-2.636734-7.347016-0.81918-11.238119-0.81918c-2.303942 0-4.991875 0.076798-8.089398 0.230394l-9.446164 0.460789-9.292567 0.38399a150.39624 150.39624 0 0 1-7.807805 0.153596c-3.302317 0-6.732632-0.38399-10.265344-1.126372s-7.014225-1.971151-10.418939-3.686307-6.553436-3.9935-9.446164-6.911828a35.403915 35.403915 0 0 1-8.831779-15.743606 24.626584 24.626584 0 0 1-0.742382-6.143846c0-2.40634 0.179196-4.684683 0.537587-6.83503s0.767981-4.223894 1.279968-6.220644c1.407965-4.991875 3.737507-10.086148 6.963026-15.231619s7.142221-10.162946 11.698907-15.078023 9.497363-9.650959 14.847629-14.258844 10.854129-8.934177 16.511587-12.978875 11.21252-7.705407 16.639584-10.956526 10.521337-6.01585 15.23162-8.319792c6.604635-3.19992 13.337267-5.529462 20.172295-6.963026s13.61886-2.175946 20.325892-2.175946c3.9935 0 7.807805 0.537587 11.391715 1.58716s6.78383 2.585535 9.522962 4.582286 4.915077 4.479888 6.527837 7.423814 2.40634 6.323042 2.40634 10.137347c0 2.201545-0.255994 4.044699-0.742381 5.555061s-1.151971 2.713532-1.945552 3.686308-1.663958 1.61276-2.636734 2.022349-1.919952 0.588785-2.918327 0.588785c-2.687933 0-5.171071-0.307192-7.423814-0.895977s-4.428689-1.279968-6.527837-2.02235-4.198295-1.459164-6.297443-2.099147-4.40309-0.972776-6.911827-0.972776c-7.398215 0-14.924427 1.484763-22.578635 4.428689s-15.103622 6.707032-22.348242 11.23812-14.105247 9.625359-20.556286 15.231619-12.236494 11.058924-17.330767 16.357991c-2.687933 2.79033-4.684683 5.60626-5.913452 8.39659s-1.868753 5.452664-1.868753 7.961401c0 3.711907 1.61276 6.604635 4.81268 8.703782s7.091023 3.148721 11.698907 3.148722c2.687933 0 5.759856-0.051199 9.138972-0.153597a796.140096 796.140096 0 0 0 20.761081-0.844779c3.353516-0.153596 6.323042-0.230394 8.934176-0.230394 6.195045 0 12.159696 0.870378 17.842754 2.636734s10.751731 4.326292 15.154821 7.731007 7.935802 7.628609 10.572536 12.671683 3.967901 10.828529 3.967901 17.330767c0 3.9935-0.588785 8.038199-1.791955 12.159696s-2.841529 8.217395-4.940677 12.364491-4.505487 8.242994-7.19342 12.287693-5.555061 8.0126-8.550186 11.929301c-5.401465 6.988625-11.417315 13.59326-18.073148 19.788306s-13.721257 11.59651-21.22187 16.204395-15.436414 8.242994-23.781805 10.956526-16.921177 4.044699-25.727357 4.044699a74.724532 74.724532 0 0 1-23.551411-4.121497c-3.891103-1.356766-7.577411-3.046324-11.033325-5.094273s-6.476638-4.377491-9.062173-6.963026-4.684683-5.478263-6.220644-8.626984-2.329542-6.579036-2.329542-10.265344c0-1.791955 0.281593-3.532712 0.819179-5.17107s1.868753-2.559936 4.070299-2.559936zM497.152371 651.631709c1.791955-7.500612 4.40309-15.18042 7.807805-23.013824s7.398215-15.718007 12.0061-23.62821 9.727757-15.666808 15.385215-23.321017a316.868878 316.868878 0 0 1 57.982551-59.928102c3.19992-2.201545 6.143846-3.660708 8.857378-4.351891s5.555061-1.049574 8.550186-1.049573c3.19992 0 6.041449 0.486388 8.550187 1.433564s4.991875 2.662333 7.500612 5.17107c0.79358 0.895978 1.638359 1.689558 2.559936 2.40634s1.740756 1.049574 2.559936 1.049574c0.204795 0 0.460788-0.230394 0.742382-0.665583l0.972775-1.587161c0.358391-0.614385 0.767981-1.228769 1.279968-1.868753s1.100772-1.126372 1.791956-1.433564c1.689558-0.79358 3.737507-1.331167 6.067048-1.58716s4.735882-0.38399 7.116622-0.383991c4.505487 0 8.857379 0.81918 13.055674 2.483138s7.910202 3.967901 11.110122 6.963026c7.60301 7.091023 13.362866 15.59001 17.330767 25.496963s5.913452 20.556286 5.913452 31.948001c0 10.188545-1.510362 21.19627-4.505488 32.997575-2.611135 10.39334-6.323042 20.505087-11.18692 30.309642s-10.546936 18.994725-17.100373 27.59611-13.849254 16.460388-21.887452 23.551412-16.613985 13.20927-25.727357 18.303542-18.610735 9.062173-28.492088 11.929302-19.941901 4.275093-30.156046 4.275093c-9.497363 0-18.021949-1.459164-25.573761-4.351891s-13.951651-6.988625-19.19952-12.287693-9.241369-11.59651-12.006099-18.892328-4.121497-15.410815-4.121497-24.293793c0-3.788705 0.230394-7.628609 0.665583-11.468513s1.151971-7.807805 2.150346-11.801305z m25.343367 10.060549a63.230419 63.230419 0 0 0-1.638359 13.490862c0 5.60626 0.972776 10.521337 2.918327 14.770831s4.710282 7.807805 8.242994 10.649334 7.807805 4.991875 12.748481 6.451039 10.367741 2.175946 16.281193 2.175945c6.988625 0 14.130847-1.100772 21.375465-3.302317s14.335642-5.401465 21.22187-9.59976 13.542061-9.241369 19.941901-15.154821 12.313292-12.594885 17.765956-20.095498 10.290943-15.641209 14.540437-24.447389 7.679808-18.149946 10.265343-28.056898c0.998375-3.60951 1.715157-7.347016 2.175946-11.238119s0.665583-7.654209 0.665583-11.238119c0-5.811055-0.639984-11.340516-1.945551-16.639584s-3.455914-10.00935-6.451039-14.105248-6.937427-7.372616-11.852504-9.830154-10.905327-3.686308-17.99635-3.686308c-0.998375 0-1.766356 0.435189-2.329542 1.279968s-1.126372 1.919952-1.715157 3.225519c-0.511987 1.100772-1.075173 2.252744-1.715157 3.455914a11.878103 11.878103 0 0 1-6.451039 5.555061 15.794805 15.794805 0 0 1-5.785455 0.895978c-1.689558 0-3.19992-0.38399-4.505487-1.126372s-2.611135-1.766356-3.891103-3.071923l-1.510362-1.433564a1.971151 1.971151 0 0 0-1.356766-0.537587c-0.895978 0-2.073548 0.40959-3.532712 1.20317s-2.943926 1.740756-4.505487 2.841529-3.046324 2.252744-4.505488 3.455914-2.585535 2.252744-3.379115 3.148721c-6.80943 6.988625-13.567661 14.617235-20.325892 22.885828s-12.978876 16.767581-18.661934 25.57376-10.674933 17.663558-14.924427 26.623335-7.270218 17.561161-9.16457 25.880953zM760.083398 546.495138c0.588785-2.611135 1.356766-5.375866 2.252744-8.319792s2.099148-5.657459 3.609509-8.089398 3.455914-4.505487 5.862254-6.143847 5.452664-2.483138 9.138971-2.483137c3.507112 0 6.630234 0.972776 9.369366 2.918327s5.222269 3.916702 7.423814 5.913452c2.611135 1.689558 5.094273 5.145471 7.500613 10.342141s4.863878 11.59651 7.423814 19.19952 5.299068 16.204395 8.242994 25.804155 6.195045 19.660308 9.753356 30.156046 7.526212 21.221869 11.929302 32.178396 9.394965 21.631459 15.001225 32.024799l3.148721 5.631859a95.076023 95.076023 0 0 0 7.142222 10.879728c1.049574 1.305567 1.817555 1.945551 2.329542 1.945552 0.307192 0 0.639984-0.742381 1.049573-2.252744s0.844779-3.302317 1.356767-5.401465l1.58716-6.451039a59.902502 59.902502 0 0 1 1.58716-5.401465 476.736882 476.736882 0 0 0 13.721257-37.426264c2.739132-8.447789 5.785455-18.482738 9.062174-30.079248a1560.127397 1560.127397 0 0 0 14.182045-54.526637c1.151971-4.966276 2.252744-10.00935 3.302317-15.154821s1.971151-10.162946 2.764731-15.078023 1.356766-9.138972 1.638359-12.748481a77.540461 77.540461 0 0 1 0.691183-8.242994c0.255994-1.894353 0.563186-3.865503 0.972776-5.913452s0.921577-3.916702 1.58716-5.63186 1.484763-3.097523 2.483138-4.198295 2.201545-1.638359 3.60951-1.638359 3.123122 0.204795 5.171071 0.588786 3.916702 0.998375 5.631859 1.791955c1.894353 2.611135 3.635109 5.734257 5.17107 9.369366s2.329542 8.575786 2.329542 14.77083c0 3.891103-0.40959 8.575786-1.20317 14.02845s-1.817555 11.289318-3.071923 17.561161a634.864128 634.864128 0 0 1-8.934177 38.245443c-1.61276 6.041449-3.123122 11.59651-4.582285 16.639584s-2.662333 9.21577-3.686308 12.518087c-1.791955 5.811055-3.686308 11.775706-5.631859 17.919552s-4.095898 12.594885-6.451039 19.353117-4.889478 13.900452-7.654208 21.452263-5.836654 15.61561-9.21577 24.216995c-1.510362 3.891103-3.225519 7.807805-5.171071 11.698907s-4.172696 7.423814-6.681433 10.572536-5.324667 5.708657-8.473388 7.654209-6.681433 2.918327-10.572536 2.918327c-2.303942 0-4.684683-0.332792-7.116622-0.972776s-4.531087-1.971151-6.220644-3.967901c-8.191795-9.497363-15.487613-20.274693-21.887453-32.331991s-12.134097-24.652184-17.17717-37.810255-9.522962-26.444139-13.414065-39.909402-7.449414-26.316142-10.649334-38.629435l-0.895978-3.455913c-0.40959-1.407965-0.767981-2.81593-1.126371-4.275093s-0.691183-2.764731-1.049574-3.967901-0.742381-2.175946-1.20317-2.918327-0.972776-1.126372-1.58716-1.126372-1.356766 1.100772-2.252744 3.302317-1.791955 4.684683-2.687933 7.423815-1.715157 5.427064-2.483138 8.012599l-1.433564 4.940677c-0.691183 2.611135-1.740756 6.323042-3.148721 11.18692l-4.735882 16.434789c-1.766356 6.118247-3.660708 12.543686-5.708657 19.353117l-5.913452 19.737106a1165.026874 1165.026874 0 0 1-12.902078 40.728582l-3.891102 12.364491c-1.279968 4.147096-2.508737 7.935802-3.60951 11.314917s-1.791955 5.657459-2.099148 6.758231c-1.20317 3.788705-2.969526 6.681433-5.324667 8.626984s-4.735882 2.918327-7.116622 2.918327c-1.510362 0-2.995125-0.537587-4.505487-1.58716s-2.81593-2.329542-3.967901-3.814305-2.099148-3.123122-2.841529-4.863878-1.126372-3.327917-1.126372-4.735882c0-0.79358 0.255994-2.073548 0.742382-3.814304s1.305567-4.735882 2.40634-8.934177l3.891102-15.462013 44.338092-143.638009z" fill="#FFFFFF" p-id="1414"></path></svg>
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699933658783" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1443" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.621959 0c-44.79888 0-79.998 36.81188-79.998 81.61076v860.77848c0 44.79888 35.19912 81.61076 79.998 81.61076h732.781681a81.969151 81.969151 0 0 0 81.61076-81.61076V324.80468L657.60916 0h-511.987201z" fill="#25B39E" p-id="1444"></path><path d="M657.60916 0v233.59416c0 25.59936 17.61236 92.79768 97.61036 92.79768h204.79488L657.60916 0z" fill="#FFFFFF" p-id="1445"></path><path d="M158.498438 694.485038c0-3.507112 0.511987-7.19342 1.510362-11.110122 3.404715-13.59326 7.372616-26.930527 11.929302-39.986201s8.678183-26.034549 12.36449-38.936626 6.78383-23.781805 9.21577-32.613585 4.633484-16.460388 6.527837-22.80903 3.660708-11.826904 5.247869-16.434789 3.353516-9.138972 5.247868-13.644459c1.20317-2.687933 2.534337-5.503862 3.967901-8.39659s3.020724-5.58066 4.735882-8.0126 3.481513-4.454289 5.324667-5.99025 3.763106-2.329542 5.785455-2.329542c4.095898 0 7.21902 0.665583 9.369366 2.02235s3.481513 2.585535 3.9935 3.686308l1.356766 6.143846c1.510362-0.102397 3.455914-0.38399 5.862254-0.819179s4.81268-0.921577 7.270218-1.433565 4.735882-0.998375 6.835029-1.510362 3.711907-0.947176 4.81268-1.356766c6.604635-1.791955 12.697283-3.353516 18.303542-4.659083s10.930927-2.38074 15.974001-3.22552 9.881353-1.484763 14.463638-1.868753 9.241369-0.588785 13.951651-0.588785c4.607885 0 8.345391 0.255994 11.238119 0.742381s5.19667 1.177571 6.911828 2.02235 2.867128 1.843154 3.532711 2.995125 0.972776 2.38074 0.972776 3.686308c0 1.20317-0.332792 2.483138-0.972776 3.814304s-1.715157 2.636734-3.225519 3.814305-3.455914 2.252744-5.862254 3.148721-5.401465 1.510362-9.010974 1.791955c-0.895978 0.204795-2.969526 0.563186-6.220645 1.126372l-10.572535 1.791955-10.80293 1.868754a129.60956 129.60956 0 0 0-7.039824 1.356766h0.153596c-0.588785 0.102397-2.022349 0.486388-4.275093 1.126372s-4.966276 1.407965-8.166196 2.252743-6.681433 1.817555-10.41894 2.918327l-11.110122 3.22552a687.163621 687.163621 0 0 0-17.637959 5.324667c-6.092648 2.40634-10.879728 5.58066-14.335642 9.522961s-6.169446 8.370991-8.166195 13.286068c-0.40959 0.895978-0.998375 2.431939-1.791956 4.582286l-2.636734 7.347016-3.071923 8.780581-3.148721 8.857378c-0.998375 2.81593-1.868753 5.350266-2.636734 7.654209s-1.279968 3.9935-1.587161 5.094272c-0.307192 0.998375-0.511987 1.894353-0.588785 2.687933s-0.153596 1.407965-0.153596 1.791955c0 0.588785 0.153596 0.895978 0.460788 0.895978 1.510362 0 4.633484-0.665583 9.369366-2.022349s10.290943-3.020724 16.639584-5.017475l20.40269-6.451039c7.244619-2.303942 14.207645-4.454289 20.837879-6.451038s12.569286-3.686308 17.765956-5.017475 8.959776-2.022349 11.238119-2.022349c3.507112 0 5.862253 0.563186 7.039824 1.715157s1.791955 2.431939 1.791955 3.814304c0 3.60951-1.279968 6.758231-3.814304 9.446164s-5.734257 5.043074-9.522962 7.039824c-1.20317 0.588785-3.430314 1.484763-6.681433 2.636734s-6.758231 2.329542-10.495738 3.532712l-10.726132 3.455914-7.347016 2.406339c-9.804555 3.788705-17.765956 6.937427-23.935402 9.369366s-11.135722 4.454289-14.924427 5.99025l-8.78058 3.532712c-2.047949 0.81918-3.942301 1.484763-5.708657 2.02235s-3.686308 1.075173-5.785456 1.58716-4.940676 1.254369-8.652583 2.150346c-0.895978 2.099148-1.945551 5.273468-3.148722 9.522962s-2.355141 8.652584-3.455913 13.20927-2.022349 8.78058-2.764731 12.671683-1.126372 6.604635-1.126372 8.089398c0 1.791955 0.255994 3.123122 0.742381 3.967901s1.100772 1.433564 1.791956 1.715157 1.407965 0.460788 2.099147 0.460788h1.638359c4.81268 0 10.572536-0.691183 17.330767-2.099147s13.414065-3.251119 20.018699-5.555061c6.297443-2.201545 11.801305-4.607885 16.511588-7.193421s9.113372-5.017475 13.286068-7.270218 8.191795-4.147096 12.159696-5.708657 8.268593-2.38074 12.978875-2.483138c0.998375 0 2.175946 0.102397 3.532712 0.307192s2.636734 0.563186 3.814304 1.126372 2.175946 1.279968 2.918327 2.175946 1.023974 2.047949 0.81918 3.455913c-0.40959 2.892728-1.843154 5.759856-4.351891 8.550186s-5.657459 5.503862-9.446164 8.089398-8.038199 5.094273-12.748481 7.500613-9.497363 4.684683-14.41244 6.835029-9.702157 4.095898-14.41244 5.862253-8.908577 3.276718-12.594885 4.582286c-9.19017 3.19992-18.149946 5.555061-26.853729 7.039824s-16.460388 2.252744-23.372215 2.252743c-7.60301 0-13.414065-1.433564-17.484363-4.275093s-6.067048-7.577411-6.067048-14.182045zM336.541986 699.579311a14.489238 14.489238 0 0 1-13.721257 1.638359c-1.356766-0.691183-2.508737-1.61276-3.455913-2.764731a12.902077 12.902077 0 0 1-2.918327-8.319792c0-2.099148 0.435189-3.865503 1.279968-5.324667s1.894353-2.687933 3.148721-3.737507 2.611135-1.919952 4.044699-2.636734 2.764731-1.305567 3.967901-1.791955c3.9935-2.611135 8.498988-5.811055 13.490863-9.59976s10.137347-7.884603 15.462013-12.236494 10.572536-8.78058 15.820404-13.286068l14.924427-12.902077 6.758231-5.990251a121.59696 121.59696 0 0 0-13.59326-31.128821l-5.017474-8.166196-3.686308-5.990251a7.782205 7.782205 0 0 1-1.279968-4.198295 14.258844 14.258844 0 0 1 7.116622-11.340516 11.417315 11.417315 0 0 1 5.785455-1.433564c3.19992 0 6.01585 0.665583 8.473388 2.022349s4.633484 3.071923 6.527837 5.171071 3.558311 4.377491 4.940677 6.835029 2.662333 4.78708 3.737506 6.963026c0.588785 1.20317 1.254369 2.79033 1.945552 4.81268s1.382365 4.121497 2.022349 6.37424l1.868753 6.604635 1.510363 5.631859c4.40309-4.198295 8.678183-8.601385 12.825279-13.209269a587.556511 587.556511 0 0 0 18.892328-22.194646c1.740756-2.201545 3.58391-4.223894 5.478263-6.067048s3.839904-3.379116 5.862253-4.582285 4.147096-1.791955 6.451039-1.791955c3.891103 0 6.681433 0.998375 8.319792 2.995125s2.483138 4.607885 2.483138 7.807804c0 3.404715-0.614385 6.527837-1.868753 9.369366s-2.867128 5.068673-4.863879 6.681433c-2.892728 2.303942-6.067048 5.068673-9.522962 8.319792s-6.937427 6.681433-10.495737 10.265344l-10.572536 10.649333c-3.507112 3.507112-6.707032 6.655834-9.59976 9.446164-1.20317 1.100772-2.227144 2.201545-3.071923 3.302318s-1.279968 2.508737-1.279968 4.198295c0 0.998375 0.281593 2.252744 0.819179 3.737506s1.254369 3.097523 2.099148 4.81268l2.559936 5.171071c0.870378 1.740756 1.58716 3.327917 2.175945 4.735881 1.20317 2.687933 2.867128 5.887853 5.017475 9.59976s4.300692 7.577411 6.451039 11.62211 4.0191 8.166196 5.631859 12.364491 2.40634 8.191795 2.40634 12.006099a20.761081 20.761081 0 0 1-3.737507 12.082898c-1.075173 1.561561-2.329542 2.764731-3.686308 3.686308s-2.636734 1.356766-3.814304 1.356766c-2.611135 0-4.684683-0.307192-6.220645-0.895977s-2.867128-1.510362-3.967901-2.687933-2.022349-2.662333-2.764731-4.428689-1.58716-3.763106-2.483137-6.067049c-1.305567-3.711907-2.995125-7.961401-5.094273-12.748481s-4.275093-9.548561-6.527837-14.258844-4.377491-9.062173-6.374241-13.132471-3.558311-7.116622-4.659083-9.21577l-8.626984 6.835029-11.468514 9.062174-17.919552 14.105247c-7.244619 5.708657-16.588385 12.953276-28.0057 21.861854zM490.752531 646.921427a158.588035 158.588035 0 0 1 22.041049-48.510787c5.299068-7.833404 11.314917-15.103622 18.073148-21.759456s14.130847-12.082898 22.117847-16.281193c2.611135-1.407965 5.452664-2.457539 8.550187-3.148722s6.246244-1.049574 9.446163-1.049573c3.302317 0 6.374241 0.307192 9.21577 0.895977s5.299068 1.61276 7.347016 2.995125 3.686308 3.19992 4.863879 5.401465 1.791955 4.889478 1.791955 8.089398c0 2.40634-0.358391 4.81268-1.049574 7.270218s-1.740756 4.659084-3.148721 6.604635-3.148721 3.532712-5.247869 4.735882-4.556686 1.791955-7.347016 1.791955c-2.201545 0-4.223894-0.40959-6.067048-1.20317a47.102822 47.102822 0 0 1-7.884603-4.275093 19.916302 19.916302 0 0 0-2.841529-1.279968c-3.9935 1.791955-7.782205 4.172696-11.314917 7.116622s-6.860628 6.220644-9.906953 9.830154-5.862253 7.449414-8.39659 11.545312-4.838279 8.191795-6.911827 12.287693-3.788705 8.114997-5.247869 12.082897-2.636734 7.577411-3.532712 10.879728c-0.691183 2.40634-1.331167 5.119872-1.868753 8.166196s-0.81918 6.01585-0.819179 8.934177c0 1.99675 0.179196 3.891103 0.537586 5.708657s0.972776 3.404715 1.868753 4.81268 2.099148 2.508737 3.60951 3.302317 3.353516 1.20317 5.555061 1.20317c3.507112 0 6.963026-0.460788 10.41894-1.356766s6.835029-2.047949 10.137346-3.455913 6.527837-3.020724 9.676559-4.863879a314.129747 314.129747 0 0 0 14.924426-9.369366c1.919952-1.279968 3.711907-2.457539 5.401465-3.455913s3.276718-1.817555 4.735882-2.483138 2.713532-0.972776 3.814305-0.972776c1.510362 0 2.892728 0.38399 4.198295 1.126372s1.945551 2.022349 1.945551 3.814305a12.79968 12.79968 0 0 1-0.895978 4.351891c-1.407965 3.891103-3.302317 7.398215-5.708657 10.495738s-5.068673 5.862253-8.0126 8.319792-6.092648 4.633484-9.446163 6.527836-6.681433 3.660708-9.983751 5.247869c-6.195045 3.302317-12.466888 5.785455-18.815529 7.423815s-11.673308 2.483138-15.974001 2.483137c-1.407965 0-3.046324-0.153596-4.940677-0.460788s-3.558311-0.742381-4.940676-1.356766c-8.089398-3.507112-14.00285-8.191795-17.689158-14.105247s-5.555061-12.902077-5.555061-20.991476c0-3.507112 0.281593-7.167821 0.81918-11.033324s1.356766-7.807805 2.457538-12.031699zM618.698133 647.689408c0-4.300692 1.151971-7.961401 3.455913-10.956526s5.350266-4.991875 9.138972-5.990251a138.287743 138.287743 0 0 1 13.644459-21.17067c3.404715-4.40309 7.270218-8.831779 11.622109-13.286068s9.138972-8.498988 14.41244-12.159696 10.905327-6.630234 16.946776-8.934177 12.466888-3.455914 19.276318-3.455913c5.811055 0 10.854129 0.691183 15.154821 2.099147s7.859004 3.353516 10.649334 5.862254 4.889478 5.478263 6.297443 8.934176 2.099148 7.167821 2.099147 11.186921c0 3.404715-0.460788 6.655834-1.356766 9.753356-1.407965 5.60626-3.9935 10.495738-7.807805 14.694032s-8.268593 7.884603-13.414064 11.033325-10.751731 5.785455-16.793181 7.884602-12.108497 3.839904-18.149946 5.247869-11.878103 2.483138-17.484363 3.22552-10.444539 1.331167-14.540436 1.715157c-0.895978 1.791955-1.663958 4.070298-2.329542 6.835029s-0.972776 5.427064-0.972776 8.012599c0 5.811055 2.303942 10.290943 6.911828 13.490863s10.956526 4.81268 19.045923 4.81268c4.710282 0 9.19017-0.537587 13.490863-1.58716s8.370991-2.40634 12.236494-4.044699 7.475013-3.455914 10.879728-5.401465 6.502237-3.865503 9.292568-5.785456c3.60951-2.40634 7.014225-4.428689 10.265343-6.067048s6.118247-2.483138 8.626985-2.483138 4.095898 0.614385 4.812679 1.868753 1.049574 2.585535 1.049574 3.967901a34.251944 34.251944 0 0 1-0.307192 4.198295 25.804155 25.804155 0 0 1-8.703783 14.566036c-10.39334 8.191795-21.068273 14.054049-32.024799 17.561161s-22.629834 5.247869-35.019925 5.247869c-2.40634 0-5.222269-0.153596-8.473388-0.460789s-6.630234-0.895978-10.137346-1.791955-6.937427-2.201545-10.342142-3.891103a31.538412 31.538412 0 0 1-15.513212-17.100372 40.037399 40.037399 0 0 1-2.483138-14.847629c0-4.710282 0.639984-9.958151 1.945552-15.743606-1.305567-0.204795-2.534337-0.870378-3.686308-2.02235s-1.715157-2.81593-1.715157-5.017474z m93.898452-41.547762c0.204795-0.511987 0.307192-0.947176 0.307192-1.356766v-1.356766c0-2.687933-0.998375-4.735882-2.995125-6.067048s-4.40309-2.022349-7.19342-2.022349c-4.40309 0-8.729382 1.151971-12.978875 3.455913s-8.242994 5.171071-12.0061 8.626984-7.065423 7.142221-9.983751 11.110123-5.145471 7.526212-6.758231 10.726132c7.19342-0.588785 13.798055-1.58716 19.788306-2.918327s11.21252-3.046324 15.666808-5.094273 8.038199-4.351891 10.80293-6.911827 4.556686-5.299068 5.350266-8.191796zM818.347541 499.929902c0.511987-3.9935 1.305567-7.705407 2.40634-11.110122s2.585535-6.220644 4.428689-8.473389 4.223894-3.379116 7.116623-3.379115c1.61276 0 3.379116 0.307192 5.324666 0.895977s3.788705 1.433564 5.555062 2.483138 3.225519 2.329542 4.428689 3.814305 1.791955 3.148721 1.791955 4.940676c0 0.79358-0.153596 1.715157-0.460788 2.764731s-0.537587 2.073548-0.742382 3.071924l-37.503062 150.293842c-1.510362 5.99025-2.739132 11.724507-3.737507 17.177171s-1.740756 9.932552-2.252744 13.414064v3.60951c0 2.995125-0.204795 6.271843-0.588785 9.830154s-1.151971 6.860628-2.252744 9.906953-2.636734 5.60626-4.582285 7.654208-4.479888 3.071923-7.577411 3.071924c-2.687933 0-5.043074-0.563186-7.039824-1.715158s-3.660708-2.636734-4.940676-4.428689-2.252744-3.737507-2.841529-5.785455-0.895978-3.967901-0.895978-5.785456c0-1.61276 0.153596-3.58391 0.460789-5.913452s0.665583-4.81268 1.126372-7.347016 0.921577-5.094273 1.433564-7.654209 0.998375-4.78708 1.510362-6.681433l38.706232-153.59616a193.275168 193.275168 0 0 1 1.126372-11.058923z" fill="#FFFFFF" p-id="1446"></path></svg>
@@ -191,6 +191,10 @@ @@ -191,6 +191,10 @@
191 <slot name="afterFullScreen"></slot> 191 <slot name="afterFullScreen"></slot>
192 </div> 192 </div>
193 </div> 193 </div>
194 - <div ref="jsonEditorElRef" class="flex-auto"></div> 194 + <div
  195 + ref="jsonEditorElRef"
  196 + class="flex-auto"
  197 + :style="{ backgroundColor: disabled ? '#f0f0f0' : '' }"
  198 + ></div>
195 </div> 199 </div>
196 </template> 200 </template>
@@ -47,6 +47,7 @@ @@ -47,6 +47,7 @@
47 isClose: { type: Boolean, default: true }, 47 isClose: { type: Boolean, default: true },
48 title: { type: String, default: '' }, 48 title: { type: String, default: '' },
49 loading: { type: Boolean }, 49 loading: { type: Boolean },
  50 + defaultExpand: { type: Boolean, default: true },
50 /** 51 /**
51 * Can it be expanded 52 * Can it be expanded
52 */ 53 */
@@ -75,7 +76,7 @@ @@ -75,7 +76,7 @@
75 76
76 const emit = defineEmits(['expand', 'change', 'hchange']); 77 const emit = defineEmits(['expand', 'change', 'hchange']);
77 78
78 - const show = ref(true); 79 + const show = ref(props.defaultExpand);
79 80
80 const { prefixCls } = useDesign('collapse-container'); 81 const { prefixCls } = useDesign('collapse-container');
81 82
@@ -15,6 +15,8 @@ export { default as ApiUpload } from './src/components/ApiUpload.vue'; @@ -15,6 +15,8 @@ export { default as ApiUpload } from './src/components/ApiUpload.vue';
15 export { default as StructForm } from './src/externalCompns/components/StructForm/StructForm.vue'; 15 export { default as StructForm } from './src/externalCompns/components/StructForm/StructForm.vue';
16 export { default as JavaScriptFunctionEditor } from './src/components/JavaScriptFunctionEditor.vue'; 16 export { default as JavaScriptFunctionEditor } from './src/components/JavaScriptFunctionEditor.vue';
17 17
  18 +export { ThingsModelForm } from './src/externalCompns/components/ThingsModelForm';
  19 +
18 //注册自定义组件 20 //注册自定义组件
19 export { 21 export {
20 JEasyCron, 22 JEasyCron,
@@ -69,7 +69,7 @@ @@ -69,7 +69,7 @@
69 name: 'BasicForm', 69 name: 'BasicForm',
70 components: { FormItem, Form, Row, FormAction }, 70 components: { FormItem, Form, Row, FormAction },
71 props: basicProps, 71 props: basicProps,
72 - emits: ['advanced-change', 'reset', 'submit', 'register'], 72 + emits: ['advanced-change', 'reset', 'submit', 'register', 'fieldValueChange'],
73 setup(props, { emit, attrs }) { 73 setup(props, { emit, attrs }) {
74 const formModel = reactive<Recordable>({}); 74 const formModel = reactive<Recordable>({});
75 const modalFn = useModalContext(); 75 const modalFn = useModalContext();
@@ -230,6 +230,7 @@ @@ -230,6 +230,7 @@
230 230
231 function setFormModel(key: string, value: any) { 231 function setFormModel(key: string, value: any) {
232 formModel[key] = value; 232 formModel[key] = value;
  233 + emit('fieldValueChange', key, value);
233 const { validateTrigger } = unref(getBindValue); 234 const { validateTrigger } = unref(getBindValue);
234 if (!validateTrigger || validateTrigger === 'change') { 235 if (!validateTrigger || validateTrigger === 'change') {
235 validateFields([key]).catch((_) => {}); 236 validateFields([key]).catch((_) => {});
@@ -4,77 +4,102 @@ @@ -4,77 +4,102 @@
4 <!-- 待完善封装InputGroup --> 4 <!-- 待完善封装InputGroup -->
5 <InputGroup compact> 5 <InputGroup compact>
6 <Select 6 <Select
7 - v-if="type !== '2'" 7 + v-if="type !== RequestMethodTypeEnum.WEBSOCKET"
8 placeholder="请求类型" 8 placeholder="请求类型"
9 - :style="{ width: type !== '2' ? 15 + '%' : 0 + '%' }"  
10 - v-model:value="valueObj.requestHttpType" 9 + :style="{ width: type !== RequestMethodTypeEnum.WEBSOCKET ? 15 + '%' : 0 + '%' }"
  10 + v-model:value="requestTypeUrlValue.requestHttpType"
11 :options="selectOptions" 11 :options="selectOptions"
12 allowClear 12 allowClear
13 @change="emitChange" 13 @change="emitChange"
14 /> 14 />
15 <Input 15 <Input
16 - @change="emitChange"  
17 placeholder="请输入接口地址" 16 placeholder="请输入接口地址"
18 - v-model:value="valueObj.requestUrl"  
19 - :style="{ width: type !== '2' ? 85 + '%' : 100 + '%' }" 17 + v-model:value="requestTypeUrlValue.requestUrl"
  18 + :style="{ width: type !== RequestMethodTypeEnum.WEBSOCKET ? 85 + '%' : 100 + '%' }"
  19 + @change="emitChange"
20 /> 20 />
21 </InputGroup> 21 </InputGroup>
22 </div> 22 </div>
23 </template> 23 </template>
24 <script lang="ts" setup> 24 <script lang="ts" setup>
25 - import { reactive, ref, watchEffect } from 'vue'; 25 + import { reactive, ref, PropType, watch } from 'vue';
26 import { InputGroup, Select, Input } from 'ant-design-vue'; 26 import { InputGroup, Select, Input } from 'ant-design-vue';
27 - import type { SelectValue } from 'ant-design-vue/lib/select';  
28 import { findDictItemByCode } from '/@/api/system/dict'; 27 import { findDictItemByCode } from '/@/api/system/dict';
29 - import { propTypes } from '/@/utils/propTypes'; 28 + import { RequestMethodTypeEnum } from '/@/views/dataview/publicApi/config/enum';
30 29
31 - type TypeInputGroup = {  
32 - requestHttpType: SelectValue | undefined; 30 + interface requestTypeUrlConfig {
  31 + requestHttpType: undefined;
33 requestUrl?: string; 32 requestUrl?: string;
34 disabled?: boolean; 33 disabled?: boolean;
35 - }; 34 + }
36 35
37 - type selectType = { label: string; value: string; disabled?: boolean }; 36 + type selectType = {
  37 + label: string;
  38 + value: string;
  39 + };
38 40
39 const props = defineProps({ 41 const props = defineProps({
40 type: { 42 type: {
41 type: String, 43 type: String,
  44 + default: RequestMethodTypeEnum.COMMOM,
42 }, 45 },
43 - value: propTypes.object.def({}), 46 + value: Object as PropType<requestTypeUrlConfig>,
44 }); 47 });
45 48
46 const emits = defineEmits(['change', 'update:value']); 49 const emits = defineEmits(['change', 'update:value']);
47 50
48 const selectOptions = ref<selectType[]>([]); 51 const selectOptions = ref<selectType[]>([]);
49 52
50 - const getOptions = async (e) => {  
51 - const res = await findDictItemByCode({  
52 - dictCode: e === '1' ? 'dataview_select_sql_request' : 'dataview_select_request', 53 + const getOptions = async (requestType) => {
  54 + // 暂且排除SQL和WEBSOCKET
  55 + const resItem = await findDictItemByCode({
  56 + dictCode: 'dataview_select_request',
53 }); 57 });
54 - if (e === '1' || e === '0') {  
55 - selectOptions.value = res.map((m) => ({ label: m.itemText, value: m.itemValue })); 58 + if (requestType === RequestMethodTypeEnum.COMMOM) {
  59 + selectOptions.value = resItem.map((m) => ({ label: m.itemText, value: m.itemValue }));
56 } else { 60 } else {
57 selectOptions.value = []; 61 selectOptions.value = [];
58 } 62 }
59 }; 63 };
60 64
61 - const valueObj = reactive<TypeInputGroup>({ 65 + const requestTypeUrlValue = reactive<requestTypeUrlConfig>({
62 requestHttpType: undefined, 66 requestHttpType: undefined,
63 requestUrl: '', 67 requestUrl: '',
64 }); 68 });
65 69
66 - watchEffect(() => {  
67 - initVal();  
68 - }); 70 + watch(
  71 + () => props.type,
  72 + () => {
  73 + initOption();
  74 + },
  75 + {
  76 + immediate: true,
  77 + }
  78 + );
  79 +
  80 + watch(
  81 + () => props.value,
  82 + () => {
  83 + initConfig();
  84 + },
  85 + {
  86 + immediate: true,
  87 + }
  88 + );
  89 +
  90 + function initOption() {
  91 + if (props.type) {
  92 + getOptions(props.type);
  93 + }
  94 + }
69 95
70 - async function initVal() {  
71 - if (props?.type) await getOptions(props?.type);  
72 - if (props?.value) for (let i in props.value) Reflect.set(valueObj, i, props.value[i]); 96 + function initConfig() {
  97 + if (props?.value)
  98 + for (let i in props.value) Reflect.set(requestTypeUrlValue, i, props.value[i]);
73 } 99 }
74 100
75 function emitChange() { 101 function emitChange() {
76 - emits('change', valueObj);  
77 - emits('update:value', valueObj); 102 + emits('change', requestTypeUrlValue);
  103 + emits('update:value', requestTypeUrlValue);
78 } 104 }
79 </script> 105 </script>
80 -<style scoped></style>  
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { Button, Transfer, Tag } from 'ant-design-vue'; 2 + import { Transfer, Select } from 'ant-design-vue';
3 import { cloneDeep, get } from 'lodash-es'; 3 import { cloneDeep, get } from 'lodash-es';
4 import { computed, CSSProperties, ExtractPropTypes, onMounted, ref, unref, watch } from 'vue'; 4 import { computed, CSSProperties, ExtractPropTypes, onMounted, ref, unref, watch } from 'vue';
5 import { BasicModal, useModal } from '/@/components/Modal'; 5 import { BasicModal, useModal } from '/@/components/Modal';
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 buttonName?: string; 29 buttonName?: string;
30 modalProps?: ExtractPropTypes<InstanceType<typeof BasicModal>['$props']>; 30 modalProps?: ExtractPropTypes<InstanceType<typeof BasicModal>['$props']>;
31 transferProps?: ExtractPropTypes<TransferType['$props']>; 31 transferProps?: ExtractPropTypes<TransferType['$props']>;
32 - buttonProps?: ExtractPropTypes<InstanceType<typeof Button>['$props']>; 32 + selectProps?: ExtractPropTypes<InstanceType<typeof Select>['$props']>;
33 disabled?: any; 33 disabled?: any;
34 }>(), 34 }>(),
35 { 35 {
@@ -52,24 +52,15 @@ @@ -52,24 +52,15 @@
52 return unref(getOptions).filter((item) => unref(targetKeys).includes(item[valueField])); 52 return unref(getOptions).filter((item) => unref(targetKeys).includes(item[valueField]));
53 }); 53 });
54 54
55 - const getShowTagOptions = computed(() => {  
56 - const { maxTagLength } = props;  
57 - return unref(targetOptions).slice(0, maxTagLength); 55 + const getSelectOptions = computed(() => {
  56 + return unref(targetOptions).map((item) => ({ ...item, label: item.title, value: item.key }));
58 }); 57 });
59 58
60 - const getSurplusOptionsLength = computed(() => {  
61 - const { maxTagLength } = props;  
62 - const surplusValue = unref(targetKeys).length - maxTagLength;  
63 - return surplusValue < 0 ? 0 : surplusValue;  
64 - });  
65 -  
66 - const count = computed(() => {  
67 - return unref(targetKeys).length;  
68 - }); 59 + const getSelectValue = computed(() => unref(getSelectOptions).map((item) => item.value));
69 60
70 const targetKeys = ref<string[]>(props.value || []); 61 const targetKeys = ref<string[]>(props.value || []);
71 62
72 - const getOptions = computed<Recordable[]>(() => { 63 + const getOptions = computed<Record<'key' | 'title', string>[]>(() => {
73 const { labelField, valueField } = props; 64 const { labelField, valueField } = props;
74 return unref(options).map((item) => ({ 65 return unref(options).map((item) => ({
75 ...item, 66 ...item,
@@ -99,13 +90,14 @@ @@ -99,13 +90,14 @@
99 }; 90 };
100 }); 91 });
101 92
102 - const getBindButtonProps = computed(() => {  
103 - const { buttonProps = {} } = props; 93 + const getBindSelectProps = computed(() => {
  94 + const { selectProps = {} } = props;
104 return { 95 return {
105 - ...buttonProps,  
106 - type: 'link',  
107 - onClick: handleOpenModal,  
108 - } as ExtractPropTypes<InstanceType<typeof Button>['$props']>; 96 + maxTagCount: 3,
  97 + ...selectProps,
  98 + open: false,
  99 + onDropdownVisibleChange: handleOpenModal,
  100 + } as ExtractPropTypes<InstanceType<typeof Select>['$props']>;
109 }); 101 });
110 102
111 const handleChange = (_targetKeys: string[], direction: 'left' | 'right', moveKeys: string[]) => { 103 const handleChange = (_targetKeys: string[], direction: 'left' | 'right', moveKeys: string[]) => {
@@ -165,22 +157,11 @@ @@ -165,22 +157,11 @@
165 <Transfer v-bind="getBindProps" /> 157 <Transfer v-bind="getBindProps" />
166 </section> 158 </section>
167 </BasicModal> 159 </BasicModal>
168 - <Button v-bind="getBindButtonProps" class="!flex !justify-center !items-center min-h-8">  
169 - <span v-if="!count">{{ $props.buttonName }}</span>  
170 - <div v-if="!!count">  
171 - <Tag  
172 - class="!px-2 !py-1 !bg-gray-50 !border-gray-100"  
173 - v-for="item in getShowTagOptions"  
174 - :key="item.key"  
175 - >  
176 - <span>  
177 - {{ item.title }}  
178 - </span>  
179 - </Tag>  
180 - <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength">  
181 - <span> +{{ getSurplusOptionsLength }}... </span>  
182 - </Tag>  
183 - </div>  
184 - </Button> 160 + <Select
  161 + v-bind="getBindSelectProps"
  162 + :options="getSelectOptions"
  163 + :value="getSelectValue"
  164 + mode="multiple"
  165 + />
185 </div> 166 </div>
186 </template> 167 </template>
1 -<script lang="ts" setup>  
2 - import { Button, Tabs, Badge, ButtonProps, Tag } from 'ant-design-vue';  
3 - import { get, isFunction, set, uniqBy } from 'lodash-es';  
4 - import { ExtractPropTypes, computed, unref, ref, nextTick, onMounted, watch } from 'vue';  
5 - import { DynamicProps } from '/#/utils';  
6 - import { BasicModal, useModal } from '/@/components/Modal';  
7 - import { BasicTable, BasicTableProps, TableRowSelection, useTable } from '/@/components/Table';  
8 - import { FETCH_SETTING } from '/@/components/Table/src/const';  
9 - import { useDesign } from '/@/hooks/web/useDesign';  
10 -  
11 - interface Options extends Recordable {  
12 - primaryKey?: string;  
13 - disabled?: boolean;  
14 - }  
15 -  
16 - enum Active {  
17 - PENDING = 'pending',  
18 - SELECTED = 'selected',  
19 - }  
20 -  
21 - interface ActionType {  
22 - setSelectedOptions: (options: Recordable[]) => void;  
23 - }  
24 -  
25 - const emit = defineEmits(['change', 'update:value']);  
26 -  
27 - const props = withDefaults(  
28 - defineProps<{  
29 - value?: string[];  
30 - labelField?: string;  
31 - valueField?: string;  
32 - primaryKey?: string;  
33 - params?: Recordable;  
34 - buttonName?: string;  
35 - pendingTableProps?: BasicTableProps;  
36 - selectedTableProps?: BasicTableProps;  
37 - maxTagLength?: number;  
38 - modalProps?: ExtractPropTypes<InstanceType<typeof BasicModal>['$props']>;  
39 - buttonProps?: ExtractPropTypes<InstanceType<typeof Button>['$props']>;  
40 - initSelectedOptions?: (actionType: ActionType) => Promise<Recordable[]>;  
41 - transformValue?: (selectedRowKeys: string[], selectedRows: Options[]) => any[];  
42 - onValueChange?: (selectedRowkeys: string[]) => any[];  
43 - onRemoveAfter?: (actionType: ActionType) => Promise<any>;  
44 - onSelectedAfter?: (actionType: ActionType) => Promise<any>;  
45 - disabled?: any;  
46 - }>(),  
47 - {  
48 - buttonName: '选择设备',  
49 - primaryKey: 'id',  
50 - maxTagLength: 2,  
51 - labelField: 'label',  
52 - valueField: 'value',  
53 - disabled: false,  
54 - }  
55 - );  
56 -  
57 - const { prefixCls } = useDesign('transfer-table-modal');  
58 -  
59 - const activeKey = ref<Active>(Active.PENDING);  
60 -  
61 - const selectedRows = ref<Options[]>([]);  
62 -  
63 - const selectedRowKeys = ref<string[]>(props.value || []);  
64 -  
65 - const pendingOptions = ref<Options[]>([]);  
66 -  
67 - const selectedConfirmQueue = ref<Options[]>([]);  
68 -  
69 - const pendingConfirmQueue = ref<Options[]>([]);  
70 -  
71 - const selectedTotal = ref(0);  
72 -  
73 - const pendingTotal = ref(0);  
74 -  
75 - const getFetchSetting = computed(() => {  
76 - const { pendingTableProps } = props;  
77 - return pendingTableProps?.fetchSetting || FETCH_SETTING;  
78 - });  
79 -  
80 - const getPendingRowSelection = computed<TableRowSelection>(() => {  
81 - const rowKeys = unref(selectedRowKeys);  
82 - return {  
83 - type: 'checkbox',  
84 - getCheckboxProps: (record: Recordable) => {  
85 - const { primaryKey } = props;  
86 - return {  
87 - ...record,  
88 - disabled: rowKeys.includes(record[primaryKey]),  
89 - };  
90 - },  
91 - onSelect: (_record: Recordable, _selected: boolean, selectedRows: Object[]) => {  
92 - pendingConfirmQueue.value = selectedRows;  
93 - },  
94 - onSelectAll: (_selected: boolean, selectedRows: Recordable[]) => {  
95 - pendingConfirmQueue.value = selectedRows;  
96 - },  
97 - };  
98 - });  
99 -  
100 - const getPendingTableBindProps = computed<Partial<DynamicProps<BasicTableProps>>>(() => {  
101 - const { pendingTableProps, primaryKey } = props;  
102 - return {  
103 - ...pendingTableProps,  
104 - rowKey: primaryKey,  
105 - api: handlePendingApiIntercept,  
106 - clickToRowSelect: false,  
107 - rowSelection: getPendingRowSelection,  
108 - };  
109 - });  
110 -  
111 - const getSelectedTableBindProps = computed<Partial<DynamicProps<BasicTableProps>>>(() => {  
112 - const { selectedTableProps, primaryKey } = props;  
113 - return {  
114 - ...selectedTableProps,  
115 - dataSource: selectedRows,  
116 - clickToRowSelect: false,  
117 - rowKey: primaryKey,  
118 - api: selectedTableProps!.api ? handleSelectedApiIntercept : undefined,  
119 - rowSelection: {  
120 - type: 'checkbox',  
121 - onSelect: (_record: Recordable, _selected: boolean, selectedRows: Object[]) => {  
122 - selectedConfirmQueue.value = selectedRows;  
123 - },  
124 - onSelectAll: (_selected: boolean, selectedRows: Recordable[]) => {  
125 - selectedConfirmQueue.value = selectedRows;  
126 - },  
127 - },  
128 - };  
129 - });  
130 -  
131 - const getModalBindProps = computed(() => {  
132 - const { modalProps = {} } = props;  
133 - return {  
134 - width: '60%',  
135 - title: '穿梭表格',  
136 - wrapClassName: prefixCls,  
137 - ...modalProps,  
138 - showOkBtn: false,  
139 - };  
140 - });  
141 -  
142 - const getBindButtonProps = computed<ButtonProps>(() => {  
143 - const { buttonProps = {} } = props;  
144 - return {  
145 - type: 'link',  
146 - ...buttonProps,  
147 - };  
148 - });  
149 -  
150 - const getShowTagOptions = computed(() => {  
151 - const { maxTagLength } = props;  
152 - return unref(selectedRows).slice(0, maxTagLength);  
153 - });  
154 -  
155 - const getSurplusOptionsLength = computed(() => {  
156 - const { maxTagLength } = props;  
157 - const surplusValue = unref(selectedRows).length - maxTagLength;  
158 - return surplusValue < 0 ? 0 : surplusValue;  
159 - });  
160 -  
161 - const [registerModal, { openModal }] = useModal();  
162 -  
163 - const [  
164 - regsterPendingTable,  
165 - {  
166 - getSelectRows: getPendingSelectRows,  
167 - getSelectRowKeys: getPendingSelectRowKeys,  
168 - reload: reloadPending,  
169 - clearSelectedRowKeys: clearPendingSelectedRowKeys,  
170 - },  
171 - ] = useTable(unref(getPendingTableBindProps));  
172 -  
173 - const [  
174 - registerSelectedTable,  
175 - { getSelectRowKeys, setProps, clearSelectedRowKeys, reload: reloadSelected },  
176 - ] = useTable(unref(getSelectedTableBindProps));  
177 -  
178 - async function handlePendingApiIntercept(params?: Recordable) {  
179 - try {  
180 - const { api } = props.pendingTableProps || {};  
181 - if (api && isFunction(api)) {  
182 - let options = await api(params);  
183 - pendingOptions.value = options;  
184 - const { totalField, listField } = unref(getFetchSetting);  
185 - const total = get(options, totalField!);  
186 - if (unref(selectedTotal) + unref(pendingTotal) !== total) {  
187 - pendingTotal.value = total;  
188 - }  
189 - let list: Recordable[] = get(options, listField!);  
190 - list = getSelectedRows(list);  
191 - options = set(options, listField!, list);  
192 - return options;  
193 - }  
194 - } catch (error) {  
195 - console.error(error);  
196 - return [];  
197 - }  
198 - return [];  
199 - }  
200 -  
201 - async function handleSelectedApiIntercept(params?: Recordable) {  
202 - try {  
203 - const { api } = props.selectedTableProps || {};  
204 - if (api && isFunction(api)) {  
205 - let options = await api(params);  
206 - pendingOptions.value = options;  
207 - const { totalField, listField } = unref(getFetchSetting);  
208 - selectedTotal.value = get(options, totalField!);  
209 - let list: Recordable[] = get(options, listField!);  
210 - list = getSelectedRows(list);  
211 - options = set(options, listField!, list);  
212 - return options;  
213 - }  
214 - } catch (error) {  
215 - console.error(error);  
216 - return [];  
217 - }  
218 - return [];  
219 - }  
220 -  
221 - const handleOpenModal = async () => {  
222 - const { disabled } = props;  
223 - if (disabled) return;  
224 - openModal(true);  
225 - await nextTick();  
226 - if (props.value && !props.value.length) {  
227 - activeKey.value = Active.PENDING;  
228 - reloadPending();  
229 - }  
230 - };  
231 -  
232 - const handleTriggerEmit = (selectedRowKeys: string[], selectedRows: Options[]) => {  
233 - const { transformValue } = props;  
234 - let value = selectedRowKeys;  
235 - if (transformValue && isFunction(transformValue)) {  
236 - value = transformValue(selectedRowKeys, selectedRows);  
237 - }  
238 - emit('change', unref(selectedRowKeys), unref(selectedRows));  
239 - emit('update:value', unref(value));  
240 - };  
241 -  
242 - const handleSelected = async () => {  
243 - const { onSelectedAfter } = props;  
244 - const currentPageSelectRows = getPendingSelectRows();  
245 - const currentPageSelectRowKeys = getPendingSelectRowKeys();  
246 - const { primaryKey } = props;  
247 - selectedRows.value = uniqBy([...unref(selectedRows), ...currentPageSelectRows], primaryKey);  
248 - selectedRowKeys.value = [...new Set([...unref(selectedRowKeys), ...currentPageSelectRowKeys])];  
249 - pendingConfirmQueue.value = [];  
250 - // selectedTotal.value = unref(selectedRowKeys).length;  
251 - pendingTotal.value = unref(pendingTotal) - currentPageSelectRows.length;  
252 - selectedTotal.value = unref(selectedTotal) + currentPageSelectRows.length;  
253 -  
254 - clearPendingSelectedRowKeys();  
255 - handleTriggerEmit(unref(selectedRowKeys), unref(selectedRows));  
256 -  
257 - if (onSelectedAfter && isFunction(onSelectedAfter)) {  
258 - await onSelectedAfter(actionType);  
259 - }  
260 - reloadPending();  
261 - };  
262 -  
263 - const handleRemoveSelected = async () => {  
264 - const { onRemoveAfter } = props;  
265 - const removeRowKeys = getSelectRowKeys();  
266 - selectedRowKeys.value = unref(selectedRowKeys).filter((key) => !removeRowKeys.includes(key));  
267 - selectedRows.value = unref(selectedRows).filter((item) => {  
268 - const { primaryKey } = props;  
269 - return unref(selectedRowKeys).includes(item[primaryKey]);  
270 - });  
271 - pendingTotal.value = unref(pendingTotal) + removeRowKeys.length;  
272 - selectedTotal.value = unref(selectedTotal) - removeRowKeys.length;  
273 -  
274 - clearSelectedRowKeys();  
275 - selectedConfirmQueue.value = [];  
276 - setProps({ dataSource: unref(selectedRows) });  
277 - handleTriggerEmit(unref(selectedRowKeys), unref(selectedRows));  
278 -  
279 - if (onRemoveAfter && isFunction(onRemoveAfter)) {  
280 - await onRemoveAfter(actionType);  
281 - }  
282 - };  
283 -  
284 - const actionType = {  
285 - setSelectedOptions,  
286 - setSelectedTotal,  
287 - reloadPending,  
288 - reloadSelected,  
289 - };  
290 -  
291 - const getSelectedRows = (options: Recordable[]) => {  
292 - const { labelField, valueField } = props;  
293 - return options.map((item) => ({ ...item, label: item[labelField], value: item[valueField] }));  
294 - };  
295 -  
296 - const getSelectedKeys = (options: Recordable[]) => {  
297 - const { primaryKey } = props;  
298 - return options.map((item) => item[primaryKey]);  
299 - };  
300 -  
301 - function setSelectedOptions(options: Recordable[]) {  
302 - selectedRows.value = getSelectedRows(options);  
303 - selectedRowKeys.value = getSelectedKeys(options);  
304 - }  
305 -  
306 - function setSelectedTotal(number: number) {  
307 - selectedTotal.value = number;  
308 - }  
309 -  
310 - const handleCheckoutPanel = async (keys: Active) => {  
311 - await nextTick();  
312 - if (keys === Active.PENDING) {  
313 - reloadPending();  
314 - } else {  
315 - reloadSelected();  
316 - setProps({  
317 - dataSource: unref(selectedRows),  
318 - });  
319 - }  
320 - };  
321 -  
322 - watch(  
323 - () => props.value,  
324 - () => {  
325 - if (props.value && !props.value.length) {  
326 - selectedRowKeys.value = [];  
327 - selectedRows.value = [];  
328 - // pendingTotal.value = 0;  
329 - selectedTotal.value = 0;  
330 - }  
331 - }  
332 - );  
333 -  
334 - onMounted(async () => {  
335 - const { initSelectedOptions } = props;  
336 - if (initSelectedOptions && isFunction(initSelectedOptions)) {  
337 - const options = await initSelectedOptions(actionType);  
338 - setSelectedOptions(options);  
339 - }  
340 - });  
341 -</script>  
342 -  
343 -<template>  
344 - <section>  
345 - <BasicModal @register="registerModal" v-bind="getModalBindProps">  
346 - <section class="bg-gray-100">  
347 - <Tabs v-model:active-key="activeKey" type="card" @change="handleCheckoutPanel">  
348 - <Tabs.TabPane :key="Active.PENDING">  
349 - <template #tab>  
350 - <div class="flex items-center justify-center">  
351 - <span>待选设备</span>  
352 - <Badge show-zero :count="pendingTotal" />  
353 - </div>  
354 - </template>  
355 - <BasicTable @register="regsterPendingTable">  
356 - <template #toolbar>  
357 - <section class="flex w-full justify-end items-center">  
358 - <!-- <Button type="primary">全选</Button> -->  
359 - <div class="text-blue-400">  
360 - <span class="mr-2">选择设备:</span>  
361 - <span>{{ pendingConfirmQueue.length }}</span>  
362 - </div>  
363 - </section>  
364 - </template>  
365 - </BasicTable>  
366 - <section class="flex justify-end px-4 pb-4">  
367 - <Button  
368 - type="primary"  
369 - @click="handleSelected"  
370 - :disabled="!pendingConfirmQueue.length"  
371 - >  
372 - <span>确定已选</span>  
373 - </Button>  
374 - </section>  
375 - </Tabs.TabPane>  
376 - <Tabs.TabPane :key="Active.SELECTED">  
377 - <template #tab>  
378 - <div class="flex items-center justify-center">  
379 - <span>已选设备</span>  
380 - <Badge show-zero :count="selectedTotal" />  
381 - </div>  
382 - </template>  
383 - <BasicTable @register="registerSelectedTable">  
384 - <template #toolbar>  
385 - <section class="flex w-full justify-end items-center">  
386 - <!-- <Button type="primary">全选</Button> -->  
387 - <div class="text-blue-400">  
388 - <span class="mr-2">选择设备:</span>  
389 - <span>{{ selectedConfirmQueue.length }}</span>  
390 - </div>  
391 - </section>  
392 - </template>  
393 - </BasicTable>  
394 - <section class="flex justify-end px-4 pb-4">  
395 - <Button  
396 - type="primary"  
397 - :disabled="!selectedConfirmQueue.length"  
398 - @click="handleRemoveSelected"  
399 - >  
400 - <span>移除已选</span>  
401 - </Button>  
402 - </section>  
403 - </Tabs.TabPane>  
404 - </Tabs>  
405 - </section>  
406 - </BasicModal>  
407 - <Button @click="handleOpenModal" v-bind="getBindButtonProps">  
408 - <span v-if="!selectedRowKeys.length">选择设备</span>  
409 - <div v-if="selectedRowKeys.length">  
410 - <Tag  
411 - class="!px-2 !py-1 !bg-gray-50 !border-gray-100"  
412 - v-for="item in getShowTagOptions"  
413 - :key="item.value"  
414 - >  
415 - <span>  
416 - {{ item.alias || item.name }}  
417 - </span>  
418 - </Tag>  
419 - <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength">  
420 - <span> +{{ getSurplusOptionsLength }}... </span>  
421 - </Tag>  
422 - </div>  
423 - </Button>  
424 - </section>  
425 -</template>  
426 -  
427 -<style lang="less">  
428 - @prefix-cls: ~'@{namespace}-transfer-table-modal';  
429 -  
430 - .@{prefix-cls} {  
431 - .vben-basic-table {  
432 - padding-top: 0;  
433 - }  
434 -  
435 - .vben-basic-form > .ant-row {  
436 - width: 100%;  
437 - }  
438 -  
439 - .ant-tabs-top-bar {  
440 - background-color: #fff;  
441 - }  
442 - }  
443 -</style>  
@@ -5,7 +5,8 @@ @@ -5,7 +5,8 @@
5 import { BasicModal } from '/@/components/Modal'; 5 import { BasicModal } from '/@/components/Modal';
6 import { PlusCircleOutlined } from '@ant-design/icons-vue'; 6 import { PlusCircleOutlined } from '@ant-design/icons-vue';
7 import { FormFieldsEnum, formSchemas } from './config'; 7 import { FormFieldsEnum, formSchemas } from './config';
8 - import { DataTypeEnum } from '../StructForm/config'; 8 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
  9 +
9 const show = ref(false); 10 const show = ref(false);
10 11
11 const props = withDefaults( 12 const props = withDefaults(
@@ -50,7 +51,7 @@ @@ -50,7 +51,7 @@
50 updateSchema([ 51 updateSchema([
51 { 52 {
52 field: FormFieldsEnum.ZOOM_FACTOR, 53 field: FormFieldsEnum.ZOOM_FACTOR,
53 - ifShow: props.dataType == DataTypeEnum.IS_BOOL ? false : true, 54 + ifShow: props.dataType == DataTypeEnum.BOOL ? false : true,
54 }, 55 },
55 ]); 56 ]);
56 } 57 }
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 import { Card } from 'ant-design-vue'; 2 import { Card } from 'ant-design-vue';
3 import { computed, nextTick, onMounted, onUpdated, unref, watch } from 'vue'; 3 import { computed, nextTick, onMounted, onUpdated, unref, watch } from 'vue';
4 - import { DataTypeEnum } from '../StructForm/config';  
5 import { BasicCreateFormParams } from './type'; 4 import { BasicCreateFormParams } from './type';
6 import { DynamicProps } from '/#/utils'; 5 import { DynamicProps } from '/#/utils';
7 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; 6 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
8 import { BasicForm, FormProps, FormSchema, useForm } from '/@/components/Form'; 7 import { BasicForm, FormProps, FormSchema, useForm } from '/@/components/Form';
  8 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
9 import { ReadAndWriteEnum } from '/@/enums/toolEnum'; 9 import { ReadAndWriteEnum } from '/@/enums/toolEnum';
10 10
11 const props = withDefaults( 11 const props = withDefaults(
@@ -86,7 +86,7 @@ @@ -86,7 +86,7 @@
86 // step: step, 86 // step: step,
87 // formatter: (value: string) => value, 87 // formatter: (value: string) => value,
88 // parser: (string: string) => { 88 // parser: (string: string) => {
89 - // if (dataType === DataTypeEnum.IS_NUMBER_INT) { 89 + // if (dataType === DataTypeEnum.NUMBER_INT) {
90 // return Number(Number(string).toFixed()); 90 // return Number(Number(string).toFixed());
91 // } 91 // }
92 // return Number(string); 92 // return Number(string);
@@ -188,19 +188,19 @@ @@ -188,19 +188,19 @@
188 dataType: dataType! as unknown as DataTypeEnum, 188 dataType: dataType! as unknown as DataTypeEnum,
189 specs: specs as Partial<Specs>, 189 specs: specs as Partial<Specs>,
190 }; 190 };
191 - if (type === DataTypeEnum.IS_NUMBER_INT || type === DataTypeEnum.IS_NUMBER_DOUBLE) { 191 + if (type === DataTypeEnum.NUMBER_INT || type === DataTypeEnum.NUMBER_DOUBLE) {
192 schemas.push(createInputNumber(params)); 192 schemas.push(createInputNumber(params));
193 } 193 }
194 194
195 - if (type === DataTypeEnum.IS_BOOL) { 195 + if (type === DataTypeEnum.BOOL) {
196 schemas.push(createSelect(params)); 196 schemas.push(createSelect(params));
197 } 197 }
198 198
199 - if (type === DataTypeEnum.IS_STRING) { 199 + if (type === DataTypeEnum.STRING) {
200 schemas.push(createInput(params)); 200 schemas.push(createInput(params));
201 } 201 }
202 202
203 - if (type === DataTypeEnum.IS_STRUCT) { 203 + if (type === DataTypeEnum.STRUCT) {
204 schemas.push(createInputJson(params)); 204 schemas.push(createInputJson(params));
205 } 205 }
206 } 206 }
1 -import { DataTypeEnum } from '../StructForm/config';  
2 import { Specs } from '/@/api/device/model/modelOfMatterModel'; 1 import { Specs } from '/@/api/device/model/modelOfMatterModel';
  2 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
3 3
4 export interface BasicCreateFormParams { 4 export interface BasicCreateFormParams {
5 identifier: string; 5 identifier: string;
1 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; 1 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
2 import { findDictItemByCode } from '/@/api/system/dict'; 2 import { findDictItemByCode } from '/@/api/system/dict';
3 import { FormSchema } from '/@/components/Table'; 3 import { FormSchema } from '/@/components/Table';
  4 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
4 import { isNullOrUnDef } from '/@/utils/is'; 5 import { isNullOrUnDef } from '/@/utils/is';
5 import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; 6 import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
6 7
7 -export enum DataTypeEnum {  
8 - IS_NUMBER_INT = 'INT',  
9 - IS_NUMBER_DOUBLE = 'DOUBLE',  
10 - IS_STRING = 'TEXT',  
11 - IS_STRUCT = 'STRUCT',  
12 - IS_BOOL = 'BOOL',  
13 -}  
14 -  
15 export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => { 8 export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => {
16 value = value || {}; 9 value = value || {};
17 const { min, max } = value; 10 const { min, max } = value;
@@ -77,10 +70,9 @@ export const formSchemas = ( @@ -77,10 +70,9 @@ export const formSchemas = (
77 try { 70 try {
78 const record = await findDictItemByCode(params); 71 const record = await findDictItemByCode(params);
79 return hasStructForm 72 return hasStructForm
80 - ? record.filter((item) => item.itemValue !== DataTypeEnum.IS_STRUCT) 73 + ? record.filter((item) => item.itemValue !== DataTypeEnum.STRUCT)
81 : record; 74 : record;
82 } catch (error) { 75 } catch (error) {
83 - console.log(error);  
84 return []; 76 return [];
85 } 77 }
86 }, 78 },
@@ -91,7 +83,7 @@ export const formSchemas = ( @@ -91,7 +83,7 @@ export const formSchemas = (
91 valueField: 'itemValue', 83 valueField: 'itemValue',
92 getPopupContainer: () => document.body, 84 getPopupContainer: () => document.body,
93 onChange: (value: string) => { 85 onChange: (value: string) => {
94 - if (value == DataTypeEnum.IS_STRUCT) { 86 + if (value == DataTypeEnum.STRUCT) {
95 updateSchema({ 87 updateSchema({
96 field: FormField.SPECS_LIST, 88 field: FormField.SPECS_LIST,
97 componentProps: { 89 componentProps: {
@@ -114,8 +106,8 @@ export const formSchemas = ( @@ -114,8 +106,8 @@ export const formSchemas = (
114 span: 18, 106 span: 18,
115 }, 107 },
116 ifShow: ({ values }) => 108 ifShow: ({ values }) =>
117 - values[FormField.TYPE] === DataTypeEnum.IS_NUMBER_INT ||  
118 - values[FormField.TYPE] === DataTypeEnum.IS_NUMBER_DOUBLE, 109 + values[FormField.TYPE] === DataTypeEnum.NUMBER_INT ||
  110 + values[FormField.TYPE] === DataTypeEnum.NUMBER_DOUBLE,
119 rules: [{ validator: validateValueRange }], 111 rules: [{ validator: validateValueRange }],
120 }, 112 },
121 { 113 {
@@ -134,8 +126,8 @@ export const formSchemas = ( @@ -134,8 +126,8 @@ export const formSchemas = (
134 }, 126 },
135 }, 127 },
136 ifShow: ({ values }) => 128 ifShow: ({ values }) =>
137 - values[FormField.TYPE] === DataTypeEnum.IS_NUMBER_INT ||  
138 - values[FormField.TYPE] === DataTypeEnum.IS_NUMBER_DOUBLE, 129 + values[FormField.TYPE] === DataTypeEnum.NUMBER_INT ||
  130 + values[FormField.TYPE] === DataTypeEnum.NUMBER_DOUBLE,
139 dynamicRules: ({ model }) => { 131 dynamicRules: ({ model }) => {
140 const valueRange = model[FormField.VALUE_RANGE] || {}; 132 const valueRange = model[FormField.VALUE_RANGE] || {};
141 const { min, max } = valueRange; 133 const { min, max } = valueRange;
@@ -199,8 +191,8 @@ export const formSchemas = ( @@ -199,8 +191,8 @@ export const formSchemas = (
199 }; 191 };
200 }, 192 },
201 ifShow: ({ values }) => 193 ifShow: ({ values }) =>
202 - values[FormField.TYPE] === DataTypeEnum.IS_NUMBER_INT ||  
203 - values[FormField.TYPE] === DataTypeEnum.IS_NUMBER_DOUBLE, 194 + values[FormField.TYPE] === DataTypeEnum.NUMBER_INT ||
  195 + values[FormField.TYPE] === DataTypeEnum.NUMBER_DOUBLE,
204 }, 196 },
205 { 197 {
206 field: FormField.BOOL_CLOSE, 198 field: FormField.BOOL_CLOSE,
@@ -214,7 +206,7 @@ export const formSchemas = ( @@ -214,7 +206,7 @@ export const formSchemas = (
214 placeholder: '如:关', 206 placeholder: '如:关',
215 }, 207 },
216 defaultValue: '关', 208 defaultValue: '关',
217 - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.IS_BOOL, 209 + ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.BOOL,
218 dynamicRules: ({ model }) => { 210 dynamicRules: ({ model }) => {
219 const close = model[FormField.BOOL_CLOSE]; 211 const close = model[FormField.BOOL_CLOSE];
220 const open = model[FormField.BOOL_OPEN]; 212 const open = model[FormField.BOOL_OPEN];
@@ -243,7 +235,7 @@ export const formSchemas = ( @@ -243,7 +235,7 @@ export const formSchemas = (
243 placeholder: '如:开', 235 placeholder: '如:开',
244 }, 236 },
245 defaultValue: '开', 237 defaultValue: '开',
246 - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.IS_BOOL, 238 + ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.BOOL,
247 dynamicRules: ({ model }) => { 239 dynamicRules: ({ model }) => {
248 const close = model[FormField.BOOL_CLOSE]; 240 const close = model[FormField.BOOL_CLOSE];
249 const open = model[FormField.BOOL_OPEN]; 241 const open = model[FormField.BOOL_OPEN];
@@ -277,7 +269,7 @@ export const formSchemas = ( @@ -277,7 +269,7 @@ export const formSchemas = (
277 suffix: () => '字节', 269 suffix: () => '字节',
278 }; 270 };
279 }, 271 },
280 - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.IS_STRING, 272 + ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.STRING,
281 }, 273 },
282 { 274 {
283 field: FormField.EXTENSION_DESC, 275 field: FormField.EXTENSION_DESC,
@@ -322,7 +314,7 @@ export const formSchemas = ( @@ -322,7 +314,7 @@ export const formSchemas = (
322 valueField: 'value', 314 valueField: 'value',
323 changeEvent: 'update:value', 315 changeEvent: 'update:value',
324 colProps: { span: 24 }, 316 colProps: { span: 24 },
325 - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.IS_STRUCT, 317 + ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.STRUCT,
326 rules: [{ required: true, validator: validateJSON }], 318 rules: [{ required: true, validator: validateJSON }],
327 }, 319 },
328 { 320 {
1 -import { DataTypeEnum } from './config';  
2 import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; 1 import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  2 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
3 import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; 3 import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
4 4
5 export enum OpenModalMode { 5 export enum OpenModalMode {
1 import { cloneDeep } from 'lodash-es'; 1 import { cloneDeep } from 'lodash-es';
2 -import { DataTypeEnum } from './config';  
3 import { StructFormValue } from './type'; 2 import { StructFormValue } from './type';
4 import { DataType, ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel'; 3 import { DataType, ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel';
5 import { isArray } from '/@/utils/is'; 4 import { isArray } from '/@/utils/is';
  5 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
6 6
7 export function transfromToStructJSON(value: StructFormValue): StructJSON { 7 export function transfromToStructJSON(value: StructFormValue): StructJSON {
8 const { 8 const {
@@ -24,21 +24,21 @@ export function transfromToStructJSON(value: StructFormValue): StructJSON { @@ -24,21 +24,21 @@ export function transfromToStructJSON(value: StructFormValue): StructJSON {
24 let dataType = {} as unknown as DataType; 24 let dataType = {} as unknown as DataType;
25 25
26 switch (type) { 26 switch (type) {
27 - case DataTypeEnum.IS_NUMBER_INT: 27 + case DataTypeEnum.NUMBER_INT:
28 dataType = { 28 dataType = {
29 type, 29 type,
30 specs: { valueRange, step, unit, unitName }, 30 specs: { valueRange, step, unit, unitName },
31 }; 31 };
32 break; 32 break;
33 33
34 - case DataTypeEnum.IS_NUMBER_DOUBLE: 34 + case DataTypeEnum.NUMBER_DOUBLE:
35 dataType = { 35 dataType = {
36 type, 36 type,
37 specs: { valueRange, step, unit, unitName }, 37 specs: { valueRange, step, unit, unitName },
38 }; 38 };
39 break; 39 break;
40 40
41 - case DataTypeEnum.IS_BOOL: 41 + case DataTypeEnum.BOOL:
42 dataType = { 42 dataType = {
43 type, 43 type,
44 specs: { 44 specs: {
@@ -48,14 +48,14 @@ export function transfromToStructJSON(value: StructFormValue): StructJSON { @@ -48,14 +48,14 @@ export function transfromToStructJSON(value: StructFormValue): StructJSON {
48 }; 48 };
49 break; 49 break;
50 50
51 - case DataTypeEnum.IS_STRING: 51 + case DataTypeEnum.STRING:
52 dataType = { 52 dataType = {
53 type, 53 type,
54 specs: { length }, 54 specs: { length },
55 }; 55 };
56 break; 56 break;
57 57
58 - case DataTypeEnum.IS_STRUCT: 58 + case DataTypeEnum.STRUCT:
59 dataType = { 59 dataType = {
60 type, 60 type,
61 specs: specs! as unknown as ModelOfMatterParams[], 61 specs: specs! as unknown as ModelOfMatterParams[],
  1 +<script setup lang="ts">
  2 + import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  3 + import { JsonPreview } from '/@/components/CodeEditor';
  4 + import { BasicModal, useModalInner } from '/@/components/Modal';
  5 +
  6 + defineProps<{
  7 + inputData?: StructJSON[];
  8 + }>();
  9 +
  10 + defineEmits(['register']);
  11 +
  12 + const data = {};
  13 +
  14 + const [register] = useModalInner();
  15 +</script>
  16 +
  17 +<template>
  18 + <BasicModal @register="register" title="">
  19 + <!-- -->
  20 + <JsonPreview :data="data" />
  21 + </BasicModal>
  22 +</template>
  1 +import { FormSchema } from '../../../types/form';
  2 +import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  3 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  4 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  5 +
  6 +export const getFormSchemas = ({
  7 + structJSON: structJson,
  8 + required,
  9 + transportType,
  10 +}: {
  11 + structJSON: StructJSON[];
  12 + required?: boolean;
  13 + transportType?: string;
  14 +}): FormSchema[] => {
  15 + const createInputNumber = ({ identifier, functionName, dataType }: StructJSON): FormSchema => {
  16 + const { specs } = dataType || {};
  17 + const { valueRange } = specs! as Specs;
  18 + const { max = 2147483647, min = -2147483648 } = valueRange || {};
  19 + return {
  20 + field: identifier,
  21 + label: functionName,
  22 + component: 'InputNumber',
  23 + rules: [
  24 + {
  25 + required,
  26 + message: `${functionName}是必填项`,
  27 + },
  28 + {
  29 + type: 'number',
  30 + trigger: 'change',
  31 + validator: (_rule, value) => {
  32 + const reg = /^[0-9]*$/;
  33 + if (!reg.test(value))
  34 + return Promise.reject(new Error(`${functionName}不是一个有效的数字`));
  35 + if (value < min || value > max)
  36 + return Promise.reject(new Error(`${functionName}取值范围在${min}~${max}之间`));
  37 +
  38 + return Promise.resolve(value);
  39 + },
  40 + },
  41 + ],
  42 + componentProps: {
  43 + max,
  44 + min,
  45 + placeholder: `请输入${functionName}`,
  46 + },
  47 + } as FormSchema;
  48 + };
  49 +
  50 + const createInput = ({ identifier, functionName, dataType }: StructJSON): FormSchema => {
  51 + const { specs } = dataType || {};
  52 + const { length = 10240 } = specs! as Partial<Specs>;
  53 + return {
  54 + field: identifier,
  55 + label: functionName,
  56 + component: 'Input',
  57 + rules: [
  58 + {
  59 + required,
  60 + message: `${functionName}是必填项`,
  61 + },
  62 + {
  63 + type: 'string',
  64 + trigger: 'change',
  65 + validator: (_rule, value) => {
  66 + if ((value?.length || 0) > length)
  67 + return Promise.reject(new Error(`${functionName}数据长度应该小于${length}`));
  68 +
  69 + return Promise.resolve(value);
  70 + },
  71 + },
  72 + ],
  73 + componentProps: {
  74 + maxLength: length,
  75 + placeholder: `请输入${functionName}`,
  76 + },
  77 + } as FormSchema;
  78 + };
  79 +
  80 + const createSelect = ({ identifier, functionName, dataType }: StructJSON): FormSchema => {
  81 + const { specs } = dataType || {};
  82 + const { boolClose, boolOpen } = specs! as Partial<Specs>;
  83 + return {
  84 + field: identifier,
  85 + label: functionName!,
  86 + component: 'Select',
  87 + rules: [
  88 + {
  89 + required,
  90 + message: `${functionName}是必填项`,
  91 + type: 'number',
  92 + },
  93 + ],
  94 + componentProps: {
  95 + options: [
  96 + { label: `${boolClose}-0`, value: 0 },
  97 + { label: `${boolOpen}-1`, value: 1 },
  98 + ],
  99 + placeholder: `请选择${functionName}`,
  100 + },
  101 + };
  102 + };
  103 +
  104 + const createStructJson = ({ identifier, functionName, dataType }: StructJSON): FormSchema => {
  105 + return {
  106 + field: identifier,
  107 + label: functionName!,
  108 + component: 'Input',
  109 + changeEvent: 'update:value',
  110 + componentProps: () => {
  111 + return {
  112 + inputData: dataType?.specs || [],
  113 + };
  114 + },
  115 + colSlot: identifier,
  116 + };
  117 + };
  118 +
  119 + const createTCPServiceCommandInput = ({ serviceCommand }: StructJSON): FormSchema => {
  120 + return {
  121 + field: 'serviceCommand',
  122 + label: '服务命令',
  123 + component: 'Input',
  124 + required,
  125 + defaultValue: serviceCommand,
  126 + componentProps: {
  127 + placeholder: `请输入服务命令`,
  128 + },
  129 + };
  130 + };
  131 +
  132 + const schemas: FormSchema[] = [];
  133 +
  134 + for (const item of structJson) {
  135 + const { dataType } = item;
  136 + const { type } = dataType || {};
  137 + if (transportType === TransportTypeEnum.TCP) {
  138 + item.serviceCommand && schemas.push(createTCPServiceCommandInput(item));
  139 + break;
  140 + }
  141 +
  142 + if (type === DataTypeEnum.BOOL) schemas.push(createSelect(item));
  143 + else if (type === DataTypeEnum.NUMBER_INT) schemas.push(createInputNumber(item));
  144 + else if (type === DataTypeEnum.NUMBER_DOUBLE) schemas.push(createInputNumber(item));
  145 + else if (type === DataTypeEnum.STRING) schemas.push(createInput(item));
  146 + else if (type === DataTypeEnum.STRUCT) schemas.push(createStructJson(item));
  147 + }
  148 +
  149 + return schemas;
  150 +};
  1 +import { ValidatorRule } from 'ant-design-vue/lib/form/interface';
  2 +export { default as ThingsModelForm } from './index.vue';
  3 +
  4 +export const validateTCPCustomCommand: ValidatorRule['validator'] = (_rule, value) => {
  5 + const reg = /^[\s0-9a-fA-F]+$/;
  6 + if (reg.test(value)) return Promise.resolve();
  7 + return Promise.reject('请输入ASCII或HEX服务命令(0~9/A~F)');
  8 +};
  9 +
  10 +export const trimBlankSpace = (string: string) => string.replace(/(?!^)(?=(\w{2})+$)/g, '');
  11 +
  12 +export const formatCommandString = (string = '') => string.replace(/(?!^)(?=(\w{2})+$)/g, ' ');
  1 +<script setup lang="ts">
  2 + import { Card } from 'ant-design-vue';
  3 + import { ComponentPublicInstance, computed, nextTick, reactive, unref, watch } from 'vue';
  4 + import { getFormSchemas } from './config';
  5 + import { ThingsModelForm } from '.';
  6 + import { DefineComponentsBasicExpose } from '/#/utils';
  7 + import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  8 + import { useForm } from '../../../hooks/useForm';
  9 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
  10 + import { BasicForm } from '/@/components/Form';
  11 +
  12 + const props = withDefaults(
  13 + defineProps<{
  14 + value?: Recordable;
  15 + inputData?: StructJSON[];
  16 + required?: boolean;
  17 + title?: string;
  18 + transportType?: string;
  19 + disabled?: boolean;
  20 + identifier?: string;
  21 + }>(),
  22 + {
  23 + inputData: () => [],
  24 + required: true,
  25 + }
  26 + );
  27 +
  28 + const thingsModelFormListElMap = reactive<
  29 + Record<string, { el: InstanceType<typeof ThingsModelForm>; structJSON: StructJSON }>
  30 + >({});
  31 +
  32 + const [register, formActionType] = useForm({
  33 + schemas: getFormSchemas({
  34 + structJSON: props.inputData || [],
  35 + required: props.required,
  36 + transportType: props.transportType,
  37 + }),
  38 + showActionButtonGroup: false,
  39 + layout: 'inline',
  40 + labelWidth: 100,
  41 + });
  42 +
  43 + const getStructFormItem = computed(() => {
  44 + const { inputData } = props;
  45 + return inputData.filter((item) => item.dataType?.type === DataTypeEnum.STRUCT);
  46 + });
  47 +
  48 + const setFormElRef = (
  49 + el: Nullable<Element | ComponentPublicInstance>,
  50 + structJSON: StructJSON
  51 + ) => {
  52 + const _structJSON = unref(getStructFormItem).find(
  53 + (item) => item.identifier === structJSON.identifier
  54 + );
  55 +
  56 + if (_structJSON)
  57 + thingsModelFormListElMap[structJSON.identifier!] = {
  58 + el: el as InstanceType<typeof ThingsModelForm>,
  59 + structJSON,
  60 + };
  61 + };
  62 +
  63 + const getFieldsValue = () => {
  64 + const basicValue = formActionType.getFieldsValue();
  65 +
  66 + const structValue: Recordable = {};
  67 + for (const key of Object.keys(thingsModelFormListElMap)) {
  68 + const item = thingsModelFormListElMap[key];
  69 + const { el } = item;
  70 + structValue[key] = el.getFieldsValue() || {};
  71 + }
  72 +
  73 + return {
  74 + ...basicValue,
  75 + ...structValue,
  76 + };
  77 + };
  78 +
  79 + const setFieldsValue = (value: Recordable) => {
  80 + formActionType.setFieldsValue(value);
  81 + };
  82 +
  83 + const validate = async () => {
  84 + await formActionType.validate();
  85 + for (const key of Object.keys(thingsModelFormListElMap))
  86 + await thingsModelFormListElMap[key]?.el?.validate?.();
  87 + };
  88 +
  89 + watch(
  90 + () => props.value,
  91 + async (value) => {
  92 + await nextTick();
  93 + formActionType.resetFields();
  94 + formActionType.setFieldsValue(value || {});
  95 + },
  96 + { immediate: true }
  97 + );
  98 +
  99 + watch(
  100 + () => [props.inputData, props.identifier],
  101 + (value) => {
  102 + if (value && value.length) {
  103 + const schemas = getFormSchemas({
  104 + structJSON: props.inputData || [],
  105 + required: props.required,
  106 + transportType: props.transportType,
  107 + });
  108 + formActionType.setProps({ schemas });
  109 + }
  110 + }
  111 + );
  112 +
  113 + watch(
  114 + () => props.disabled,
  115 + (value) => {
  116 + formActionType.setProps({ disabled: value });
  117 + }
  118 + );
  119 +
  120 + defineExpose<DefineComponentsBasicExpose>({
  121 + getFieldsValue,
  122 + setFieldsValue,
  123 + validate,
  124 + });
  125 +</script>
  126 +
  127 +<template>
  128 + <Card
  129 + class="!border-2 !border-dashed"
  130 + :title="title"
  131 + :style="{ backgroundColor: disabled ? '#f0f0f0' : '', borderColor: disabled ? '#e3e3e3' : '' }"
  132 + >
  133 + <BasicForm class="things-model-form" @register="register" :disabled="disabled">
  134 + <template
  135 + v-for="item in getStructFormItem"
  136 + #[item.identifier!]="{ model, field }"
  137 + :key="item.identifier"
  138 + >
  139 + <ThingsModelForm
  140 + class="!ml-10"
  141 + :ref="(el) => setFormElRef(el, item)"
  142 + v-model:value="model[field]"
  143 + :input-data="(item.dataType?.specs as StructJSON[]) || []"
  144 + :title="item.functionName"
  145 + :disabled="disabled"
  146 + />
  147 + </template>
  148 + </BasicForm>
  149 + </Card>
  150 +</template>
  151 +
  152 +<style lang="less" scoped>
  153 + .things-model-form {
  154 + > :deep(.ant-row) {
  155 + width: 100%;
  156 + }
  157 +
  158 + :deep(.ant-input-number) {
  159 + width: 100%;
  160 + }
  161 +
  162 + :deep(.ant-form-item-label) {
  163 + label {
  164 + width: 100%;
  165 + display: block;
  166 + overflow: hidden;
  167 + text-overflow: ellipsis;
  168 + }
  169 + }
  170 + }
  171 +
  172 + :deep(.ant-form-item-control) {
  173 + margin-left: 6px;
  174 + }
  175 +</style>
@@ -123,6 +123,7 @@ export type ComponentType = @@ -123,6 +123,7 @@ export type ComponentType =
123 | 'TransferModal' 123 | 'TransferModal'
124 | 'TransferTableModal' 124 | 'TransferTableModal'
125 | 'ObjectModelValidateForm' 125 | 'ObjectModelValidateForm'
  126 + | 'ThingsModelForm'
126 | 'DevicePicker' 127 | 'DevicePicker'
127 | 'ProductPicker' 128 | 'ProductPicker'
128 | 'PollCommandInput' 129 | 'PollCommandInput'
@@ -138,4 +139,8 @@ export type ComponentType = @@ -138,4 +139,8 @@ export type ComponentType =
138 | 'RelationsQuery' 139 | 'RelationsQuery'
139 | 'CredentialsCard' 140 | 'CredentialsCard'
140 | 'ApiComplete' 141 | 'ApiComplete'
141 - | 'DeviceProfileForm'; 142 + | 'DeviceProfileForm'
  143 + | 'ConditionFilter'
  144 + | 'TimeRangePicker'
  145 + | 'TriggerDurationInput'
  146 + | 'AlarmProfileSelect';
@@ -398,8 +398,6 @@ @@ -398,8 +398,6 @@
398 display: flex; 398 display: flex;
399 align-items: center; 399 align-items: center;
400 justify-content: center; 400 justify-content: center;
401 - height: 670px;  
402 - max-height: 670px;  
403 } 401 }
404 } 402 }
405 403
@@ -25,19 +25,18 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) { @@ -25,19 +25,18 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
25 const { t } = useI18n(); 25 const { t } = useI18n();
26 26
27 const configRef = ref<PaginationProps>({ 27 const configRef = ref<PaginationProps>({
28 - hideOnSinglePage:true 28 + hideOnSinglePage: true,
29 }); 29 });
30 const show = ref(true); 30 const show = ref(true);
31 31
32 watchEffect(() => { 32 watchEffect(() => {
33 const { pagination } = unref(refProps); 33 const { pagination } = unref(refProps);
34 - 34 +
35 if (!isBoolean(pagination) && pagination) { 35 if (!isBoolean(pagination) && pagination) {
36 configRef.value = { 36 configRef.value = {
37 ...unref(configRef), 37 ...unref(configRef),
38 ...(pagination ?? {}), 38 ...(pagination ?? {}),
39 - };  
40 - 39 + };
41 } 40 }
42 }); 41 });
43 42
@@ -88,7 +88,7 @@ export function useTableScroll( @@ -88,7 +88,7 @@ export function useTableScroll(
88 88
89 bodyEl!.style.height = 'unset'; 89 bodyEl!.style.height = 'unset';
90 90
91 - if (!unref(getCanResize) || tableData.length === 0) return; 91 + if (!unref(getCanResize)) return;
92 92
93 await nextTick(); 93 await nextTick();
94 //Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight 94 //Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
@@ -143,7 +143,12 @@ export function useTableScroll( @@ -143,7 +143,12 @@ export function useTableScroll(
143 height = (height > maxHeight! ? (maxHeight as number) : height) ?? height; 143 height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
144 setHeight(height); 144 setHeight(height);
145 145
146 - bodyEl!.style.height = `${height}px`; 146 + if (tableData.length) {
  147 + bodyEl!.style.height = `${height}px`;
  148 + } else {
  149 + const emptyPlaceholder: HTMLDivElement = tableEl.querySelector('.ant-table-placeholder')!;
  150 + emptyPlaceholder && (emptyPlaceholder.style.height = `${height}px`);
  151 + }
147 } 152 }
148 useWindowSizeFn(calcTableHeight, 280); 153 useWindowSizeFn(calcTableHeight, 280);
149 onMountedOrActivated(() => { 154 onMountedOrActivated(() => {
@@ -11,3 +11,19 @@ export enum AlarmStatusMean { @@ -11,3 +11,19 @@ export enum AlarmStatusMean {
11 CLEARED_ACK = '清除已确认', 11 CLEARED_ACK = '清除已确认',
12 ACTIVE_ACK = '激活已确认', 12 ACTIVE_ACK = '激活已确认',
13 } 13 }
  14 +
  15 +export enum AlarmLevelEnum {
  16 + CRITICAL = 'CRITICAL',
  17 + MAJOR = 'MAJOR',
  18 + MINOR = 'MINOR',
  19 + WARNING = 'WARNING',
  20 + INDETERMINATE = 'INDETERMINATE',
  21 +}
  22 +
  23 +export enum AlarmLevelNameEnum {
  24 + CRITICAL = '紧急',
  25 + MAJOR = '重要',
  26 + MINOR = '次要',
  27 + WARNING = '警告',
  28 + INDETERMINATE = '不确定',
  29 +}
@@ -23,4 +23,7 @@ export enum DictEnum { @@ -23,4 +23,7 @@ export enum DictEnum {
23 MESSAGE_TYPES_FILTER = 'message_types_filter', 23 MESSAGE_TYPES_FILTER = 'message_types_filter',
24 // 实体类型 规则节点 Filter originator types switch 24 // 实体类型 规则节点 Filter originator types switch
25 ORIGINATOR_TYPES = 'originator_types', 25 ORIGINATOR_TYPES = 'originator_types',
  26 +
  27 + // 产品品类领域
  28 + CATEGORY_FIELD = 'category_field',
26 } 29 }
  1 +/**
  2 + * 运算符枚举值
  3 + */
  4 +
  5 +// 数字运算符或者时间运算符
  6 +export enum NumberOperationEnum {
  7 + EQUAL = 'EQUAL',
  8 + NOT_EQUAL = 'NOT_EQUAL',
  9 + LESS = 'LESS',
  10 + LESS_OR_EQUAL = 'LESS_OR_EQUAL',
  11 + GREATER = 'GREATER',
  12 + GREATER_OR_EQUAL = 'GREATER_OR_EQUAL',
  13 +}
  14 +
  15 +export enum NumberOperationNameEnum {
  16 + EQUAL = '等于',
  17 + NOT_EQUAL = '不等于',
  18 + LESS = '小于',
  19 + LESS_OR_EQUAL = '小于等于',
  20 + GREATER = '大于',
  21 + GREATER_OR_EQUAL = '大于等于',
  22 +}
  23 +
  24 +export enum StringOperationEnum {
  25 + EQUAL = 'EQUAL',
  26 + NOT_EQUAL = 'NOT_EQUAL',
  27 + STARTS_WITH = 'STARTS_WITH',
  28 + ENDS_WITH = 'ENDS_WITH',
  29 + CONTAINS = 'CONTAINS',
  30 + NOT_CONTAINS = 'NOT_CONTAINS',
  31 +}
  32 +
  33 +export enum StringOperationNameEnum {
  34 + EQUAL = '等于',
  35 + NOT_EQUAL = '不等于',
  36 + STARTS_WITH = '开始于',
  37 + ENDS_WITH = '结束于',
  38 + CONTAINS = '包含',
  39 + NOT_CONTAINS = '不包含',
  40 +}
  41 +
  42 +export enum BooleanOperationEnum {
  43 + EQUAL = 'EQUAL',
  44 + NOT_EQUAL = 'NOT_EQUAL',
  45 +}
  46 +
  47 +export enum BooleanOperationNameEnum {
  48 + EQUAL = '等于',
  49 + NOT_EQUAL = '不等于',
  50 +}
  51 +
  52 +export enum BooleanOperationValueEnum {
  53 + TRUE = 'true',
  54 + FALSE = 'false',
  55 +}
  56 +
  57 +export enum BooleanOperationValueNameEnum {
  58 + TRUE = '真',
  59 + FALSE = '假',
  60 +}
  61 +
  62 +export enum FlipFlopTypeEnum {
  63 + SIMPLE = 'SIMPLE',
  64 + DURATION = 'DURATION',
  65 + REPEATING = 'REPEATING',
  66 +}
  67 +
  68 +export enum ScheduleTypeEnum {
  69 + ANY_TIME = 'ANY_TIME',
  70 + SPECIFIC_TIME = 'SPECIFIC_TIME',
  71 + CUSTOM = 'CUSTOM',
  72 +}
  73 +
  74 +export enum ScheduleTypeNameEnum {
  75 + ANY_TIME = '始终启用',
  76 + SPECIFIC_TIME = '定时启用',
  77 + CUSTOM = '自定义启用',
  78 +}
  79 +
  80 +export enum FlipFlopTypeNameEnum {
  81 + SIMPLE = '简单',
  82 + DURATION = '持续时长',
  83 + REPEATING = '重复次数',
  84 +}
  85 +
  86 +export enum TriggerTypeEnum {
  87 + DEVICE_TRIGGER = 'DEVICE_TRIGGER',
  88 + SCHEDULE_TRIGGER = 'SCHEDULE_TRIGGER',
  89 + SCENE_TRIGGER = 'SCENE_TRIGGER',
  90 + HAND_ACT = 'HAND_ACT',
  91 +}
  92 +
  93 +export enum TriggerTypeNameEnum {
  94 + DEVICE_TRIGGER = '设备触发',
  95 + SCHEDULE_TRIGGER = '定时触发',
  96 + SCENE_TRIGGER = '场景触发',
  97 + HAND_ACT = '手动触发',
  98 +}
  99 +
  100 +export enum DeviceTriggerTypeEum {
  101 + TIME_SERIES = 'TIME_SERIES',
  102 +}
  103 +
  104 +export enum DeviceTriggerTypeNameEum {
  105 + TIME_SERIES = '属性触发',
  106 +}
  107 +
  108 +export enum TriggerEntityTypeEnum {
  109 + ALL = 'ALL',
  110 + PART = 'PART',
  111 +}
  112 +
  113 +export enum TriggerEntityTypeNameEnum {
  114 + ALL = '全部',
  115 + PART = '部分',
  116 +}
  117 +
  118 +export enum TriggerValueTypeEnum {
  119 + NUMERIC = 'NUMERIC',
  120 + BOOLEAN = 'BOOLEAN',
  121 + STRING = 'STRING',
  122 + DATE_TIME = 'DATE_TIME',
  123 +}
  124 +
  125 +export enum TriggerValueTypeNameEnum {
  126 + NUMERIC = '数字',
  127 + BOOLEAN = '布尔值',
  128 + STRING = '字符串',
  129 + DATE_TIME = '时间',
  130 +}
  131 +
  132 +export enum TriggerUnitEnum {
  133 + SECONDS = 'SECONDS',
  134 + MINUTES = 'MINUTES',
  135 + HOURS = 'HOURS',
  136 + DAYS = 'DAYS',
  137 +}
  138 +
  139 +export enum TriggerUnitNameEnum {
  140 + SECONDS = '秒',
  141 + MINUTES = '分',
  142 + HOURS = '时',
  143 + DAYS = '天',
  144 +}
  145 +
  146 +export enum ExecutionActionEnum {
  147 + DEVICE_OUT = 'DEVICE_OUT',
  148 + MSG_NOTIFY = 'MSG_NOTIFY',
  149 +}
  150 +
  151 +export enum ExecutionActionNameEnum {
  152 + DEVICE_OUT = '设备输出',
  153 + MSG_NOTIFY = '告警输出',
  154 +}
  155 +
  156 +export enum CommandTypeEnum {
  157 + CUSTOM = 0,
  158 + SERVICE = 1,
  159 + ATTRIBUTE = 2,
  160 + API = 'api',
  161 +}
  162 +
  163 +export enum CommandTypeNameEnum {
  164 + CUSTOM = '自定义',
  165 + SERVICE = '服务',
  166 + ATTRIBUTE = '属性',
  167 +}
  1 +export enum DataTypeEnum {
  2 + NUMBER_INT = 'INT',
  3 + NUMBER_DOUBLE = 'DOUBLE',
  4 + STRING = 'TEXT',
  5 + STRUCT = 'STRUCT',
  6 + BOOL = 'BOOL',
  7 +}
1 -/**  
2 - * 运算符枚举值  
3 - */  
4 -  
5 -// 数字运算符或者时间运算符  
6 -export enum Number_Operation {  
7 - EQUAL = 'EQUAL',  
8 - NOT_EQUAL = 'NOT_EQUAL',  
9 - LESS = 'LESS',  
10 - LESS_OR_EQUAL = 'LESS_OR_EQUAL',  
11 - GREATER = 'GREATER',  
12 - GREATER_OR_EQUAL = 'GREATER_OR_EQUAL',  
13 -}  
14 -  
15 -export enum String_Operation {  
16 - EQUAL = 'EQUAL',  
17 - NOT_EQUAL = 'NOT_EQUAL',  
18 - BEGAN_IN = 'STARTS_WITH',  
19 - END_IN = 'ENDS_WITH',  
20 - INCLUDE = 'CONTAINS',  
21 - NOT_INCLUDE = 'NOT_CONTAINS',  
22 -}  
23 -  
24 -export enum Boolean_Operation {  
25 - EQUAL = 'EQUAL',  
26 - NOT_EQUAL = 'NOT_EQUAL',  
27 -}  
@@ -7,7 +7,7 @@ export enum DataActionModeEnum { @@ -7,7 +7,7 @@ export enum DataActionModeEnum {
7 } 7 }
8 8
9 export enum DataActionModeNameEnum { 9 export enum DataActionModeNameEnum {
10 - CREATE = '创建', 10 + CREATE = '新增',
11 READ = '查看', 11 READ = '查看',
12 UPDATE = '编辑', 12 UPDATE = '编辑',
13 DELETE = '删除', 13 DELETE = '删除',
@@ -40,6 +40,11 @@ export enum ServiceCallTypeEnum { @@ -40,6 +40,11 @@ export enum ServiceCallTypeEnum {
40 SYNC = 'SYNC', 40 SYNC = 'SYNC',
41 } 41 }
42 42
  43 +export enum ServiceCallTypeNameEnum {
  44 + ASYNC = '异步',
  45 + SYNC = '同步',
  46 +}
  47 +
43 export enum CommandDeliveryWayEnum { 48 export enum CommandDeliveryWayEnum {
44 ONE_WAY = 'oneway', 49 ONE_WAY = 'oneway',
45 TWO_WAY = 'twoway', 50 TWO_WAY = 'twoway',
@@ -5,4 +5,5 @@ @@ -5,4 +5,5 @@
5 export const enum UserDropDownItemEnum { 5 export const enum UserDropDownItemEnum {
6 FORGOT_PASSWORD = 'system:password:view', //忘记密码权限标识key 6 FORGOT_PASSWORD = 'system:password:view', //忘记密码权限标识key
7 ABOUT_SOFTWARE = 'system:about_software:view', //关于软件权限标识key 7 ABOUT_SOFTWARE = 'system:about_software:view', //关于软件权限标识key
  8 + PERSONAL_CENTER = 'system:personal_center:view', //个人中心权限标识key
8 } 9 }
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 <template #overlay> 12 <template #overlay>
13 <Menu @click="handleMenuClick"> 13 <Menu @click="handleMenuClick">
14 <MenuItem 14 <MenuItem
  15 + v-if="hasPermission(UserDropDownItemEnum.PERSONAL_CENTER)"
15 key="personal" 16 key="personal"
16 :text="t('layout.header.dropdownItemPersonal')" 17 :text="t('layout.header.dropdownItemPersonal')"
17 icon="ion:document-text-outline" 18 icon="ion:document-text-outline"
1 export const createPickerSearch = (searchValue = false) => { 1 export const createPickerSearch = (searchValue = false) => {
2 return { 2 return {
3 showSearch: true, 3 showSearch: true,
  4 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
4 filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => { 5 filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
5 let { label, value } = option; 6 let { label, value } = option;
6 label = label.toLowerCase(); 7 label = label.toLowerCase();
@@ -120,6 +120,22 @@ export const emailRule: Rule[] = [ @@ -120,6 +120,22 @@ export const emailRule: Rule[] = [
120 }, 120 },
121 ]; 121 ];
122 122
  123 +// 钉钉验证
  124 +export const dingRule: Rule[] = [
  125 + {
  126 + validator: (_, value: string) => {
  127 + const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
  128 + if (!value) {
  129 + return Promise.resolve();
  130 + } else if (!reg.test(value)) {
  131 + return Promise.reject('手机号格式不正确');
  132 + }
  133 + return Promise.resolve();
  134 + },
  135 + validateTrigger: 'blur',
  136 + },
  137 +];
  138 +
123 // 中文正则 139 // 中文正则
124 export const ChineseRegexp = /[\u4E00-\u9FA5]/; 140 export const ChineseRegexp = /[\u4E00-\u9FA5]/;
125 141
1 import { BasicColumn, FormSchema } from '/@/components/Table'; 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 -import { emailRule, phoneRule } from '/@/utils/rules'; 2 +import { dingRule, emailRule, phoneRule } from '/@/utils/rules';
3 import { useComponentRegister } from '/@/components/Form'; 3 import { useComponentRegister } from '/@/components/Form';
4 import { OrgTreeSelect } from '../../common/OrgTreeSelect'; 4 import { OrgTreeSelect } from '../../common/OrgTreeSelect';
5 useComponentRegister('OrgTreeSelect', OrgTreeSelect); 5 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
@@ -31,6 +31,11 @@ export const columns: BasicColumn[] = [ @@ -31,6 +31,11 @@ export const columns: BasicColumn[] = [
31 width: 180, 31 width: 180,
32 }, 32 },
33 { 33 {
  34 + title: '钉钉',
  35 + dataIndex: 'dingtalk',
  36 + width: 160,
  37 + },
  38 + {
34 title: '备注', 39 title: '备注',
35 dataIndex: 'remark', 40 dataIndex: 'remark',
36 width: 120, 41 width: 120,
@@ -109,6 +114,17 @@ export const formSchema: FormSchema[] = [ @@ -109,6 +114,17 @@ export const formSchema: FormSchema[] = [
109 }, 114 },
110 }, 115 },
111 { 116 {
  117 + field: 'dingtalk',
  118 + label: '钉钉',
  119 + component: 'Input',
  120 + helpMessage: '请输入与钉钉相关联的手机号',
  121 + rules: dingRule,
  122 + componentProps: {
  123 + placeholder: '不填默认为手机号码',
  124 + maxLength: 11,
  125 + },
  126 + },
  127 + {
112 field: 'remark', 128 field: 'remark',
113 label: '备注', 129 label: '备注',
114 component: 'InputTextArea', 130 component: 'InputTextArea',
@@ -127,10 +127,19 @@ export function useAlarmNotify(params: UseAlarmNotifyParams = {}) { @@ -127,10 +127,19 @@ export function useAlarmNotify(params: UseAlarmNotifyParams = {}) {
127 clearInterval(timeout as NodeJS.Timer); 127 clearInterval(timeout as NodeJS.Timer);
128 timeout = null; 128 timeout = null;
129 }; 129 };
  130 +
130 onMounted(() => { 131 onMounted(() => {
131 if (getRoleHasNotifyFlag()) polling(); 132 if (getRoleHasNotifyFlag()) polling();
132 }); 133 });
133 134
  135 + document.addEventListener('visibilitychange', () => {
  136 + if (document.hidden) {
  137 + clearTimeout();
  138 + } else {
  139 + polling();
  140 + }
  141 + });
  142 +
134 onUnmounted(() => { 143 onUnmounted(() => {
135 clearTimeout(); 144 clearTimeout();
136 }); 145 });
@@ -16,15 +16,17 @@ @@ -16,15 +16,17 @@
16 value?: string | string[]; 16 value?: string | string[];
17 apiTreeSelectProps?: Recordable; 17 apiTreeSelectProps?: Recordable;
18 showCreate?: boolean; 18 showCreate?: boolean;
  19 + disabled?: boolean;
19 }>(), 20 }>(),
20 { 21 {
21 showCreate: true, 22 showCreate: true,
  23 + disabled: false,
22 } 24 }
23 ); 25 );
24 26
25 const needReload = ref(true); 27 const needReload = ref(true);
26 28
27 - const emit = defineEmits(['change']); 29 + const emit = defineEmits(['change', 'optionsChange']);
28 30
29 const handleOpenCreate = () => { 31 const handleOpenCreate = () => {
30 openDrawer(true, { isUpdate: false }); 32 openDrawer(true, { isUpdate: false });
@@ -35,13 +37,14 @@ @@ -35,13 +37,14 @@
35 const orgList = ref<Recordable[]>([]); 37 const orgList = ref<Recordable[]>([]);
36 38
37 const getBindProps = computed<Recordable>(() => { 39 const getBindProps = computed<Recordable>(() => {
38 - const { value, apiTreeSelectProps = {} } = props; 40 + const { value, apiTreeSelectProps = {}, disabled } = props;
39 const { params = {} } = apiTreeSelectProps; 41 const { params = {} } = apiTreeSelectProps;
40 return { 42 return {
41 replaceFields: { children: 'children', key: 'id', title: 'name', value: 'id' }, 43 replaceFields: { children: 'children', key: 'id', title: 'name', value: 'id' },
42 getPopupContainer: () => document.body, 44 getPopupContainer: () => document.body,
43 placeholder: '请选择所属组织', 45 placeholder: '请选择所属组织',
44 maxLength: 250, 46 maxLength: 250,
  47 + disabled,
45 ...apiTreeSelectProps, 48 ...apiTreeSelectProps,
46 value, 49 value,
47 dropdownStyle: { maxHeight: '300px' }, 50 dropdownStyle: { maxHeight: '300px' },
@@ -63,6 +66,7 @@ @@ -63,6 +66,7 @@
63 onChange: (...args: any[]) => { 66 onChange: (...args: any[]) => {
64 emit('change', ...args); 67 emit('change', ...args);
65 }, 68 },
  69 + onOptionsChange: (...args: any[]) => emit('optionsChange', ...args),
66 }; 70 };
67 }); 71 });
68 72
@@ -80,7 +84,9 @@ @@ -80,7 +84,9 @@
80 <template> 84 <template>
81 <section class="flex"> 85 <section class="flex">
82 <ApiTreeSelect v-bind="getBindProps" /> 86 <ApiTreeSelect v-bind="getBindProps" />
83 - <Button v-if="getShowCreate" type="link" @click="handleOpenCreate">新增组织</Button> 87 + <Button v-if="getShowCreate" type="link" @click="handleOpenCreate" :disabled="disabled"
  88 + >新增组织</Button
  89 + >
84 <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" /> 90 <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" />
85 </section> 91 </section>
86 </template> 92 </template>
@@ -222,6 +222,14 @@ @@ -222,6 +222,14 @@
222 </div> 222 </div>
223 </template> 223 </template>
224 <template class="ant-card-actions" #actions> 224 <template class="ant-card-actions" #actions>
  225 + <Tooltip title="预览">
  226 + <AuthIcon
  227 + :auth="ConfigurationTemplatePermission.PREVIEW"
  228 + class="!text-lg"
  229 + icon="ant-design:eye-outlined"
  230 + @click="handlePreview(item)"
  231 + />
  232 + </Tooltip>
225 <Tooltip v-if="!isCustomerUser" title="设计"> 233 <Tooltip v-if="!isCustomerUser" title="设计">
226 <AuthIcon 234 <AuthIcon
227 :auth="ConfigurationTemplatePermission.DESIGN" 235 :auth="ConfigurationTemplatePermission.DESIGN"
@@ -96,6 +96,10 @@ @@ -96,6 +96,10 @@
96 top: '48%', 96 top: '48%',
97 z: 10, 97 z: 10,
98 style: { 98 style: {
  99 + width: 35,
  100 + textAlign: 'left',
  101 + overflow: 'truncate',
  102 + textVerticalAlign: 'middle',
99 fill: '#1a1a1a', 103 fill: '#1a1a1a',
100 text: value + '个', 104 text: value + '个',
101 font: '0.85rem Microsoft YaHei', 105 font: '0.85rem Microsoft YaHei',
@@ -232,12 +232,6 @@ export const schemas = (): FormSchema[] => { @@ -232,12 +232,6 @@ export const schemas = (): FormSchema[] => {
232 component: 'InputGroup', 232 component: 'InputGroup',
233 required: true, 233 required: true,
234 colProps: { span: 24 }, 234 colProps: { span: 24 },
235 - componentProps: ({ formActionType }) => {  
236 - const { getFieldsValue } = formActionType;  
237 - return {  
238 - type: getFieldsValue().requestContentType,  
239 - };  
240 - },  
241 }, 235 },
242 { 236 {
243 field: 'fillAddress', 237 field: 'fillAddress',
@@ -127,6 +127,15 @@ @@ -127,6 +127,15 @@
127 showActionButtonGroup: false, 127 showActionButtonGroup: false,
128 }); 128 });
129 129
  130 + const updatePartFormScheme = (requestType: string) => {
  131 + updateSchema({
  132 + field: 'requestHttpTypeAndUrl',
  133 + componentProps: {
  134 + type: requestType,
  135 + },
  136 + });
  137 + };
  138 +
130 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => { 139 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
131 await resetFields(); 140 await resetFields();
132 await nextTick(); 141 await nextTick();
@@ -152,13 +161,12 @@ @@ -152,13 +161,12 @@
152 requestUrl: data.record?.requestUrl, 161 requestUrl: data.record?.requestUrl,
153 }, 162 },
154 }); 163 });
155 - await nextTick(() => simpleRequestRef.value?.setValue(data.record, false, []));  
156 - updateSchema({  
157 - field: 'requestHttpTypeAndUrl',  
158 - componentProps: {  
159 - type: String(data.record?.requestContentType),  
160 - }, 164 + await nextTick(() => {
  165 + simpleRequestRef.value?.setValue(data.record, false, []);
  166 + updatePartFormScheme(String(data.record?.requestContentType));
161 }); 167 });
  168 + } else {
  169 + await updatePartFormScheme(RequestMethodTypeEnum.COMMOM);
162 } 170 }
163 }); 171 });
164 172
  1 +<script setup lang="ts">
  2 + import { computed, unref } from 'vue';
  3 + import { ref } from 'vue';
  4 + import { BasicModal, useModalInner } from '/@/components/Modal';
  5 + import { schemas } from '../index';
  6 + import { useForm, BasicForm } from '/@/components/Form';
  7 +
  8 + import { deviceProfileCategory } from '/@/api/device/classModal';
  9 + import { useMessage } from '/@/hooks/web/useMessage';
  10 +
  11 + const emit = defineEmits(['handleReload', 'register']);
  12 +
  13 + const isUpdate = ref<Boolean>(false);
  14 + const getTitle = computed(() => (!unref(isUpdate) ? '新增品类' : '编辑品类'));
  15 + const { createMessage } = useMessage();
  16 +
  17 + const [registerForm, { getFieldsValue, setFieldsValue, validate }] = useForm({
  18 + labelWidth: 140,
  19 + schemas,
  20 + actionColOptions: {
  21 + span: 14,
  22 + },
  23 + showActionButtonGroup: false,
  24 + });
  25 + const recordInfo = ref<any>({});
  26 +
  27 + const [register, { closeModal, setModalProps }] = useModalInner(async (data) => {
  28 + console.log(123131, data);
  29 + setModalProps({ confirmLoading: false, loading: true });
  30 + isUpdate.value = data?.isUpdate;
  31 + recordInfo.value = data?.record;
  32 + if (data?.record) {
  33 + setFieldsValue(data?.record);
  34 + }
  35 + setModalProps({ loading: false });
  36 + });
  37 +
  38 + const handleCancel = () => {
  39 + closeModal();
  40 + };
  41 +
  42 + const handleOk = async () => {
  43 + await validate();
  44 + let values = getFieldsValue();
  45 + if (unref(isUpdate)) {
  46 + values = { ...values, id: unref(recordInfo).id };
  47 + }
  48 + await deviceProfileCategory(values);
  49 + createMessage.success('操作成功');
  50 + emit('handleReload');
  51 + handleCancel();
  52 + };
  53 +</script>
  54 +
  55 +<template>
  56 + <div>
  57 + <BasicModal
  58 + v-bind="$attrs"
  59 + width="30rem"
  60 + :title="getTitle"
  61 + @register="register"
  62 + @cancel="handleCancel"
  63 + @ok="handleOk"
  64 + destroyOnClose
  65 + >
  66 + <div>
  67 + <BasicForm @register="registerForm" />
  68 + </div>
  69 + </BasicModal>
  70 + </div>
  71 +</template>
  1 +import classModal from './classModal.vue';
  2 +
  3 +export { classModal };
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +import { findDictItemByCode } from '/@/api/system/dict';
  3 +
  4 +import { DictEnum } from '/@/enums/dictEnum';
  5 +
  6 +export const columns: BasicColumn[] = [
  7 + {
  8 + title: '名称',
  9 + dataIndex: 'name',
  10 + },
  11 + {
  12 + title: '领域',
  13 + dataIndex: 'dictItemName',
  14 + },
  15 + {
  16 + title: '状态',
  17 + dataIndex: 'status',
  18 + slots: { customRender: 'status' },
  19 + },
  20 +];
  21 +
  22 +export const searchFormSchema: FormSchema[] = [
  23 + {
  24 + field: 'name',
  25 + label: '名称',
  26 + component: 'Input',
  27 + colProps: { span: 6 },
  28 + componentProps: {
  29 + maxLength: 255,
  30 + placeholder: '请输入品类名称',
  31 + },
  32 + },
  33 + {
  34 + field: 'status',
  35 + label: '状态',
  36 + component: 'Select',
  37 + colProps: { span: 6 },
  38 + componentProps: {
  39 + options: [
  40 + { label: '启用', value: '1' },
  41 + { label: '禁用', value: '0' },
  42 + ],
  43 + placeholder: '请选择品类状态',
  44 + },
  45 + },
  46 + {
  47 + field: 'dictItemId',
  48 + label: '领域',
  49 + component: 'ApiSelect',
  50 + colProps: { span: 6 },
  51 + componentProps() {
  52 + return {
  53 + api: findDictItemByCode,
  54 + params: {
  55 + dictCode: DictEnum.CATEGORY_FIELD,
  56 + },
  57 + labelField: 'itemText',
  58 + valueField: 'itemValue',
  59 + placeholder: '请选择领域',
  60 + getPopupContainer: () => document.body,
  61 + };
  62 + },
  63 + },
  64 +];
  65 +
  66 +export const schemas: FormSchema[] = [
  67 + {
  68 + field: 'name',
  69 + label: '品类名称',
  70 + component: 'Input',
  71 + colProps: { span: 21 },
  72 + required: true,
  73 + componentProps: {
  74 + maxLength: 20,
  75 + },
  76 + },
  77 + {
  78 + field: 'dictItemId',
  79 + label: '领域',
  80 + component: 'ApiSelect',
  81 + colProps: { span: 21 },
  82 + required: true,
  83 + componentProps({ formActionType }) {
  84 + return {
  85 + api: findDictItemByCode,
  86 + params: {
  87 + dictCode: DictEnum.CATEGORY_FIELD,
  88 + },
  89 + onChange(value, options) {
  90 + formActionType.setFieldsValue({ dictItemName: value ? options.label : '' });
  91 + },
  92 + labelField: 'itemText',
  93 + valueField: 'itemValue',
  94 + placeholder: '请选择领域',
  95 + getPopupContainer: () => document.body,
  96 + };
  97 + },
  98 + },
  99 + {
  100 + field: 'dictItemName',
  101 + label: '领域名称',
  102 + ifShow: false,
  103 + component: 'Input',
  104 + },
  105 + {
  106 + field: 'status',
  107 + label: '状态',
  108 + component: 'RadioButtonGroup',
  109 + defaultValue: 1,
  110 + componentProps: {
  111 + options: [
  112 + { label: '启用', value: 1 },
  113 + { label: '禁用', value: 0 },
  114 + ],
  115 + },
  116 + },
  117 +];
  1 +<script setup lang="ts">
  2 + import { ref } from 'vue';
  3 + import { columns, searchFormSchema } from '.';
  4 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  5 + import { Button, Popconfirm, Switch } from 'ant-design-vue';
  6 + import { useBatchOperation } from '/@/utils/useBatchOperation';
  7 + import { useMessage } from '/@/hooks/web/useMessage';
  8 + import { Authority } from '/@/components/Authority';
  9 + import { useModal } from '/@/components/Modal';
  10 + import { classModal } from './components/index';
  11 + import { useDrawer } from '/@/components/Drawer';
  12 + import { USER_INFO_KEY } from '/@/enums/cacheEnum';
  13 + import { getAuthCache } from '/@/utils/auth';
  14 + import { authBtn } from '/@/enums/roleEnum';
  15 + import {
  16 + getDeviceClassList,
  17 + deleteDeviceClass,
  18 + deviceProfileCategory,
  19 + } from '/@/api/device/classModal';
  20 +
  21 + import { BasicDrawer } from '/@/components/Drawer';
  22 + import PhysicalModelManagementStep from '/@/views/device/profiles/step/PhysicalModelManagementStep.vue';
  23 + const [
  24 + registerTable,
  25 + { reload, setLoading, getSelectRowKeys, setSelectedRowKeys, getRowSelection },
  26 + ] = useTable({
  27 + title: '产品品类列表',
  28 + api: getDeviceClassList,
  29 + columns,
  30 + formConfig: {
  31 + labelWidth: 100,
  32 + schemas: searchFormSchema,
  33 + },
  34 + immediate: true,
  35 + useSearchForm: true,
  36 + showTableSetting: true,
  37 + bordered: true,
  38 + showIndexColumn: false,
  39 + clickToRowSelect: false,
  40 + rowKey: 'id',
  41 + // searchInfo: searchInfo,
  42 + actionColumn: {
  43 + width: 230,
  44 + title: '操作',
  45 + slots: { customRender: 'action' },
  46 + fixed: 'right',
  47 + },
  48 + rowSelection: {
  49 + type: 'checkbox',
  50 + getCheckboxProps: (record: any) => {
  51 + return { disabled: !!record.status };
  52 + },
  53 + },
  54 + });
  55 +
  56 + const [registerModal, { openModal }] = useModal();
  57 + const [registerDetailDrawer, { openDrawer }] = useDrawer();
  58 +
  59 + const { createMessage } = useMessage();
  60 + const { isExistOption } = useBatchOperation(getRowSelection, setSelectedRowKeys);
  61 +
  62 + const switchLoading = ref<boolean>(false);
  63 +
  64 + const userInfo: any = getAuthCache(USER_INFO_KEY);
  65 + const role: string = userInfo.roles[0];
  66 +
  67 + const handleReload = () => {
  68 + setSelectedRowKeys([]);
  69 + reload();
  70 + };
  71 +
  72 + // 新增
  73 + const handleCreate = () => {
  74 + openModal(true, {
  75 + isUpdate: false,
  76 + });
  77 + };
  78 +
  79 + // 编辑
  80 + const handleEdit = (record?: any) => {
  81 + openModal(true, {
  82 + isUpdate: true,
  83 + record,
  84 + });
  85 + };
  86 +
  87 + // 详情
  88 + const registerDetailRecord = ref<any>({});
  89 + const handleDetail = (record?: any) => {
  90 + openDrawer(true);
  91 + registerDetailRecord.value = {
  92 + ...record,
  93 + ifShowClass: true,
  94 + };
  95 + };
  96 +
  97 + // 状态->编辑
  98 + const handleSwitch = async (e: any, record: any) => {
  99 + switchLoading.value = true;
  100 + console.log(e, record);
  101 + await deviceProfileCategory({ ...record, status: e });
  102 + switchLoading.value = false;
  103 + createMessage.success('操作成功');
  104 + handleReload();
  105 + };
  106 +
  107 + // 删除
  108 + const handleDelete = async (record?: any) => {
  109 + let ids: string[] = [];
  110 + if (record) {
  111 + ids = [record.id];
  112 + } else {
  113 + ids = getSelectRowKeys();
  114 + }
  115 + try {
  116 + setLoading(true);
  117 + await deleteDeviceClass({ ids });
  118 + createMessage.success('删除成功');
  119 + handleReload();
  120 + } catch (error) {
  121 + throw error;
  122 + } finally {
  123 + setLoading(false);
  124 + }
  125 + };
  126 +</script>
  127 +<template>
  128 + <div>
  129 + <BasicTable style="flex: auto" @register="registerTable">
  130 + <template #toolbar>
  131 + <Authority value="api:yt:product:category:post">
  132 + <Button type="primary" @click="handleCreate"> 新增品类 </Button>
  133 + </Authority>
  134 + <Authority value="api:yt:product:category:delete">
  135 + <Popconfirm
  136 + title="您确定要批量删除数据"
  137 + ok-text="确定"
  138 + cancel-text="取消"
  139 + @confirm="handleDelete()"
  140 + >
  141 + <a-button color="error" :disabled="!isExistOption"> 批量删除 </a-button>
  142 + </Popconfirm>
  143 + </Authority>
  144 + </template>
  145 + <template #status="{ record }">
  146 + <Switch
  147 + @click="(e) => handleSwitch(e, record)"
  148 + :loading="switchLoading"
  149 + v-model:checked="record.status"
  150 + checked-children="启用"
  151 + un-checked-children="禁用"
  152 + :checkedValue="1"
  153 + :unCheckedValue="0"
  154 + />
  155 + </template>
  156 + <template #action="{ record }">
  157 + <TableAction
  158 + :actions="[
  159 + {
  160 + label: '详情',
  161 + icon: 'ant-design:eye-outlined',
  162 + auth: 'api:yt:product:category:get',
  163 + onClick: handleDetail.bind(null, record),
  164 + },
  165 + {
  166 + label: '编辑',
  167 + auth: 'api:yt:product:category:update',
  168 + icon: 'clarity:note-edit-line',
  169 + ifShow: authBtn(role),
  170 + onClick: handleEdit.bind(null, record),
  171 + },
  172 + {
  173 + label: '删除',
  174 + auth: 'api:yt:product:category:delete',
  175 + icon: 'ant-design:delete-outlined',
  176 + ifShow: authBtn(role) && !record.status,
  177 + color: 'error',
  178 + popConfirm: {
  179 + title: '是否确认删除',
  180 + confirm: handleDelete.bind(null, record),
  181 + },
  182 + },
  183 + ]"
  184 + />
  185 + </template>
  186 + </BasicTable>
  187 + <classModal @register="registerModal" @handleReload="handleReload" />
  188 + <!-- <physicalModel @register="registerDetailDrawer" :record /> -->
  189 + <BasicDrawer title="物模型" @register="registerDetailDrawer" width="60%" destroy-on-close>
  190 + <PhysicalModelManagementStep :record="registerDetailRecord" />
  191 + </BasicDrawer>
  192 + </div>
  193 +</template>
@@ -16,6 +16,9 @@ import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; @@ -16,6 +16,9 @@ import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
16 import { createImgPreview } from '/@/components/Preview'; 16 import { createImgPreview } from '/@/components/Preview';
17 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; 17 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
18 18
  19 +import { getOrganizationList } from '/@/api/system/system';
  20 +import { copyTransFun } from '/@/utils/fnUtils';
  21 +
19 useComponentRegister('JSONEditor', JSONEditor); 22 useComponentRegister('JSONEditor', JSONEditor);
20 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm); 23 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
21 24
@@ -263,22 +266,15 @@ export const step1Schemas: FormSchema[] = [ @@ -263,22 +266,15 @@ export const step1Schemas: FormSchema[] = [
263 ifShow: ({ values }) => isGateWay(values.deviceType), 266 ifShow: ({ values }) => isGateWay(values.deviceType),
264 }, 267 },
265 { 268 {
266 - field: 'organizationId',  
267 - label: '所属组织',  
268 - component: 'Input',  
269 - required: true,  
270 - slot: 'addOrg',  
271 - },  
272 - {  
273 field: 'gatewayId', 269 field: 'gatewayId',
274 label: '网关设备', 270 label: '网关设备',
275 required: true, 271 required: true,
276 component: 'ApiSelect', 272 component: 'ApiSelect',
277 - ifShow: ({ values }) => values.deviceType === 'SENSOR' && values.organizationId, 273 + ifShow: ({ values }) => values.deviceType === 'SENSOR',
278 componentProps: ({ formModel, formActionType }) => { 274 componentProps: ({ formModel, formActionType }) => {
279 - const { organizationId, transportType } = formModel; 275 + const { transportType } = formModel;
280 const { validateFields } = formActionType; 276 const { validateFields } = formActionType;
281 - if (![organizationId, transportType].every(Boolean)) return {}; 277 + if (!transportType) return {};
282 return { 278 return {
283 api: async (params: Recordable) => { 279 api: async (params: Recordable) => {
284 try { 280 try {
@@ -291,19 +287,36 @@ export const step1Schemas: FormSchema[] = [ @@ -291,19 +287,36 @@ export const step1Schemas: FormSchema[] = [
291 }, 287 },
292 showSearch: true, 288 showSearch: true,
293 params: { 289 params: {
294 - organizationId,  
295 transportType, 290 transportType,
296 }, 291 },
297 valueField: 'tbDeviceId', 292 valueField: 'tbDeviceId',
298 labelField: 'alias', 293 labelField: 'alias',
299 - onChange: async () => { 294 + onChange: async (value, options) => {
300 await nextTick(); 295 await nextTick();
  296 + if (value) {
  297 + const data = await getOrganizationList({ organizationId: options?.organizationId });
  298 + copyTransFun(data as any as any[]);
  299 + formModel.organizationList = data;
  300 + }
301 validateFields(['gatewayId']); 301 validateFields(['gatewayId']);
302 }, 302 },
303 }; 303 };
304 }, 304 },
305 }, 305 },
306 { 306 {
  307 + field: 'organizationList',
  308 + label: '依据网关设备请求的组织数组',
  309 + component: 'Input',
  310 + ifShow: false,
  311 + },
  312 + {
  313 + field: 'organizationId',
  314 + label: '所属组织',
  315 + component: 'Select',
  316 + required: true,
  317 + slot: 'addOrg',
  318 + },
  319 + {
307 field: 'label', 320 field: 'label',
308 label: '设备标签', 321 label: '设备标签',
309 component: 'Input', 322 component: 'Input',
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 placeholder="请选择组织" 14 placeholder="请选择组织"
15 allow-clear 15 allow-clear
16 tree-default-expand-all 16 tree-default-expand-all
17 - :tree-data="treeData" 17 + :tree-data="model?.['organizationList'] || treeData"
18 /> 18 />
19 </div> 19 </div>
20 <div> 20 <div>
@@ -49,44 +49,46 @@ @@ -49,44 +49,46 @@
49 centered 49 centered
50 > 50 >
51 <div> 51 <div>
52 - <Form :label-col="labelCol" :colon="false" :rules="rules" :model="positionState">  
53 - <Row :gutter="20" class="mt-4">  
54 - <Col :span="20">  
55 - <FormItem label="搜索位置">  
56 - <AutoComplete  
57 - v-model:value="positionState.address"  
58 - :options="dataSource"  
59 - style="width: 100%"  
60 - placeholder="搜索位置"  
61 - @search="debounceSearch"  
62 - @select="handleSelect"  
63 - backfill  
64 - />  
65 - </FormItem>  
66 - </Col>  
67 - </Row>  
68 - <Row :gutter="20" class="">  
69 - <Col :span="10">  
70 - <FormItem label="经度" name="longitude">  
71 - <Input  
72 - @blur="redirectPosition"  
73 - @change="redirectPosition"  
74 - v-model:value="positionState.longitude"  
75 - />  
76 - </FormItem>  
77 - </Col>  
78 - <Col :span="10">  
79 - <FormItem label="纬度" name="latitude">  
80 - <Input  
81 - @blur="redirectPosition"  
82 - @change="redirectPosition"  
83 - v-model:value="positionState.latitude"  
84 - />  
85 - </FormItem>  
86 - </Col>  
87 - </Row>  
88 - </Form>  
89 - <div ref="wrapRef" style="height: 300px; width: 90%" class="ml-6"></div> 52 + <Spin :spinning="spinning">
  53 + <Form :label-col="labelCol" :colon="false" :rules="rules" :model="positionState">
  54 + <Row :gutter="20" class="mt-4">
  55 + <Col :span="20">
  56 + <FormItem label="搜索位置">
  57 + <AutoComplete
  58 + v-model:value="positionState.address"
  59 + :options="dataSource"
  60 + style="width: 100%"
  61 + placeholder="搜索位置"
  62 + @search="debounceSearch"
  63 + @select="handleSelect"
  64 + backfill
  65 + />
  66 + </FormItem>
  67 + </Col>
  68 + </Row>
  69 + <Row :gutter="20" class="">
  70 + <Col :span="10">
  71 + <FormItem label="经度" name="longitude">
  72 + <Input
  73 + @blur="redirectPosition"
  74 + @change="redirectPosition"
  75 + v-model:value="positionState.longitude"
  76 + />
  77 + </FormItem>
  78 + </Col>
  79 + <Col :span="10">
  80 + <FormItem label="纬度" name="latitude">
  81 + <Input
  82 + @blur="redirectPosition"
  83 + @change="redirectPosition"
  84 + v-model:value="positionState.latitude"
  85 + />
  86 + </FormItem>
  87 + </Col>
  88 + </Row>
  89 + </Form>
  90 + <div ref="wrapRef" style="height: 300px; width: 90%" class="ml-6"></div>
  91 + </Spin>
90 </div> 92 </div>
91 </Modal> 93 </Modal>
92 <DeptDrawer @register="registerModal" @success="handleSuccess" /> 94 <DeptDrawer @register="registerModal" @success="handleSuccess" />
@@ -97,7 +99,7 @@ @@ -97,7 +99,7 @@
97 import { BasicForm, useForm } from '/@/components/Form'; 99 import { BasicForm, useForm } from '/@/components/Form';
98 import { step1Schemas } from '../../config/data'; 100 import { step1Schemas } from '../../config/data';
99 import { useScript } from '/@/hooks/web/useScript'; 101 import { useScript } from '/@/hooks/web/useScript';
100 - import { Input, message, Modal, Form, Row, Col, AutoComplete } from 'ant-design-vue'; 102 + import { Input, message, Modal, Form, Row, Col, AutoComplete, Spin } from 'ant-design-vue';
101 import { EnvironmentTwoTone } from '@ant-design/icons-vue'; 103 import { EnvironmentTwoTone } from '@ant-design/icons-vue';
102 import { upload } from '/@/api/oss/ossFileUploader'; 104 import { upload } from '/@/api/oss/ossFileUploader';
103 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils'; 105 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
@@ -113,6 +115,8 @@ @@ -113,6 +115,8 @@
113 import { toRaw } from 'vue'; 115 import { toRaw } from 'vue';
114 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 116 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
115 import { buildUUID } from '/@/utils/uuid'; 117 import { buildUUID } from '/@/utils/uuid';
  118 + import { useMessage } from '/@/hooks/web/useMessage';
  119 + import { computed } from 'vue';
116 120
117 export default defineComponent({ 121 export default defineComponent({
118 components: { 122 components: {
@@ -126,6 +130,7 @@ @@ -126,6 +130,7 @@
126 Row, 130 Row,
127 Col, 131 Col,
128 DeptDrawer, 132 DeptDrawer,
  133 + Spin,
129 }, 134 },
130 props: { 135 props: {
131 isUpdate: { 136 isUpdate: {
@@ -134,9 +139,14 @@ @@ -134,9 +139,14 @@
134 }, 139 },
135 emits: ['next'], 140 emits: ['next'],
136 setup(props, { emit }) { 141 setup(props, { emit }) {
  142 + const { createMessage } = useMessage();
137 const orgData: any = reactive({ 143 const orgData: any = reactive({
138 treeData: [], 144 treeData: [],
139 }); 145 });
  146 + const isUpdate1 = computed(() => {
  147 + const { isUpdate } = props;
  148 + return isUpdate;
  149 + });
140 const getOrganizationListFunc = async () => { 150 const getOrganizationListFunc = async () => {
141 const data = await getOrganizationList(); 151 const data = await getOrganizationList();
142 copyTransFun(data as any as any[]); 152 copyTransFun(data as any as any[]);
@@ -168,17 +178,27 @@ @@ -168,17 +178,27 @@
168 const devicePic = ref(''); 178 const devicePic = ref('');
169 const loading = ref(false); 179 const loading = ref(false);
170 180
171 - const [register, { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema }] =  
172 - useForm({  
173 - labelWidth: 140,  
174 - schemas: step1Schemas,  
175 - actionColOptions: {  
176 - span: 14,  
177 - },  
178 - labelAlign: 'right',  
179 - showResetButton: false,  
180 - showSubmitButton: false,  
181 - }); 181 + const [
  182 + register,
  183 + {
  184 + validate,
  185 + resetFields,
  186 + setFieldsValue,
  187 + getFieldsValue,
  188 + updateSchema,
  189 + clearValidate,
  190 + validateFields,
  191 + },
  192 + ] = useForm({
  193 + labelWidth: 140,
  194 + schemas: step1Schemas,
  195 + actionColOptions: {
  196 + span: 14,
  197 + },
  198 + labelAlign: 'right',
  199 + showResetButton: false,
  200 + showSubmitButton: false,
  201 + });
182 async function nextStep() { 202 async function nextStep() {
183 try { 203 try {
184 let values = await validate(); 204 let values = await validate();
@@ -220,21 +240,53 @@ @@ -220,21 +240,53 @@
220 }; 240 };
221 241
222 // 地图的弹框 242 // 地图的弹框
  243 + const spinning = ref<boolean>(false);
223 const visible = ref(false); 244 const visible = ref(false);
224 - const selectPosition = () => { 245 + const selectPosition = async () => {
225 visible.value = true; 246 visible.value = true;
226 - if (!positionState.longitude) {  
227 - positionState.longitude = '104.05326410962411';  
228 - positionState.latitude = '30.54855093076791';  
229 -  
230 - // 根据经纬度获取详细位置  
231 - if (positionState.longitude && positionState.latitude) {  
232 - var pt = new BMap.Point(positionState.longitude, positionState.latitude);  
233 - getAddrByPoint(pt); 247 + if (
  248 + !unref(isUpdate1) &&
  249 + unref(devicePositionState).longitude &&
  250 + unref(devicePositionState).latitude
  251 + ) {
  252 + positionState.longitude = unref(devicePositionState).longitude;
  253 + positionState.latitude = unref(devicePositionState).latitude;
  254 + }
  255 + if (!positionState.longitude || !positionState.latitude) {
  256 + let getLocalCity = new BMap.LocalCity();
  257 + let getLocation = new BMap.Geolocation();
  258 + const userAgent = navigator.userAgent;
  259 + try {
  260 + spinning.value = true;
  261 + if (
  262 + (userAgent.indexOf('Chrome') > -1 || userAgent.indexOf('WebKit') > -1) &&
  263 + userAgent.indexOf('Edg') == -1
  264 + ) {
  265 + //判断是否是谷歌 或则是谷歌的内核 '104.05326410962411'; '30.54855093076791';
  266 + await getLocalCity.get(function (res) {
  267 + positionState.longitude = res?.center.lng || '104.05326410962411';
  268 + positionState.latitude = res?.center.lat || '30.54855093076791';
  269 + var pt = new BMap.Point(positionState.longitude, positionState.latitude);
  270 + getAddrByPoint(pt);
  271 + spinning.value = false;
  272 + });
  273 + } else {
  274 + await getLocation.getCurrentPosition(function (res) {
  275 + positionState.longitude = res?.longitude || '104.05326410962411';
  276 + positionState.latitude = res?.latitude || '30.54855093076791';
  277 + var pt = new BMap.Point(positionState.longitude, positionState.latitude);
  278 + getAddrByPoint(pt);
  279 + spinning.value = false;
  280 + });
  281 + }
  282 + } catch (err) {
  283 + createMessage.error('获取定位失败');
234 } 284 }
235 initMap(positionState.longitude, positionState.latitude); 285 initMap(positionState.longitude, positionState.latitude);
236 } else { 286 } else {
237 initMap(positionState.longitude, positionState.latitude); 287 initMap(positionState.longitude, positionState.latitude);
  288 + var pt = new BMap.Point(positionState.longitude, positionState.latitude);
  289 + getAddrByPoint(pt);
238 } 290 }
239 }; 291 };
240 292
@@ -447,11 +499,11 @@ @@ -447,11 +499,11 @@
447 }); 499 });
448 } 500 }
449 501
450 - const handleTreeOrg = () => {  
451 - const clearGatewayId = {  
452 - gatewayId: '',  
453 - };  
454 - setFieldsValue(clearGatewayId); 502 + const handleTreeOrg = (option: string) => {
  503 + if (option) clearValidate('organizationId');
  504 + else {
  505 + validateFields(['organizationId']);
  506 + }
455 }; 507 };
456 508
457 return { 509 return {
@@ -488,6 +540,7 @@ @@ -488,6 +540,7 @@
488 handleSuccess, 540 handleSuccess,
489 handleTreeOrg, 541 handleTreeOrg,
490 devicePositionState, 542 devicePositionState,
  543 + spinning,
491 }; 544 };
492 }, 545 },
493 }); 546 });
@@ -15,13 +15,13 @@ @@ -15,13 +15,13 @@
15 import { useHistoryData } from '../../hook/useHistoryData'; 15 import { useHistoryData } from '../../hook/useHistoryData';
16 import { formatToDateTime } from '/@/utils/dateUtil'; 16 import { formatToDateTime } from '/@/utils/dateUtil';
17 import { useTable, BasicTable, BasicColumn } from '/@/components/Table'; 17 import { useTable, BasicTable, BasicColumn } from '/@/components/Table';
18 - import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';  
19 import { 18 import {
20 ModeSwitchButton, 19 ModeSwitchButton,
21 TABLE_CHART_MODE_LIST, 20 TABLE_CHART_MODE_LIST,
22 EnumTableChartMode, 21 EnumTableChartMode,
23 } from '/@/components/Widget'; 22 } from '/@/components/Widget';
24 import { SchemaFiled } from '/@/views/visual/palette/components/HistoryTrendModal/config'; 23 import { SchemaFiled } from '/@/views/visual/palette/components/HistoryTrendModal/config';
  24 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
25 25
26 interface DeviceDetail { 26 interface DeviceDetail {
27 tbDeviceId: string; 27 tbDeviceId: string;
@@ -188,7 +188,7 @@ @@ -188,7 +188,7 @@
188 method.setFieldsValue({ keys: props.attr }); 188 method.setFieldsValue({ keys: props.attr });
189 const attrInfo = unref(deviceAttrs).find((item) => item.identifier === props.attr); 189 const attrInfo = unref(deviceAttrs).find((item) => item.identifier === props.attr);
190 if ( 190 if (
191 - [DataTypeEnum.IS_STRING, DataTypeEnum.IS_STRUCT].includes( 191 + [DataTypeEnum.STRING, DataTypeEnum.STRUCT].includes(
192 attrInfo?.detail.dataType.type as unknown as DataTypeEnum 192 attrInfo?.detail.dataType.type as unknown as DataTypeEnum
193 ) 193 )
194 ) { 194 ) {
@@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
16 import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel'; 16 import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
17 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; 17 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
18 import { isArray, isNull, isObject } from '/@/utils/is'; 18 import { isArray, isNull, isObject } from '/@/utils/is';
19 - import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';  
20 import { useGlobSetting } from '/@/hooks/setting'; 19 import { useGlobSetting } from '/@/hooks/setting';
21 import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget'; 20 import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget';
22 import { toRaw } from 'vue'; 21 import { toRaw } from 'vue';
@@ -25,6 +24,7 @@ @@ -25,6 +24,7 @@
25 import { ModalParamsType } from '/#/utils'; 24 import { ModalParamsType } from '/#/utils';
26 import { AreaChartOutlined } from '@ant-design/icons-vue'; 25 import { AreaChartOutlined } from '@ant-design/icons-vue';
27 import { SvgIcon } from '/@/components/Icon'; 26 import { SvgIcon } from '/@/components/Icon';
  27 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
28 28
29 interface ReceiveMessage { 29 interface ReceiveMessage {
30 data: { 30 data: {
@@ -220,7 +220,7 @@ @@ -220,7 +220,7 @@
220 }; 220 };
221 221
222 const isStructAndTextType = (type: DataTypeEnum) => { 222 const isStructAndTextType = (type: DataTypeEnum) => {
223 - return [DataTypeEnum.IS_STRUCT, DataTypeEnum.IS_STRING].includes(type); 223 + return [DataTypeEnum.STRUCT, DataTypeEnum.STRING].includes(type);
224 }; 224 };
225 225
226 const setDataSource = () => { 226 const setDataSource = () => {
@@ -311,7 +311,7 @@ @@ -311,7 +311,7 @@
311 }); 311 });
312 312
313 const formatValue = (item: DataSource) => { 313 const formatValue = (item: DataSource) => {
314 - return item.type === DataTypeEnum.IS_BOOL 314 + return item.type === DataTypeEnum.BOOL
315 ? !isNull(item.value) 315 ? !isNull(item.value)
316 ? !!Number(item.value) 316 ? !!Number(item.value)
317 ? item.boolOpen 317 ? item.boolOpen
@@ -6,7 +6,6 @@ @@ -6,7 +6,6 @@
6 import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; 6 import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
7 import { BasicForm, useForm } from '/@/components/Form'; 7 import { BasicForm, useForm } from '/@/components/Form';
8 import { BasicModal, useModalInner } from '/@/components/Modal'; 8 import { BasicModal, useModalInner } from '/@/components/Modal';
9 - import { DataTypeEnum } from '/@/views/device/profiles/step/cpns/physical/cpns/config';  
10 import { sendCommandOneway } from '/@/api/dataBoard'; 9 import { sendCommandOneway } from '/@/api/dataBoard';
11 import { useMessage } from '/@/hooks/web/useMessage'; 10 import { useMessage } from '/@/hooks/web/useMessage';
12 import { unref } from 'vue'; 11 import { unref } from 'vue';
@@ -14,6 +13,7 @@ @@ -14,6 +13,7 @@
14 import { TaskTypeEnum } from '/@/views/task/center/config'; 13 import { TaskTypeEnum } from '/@/views/task/center/config';
15 import { SingleToHex, formSchemasConfig } from './config'; 14 import { SingleToHex, formSchemasConfig } from './config';
16 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const'; 15 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  16 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
17 17
18 defineEmits(['register']); 18 defineEmits(['register']);
19 const props = defineProps<{ deviceId: string; deviceName: string }>(); 19 const props = defineProps<{ deviceId: string; deviceName: string }>();
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 51
52 let schemas = [{ dataType: dataType, identifier, functionName: name } as StructJSON]; 52 let schemas = [{ dataType: dataType, identifier, functionName: name } as StructJSON];
53 53
54 - if (type === DataTypeEnum.IS_STRUCT) { 54 + if (type === DataTypeEnum.STRUCT) {
55 schemas = dataType?.specs as StructJSON[]; 55 schemas = dataType?.specs as StructJSON[];
56 } 56 }
57 57
1 import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; 1 import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
2 import { JSONEditor } from '/@/components/CodeEditor'; 2 import { JSONEditor } from '/@/components/CodeEditor';
3 import { FormSchema, useComponentRegister } from '/@/components/Form'; 3 import { FormSchema, useComponentRegister } from '/@/components/Form';
  4 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
4 import { useJsonParse } from '/@/hooks/business/useJsonParse'; 5 import { useJsonParse } from '/@/hooks/business/useJsonParse';
5 -import { DataTypeEnum } from '/@/views/device/profiles/step/cpns/physical/cpns/config';  
6 6
7 export interface BasicCreateFormParams { 7 export interface BasicCreateFormParams {
8 identifier: string; 8 identifier: string;
@@ -19,9 +19,9 @@ const validateDouble = ( @@ -19,9 +19,9 @@ const validateDouble = (
19 max?: number | string 19 max?: number | string
20 ) => { 20 ) => {
21 min = 21 min =
22 - Number(min) || type === DataTypeEnum.IS_NUMBER_INT ? Number.MIN_SAFE_INTEGER : Number.MIN_VALUE; 22 + Number(min) || type === DataTypeEnum.NUMBER_INT ? Number.MIN_SAFE_INTEGER : Number.MIN_VALUE;
23 max = 23 max =
24 - Number(max) || type === DataTypeEnum.IS_NUMBER_INT ? Number.MAX_SAFE_INTEGER : Number.MAX_VALUE; 24 + Number(max) || type === DataTypeEnum.NUMBER_INT ? Number.MAX_SAFE_INTEGER : Number.MAX_VALUE;
25 25
26 return { 26 return {
27 flag: value < min || value > max, 27 flag: value < min || value > max,
@@ -56,13 +56,11 @@ export const useGenDynamicForm = () => { @@ -56,13 +56,11 @@ export const useGenDynamicForm = () => {
56 }, 56 },
57 ], 57 ],
58 componentProps: { 58 componentProps: {
59 - max:  
60 - max ?? type === DataTypeEnum.IS_NUMBER_INT ? Number.MAX_SAFE_INTEGER : Number.MAX_VALUE,  
61 - min:  
62 - min ?? type === DataTypeEnum.IS_NUMBER_INT ? Number.MIN_SAFE_INTEGER : Number.MIN_VALUE, 59 + max: max ?? type === DataTypeEnum.NUMBER_INT ? Number.MAX_SAFE_INTEGER : Number.MAX_VALUE,
  60 + min: min ?? type === DataTypeEnum.NUMBER_INT ? Number.MIN_SAFE_INTEGER : Number.MIN_VALUE,
63 step, 61 step,
64 placeholder: `请输入${functionName}`, 62 placeholder: `请输入${functionName}`,
65 - precision: type === DataTypeEnum.IS_NUMBER_INT ? 0 : 2, 63 + precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2,
66 }, 64 },
67 } as FormSchema; 65 } as FormSchema;
68 }; 66 };
@@ -141,11 +139,11 @@ export const useGenDynamicForm = () => { @@ -141,11 +139,11 @@ export const useGenDynamicForm = () => {
141 }; 139 };
142 140
143 const schemaMethod = { 141 const schemaMethod = {
144 - [DataTypeEnum.IS_BOOL]: createSelect,  
145 - [DataTypeEnum.IS_NUMBER_DOUBLE]: createInputNumber,  
146 - [DataTypeEnum.IS_NUMBER_INT]: createInputNumber,  
147 - [DataTypeEnum.IS_STRING]: createInput,  
148 - [DataTypeEnum.IS_STRUCT]: createInputJson, 142 + [DataTypeEnum.BOOL]: createSelect,
  143 + [DataTypeEnum.NUMBER_DOUBLE]: createInputNumber,
  144 + [DataTypeEnum.NUMBER_INT]: createInputNumber,
  145 + [DataTypeEnum.STRING]: createInput,
  146 + [DataTypeEnum.STRUCT]: createInputJson,
149 }; 147 };
150 148
151 const fieldTypeMap = new Map<string, DataTypeEnum>(); 149 const fieldTypeMap = new Map<string, DataTypeEnum>();
@@ -176,7 +174,7 @@ export const useGenDynamicForm = () => { @@ -176,7 +174,7 @@ export const useGenDynamicForm = () => {
176 const dataType = fieldTypeMap.get(next)!; 174 const dataType = fieldTypeMap.get(next)!;
177 175
178 let itemValue = value[next]; 176 let itemValue = value[next];
179 - if (dataType === DataTypeEnum.IS_STRUCT) { 177 + if (dataType === DataTypeEnum.STRUCT) {
180 const { value } = useJsonParse(itemValue); 178 const { value } = useJsonParse(itemValue);
181 itemValue = value; 179 itemValue = value;
182 } 180 }
@@ -322,4 +322,8 @@ @@ -322,4 +322,8 @@
322 .profile-list:deep(.ant-card-body) { 322 .profile-list:deep(.ant-card-body) {
323 @apply !p-4; 323 @apply !p-4;
324 } 324 }
  325 +
  326 + :deep(.ant-spin-nested-loading) {
  327 + height: 40rem;
  328 + }
325 </style> 329 </style>
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 :maskClosable="false" 4 :maskClosable="false"
5 v-bind="$attrs" 5 v-bind="$attrs"
6 :width="dynamicWidth" 6 :width="dynamicWidth"
  7 + :destroy-on-close="true"
7 @register="register" 8 @register="register"
8 @ok="handleSubmit" 9 @ok="handleSubmit"
9 @cancel="handleCancel" 10 @cancel="handleCancel"
@@ -125,6 +126,8 @@ @@ -125,6 +126,8 @@
125 const title = unref(isUpdate) ? '编辑产品' : '新增产品'; 126 const title = unref(isUpdate) ? '编辑产品' : '新增产品';
126 setModalProps({ title, showOkBtn: true, showCancelBtn: true }); 127 setModalProps({ title, showOkBtn: true, showCancelBtn: true });
127 if (unref(isUpdate)) { 128 if (unref(isUpdate)) {
  129 + const { deviceType } = res || {};
  130 + deviceTypeStr.value = deviceType;
128 await setDeviceConfEditFormData(res); 131 await setDeviceConfEditFormData(res);
129 await setTransConfEditFormData(res); 132 await setTransConfEditFormData(res);
130 handleStepNext(false, res); 133 handleStepNext(false, res);
@@ -157,7 +160,6 @@ @@ -157,7 +160,6 @@
157 }; 160 };
158 const deviceTypeStr = ref(''); 161 const deviceTypeStr = ref('');
159 const handleGetDeviceType = async (e) => { 162 const handleGetDeviceType = async (e) => {
160 - console.log(e);  
161 deviceTypeStr.value = e; 163 deviceTypeStr.value = e;
162 await nextTick(); 164 await nextTick();
163 TransConStRef.value?.updateDisabled(e); 165 TransConStRef.value?.updateDisabled(e);
  1 +<script lang="ts" setup>
  2 + import { columns, searchFormSchema } from './config';
  3 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  4 + import { BasicTable } from '/@/components/Table';
  5 + import { useTable } from '/@/components/Table';
  6 + import { Button } from 'ant-design-vue';
  7 + import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
  8 + import { getDeviceClassList } from '/@/api/device/classModal';
  9 + import { ref } from 'vue';
  10 + const emits = defineEmits([
  11 + 'handleOpenListDrawer',
  12 + 'handleSelectCategory',
  13 + 'handleClose',
  14 + 'register',
  15 + ]);
  16 +
  17 + const categoryId = ref<string | undefined | null>();
  18 + const [registerDrawer] = useDrawerInner((data) => {
  19 + categoryId.value = data.categoryId || {};
  20 + });
  21 + const [registerTable] = useTable({
  22 + title: '',
  23 + api: getDeviceClassList,
  24 + columns,
  25 + formConfig: {
  26 + schemas: searchFormSchema,
  27 + },
  28 + searchInfo: {
  29 + status: 1,
  30 + },
  31 + resizeHeightOffset: 50,
  32 + useSearchForm: true,
  33 + showTableSetting: false,
  34 + bordered: true,
  35 + showIndexColumn: false,
  36 + clickToRowSelect: false,
  37 + rowKey: 'id',
  38 + actionColumn: {
  39 + width: 100,
  40 + title: '操作',
  41 + slots: { customRender: 'action' },
  42 + fixed: 'right',
  43 + },
  44 + });
  45 +
  46 + const closeDrawer = () => {
  47 + emits('handleClose');
  48 + };
  49 +
  50 + const handleChecked = (item?: any) => {
  51 + emits('handleSelectCategory', item);
  52 + };
  53 +
  54 + const handleOpenListDrawer = (record?: any) => {
  55 + emits('handleOpenListDrawer', record);
  56 + };
  57 +</script>
  58 +
  59 +<template>
  60 + <BasicDrawer
  61 + :zIndex="1100"
  62 + v-bind="$attrs"
  63 + @register="registerDrawer"
  64 + :showFooter="true"
  65 + destroyOnClose
  66 + @close="closeDrawer"
  67 + title="选择品类"
  68 + width="32%"
  69 + >
  70 + <BasicTable @register="registerTable">
  71 + <template #name="{ record }">
  72 + <span>{{ record.name }}</span>
  73 + <ExclamationCircleOutlined
  74 + style="color: #377dff"
  75 + @click.stop="handleOpenListDrawer(record)"
  76 + class="ml-1"
  77 + />
  78 + </template>
  79 + <template #action="{ record }">
  80 + <Button type="link" :disabled="categoryId === record.id" @click="handleChecked(record)">{{
  81 + categoryId === record.id ? '已选择' : '选择'
  82 + }}</Button>
  83 + </template>
  84 + </BasicTable>
  85 + </BasicDrawer>
  86 +</template>
  1 +<script lang="ts" setup>
  2 + import { columnsDrawer } from './config';
  3 + import { unref, ref, watch, nextTick } from 'vue';
  4 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  5 + import { BasicTable } from '/@/components/Table';
  6 + import { useTable } from '/@/components/Table';
  7 + import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
  8 + import { getModelList } from '/@/api/device/modelOfMatter';
  9 +
  10 + defineEmits(['register']);
  11 +
  12 + const props = defineProps<{ cateforyListInfo: any }>();
  13 +
  14 + const recordInfo = ref<any>({});
  15 + const secondVisible = ref<Boolean>(false);
  16 +
  17 + watch(
  18 + () => props.cateforyListInfo,
  19 + async () => {
  20 + recordInfo.value = props.cateforyListInfo.record || {};
  21 + secondVisible.value = props.cateforyListInfo.isRight || false;
  22 + await nextTick();
  23 + reload();
  24 + }
  25 + );
  26 + const [registerListDrawer, { closeDrawer }] = useDrawerInner();
  27 + const [registerTable, { reload }] = useTable({
  28 + title: '',
  29 + api: async (params: Record<'page' | 'pageSize', number>) => {
  30 + return await getModelList({
  31 + ...params,
  32 + id: unref(recordInfo).id,
  33 + selectType: 'category',
  34 + });
  35 + },
  36 + columns: columnsDrawer,
  37 + useSearchForm: false,
  38 + showTableSetting: false,
  39 + bordered: true,
  40 + showIndexColumn: false,
  41 + clickToRowSelect: false,
  42 + rowKey: 'id',
  43 + });
  44 +
  45 + const handleClose = () => {
  46 + closeDrawer();
  47 + };
  48 +</script>
  49 +
  50 +<template>
  51 + <BasicDrawer
  52 + :zIndex="1100"
  53 + @register="registerListDrawer"
  54 + wrap-class-name="secondDrawer"
  55 + :wrap-style="{
  56 + right: secondVisible ? '32%' : 0,
  57 + transition: 'none',
  58 + }"
  59 + v-bind="$attrs"
  60 + destroyOnClose
  61 + @close="handleClose"
  62 + title="物模型"
  63 + width="36%"
  64 + >
  65 + <BasicTable @register="registerTable">
  66 + <template #name="{ record }">
  67 + <span>{{ record.name }}</span>
  68 + <ExclamationCircleOutlined class="ml-1" />
  69 + </template>
  70 + </BasicTable>
  71 + </BasicDrawer>
  72 +</template>
  1 +<template>
  2 + <BasicModal
  3 + title="导入物模型"
  4 + :maskClosable="false"
  5 + destroyOnClose
  6 + v-bind="$attrs"
  7 + width="25rem"
  8 + @register="register"
  9 + @cancel="handleCancel"
  10 + :showOkBtn="false"
  11 + >
  12 + <div class="w-full h-full" ref="loadingRef">
  13 + <!-- <div class="flex justify-end">
  14 + <Button @click="handleTemplateDownload" type="link">EXCEL模板下载</Button>
  15 + </div> -->
  16 + <div class="flex justify-evenly items-center h-50 !w-full">
  17 + <Upload
  18 + accept=".json,"
  19 + :show-upload-list="false"
  20 + :customRequest="handleImportModel"
  21 + class="flex justify-center items-center"
  22 + >
  23 + <div class="flex flex-col justify-center items-center">
  24 + <img :src="JSONImage" alt="avatar" class="w-20 h-20" />
  25 + </div>
  26 + </Upload>
  27 + <Upload
  28 + accept=".csv,.xls,.xlsx"
  29 + :show-upload-list="false"
  30 + :customRequest="handleCSVImport"
  31 + class="flex justify-center items-center"
  32 + >
  33 + <div class="flex flex-col justify-center items-center">
  34 + <img :src="CSVImage" alt="avatar" class="w-20 h-20" />
  35 + </div>
  36 + </Upload>
  37 + </div>
  38 + </div>
  39 + </BasicModal>
  40 +</template>
  41 +<script lang="ts" setup>
  42 + import { ref, unref } from 'vue';
  43 + import { Upload } from 'ant-design-vue';
  44 + import { BasicModal, useModalInner } from '/@/components/Modal';
  45 + import { DeviceRecord } from '/@/api/device/model/deviceModel';
  46 + import { useMessage } from '/@/hooks/web/useMessage';
  47 + import { isObject, isString } from '/@/utils/is';
  48 + import {
  49 + importCsvCategory,
  50 + importModelCategory,
  51 + importModelOfMatter,
  52 + importCsvDeviceProfileId,
  53 + } from '/@/api/device/modelOfMatter';
  54 + // import XLSX, { CellObject } from 'xlsx';
  55 + import { useLoading } from '/@/components/Loading';
  56 + import JSONImage from '/@/assets/svg/JSON.svg';
  57 + import CSVImage from '/@/assets/svg/excel.svg';
  58 +
  59 + const emits = defineEmits(['register', 'handleImportCSV', 'handleReload']);
  60 +
  61 + defineProps<{
  62 + record: DeviceRecord;
  63 + }>();
  64 +
  65 + const { createMessage } = useMessage();
  66 + const loadingRef = ref();
  67 + const [openLoading, closeLoading] = useLoading({
  68 + target: loadingRef,
  69 + props: {
  70 + tip: '加载中',
  71 + absolute: true,
  72 + },
  73 + });
  74 +
  75 + const isEmptyObject = (value: any) => isObject(value) && !Object.keys(value).length;
  76 +
  77 + const ImportInfo = ref<{ id?: string; isCateGory?: string | Boolean }>({});
  78 +
  79 + const [register, { closeModal }] = useModalInner(async (data) => {
  80 + ImportInfo.value = data;
  81 + });
  82 + // 导入loading
  83 + const importLoading = ref(false);
  84 +
  85 + const paseJSON = (string: string) => {
  86 + let data = null;
  87 + let flag = false;
  88 + try {
  89 + if (!isString(string)) return { flag: false, data };
  90 + data = JSON.parse(string);
  91 + flag = true;
  92 + if (!isObject(data)) flag = false;
  93 + } catch (error) {}
  94 + return { flag, data };
  95 + };
  96 +
  97 + // JSON导入
  98 + const handleImportModel = async (data: { file: File }) => {
  99 + const fileReader = new FileReader();
  100 + const { isCateGory, id } = unref(ImportInfo);
  101 +
  102 + fileReader.onload = async () => {
  103 + const { flag, data } = paseJSON(fileReader.result as string);
  104 + if (!flag) {
  105 + createMessage.warning('JSON解析失败,请导入正确的JSON');
  106 + return;
  107 + }
  108 + openLoading();
  109 + try {
  110 + importLoading.value = true;
  111 +
  112 + Object.keys(data || {}).forEach((key) => {
  113 + const value = (data || {})[key];
  114 + if (value && isEmptyObject(value)) {
  115 + (data || {})[key] = [];
  116 + }
  117 + });
  118 +
  119 + const result = isCateGory
  120 + ? await importModelCategory({
  121 + categoryId: id,
  122 + data: data!,
  123 + functionType: 'all',
  124 + })
  125 + : await importModelOfMatter({
  126 + tkDeviceProfileId: id,
  127 + data: data!,
  128 + functionType: 'all',
  129 + });
  130 +
  131 + result
  132 + ? createMessage.success('导入成功~')
  133 + : createMessage.error('JSON解析失败,请导入正确的JSON');
  134 +
  135 + result && emits('handleReload');
  136 + } catch (error) {
  137 + throw error;
  138 + } finally {
  139 + importLoading.value = false;
  140 + closeLoading();
  141 + closeModal();
  142 + }
  143 + };
  144 +
  145 + fileReader.readAsText(data.file, 'utf-8');
  146 + };
  147 +
  148 + // CSV导入
  149 + const handleCSVImport = async ({ file }) => {
  150 + const { isCateGory, id } = unref(ImportInfo);
  151 +
  152 + try {
  153 + openLoading();
  154 + const formData = new FormData();
  155 + formData.set('file', file);
  156 + const flag = isCateGory
  157 + ? await importCsvCategory({ categoryId: id, deviceProfileId: undefined, file: formData })
  158 + : await importCsvDeviceProfileId({
  159 + categoryId: undefined,
  160 + deviceProfileId: id,
  161 + file: formData,
  162 + });
  163 + flag && createMessage.info(flag?.message);
  164 + } catch (msg) {
  165 + console.log(msg, 'msg');
  166 + } finally {
  167 + closeLoading();
  168 + closeModal();
  169 + emits('handleReload');
  170 + }
  171 + };
  172 +
  173 + // const downloadFile = (data: string, fileName: string, type: string, ext: string) => {
  174 + // const blob = new Blob([data], { type: type });
  175 + // const objectURL = URL.createObjectURL(blob);
  176 + // const element = document.createElement('a');
  177 + // element.href = objectURL;
  178 + // element.download = `${fileName}.${ext}`;
  179 + // element.style.display = 'none';
  180 + // document.body.appendChild(element);
  181 + // element.click();
  182 + // element.remove();
  183 + // URL.revokeObjectURL(objectURL);
  184 + // };
  185 +
  186 + // 模板下载
  187 + // const handleTemplateDownload = () => {
  188 +
  189 + // };
  190 +
  191 + const handleCancel = () => {
  192 + closeModal();
  193 + };
  194 +</script>
  195 +
  196 +<style lang="less" scope></style>
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +import { findDictItemByCode } from '/@/api/system/dict';
  3 +import { h, unref } from 'vue';
  4 +import { Tooltip } from 'ant-design-vue';
  5 +
  6 +import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
  7 +import { useMessage } from '/@/hooks/web/useMessage';
  8 +import { useClipboard } from '@vueuse/core';
  9 +import { DictEnum } from '/@/enums/dictEnum';
  10 +
  11 +// import {
  12 +// EventType,
  13 +// EventTypeColor,
  14 +// EventTypeName,
  15 +// } from '/@/views/device/list/cpns/tabs/EventManage/config';
  16 +
  17 +export const columns: BasicColumn[] = [
  18 + {
  19 + title: '品类名称',
  20 + dataIndex: 'name',
  21 + slots: { customRender: 'name' },
  22 + },
  23 + {
  24 + title: '领域',
  25 + dataIndex: 'dictItemName',
  26 + },
  27 +];
  28 +
  29 +export const formatFunctionType: Record<FunctionType, string> = {
  30 + [FunctionType.PROPERTIES]: '属性',
  31 + [FunctionType.EVENTS]: '事件',
  32 + [FunctionType.SERVICE]: '服务',
  33 +};
  34 +
  35 +const handleCopy = async (value: string) => {
  36 + const { createMessage } = useMessage();
  37 + const { copied, copy } = useClipboard({ legacy: true });
  38 + await copy(value);
  39 + if (unref(copied)) createMessage.success('复制成功~');
  40 + else createMessage.error('复制失败~');
  41 +};
  42 +
  43 +export const columnsDrawer: BasicColumn[] = [
  44 + {
  45 + title: '功能类型',
  46 + dataIndex: 'functionType',
  47 + width: 90,
  48 + format: (text: FunctionType) => {
  49 + return formatFunctionType[text];
  50 + },
  51 + },
  52 + {
  53 + title: '功能名称',
  54 + dataIndex: 'functionName',
  55 + width: 90,
  56 + ellipsis: true,
  57 + },
  58 + {
  59 + title: '标识符',
  60 + dataIndex: 'identifier',
  61 + width: 90,
  62 + ellipsis: true,
  63 + customRender: ({ text }: Record<'text', string>) => {
  64 + return h(Tooltip, { title: text }, () =>
  65 + h('span', { class: 'cursor-pointer', onClick: () => handleCopy(text) }, text)
  66 + );
  67 + },
  68 + },
  69 + {
  70 + title: '数据类型',
  71 + dataIndex: 'functionJson.dataType.type',
  72 + width: 100,
  73 + format: (text: string) => {
  74 + return text || '--';
  75 + },
  76 + ellipsis: true,
  77 + },
  78 + // {
  79 + // title: '事件类型',
  80 + // dataIndex: 'eventType',
  81 + // customRender({ text }) {
  82 + // return h(
  83 + // Tag,
  84 + // {
  85 + // color: EventTypeColor[text as EventType],
  86 + // },
  87 + // () => EventTypeName[text as EventType]
  88 + // );
  89 + // },
  90 + // ellipsis: true,
  91 + // },
  92 + // {
  93 + // title: '状态',
  94 + // dataIndex: 'status',
  95 + // width: 100,
  96 + // customRender: (value: Record<'text', number>) => {
  97 + // const { text } = value;
  98 + // return h(
  99 + // Tag,
  100 + // {
  101 + // color: text ? 'green' : 'red',
  102 + // },
  103 + // () => (text ? '已发布' : '待发布')
  104 + // );
  105 + // },
  106 + // },
  107 +];
  108 +
  109 +export const searchFormSchema: FormSchema[] = [
  110 + {
  111 + field: 'dictItemId',
  112 + label: '',
  113 + component: 'ApiSelect',
  114 + colProps: { span: 7 },
  115 + componentProps() {
  116 + return {
  117 + api: findDictItemByCode,
  118 + params: {
  119 + dictCode: DictEnum.CATEGORY_FIELD,
  120 + },
  121 + labelField: 'itemText',
  122 + valueField: 'itemValue',
  123 + placeholder: '请选择领域',
  124 + // getPopupContainer: () => document.body,
  125 + };
  126 + },
  127 + },
  128 + {
  129 + field: 'name',
  130 + label: '',
  131 + component: 'Input',
  132 + colProps: { span: 9 },
  133 + componentProps: {
  134 + placeholder: '请输入品类名称',
  135 + },
  136 + },
  137 +];
@@ -42,6 +42,12 @@ export enum ModelOfMatterPermission { @@ -42,6 +42,12 @@ export enum ModelOfMatterPermission {
42 DELETE = 'api:yt:things_model:delete', 42 DELETE = 'api:yt:things_model:delete',
43 RELEASE = 'api:yt:things_model:release', 43 RELEASE = 'api:yt:things_model:release',
44 } 44 }
  45 +export enum ModelCategoryPermission {
  46 + CREATE = 'api:yt:things_model:category:post',
  47 + UPDATE = 'api:yt:things_model:category:put',
  48 + DELETE = 'api:yt:things_model:category:delete',
  49 + RELEASE = 'api:yt:things_model:category:release',
  50 +}
45 51
46 const handleCopy = async (value: string) => { 52 const handleCopy = async (value: string) => {
47 const { createMessage } = useMessage(); 53 const { createMessage } = useMessage();
@@ -212,6 +218,41 @@ export const step1Schemas: FormSchema[] = [ @@ -212,6 +218,41 @@ export const step1Schemas: FormSchema[] = [
212 }, 218 },
213 }, 219 },
214 { 220 {
  221 + field: 'category',
  222 + label: '产品品类',
  223 + component: 'RadioGroup',
  224 + defaultValue: 2,
  225 + required: true,
  226 + componentProps({ formModel }) {
  227 + return {
  228 + options: [
  229 + { label: '自定义品类', value: 2 },
  230 + { label: '标准品类', value: 1 },
  231 + ],
  232 + onChange() {
  233 + formModel.categoryId = null;
  234 + formModel.categoryName = undefined;
  235 + },
  236 + };
  237 + },
  238 + },
  239 + {
  240 + field: 'categoryId',
  241 + label: '产品品类',
  242 + component: 'Input',
  243 + defaultValue: undefined,
  244 + ifShow: false,
  245 + },
  246 + {
  247 + field: 'categoryName',
  248 + label: ' ',
  249 + component: 'ApiSelect',
  250 + colProps: { span: 14 },
  251 + slot: 'categoryName',
  252 + required: true,
  253 + ifShow: ({ model }) => model.category === 1,
  254 + },
  255 + {
215 field: 'name', 256 field: 'name',
216 label: '产品名称', 257 label: '产品名称',
217 required: true, 258 required: true,
@@ -325,6 +366,14 @@ export const columns: BasicColumn[] = [ @@ -325,6 +366,14 @@ export const columns: BasicColumn[] = [
325 width: 120, 366 width: 120,
326 }, 367 },
327 { 368 {
  369 + title: '产品品类',
  370 + dataIndex: 'categoryName',
  371 + width: 120,
  372 + format: (text) => {
  373 + return text ? text : '自定义品类';
  374 + },
  375 + },
  376 + {
328 title: '设备类型', 377 title: '设备类型',
329 dataIndex: 'deviceType', 378 dataIndex: 'deviceType',
330 width: 90, 379 width: 90,
1 <template> 1 <template>
2 <div class="step1"> 2 <div class="step1">
3 - <BasicForm @register="register" /> 3 + <BasicForm @register="register">
  4 + <template #categoryName="{ model }">
  5 + <div class="flex">
  6 + <Input
  7 + v-model:value="model.categoryName"
  8 + placeholder="请选择所属品类"
  9 + style="width: 300px"
  10 + :disabled="cateGoryDisabled"
  11 + @focus="handleFocus"
  12 + />
  13 + <Button type="link" :disabled="!model.categoryName" @click="handleCategoryId"
  14 + >查看功能</Button
  15 + >
  16 + </div>
  17 + </template>
  18 + </BasicForm>
  19 + <CategoryList
  20 + @register="registerDrawer"
  21 + @handleOpenListDrawer="handleOpenListDrawer"
  22 + @handleSelectCategory="handleSelectCategory"
  23 + @handleClose="handleClose"
  24 + />
  25 + <CategoryListDrawer @register="registerListDrawer" :cateforyListInfo="cateforyListInfo" />
4 </div> 26 </div>
5 </template> 27 </template>
6 <script lang="ts" setup> 28 <script lang="ts" setup>
7 - import { nextTick } from 'vue'; 29 + import { nextTick, ref } from 'vue';
8 import { BasicForm, useForm } from '/@/components/Form'; 30 import { BasicForm, useForm } from '/@/components/Form';
9 import { step1Schemas } from '../device.profile.data'; 31 import { step1Schemas } from '../device.profile.data';
10 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 32 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
11 import { buildUUID } from '/@/utils/uuid'; 33 import { buildUUID } from '/@/utils/uuid';
  34 + import { Input, Button } from 'ant-design-vue';
  35 + import { useDrawer } from '/@/components/Drawer';
  36 + import CategoryList from '../components/CategoryList.vue';
  37 + import CategoryListDrawer from '../components/CategoryListDrawer.vue';
12 38
13 const emits = defineEmits(['next', 'emitDeviceType']); 39 const emits = defineEmits(['next', 'emitDeviceType']);
14 const props = defineProps({ 40 const props = defineProps({
15 ifShowBtn: { type: Boolean, default: true }, 41 ifShowBtn: { type: Boolean, default: true },
16 }); 42 });
  43 + const [registerDrawer, { openDrawer, closeDrawer }] = useDrawer();
  44 + const [registerListDrawer, { openDrawer: openListDrawer, closeDrawer: closeListDrawer }] =
  45 + useDrawer();
17 46
18 const [register, { validate, setFieldsValue, resetFields, updateSchema, getFieldsValue }] = 47 const [register, { validate, setFieldsValue, resetFields, updateSchema, getFieldsValue }] =
19 useForm({ 48 useForm({
@@ -29,6 +58,9 @@ @@ -29,6 +58,9 @@
29 }, 58 },
30 submitFunc: customSubmitFunc, 59 submitFunc: customSubmitFunc,
31 }); 60 });
  61 +
  62 + const cateGoryDisabled = ref<boolean>(false);
  63 +
32 const editOrAddNameStatus = (nameStatus) => 64 const editOrAddNameStatus = (nameStatus) =>
33 updateSchema({ 65 updateSchema({
34 field: 'name', 66 field: 'name',
@@ -48,19 +80,20 @@ @@ -48,19 +80,20 @@
48 emits('emitDeviceType', values?.deviceType); 80 emits('emitDeviceType', values?.deviceType);
49 } 81 }
50 //回显数据 82 //回显数据
51 - const setFormData = (v) => { 83 + const setFormData = async (v) => {
52 if (v.image) { 84 if (v.image) {
53 setFieldsValue({ 85 setFieldsValue({
54 image: [{ uid: buildUUID(), name: 'name', url: v.image } as FileItem], 86 image: [{ uid: buildUUID(), name: 'name', url: v.image } as FileItem],
55 }); 87 });
56 } 88 }
57 const { image, ...params } = v; 89 const { image, ...params } = v;
58 - console.log(image);  
59 - setFieldsValue({ ...params }); 90 + console.log(image, props.isUpdate);
  91 + setFieldsValue({ ...params, category: params?.categoryId ? 1 : 2 });
60 }; 92 };
61 //获取数据 93 //获取数据
62 async function getFormData() { 94 async function getFormData() {
63 - const values = await validate(); 95 + await validate();
  96 + const values = getFieldsValue();
64 if (!values) return; 97 if (!values) return;
65 if (Reflect.has(values, 'image')) { 98 if (Reflect.has(values, 'image')) {
66 const file = (values.image || []).at(0) || {}; 99 const file = (values.image || []).at(0) || {};
@@ -74,12 +107,65 @@ @@ -74,12 +107,65 @@
74 }; 107 };
75 108
76 const editOrAddDeviceTypeStatus = (status: boolean) => { 109 const editOrAddDeviceTypeStatus = (status: boolean) => {
77 - updateSchema({  
78 - field: 'deviceType',  
79 - componentProps: {  
80 - disabled: status, 110 + updateSchema([
  111 + {
  112 + field: 'deviceType',
  113 + componentProps: {
  114 + disabled: status,
  115 + },
81 }, 116 },
82 - }); 117 + {
  118 + field: 'category',
  119 + componentProps({ formModel }) {
  120 + return {
  121 + options: [
  122 + { label: '自定义品类', value: 2 },
  123 + { label: '标准品类', value: 1 },
  124 + ],
  125 + onChange() {
  126 + formModel.categoryId = null;
  127 + formModel.categoryName = undefined;
  128 + },
  129 + disabled: status,
  130 + };
  131 + },
  132 + },
  133 + ]);
  134 +
  135 + cateGoryDisabled.value = status;
  136 + };
  137 +
  138 + // 查看功能
  139 + const handleCategoryId = () => {
  140 + const { categoryId } = getFieldsValue() || {};
  141 + if (!categoryId) return;
  142 + cateforyListInfo.value = { record: { id: categoryId }, isRight: false };
  143 + openListDrawer(true);
  144 + };
  145 +
  146 + // 获取焦点
  147 + const handleFocus = () => {
  148 + const { categoryId } = getFieldsValue() || {};
  149 + openDrawer(true, { categoryId });
  150 + };
  151 +
  152 + //打开品类名称
  153 + const cateforyListInfo = ref<any>({});
  154 + const handleOpenListDrawer = (record?: any) => {
  155 + cateforyListInfo.value = { record, isRight: true };
  156 + openListDrawer(true);
  157 + };
  158 +
  159 + // 选择产品品类
  160 + const handleSelectCategory = (item) => {
  161 + closeDrawer();
  162 + closeListDrawer();
  163 + item.id && setFieldsValue({ categoryId: item.id, categoryName: item?.name });
  164 + };
  165 +
  166 + // 关闭抽屉
  167 + const handleClose = () => {
  168 + closeListDrawer();
83 }; 169 };
84 170
85 defineExpose({ 171 defineExpose({
@@ -27,25 +27,33 @@ @@ -27,25 +27,33 @@
27 </div> 27 </div>
28 <div class="flex justify-between items-end"> 28 <div class="flex justify-between items-end">
29 <div class="flex gap-2"> 29 <div class="flex gap-2">
30 - <Authority :value="ModelOfMatterPermission.CREATE"> 30 + <Authority :value="[ModelOfMatterPermission.CREATE, ModelCategoryPermission.CREATE]">
31 <Button v-if="isShowBtn" type="primary" @click="handleCreateOrEdit()"> 31 <Button v-if="isShowBtn" type="primary" @click="handleCreateOrEdit()">
32 新增物模型 32 新增物模型
33 </Button> 33 </Button>
34 </Authority> 34 </Authority>
35 - <Button type="primary" @click="handleOpenTsl"> 物模型TSL </Button>  
36 - <Authority value="api:yt:things_model:import">  
37 - <Upload 35 + <Button v-if="!record.ifShowClass" type="primary" @click="handleOpenTsl">
  36 + 物模型TSL</Button
  37 + >
  38 + <Button v-else type="primary" @click="handleExport" :loading="loading"
  39 + >导出物模型</Button
  40 + >
  41 + <Authority
  42 + :value="['api:yt:things_model:import', 'api:yt:things_model:category:import']"
  43 + >
  44 + <!-- <Upload
38 v-if="isShowBtn" 45 v-if="isShowBtn"
39 accept=".json," 46 accept=".json,"
40 :show-upload-list="false" 47 :show-upload-list="false"
41 :customRequest="handleImportModel" 48 :customRequest="handleImportModel"
42 > 49 >
43 <Button type="primary" :loading="importLoading"> 导入物模型 </Button> 50 <Button type="primary" :loading="importLoading"> 导入物模型 </Button>
44 - </Upload> 51 + </Upload> -->
  52 + <Button type="primary" @click="handleSelectImport">导入物模型</Button>
45 </Authority> 53 </Authority>
46 </div> 54 </div>
47 <div class="flex gap-2"> 55 <div class="flex gap-2">
48 - <Authority :value="ModelOfMatterPermission.RELEASE"> 56 + <Authority :value="[ModelOfMatterPermission.RELEASE]">
49 <Popconfirm 57 <Popconfirm
50 title="是否需要发布上线?" 58 title="是否需要发布上线?"
51 ok-text="确定" 59 ok-text="确定"
@@ -61,7 +69,7 @@ @@ -61,7 +69,7 @@
61 <Button v-if="isShowBtn" class="!bg-gray-200" type="text" @click="handleReturn"> 69 <Button v-if="isShowBtn" class="!bg-gray-200" type="text" @click="handleReturn">
62 返回 70 返回
63 </Button> 71 </Button>
64 - <Authority :value="ModelOfMatterPermission.DELETE"> 72 + <Authority :value="[ModelOfMatterPermission.DELETE, ModelCategoryPermission.DELETE]">
65 <Popconfirm 73 <Popconfirm
66 title="您确定要批量删除数据" 74 title="您确定要批量删除数据"
67 ok-text="确定" 75 ok-text="确定"
@@ -94,14 +102,14 @@ @@ -94,14 +102,14 @@
94 { 102 {
95 label: '编辑', 103 label: '编辑',
96 icon: 'clarity:note-edit-line', 104 icon: 'clarity:note-edit-line',
97 - auth: ModelOfMatterPermission.UPDATE, 105 + auth: [ModelOfMatterPermission.UPDATE, ModelCategoryPermission.UPDATE],
98 onClick: handleCreateOrEdit.bind(null, record), 106 onClick: handleCreateOrEdit.bind(null, record),
99 ifShow: !isShowBtn ? false : true, 107 ifShow: !isShowBtn ? false : true,
100 }, 108 },
101 { 109 {
102 label: '删除', 110 label: '删除',
103 icon: 'ant-design:delete-outlined', 111 icon: 'ant-design:delete-outlined',
104 - auth: ModelOfMatterPermission.DELETE, 112 + auth: [ModelOfMatterPermission.DELETE, ModelCategoryPermission.DELETE],
105 color: 'error', 113 color: 'error',
106 ifShow: !isShowBtn ? false : true, 114 ifShow: !isShowBtn ? false : true,
107 popConfirm: { 115 popConfirm: {
@@ -119,6 +127,11 @@ @@ -119,6 +127,11 @@
119 @success="handleSuccess" 127 @success="handleSuccess"
120 /> 128 />
121 <PhysicalModelTsl :record="$props.record" @register="registerModalTsl" /> 129 <PhysicalModelTsl :record="$props.record" @register="registerModalTsl" />
  130 + <SelectImport
  131 + :record="$props.record"
  132 + @register="registerModalSelect"
  133 + @handleReload="handleReload"
  134 + />
122 </div> 135 </div>
123 </template> 136 </template>
124 <script lang="ts" setup> 137 <script lang="ts" setup>
@@ -127,25 +140,32 @@ @@ -127,25 +140,32 @@
127 import { 140 import {
128 modelOfMatterForm, 141 modelOfMatterForm,
129 ModelOfMatterPermission, 142 ModelOfMatterPermission,
  143 + ModelCategoryPermission,
130 physicalColumn, 144 physicalColumn,
131 } from '../device.profile.data'; 145 } from '../device.profile.data';
132 import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; 146 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
133 import { Authority } from '/@/components/Authority'; 147 import { Authority } from '/@/components/Authority';
134 import PhysicalModelModal from './cpns/physical/PhysicalModelModal.vue'; 148 import PhysicalModelModal from './cpns/physical/PhysicalModelModal.vue';
135 import PhysicalModelTsl from './cpns/physical/PhysicalModelTsl.vue'; 149 import PhysicalModelTsl from './cpns/physical/PhysicalModelTsl.vue';
136 - import { Popconfirm, Button, Alert, Upload } from 'ant-design-vue'; 150 + import { Popconfirm, Button, Alert } from 'ant-design-vue';
137 import { useMessage } from '/@/hooks/web/useMessage'; 151 import { useMessage } from '/@/hooks/web/useMessage';
138 import { DeviceRecord } from '/@/api/device/model/deviceModel'; 152 import { DeviceRecord } from '/@/api/device/model/deviceModel';
139 import { 153 import {
140 deleteModel, 154 deleteModel,
  155 + deleteModelCategory,
141 getModelList, 156 getModelList,
142 - importModelOfMatter,  
143 releaseModel, 157 releaseModel,
144 } from '/@/api/device/modelOfMatter'; 158 } from '/@/api/device/modelOfMatter';
145 import { OpenModelOfMatterModelParams, OpenModelMode } from './cpns/physical/types'; 159 import { OpenModelOfMatterModelParams, OpenModelMode } from './cpns/physical/types';
146 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; 160 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
147 - import { ref } from 'vue';  
148 - import { isObject, isString } from '/@/utils/is'; 161 + import { ref, unref } from 'vue';
  162 + import { isObject } from '/@/utils/is';
  163 + import { useRole } from '/@/hooks/business/useRole';
  164 + import { ExportModelCategory } from '/@/api/device/modelOfMatter';
  165 + import { FunctionType } from './cpns/physical/cpns/config';
  166 + import SelectImport from '../components/SelectImport.vue';
  167 +
  168 + const { isPlatformAdmin, isSysadmin } = useRole();
149 defineEmits(['register']); 169 defineEmits(['register']);
150 170
151 const props = defineProps<{ 171 const props = defineProps<{
@@ -156,10 +176,15 @@ @@ -156,10 +176,15 @@
156 const isShowBtn = ref(false); 176 const isShowBtn = ref(false);
157 const [registerModal, { openModal }] = useModal(); 177 const [registerModal, { openModal }] = useModal();
158 const [registerModalTsl, { openModal: openModalTsl }] = useModal(); 178 const [registerModalTsl, { openModal: openModalTsl }] = useModal();
  179 + const [registerModalSelect, { openModal: openModalSelect }] = useModal();
159 180
160 const [registerTable, { reload, setProps }] = useTable({ 181 const [registerTable, { reload, setProps }] = useTable({
161 api: async (params: Record<'page' | 'pageSize', number>) => { 182 api: async (params: Record<'page' | 'pageSize', number>) => {
162 - return await getModelList({ ...params, deviceProfileId: props.record.id }); 183 + return await getModelList({
  184 + ...params,
  185 + id: props.record.id,
  186 + selectType: props.record.ifShowClass ? 'category' : undefined,
  187 + });
163 }, 188 },
164 columns: physicalColumn, 189 columns: physicalColumn,
165 showIndexColumn: false, 190 showIndexColumn: false,
@@ -184,9 +209,10 @@ @@ -184,9 +209,10 @@
184 const handleSuccess = () => { 209 const handleSuccess = () => {
185 reload(); 210 reload();
186 }; 211 };
187 -  
188 const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( 212 const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
189 - deleteModel, 213 + props.record.ifShowClass && (unref(isPlatformAdmin) || unref(isSysadmin))
  214 + ? deleteModelCategory
  215 + : deleteModel,
190 handleSuccess, 216 handleSuccess,
191 setProps 217 setProps
192 ); 218 );
@@ -242,59 +268,76 @@ @@ -242,59 +268,76 @@
242 } 268 }
243 }; 269 };
244 270
245 - const paseJSON = (string: string) => {  
246 - let data = null;  
247 - let flag = false;  
248 - try {  
249 - if (!isString(string)) return { flag: false, data };  
250 - data = JSON.parse(string);  
251 - flag = true;  
252 - if (!isObject(data)) flag = false;  
253 - } catch (error) {}  
254 - return { flag, data };  
255 - };  
256 -  
257 const isEmptyObject = (value: any) => isObject(value) && !Object.keys(value).length; 271 const isEmptyObject = (value: any) => isObject(value) && !Object.keys(value).length;
258 272
259 - const importLoading = ref(false);  
260 - const handleImportModel = async (data: { file: File }) => {  
261 - const fileReader = new FileReader();  
262 -  
263 - fileReader.onload = async () => {  
264 - const { flag, data } = paseJSON(fileReader.result as string);  
265 - if (!flag) {  
266 - createMessage.warning('JSON解析失败,请导入正确的JSON~');  
267 - return;  
268 - }  
269 - try {  
270 - importLoading.value = true; 273 + // 选择导入物模型的方式
  274 + const handleSelectImport = () => {
  275 + openModalSelect(true, {
  276 + id: props.record.id,
  277 + isCateGory: (unref(isPlatformAdmin) || unref(isSysadmin)) && props.record.ifShowClass,
  278 + });
  279 + };
271 280
272 - Object.keys(data || {}).forEach((key) => {  
273 - const value = (data || {})[key];  
274 - if (value && isEmptyObject(value)) {  
275 - (data || {})[key] = [];  
276 - }  
277 - }); 281 + const handleReload = () => {
  282 + reload();
  283 + };
278 284
279 - const result = await importModelOfMatter({  
280 - tkDeviceProfileId: props.record.id,  
281 - data: data!,  
282 - functionType: 'all',  
283 - }); 285 + // 导出物模型
  286 + const loading = ref<boolean>(false);
284 287
285 - result  
286 - ? createMessage.success('导入成功~')  
287 - : createMessage.error('JSON解析失败,请导入正确的JSON~'); 288 + const getAllCategory = () => {
  289 + const { id: deviceProfileId } = props.record;
  290 + return Promise.all([
  291 + ExportModelCategory({
  292 + deviceProfileId,
  293 + functionType: FunctionType.EVENTS,
  294 + }),
  295 + ExportModelCategory({
  296 + deviceProfileId,
  297 + functionType: FunctionType.PROPERTIES,
  298 + }),
  299 + ExportModelCategory({
  300 + deviceProfileId,
  301 + functionType: FunctionType.SERVICE,
  302 + }),
  303 + ]);
  304 + };
  305 + const exportJSONFile = (value: Recordable) => {
  306 + const blob = new Blob([JSON.stringify(value, null, 2)], { type: 'text/json' });
  307 + const objectURL = URL.createObjectURL(blob);
  308 + const element = document.createElement('a');
  309 + element.href = objectURL;
  310 + element.download = `${props.record.name}-model.json`;
  311 + element.style.display = 'none';
  312 + document.body.appendChild(element);
  313 + element.click();
  314 + element.remove();
  315 + URL.revokeObjectURL(objectURL);
  316 + };
288 317
289 - result && reload();  
290 - } catch (error) {  
291 - throw error;  
292 - } finally {  
293 - importLoading.value = false;  
294 - }  
295 - }; 318 + const isEmptyStr = (s: any) => {
  319 + if (s == undefined || s == null || s == '') {
  320 + return true;
  321 + }
  322 + return false;
  323 + };
296 324
297 - fileReader.readAsText(data.file, 'utf-8'); 325 + //产品品类物模型导出
  326 + const handleExport = async () => {
  327 + loading.value = true;
  328 + try {
  329 + const [events, properties, services] = await getAllCategory();
  330 + const value = {
  331 + properties: isEmptyObject(properties) || isEmptyStr(properties) ? [] : properties,
  332 + services: isEmptyObject(services) || isEmptyStr(services) ? [] : services,
  333 + events: isEmptyObject(events) || isEmptyStr(events) ? [] : events,
  334 + };
  335 + exportJSONFile(value);
  336 + } catch (error) {
  337 + throw error;
  338 + } finally {
  339 + loading.value = false;
  340 + }
298 }; 341 };
299 342
300 defineExpose({}); 343 defineExpose({});
@@ -60,11 +60,19 @@ @@ -60,11 +60,19 @@
60 import Service from './cpns/Service.vue'; 60 import Service from './cpns/Service.vue';
61 import Events from './cpns/Events.vue'; 61 import Events from './cpns/Events.vue';
62 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; 62 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
63 - import { createModel, updateModel } from '/@/api/device/modelOfMatter'; 63 + import {
  64 + createModel,
  65 + updateModel,
  66 + createModelCategory,
  67 + updateModelCategory,
  68 + } from '/@/api/device/modelOfMatter';
64 import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel'; 69 import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
65 import { useMessage } from '/@/hooks/web/useMessage'; 70 import { useMessage } from '/@/hooks/web/useMessage';
66 import { OpenModelMode, OpenModelOfMatterModelParams } from './types/index'; 71 import { OpenModelMode, OpenModelOfMatterModelParams } from './types/index';
67 import { FunctionType } from './cpns/config'; 72 import { FunctionType } from './cpns/config';
  73 + import { useRole } from '/@/hooks/business/useRole';
  74 +
  75 + const { isPlatformAdmin, isSysadmin } = useRole();
68 76
69 const emit = defineEmits(['register', 'success']); 77 const emit = defineEmits(['register', 'success']);
70 78
@@ -158,14 +166,28 @@ @@ -158,14 +166,28 @@
158 params = (await EventsRef.value?.getFormData()) || {}; 166 params = (await EventsRef.value?.getFormData()) || {};
159 } 167 }
160 params.deviceProfileId = props.record.id; 168 params.deviceProfileId = props.record.id;
  169 +
161 if (unref(openModalMode) === OpenModelMode.CREATE) { 170 if (unref(openModalMode) === OpenModelMode.CREATE) {
162 - await createModel(params); 171 + (unref(isSysadmin) || unref(isPlatformAdmin)) && props.record.ifShowClass
  172 + ? await createModelCategory({
  173 + ...params,
  174 + deviceProfileId: undefined,
  175 + categoryId: props.record.id,
  176 + })
  177 + : await createModel(params);
163 createMessage.success('创建成功'); 178 createMessage.success('创建成功');
164 } else { 179 } else {
165 params.id = unref(openModalParams)?.record.id; 180 params.id = unref(openModalParams)?.record.id;
166 - await updateModel(params); 181 + (unref(isSysadmin) || unref(isPlatformAdmin)) && props.record.ifShowClass
  182 + ? await updateModelCategory({
  183 + ...params,
  184 + deviceProfileId: undefined,
  185 + categoryId: props.record.id,
  186 + })
  187 + : await updateModel(params);
167 createMessage.success('修改成功'); 188 createMessage.success('修改成功');
168 } 189 }
  190 +
169 closeModal(); 191 closeModal();
170 emit('success'); 192 emit('success');
171 } catch (error) { 193 } catch (error) {
@@ -73,9 +73,18 @@ @@ -73,9 +73,18 @@
73 const getAllModel = () => { 73 const getAllModel = () => {
74 const { id: deviceProfileId } = props.record; 74 const { id: deviceProfileId } = props.record;
75 return Promise.all([ 75 return Promise.all([
76 - getModelTsl({ deviceProfileId, functionType: FunctionType.EVENTS }),  
77 - getModelTsl({ deviceProfileId, functionType: FunctionType.PROPERTIES }),  
78 - getModelTsl({ deviceProfileId, functionType: FunctionType.SERVICE }), 76 + getModelTsl({
  77 + deviceProfileId,
  78 + functionType: FunctionType.EVENTS,
  79 + }),
  80 + getModelTsl({
  81 + deviceProfileId,
  82 + functionType: FunctionType.PROPERTIES,
  83 + }),
  84 + getModelTsl({
  85 + deviceProfileId,
  86 + functionType: FunctionType.SERVICE,
  87 + }),
79 ]); 88 ]);
80 }; 89 };
81 90
@@ -84,7 +84,10 @@ @@ -84,7 +84,10 @@
84 const handleSwitchTsl = async (functionType: FunctionType) => { 84 const handleSwitchTsl = async (functionType: FunctionType) => {
85 try { 85 try {
86 loading.value = true; 86 loading.value = true;
87 - const record = await getModelTsl({ deviceProfileId: props.record.id, functionType }); 87 + const record = await getModelTsl({
  88 + deviceProfileId: props.record.id,
  89 + functionType,
  90 + });
88 jsonValue.value = JSON.stringify( 91 jsonValue.value = JSON.stringify(
89 { 92 {
90 [functionType]: record, 93 [functionType]: record,
1 import { FormSchema } from '/@/components/Table'; 1 import { FormSchema } from '/@/components/Table';
2 import { findDictItemByCode } from '/@/api/system/dict'; 2 import { findDictItemByCode } from '/@/api/system/dict';
  3 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
3 4
4 export enum FormField { 5 export enum FormField {
5 FUNCTION_NAME = 'functionName', 6 FUNCTION_NAME = 'functionName',
@@ -40,30 +41,19 @@ export enum AssessMode { @@ -40,30 +41,19 @@ export enum AssessMode {
40 WRITE = 'w', 41 WRITE = 'w',
41 } 42 }
42 43
43 -/**  
44 - * 新增参数 动态显示表单  
45 - */  
46 -export enum DataTypeEnum {  
47 - IS_NUMBER_INT = 'INT',  
48 - IS_NUMBER_DOUBLE = 'DOUBLE',  
49 - IS_STRING = 'TEXT',  
50 - IS_STRUCT = 'STRUCT',  
51 - IS_BOOL = 'BOOL',  
52 -}  
53 -  
54 const isNumber = (type: string) => { 44 const isNumber = (type: string) => {
55 - return type === DataTypeEnum.IS_NUMBER_INT || type === DataTypeEnum.IS_NUMBER_DOUBLE; 45 + return type === DataTypeEnum.NUMBER_INT || type === DataTypeEnum.NUMBER_DOUBLE;
56 }; 46 };
57 47
58 const isString = (type: string) => { 48 const isString = (type: string) => {
59 - return type === DataTypeEnum.IS_STRING; 49 + return type === DataTypeEnum.STRING;
60 }; 50 };
61 const isStruct = (type: string) => { 51 const isStruct = (type: string) => {
62 - return type === DataTypeEnum.IS_STRUCT; 52 + return type === DataTypeEnum.STRUCT;
63 }; 53 };
64 54
65 const isBool = (type: string) => { 55 const isBool = (type: string) => {
66 - return type === DataTypeEnum.IS_BOOL; 56 + return type === DataTypeEnum.BOOL;
67 }; 57 };
68 58
69 export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => { 59 export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => {
@@ -138,13 +138,90 @@ @@ -138,13 +138,90 @@
138 }); 138 });
139 } 139 }
140 140
  141 + const cipherLength = 7;
  142 +
  143 + //字符串星号替代
  144 + function parseStringToStar(originalStr: string, startLength: number, endLength: number) {
  145 + //字符串大于7位,前3后4明文,其他以星号替代
  146 + //字符串小于7位,前1后1明文,其他以星号替代
  147 + return originalStr?.length > startLength
  148 + ? originalStr?.substr(0, startLength) +
  149 + new Array(originalStr?.length - endLength)?.join('*') +
  150 + originalStr?.substr(-endLength)
  151 + : originalStr;
  152 + }
  153 +
  154 + //长度 都大于或者小于7或者其中一个大于7,一个小于7个字符的处理
  155 + //二维数组优化if else if else
  156 + const mapConditionConfig = [
  157 + [
  158 + (startParam, endParam) =>
  159 + String(startParam)?.length > cipherLength && String(endParam)?.length > cipherLength,
  160 + (startParam) => parseStringToStar(startParam, 3, 4),
  161 + (endParam) => parseStringToStar(endParam, 3, 4),
  162 + ],
  163 + [
  164 + (startParam, endParam) =>
  165 + String(startParam)?.length < cipherLength && String(endParam)?.length < cipherLength,
  166 + (startParam) => parseStringToStar(startParam, 1, 1),
  167 + (endParam) => parseStringToStar(endParam, 1, 1),
  168 + ],
  169 + [
  170 + (startParam, endParam) =>
  171 + String(startParam)?.length > cipherLength || String(endParam)?.length < cipherLength,
  172 + (startParam) => parseStringToStar(startParam, 3, 4),
  173 + (endParam) => parseStringToStar(endParam, 1, 1),
  174 + ],
  175 + [
  176 + (startParam, endParam) =>
  177 + String(startParam)?.length < cipherLength || String(endParam)?.length > cipherLength,
  178 + (startParam) => parseStringToStar(startParam, 1, 1),
  179 + (endParam) => parseStringToStar(endParam, 3, 4),
  180 + ],
  181 + ];
  182 +
  183 + const handleStandardLength = (
  184 + startKey: string,
  185 + endKey: string,
  186 + startParam: string,
  187 + endParam: string
  188 + ) => {
  189 + const findDateFlow: any = mapConditionConfig.find((item: Function[]) =>
  190 + item[0](startParam, endParam)
  191 + );
  192 + if (!findDateFlow) return;
  193 + const cipherData: Recordable = {};
  194 + cipherData[startKey] = findDateFlow[1](startParam);
  195 + cipherData[endKey] = findDateFlow[2](endParam);
  196 + return cipherData;
  197 + };
  198 +
  199 + const handleCipherTextConfig = (e) => {
  200 + /**
  201 + * username和password 存在则是邮件配置
  202 + * secretId和secretKey 存在则是腾讯云配置信息
  203 + * accessKeyId和accessKeySecret 存在则是阿里云配置信息
  204 + */
  205 + const { username, password, secretId, secretKey, accessKeyId, accessKeySecret } = e;
  206 + return JSON.parse(
  207 + JSON.stringify({
  208 + ...e,
  209 + ...handleStandardLength('username', 'password', username, password),
  210 + ...handleStandardLength('secretId', 'secretKey', secretId, secretKey),
  211 + ...handleStandardLength('accessKeyId', 'accessKeySecret', accessKeyId, accessKeySecret),
  212 + })
  213 + );
  214 + };
  215 +
141 function showData(record: Recordable) { 216 function showData(record: Recordable) {
142 Modal.info({ 217 Modal.info({
143 title: '当前配置', 218 title: '当前配置',
144 width: 600, 219 width: 600,
145 centered: true, 220 centered: true,
146 maskClosable: true, 221 maskClosable: true,
147 - content: h(JsonPreview, { data: JSON.parse(JSON.stringify(record.config)) }), 222 + content: h(JsonPreview, {
  223 + data: handleCipherTextConfig(record.config),
  224 + }),
148 }); 225 });
149 } 226 }
150 const statusChange = async (checked, record) => { 227 const statusChange = async (checked, record) => {
@@ -184,10 +184,12 @@ @@ -184,10 +184,12 @@
184 businessText.value === BusinessReportConfigTextEnum.BUSINESS_ADD_TEXT 184 businessText.value === BusinessReportConfigTextEnum.BUSINESS_ADD_TEXT
185 ? await createOrEditReportManage(data) 185 ? await createOrEditReportManage(data)
186 : putReportConfigManage({ ...restData.data, ...data }); 186 : putReportConfigManage({ ...restData.data, ...data });
187 - emits('success');  
188 createMessage.success(`${businessText.value}成功`); 187 createMessage.success(`${businessText.value}成功`);
189 closeDrawer(); 188 closeDrawer();
190 handleClose(); 189 handleClose();
  190 + setTimeout(() => {
  191 + emits('success');
  192 + }, 500);
191 } finally { 193 } finally {
192 setDrawerProps({ confirmLoading: false }); 194 setDrawerProps({ confirmLoading: false });
193 } 195 }
@@ -471,7 +471,6 @@ export const formSchema: BFormSchema[] = [ @@ -471,7 +471,6 @@ export const formSchema: BFormSchema[] = [
471 dynamicRules: () => { 471 dynamicRules: () => {
472 return [ 472 return [
473 { 473 {
474 - // required: model[SchemaFiled.AGG] !== AggregateDataEnum.NONE,  
475 required: true, 474 required: true,
476 message: '间隔时间为必填项', 475 message: '间隔时间为必填项',
477 type: 'number', 476 type: 'number',
@@ -479,7 +478,11 @@ export const formSchema: BFormSchema[] = [ @@ -479,7 +478,11 @@ export const formSchema: BFormSchema[] = [
479 ]; 478 ];
480 }, 479 },
481 ifShow({ values }) { 480 ifShow({ values }) {
482 - return values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD && exectueIsImmed(values.executeWay); 481 + return (
  482 + values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD &&
  483 + exectueIsImmed(values.executeWay) &&
  484 + values[SchemaFiled.DATA_TYPE] !== DataTypeEnum.ORIGINAL
  485 + );
483 }, 486 },
484 componentProps({ formModel, formActionType }) { 487 componentProps({ formModel, formActionType }) {
485 const options = 488 const options =
@@ -166,6 +166,8 @@ export const useHooks = () => { @@ -166,6 +166,8 @@ export const useHooks = () => {
166 dataRange: [value?.startTs, value?.endTs], 166 dataRange: [value?.startTs, value?.endTs],
167 dateGroupGap: value?.interval, 167 dateGroupGap: value?.interval,
168 }; 168 };
  169 + Reflect.deleteProperty(value, 'startTs');
  170 + Reflect.deleteProperty(value, 'interval');
169 } 171 }
170 const spanDisance = executeContent?.split(' '); 172 const spanDisance = executeContent?.split(' ');
171 const cronTime = 173 const cronTime =
@@ -34,7 +34,12 @@ @@ -34,7 +34,12 @@
34 @change="(value) => handleChangeChars(value, item.device, item)" 34 @change="(value) => handleChangeChars(value, item.device, item)"
35 v-bind="createPickerSearch()" 35 v-bind="createPickerSearch()"
36 placeholder="请选择设备属性" 36 placeholder="请选择设备属性"
37 - :options="item?.attributes?.map((item1) => ({ label: item1, value: item1 }))" 37 + :options="
  38 + item?.attributes?.map((attrItem: any) => ({
  39 + label: attrItem.label,
  40 + value: attrItem.value,
  41 + }))
  42 + "
38 /> 43 />
39 </div> 44 </div>
40 <div class="w-full h-full flex justify-center items-center"> 45 <div class="w-full h-full flex justify-center items-center">
@@ -64,6 +69,8 @@ @@ -64,6 +69,8 @@
64 import { ExecuteReportRecord } from '/@/api/export/model/exportModel'; 69 import { ExecuteReportRecord } from '/@/api/export/model/exportModel';
65 import { Select, Spin, Empty } from 'ant-design-vue'; 70 import { Select, Spin, Empty } from 'ant-design-vue';
66 import { createPickerSearch } from '/@/utils/pickerSearch'; 71 import { createPickerSearch } from '/@/utils/pickerSearch';
  72 + import { getDeviceDetail } from '/@/api/device/deviceManager';
  73 + import { getAttribute } from '/@/api/ruleengine/ruleengineApi';
67 74
68 interface ResponsData { 75 interface ResponsData {
69 attr: string; 76 attr: string;
@@ -141,20 +148,66 @@ @@ -141,20 +148,66 @@
141 return chartOption; 148 return chartOption;
142 }; 149 };
143 150
  151 + const handleDeviceProfileAttributes = async (entityId: string) => {
  152 + const deviceDetailRes = await getDeviceDetail(entityId);
  153 + const { deviceProfileId } = deviceDetailRes;
  154 + if (!deviceProfileId) return;
  155 + const attributeRes = await getAttribute(deviceProfileId);
  156 + return handleDataFormat(deviceDetailRes, attributeRes);
  157 + };
  158 +
  159 + const handleDataFormat = (deviceDetail: any, attributes: any) => {
  160 + const { name, tbDeviceId } = deviceDetail;
  161 + const attribute = attributes.map((item: any) => ({
  162 + identifier: item.identifier,
  163 + name: item.name,
  164 + detail: item.detail,
  165 + }));
  166 + return {
  167 + name,
  168 + tbDeviceId,
  169 + attribute,
  170 + };
  171 + };
  172 +
144 const [register, { setModalProps }] = useModalInner( 173 const [register, { setModalProps }] = useModalInner(
145 async (data: { record: ExecuteReportRecord }) => { 174 async (data: { record: ExecuteReportRecord }) => {
146 setModalProps({ loading: true }); 175 setModalProps({ loading: true });
147 try { 176 try {
148 currentRecord = data.record; 177 currentRecord = data.record;
149 const deviceInfo = data.record.executeCondition.executeAttributes || []; 178 const deviceInfo = data.record.executeCondition.executeAttributes || [];
150 - chartInstance.value = deviceInfo.map((item) => ({  
151 - ...item,  
152 - active: item.attributes.at(0),  
153 - })); 179 + let attributesList: Recordable[] = [];
  180 + // 处理为物模型里的标识符名
  181 + const reflectDeviceList = deviceInfo.map(async (item) => {
  182 + const { device, attributes } = item as Recordable;
  183 + if (!device) return;
  184 + const thingsModel = (await handleDeviceProfileAttributes(item.device)) as Recordable;
  185 + const { tbDeviceId, attribute } = thingsModel as Recordable;
  186 + if (!tbDeviceId) return;
  187 + if (device === tbDeviceId) {
  188 + attributesList = attributes.reduce((acc, curr) => {
  189 + attribute.forEach((item: Recordable) => {
  190 + if (item.identifier === curr) {
  191 + acc.push({
  192 + label: item.name,
  193 + value: item.identifier,
  194 + });
  195 + }
  196 + });
  197 + return [...acc];
  198 + }, []);
  199 + }
  200 + return {
  201 + ...item,
  202 + active: attributes.at(0),
  203 + attributes: attributesList,
  204 + };
  205 + });
  206 + chartInstance.value = (await Promise.all(reflectDeviceList)) as any as ChartInstance[];
154 for (const item of unref(chartInstance)) { 207 for (const item of unref(chartInstance)) {
155 const { attributes, device } = item; 208 const { attributes, device } = item;
156 209
157 - const keys = attributes.length ? attributes.at(0) : ''; 210 + const keys = attributes.length ? (attributes as any[]).at(0)?.value : '';
158 211
159 const sendParams = { 212 const sendParams = {
160 ...data.record.executeCondition.executeCondition, 213 ...data.record.executeCondition.executeCondition,
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { Button, Tabs, Tag } from 'ant-design-vue'; 2 + import { Button, Tabs, Select } from 'ant-design-vue';
3 import { remove, uniqBy, cloneDeep } from 'lodash'; 3 import { remove, uniqBy, cloneDeep } from 'lodash';
4 - import { computed, nextTick, onMounted, ref, unref, toRaw } from 'vue'; 4 + import { nextTick, onMounted, ref, unref, toRaw, watch } from 'vue';
5 import { 5 import {
6 deviceTableColumn, 6 deviceTableColumn,
7 deviceTableFormSchema, 7 deviceTableFormSchema,
@@ -21,8 +21,7 @@ @@ -21,8 +21,7 @@
21 21
22 const props = withDefaults( 22 const props = withDefaults(
23 defineProps<{ 23 defineProps<{
24 - getPendingTableParams: (params: Recordable) => any;  
25 - getSelectedTableParams: (params: Recordable) => any; 24 + params?: Recordable;
26 value?: (Recordable & DeviceModel)[]; 25 value?: (Recordable & DeviceModel)[];
27 maxTagLength?: number; 26 maxTagLength?: number;
28 openModalValidate?: () => boolean; 27 openModalValidate?: () => boolean;
@@ -34,6 +33,7 @@ @@ -34,6 +33,7 @@
34 value: () => [], 33 value: () => [],
35 maxTagLength: 2, 34 maxTagLength: 2,
36 primaryKey: 'tbDeviceId', 35 primaryKey: 'tbDeviceId',
  36 + params: () => ({}),
37 } 37 }
38 ); 38 );
39 39
@@ -56,23 +56,6 @@ @@ -56,23 +56,6 @@
56 56
57 const selectedConfirmQueue = ref<DeviceModel[]>([]); 57 const selectedConfirmQueue = ref<DeviceModel[]>([]);
58 58
59 - const getShowTagOptions = computed(() => {  
60 - const { maxTagLength } = props;  
61 - return unref(selectedTotalList).slice(0, maxTagLength);  
62 - });  
63 -  
64 - const getSurplusOptionsLength = computed(() => {  
65 - const { maxTagLength } = props;  
66 - const surplusValue = unref(selectedTotalList).length - maxTagLength;  
67 - return surplusValue < 0 ? 0 : surplusValue;  
68 - });  
69 -  
70 - // const pendingListCount = computed(() => {  
71 - // const { value } = props;  
72 - // const selectedList = unref(pendingTotalList).filter((item) => value.includes(item.id));  
73 - // return unref(pendingTotalList).length - selectedList.length;  
74 - // });  
75 -  
76 const [registerModal, { openModal }] = useModal(); 59 const [registerModal, { openModal }] = useModal();
77 60
78 const [regsterPendingTable, pendingTableActionType] = useTable({ 61 const [regsterPendingTable, pendingTableActionType] = useTable({
@@ -82,9 +65,8 @@ @@ -82,9 +65,8 @@
82 immediate: false, 65 immediate: false,
83 clickToRowSelect: false, 66 clickToRowSelect: false,
84 beforeFetch: (params) => { 67 beforeFetch: (params) => {
85 - const { getPendingTableParams } = props;  
86 - const data = getPendingTableParams?.(params) || {};  
87 - Object.assign(params, { ...data, selected: false }); 68 + const { params: otherParams } = props;
  69 + Object.assign(params, { ...otherParams, selected: false });
88 return params; 70 return params;
89 }, 71 },
90 afterFetch: (list: DeviceModel[]) => { 72 afterFetch: (list: DeviceModel[]) => {
@@ -150,9 +132,8 @@ @@ -150,9 +132,8 @@
150 dataSource: selectedTotalList, 132 dataSource: selectedTotalList,
151 clickToRowSelect: false, 133 clickToRowSelect: false,
152 beforeFetch: (params) => { 134 beforeFetch: (params) => {
153 - const { getSelectedTableParams } = props;  
154 - const data = getSelectedTableParams?.(params) || {};  
155 - Object.assign(params, { ...data, selected: false }); 135 + const { params: otherParams } = props;
  136 + Object.assign(params, { ...otherParams, selected: false });
156 return params; 137 return params;
157 }, 138 },
158 rowSelection: { 139 rowSelection: {
@@ -219,18 +200,31 @@ @@ -219,18 +200,31 @@
219 200
220 if (openModalValidate && isFunction(openModalValidate) && !openModalValidate()) return; 201 if (openModalValidate && isFunction(openModalValidate) && !openModalValidate()) return;
221 202
  203 + activeKey.value = Active.PENDING;
222 openModal(true); 204 openModal(true);
223 await nextTick(); 205 await nextTick();
224 pendingTableActionType.reload(); 206 pendingTableActionType.reload();
225 }; 207 };
226 208
  209 + watch(
  210 + () => props.params,
  211 + () => {
  212 + selectedTotalList.value = [];
  213 + selectedConfirmQueue.value = [];
  214 + pendingConfirmQueue.value = [];
  215 + try {
  216 + pendingTableActionType.clearSelectedRowKeys();
  217 + selectedTableActionType.clearSelectedRowKeys();
  218 + } catch (error) {}
  219 + }
  220 + );
  221 +
227 onMounted(async () => { 222 onMounted(async () => {
228 - const { getSelectedTableParams } = props;  
229 - const data = getSelectedTableParams?.({}) || {};  
230 - if (!data?.convertConfigId || !data?.deviceProfileIds) { 223 + const { params } = props;
  224 + if (!params?.convertConfigId || !params?.deviceProfileIds) {
231 return; 225 return;
232 } 226 }
233 - const { items } = await devicePage({ page: 1, pageSize: 10, ...data, selected: true }); 227 + const { items } = await devicePage({ page: 1, pageSize: 10, ...params, selected: true });
234 selectedTotalList.value = items; 228 selectedTotalList.value = items;
235 }); 229 });
236 </script> 230 </script>
@@ -239,7 +233,7 @@ @@ -239,7 +233,7 @@
239 <section> 233 <section>
240 <BasicModal 234 <BasicModal
241 @register="registerModal" 235 @register="registerModal"
242 - title="穿梭表格" 236 + title="设备选择"
243 width="60%" 237 width="60%"
244 :wrapClassName="prefixCls" 238 :wrapClassName="prefixCls"
245 :showOkBtn="false" 239 :showOkBtn="false"
@@ -251,13 +245,11 @@ @@ -251,13 +245,11 @@
251 <template #tab> 245 <template #tab>
252 <div class="flex items-center justify-center"> 246 <div class="flex items-center justify-center">
253 <span>待选设备</span> 247 <span>待选设备</span>
254 - <!-- <Badge show-zero :count="pendingListCount" /> -->  
255 </div> 248 </div>
256 </template> 249 </template>
257 <BasicTable @register="regsterPendingTable"> 250 <BasicTable @register="regsterPendingTable">
258 <template #toolbar> 251 <template #toolbar>
259 <section class="flex w-full justify-end items-center"> 252 <section class="flex w-full justify-end items-center">
260 - <!-- <Button type="primary">全选</Button> -->  
261 <div class="text-blue-400"> 253 <div class="text-blue-400">
262 <span class="mr-2">选择设备:</span> 254 <span class="mr-2">选择设备:</span>
263 <span>{{ pendingConfirmQueue.length }}</span> 255 <span>{{ pendingConfirmQueue.length }}</span>
@@ -279,7 +271,6 @@ @@ -279,7 +271,6 @@
279 <template #tab> 271 <template #tab>
280 <div class="flex items-center justify-center"> 272 <div class="flex items-center justify-center">
281 <span>已选设备</span> 273 <span>已选设备</span>
282 - <!-- <Badge show-zero :count="selectedTotalList.length" /> -->  
283 </div> 274 </div>
284 </template> 275 </template>
285 <BasicTable @register="registerSelectedTable"> 276 <BasicTable @register="registerSelectedTable">
@@ -305,23 +296,22 @@ @@ -305,23 +296,22 @@
305 </Tabs> 296 </Tabs>
306 </section> 297 </section>
307 </BasicModal> 298 </BasicModal>
308 - <Button @click="handleOpenModal" type="link" :disabled="disabled">  
309 - <span v-if="!selectedTotalList.length">选择设备</span>  
310 - <div v-if="selectedTotalList.length">  
311 - <Tag  
312 - class="!px-2 !py-1 !bg-gray-50 !border-gray-100"  
313 - v-for="item in getShowTagOptions"  
314 - :key="item[primaryKey]"  
315 - >  
316 - <span>  
317 - {{ item.alias || item.name }}  
318 - </span>  
319 - </Tag>  
320 - <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength">  
321 - <span> +{{ getSurplusOptionsLength }}... </span>  
322 - </Tag>  
323 - </div>  
324 - </Button> 299 + <Select
  300 + placeholder="请选择设备"
  301 + :disabled="disabled"
  302 + :value="selectedTotalList.map((item) => item[primaryKey])"
  303 + mode="multiple"
  304 + :maxTagCount="3"
  305 + :open="false"
  306 + @dropdownVisibleChange="handleOpenModal"
  307 + :options="
  308 + selectedTotalList.map((item) => ({
  309 + ...item,
  310 + label: item.alias || item.name,
  311 + value: item[primaryKey],
  312 + }))
  313 + "
  314 + />
325 </section> 315 </section>
326 </template> 316 </template>
327 317
@@ -73,6 +73,10 @@ export const modeForm = (disabled: boolean): FormSchema[] => { @@ -73,6 +73,10 @@ export const modeForm = (disabled: boolean): FormSchema[] => {
73 labelField: 'name', 73 labelField: 'name',
74 valueField: 'tbProfileId', 74 valueField: 'tbProfileId',
75 disabled, 75 disabled,
  76 + selectProps: {
  77 + disabled,
  78 + placeholder: '请选择产品',
  79 + },
76 transferProps: { 80 transferProps: {
77 listStyle: { height: '400px' }, 81 listStyle: { height: '400px' },
78 showSearch: true, 82 showSearch: true,
@@ -98,26 +102,14 @@ export const modeForm = (disabled: boolean): FormSchema[] => { @@ -98,26 +102,14 @@ export const modeForm = (disabled: boolean): FormSchema[] => {
98 valueField: 'value', 102 valueField: 'value',
99 changeEvent: 'update:value', 103 changeEvent: 'update:value',
100 rules: [{ required: true, message: '数据源设备为必选项', type: 'array' }], 104 rules: [{ required: true, message: '数据源设备为必选项', type: 'array' }],
101 - componentProps: ({ formActionType }) => {  
102 - const { getFieldsValue } = formActionType; 105 + componentProps: ({ formModel }) => {
  106 + const convertConfigId = formModel[BasicInfoFormField.CONVERT_CONFIG_ID];
  107 + const deviceProfileIds = formModel[BasicInfoFormField.DATA_SOURCE_PRODUCT];
103 return { 108 return {
104 disabled, 109 disabled,
105 - getPendingTableParams: () => {  
106 - const values = getFieldsValue();  
107 - const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID);  
108 - const deviceProfileIds = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_PRODUCT);  
109 - return { convertConfigId, deviceProfileIds };  
110 - },  
111 - getSelectedTableParams: () => {  
112 - const values = getFieldsValue();  
113 - const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID);  
114 - const deviceProfileIds = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_PRODUCT);  
115 - return { convertConfigId, deviceProfileIds };  
116 - }, 110 + params: { convertConfigId, deviceProfileIds },
117 transformValue: handleGroupDevice, 111 transformValue: handleGroupDevice,
118 openModalValidate: () => { 112 openModalValidate: () => {
119 - const values = getFieldsValue();  
120 - const deviceProfileIds = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_PRODUCT);  
121 if (!deviceProfileIds || !deviceProfileIds?.length) { 113 if (!deviceProfileIds || !deviceProfileIds?.length) {
122 createMessage.warning('请选择数据源设备'); 114 createMessage.warning('请选择数据源设备');
123 } 115 }
@@ -9,6 +9,7 @@ export const formSchemas: FormSchema[] = [ @@ -9,6 +9,7 @@ export const formSchemas: FormSchema[] = [
9 field: CheckExistenceFieldsEnum.MESSAGE_NAMES, 9 field: CheckExistenceFieldsEnum.MESSAGE_NAMES,
10 component: 'Select', 10 component: 'Select',
11 label: CheckExistenceFieldsNameEnum.MESSAGE_NAMES, 11 label: CheckExistenceFieldsNameEnum.MESSAGE_NAMES,
  12 + rules: [{ required: true, type: 'array' }],
12 componentProps: { 13 componentProps: {
13 mode: 'tags', 14 mode: 'tags',
14 open: false, 15 open: false,
@@ -20,6 +21,7 @@ export const formSchemas: FormSchema[] = [ @@ -20,6 +21,7 @@ export const formSchemas: FormSchema[] = [
20 field: CheckExistenceFieldsEnum.METADATA_NAMES, 21 field: CheckExistenceFieldsEnum.METADATA_NAMES,
21 component: 'Select', 22 component: 'Select',
22 label: CheckExistenceFieldsNameEnum.METADATA_NAMES, 23 label: CheckExistenceFieldsNameEnum.METADATA_NAMES,
  24 + rules: [{ required: true, type: 'array' }],
23 componentProps: { 25 componentProps: {
24 mode: 'tags', 26 mode: 'tags',
25 open: false, 27 open: false,
@@ -25,7 +25,7 @@ export const getFormSchemas = (route: RouteLocationNormalizedLoaded): FormSchema @@ -25,7 +25,7 @@ export const getFormSchemas = (route: RouteLocationNormalizedLoaded): FormSchema
25 component: 'ApiSearchSelect', 25 component: 'ApiSearchSelect',
26 componentProps: () => { 26 componentProps: () => {
27 return { 27 return {
28 - placeholder: '请选择所属产品', 28 + placeholder: '请选择规则链',
29 showSearch: true, 29 showSearch: true,
30 params: { 30 params: {
31 pageSize: 50, 31 pageSize: 50,
@@ -62,7 +62,7 @@ export const formSchemas: FormSchema[] = [ @@ -62,7 +62,7 @@ export const formSchemas: FormSchema[] = [
62 { 62 {
63 field: FormFieldsEnum.STATUS, 63 field: FormFieldsEnum.STATUS,
64 label: '状态', 64 label: '状态',
65 - component: 'Input', 65 + component: 'Select',
66 componentProps: { 66 componentProps: {
67 options: Object.keys(StatusEnum).map((value) => ({ label: StatusNameEnum[value], value })), 67 options: Object.keys(StatusEnum).map((value) => ({ label: StatusNameEnum[value], value })),
68 allowClear: true, 68 allowClear: true,
1 -<template>  
2 - <div>  
3 - <BasicDrawer  
4 - v-bind="$attrs"  
5 - @register="registerDrawer"  
6 - @ok="handleSubmit"  
7 - destroy-on-close  
8 - width="50%"  
9 - showFooter  
10 - @close="handleClose"  
11 - :title="title"  
12 - >  
13 - <div>  
14 - <!-- 基础表单 -->  
15 - <BasicForm @register="registerForm" />  
16 - <!-- 基础表单 -->  
17 - <!-- 触发器-begin -->  
18 - <!-- <Divider orientation="left">触发器</Divider> -->  
19 - <Divider orientation="left">  
20 - <a-tooltip>  
21 - <template #title>触发器不可为空,消息只要满足触发条件中任意一个即可触发。</template>  
22 - <label class="validate-dot"></label> 触发器  
23 - <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />  
24 - </a-tooltip>  
25 - </Divider>  
26 - <div>  
27 - <template v-for="(item, index) in triggerData" :key="item">  
28 - <TriggerOrCondition  
29 - class="mt-4"  
30 - title="触发器"  
31 - :index="index"  
32 - :provideOrgid="provideOrgid"  
33 - :ref="skipUnwrap.triggerItemRefs"  
34 - @delete="deleteTriggerOrCondition"  
35 - />  
36 - </template>  
37 - <!-- 按钮 -->  
38 - <a-button type="primary" class="mt-4" @click="addTrigger" v-if="isView">  
39 - <PlusOutlined />  
40 - 触发器(OR)<span style="visibility: hidden">发</span>  
41 - </a-button>  
42 - <!-- 按钮 -->  
43 - </div>  
44 - <!-- 触发器-end -->  
45 -  
46 - <!-- 执行条件-begin -->  
47 - <Divider orientation="left">  
48 - <a-tooltip>  
49 - <template #title>执行条件可为空,消息需要满足所有执行条件才会被处理。</template>  
50 - 执行条件  
51 - <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />  
52 - </a-tooltip>  
53 - </Divider>  
54 - <div>  
55 - <template v-for="(item, index) in conditionData" :key="item">  
56 - <TriggerOrCondition  
57 - class="mt-4"  
58 - title="执行条件"  
59 - :index="index"  
60 - :provideOrgid="provideOrgid"  
61 - :ref="skipUnwrap.conditionItemRefs"  
62 - @delete="deleteTriggerOrCondition"  
63 - />  
64 - </template>  
65 - <!-- 按钮 -->  
66 - <a-button type="primary" class="mt-4" @click="addCondition" v-if="isView">  
67 - <PlusOutlined />  
68 - 执行条件(AND)  
69 - </a-button>  
70 - <!-- 按钮 -->  
71 - </div>  
72 - <!-- 执行条件-end -->  
73 -  
74 - <!-- 执行动作-begin -->  
75 - <Divider orientation="left">  
76 - <a-tooltip>  
77 - <template #title  
78 - >触发器和执行条件都满足时,场景联动会做什么,例如:设备联动、告警通知等。</template  
79 - >  
80 - <label class="validate-dot"></label> 执行动作  
81 - <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />  
82 - </a-tooltip>  
83 - </Divider>  
84 - <div>  
85 - <template v-for="(item, index) in actionData" :key="item">  
86 - <Action  
87 - class="mt-4"  
88 - :actionIndex="index"  
89 - :actionData="actionData"  
90 - :triggerData="triggerData"  
91 - :ref="skipUnwrap.actionItemRefs"  
92 - :provideOrgid="provideOrgid"  
93 - :deviceList="getMasterDeviceList"  
94 - :arr="arr"  
95 - @deleteAction="deleteAction"  
96 - @getActionFormArr="getActionFormArr"  
97 - @dynamicChangeAlarmConfig="handleDynamicChangeAlarmConfig"  
98 - />  
99 - </template>  
100 - <!-- 按钮 -->  
101 - <a-button type="primary" class="mt-4" @click="addAction" v-if="isView">  
102 - <PlusOutlined />  
103 - 新增执行动作  
104 - </a-button>  
105 - <!-- 按钮 -->  
106 - </div>  
107 - <!-- 执行动作-end -->  
108 - </div>  
109 - </BasicDrawer>  
110 - </div>  
111 -</template>  
112 -<script lang="ts" setup>  
113 - import { ref, watch, unref, computed, nextTick } from 'vue';  
114 - import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';  
115 - import { formSchema, organizationId as organizationIdRef } from './config/config.data';  
116 - import { BasicForm, useForm } from '/@/components/Form';  
117 - import { genTriggerOrConditionData, genActionData } from './config/formatData';  
118 - import { Divider } from 'ant-design-vue';  
119 - import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue';  
120 - import { useMessage } from '/@/hooks/web/useMessage';  
121 - import {  
122 - screenLinkPageAddApi,  
123 - screenLinkPageByDeptIdGetDevice,  
124 - getOrganizationAlarmConfig,  
125 - } from '/@/api/ruleengine/ruleengineApi';  
126 - import TriggerOrCondition from './cpns/Trigger-Condition.vue';  
127 - import Action from './cpns/Action.vue';  
128 - import { findOperation } from './config/formatData';  
129 - import { formatToDateTime } from '/@/utils/dateUtil';  
130 - import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';  
131 - import { isEmpty } from '/@/utils/is';  
132 - import { add } from '/@/components/Form/src/componentMap';  
133 -  
134 - add('ObjectModelValidateForm', ObjectModelValidateForm);  
135 -  
136 - const emit = defineEmits(['register', 'success']);  
137 - const provideOrgid = ref('');  
138 -  
139 - const { createMessage } = useMessage();  
140 - const triggerData = ref<number[]>([]);  
141 - const conditionData = ref<number[]>([]);  
142 - const actionData = ref<number[]>([]);  
143 - const skipUnwrap = {  
144 - triggerItemRefs: ref<InstanceType<typeof TriggerOrCondition>[]>([]),  
145 - conditionItemRefs: ref<InstanceType<typeof TriggerOrCondition>[]>([]),  
146 - actionItemRefs: ref<InstanceType<typeof Action>[]>([]),  
147 - };  
148 - const title = computed(  
149 - () => `${isUpdate.value === 3 ? '查看' : isUpdate.value ? '编辑' : '新增'}场景联动`  
150 - );  
151 - let getTriggerFormValue = ref([]);  
152 - let getConditionFormValue = ref([]);  
153 - let getActionFormValue = ref([]);  
154 - const editEntryIdData = ref([]);  
155 - const editAlarmConfigData = ref([]);  
156 - const isUpdate = ref<boolean | number>(false);  
157 - const id = ref(undefined);  
158 - const tenantId = ref(undefined);  
159 - const isView = ref(true);  
160 - const [  
161 - registerForm,  
162 - { resetFields, validate, setFieldsValue, getFieldsValue, setProps, updateSchema },  
163 - ] = useForm({  
164 - labelWidth: 120,  
165 - schemas: formSchema,  
166 - showActionButtonGroup: false,  
167 - });  
168 - const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {  
169 - setDrawerProps({ confirmLoading: false, loading: true });  
170 - isUpdate.value = data.isUpdate;  
171 - getActionFormArr();  
172 - if (!unref(isUpdate)) {  
173 - resetFields();  
174 - //初始化执行动作  
175 - actionData.value = [Date.now()];  
176 - } else {  
177 - // 取值  
178 - const {  
179 - id: recordId,  
180 - tenantId: recordTenantId,  
181 - organizationId,  
182 - triggers,  
183 - doConditions,  
184 - doActions,  
185 - } = data.record;  
186 - organizationIdRef.value = organizationId;  
187 - // 赋值  
188 - await setFieldsValue(data.record);  
189 - id.value = recordId;  
190 - tenantId.value = recordTenantId;  
191 - //TODO-fengtao-把组织id传给子组件  
192 - provideOrgid.value = organizationId;  
193 - //TODO-fengtao获取当前执行动作下的设备(master那个接口)  
194 - // getMasterDeviceList.value = await byOrganizationIdGetMasterDevice(organizationId);  
195 - //TODO-fengtao  
196 - // 获取当前组织下的设备列表  
197 - const options = await screenLinkPageByDeptIdGetDevice({  
198 - organizationId,  
199 - });  
200 - // 获取当前组织下的告警配置  
201 - const alarmConfig = await getOrganizationAlarmConfig({ organizationId });  
202 -  
203 - // 生成回显时对应得组件数量  
204 - triggerData.value = [...new Array(triggers.length).keys()];  
205 - conditionData.value = [...new Array(doConditions.length).keys()];  
206 - actionData.value = [...new Array(doActions.length).keys()];  
207 - // 回显设备列表  
208 - editEntryIdData.value = options.items.map((item) => {  
209 - return {  
210 - value: item.tbDeviceId,  
211 - label: item.name,  
212 - };  
213 - });  
214 - editAlarmConfigData.value = alarmConfig.map((item) => {  
215 - return {  
216 - value: item.id,  
217 - label: item.name,  
218 - status: item.status,  
219 - };  
220 - });  
221 - deviceList.value = editEntryIdData.value;  
222 - nextTick(() => {  
223 - setEditFields(skipUnwrap.triggerItemRefs, editEntryIdData);  
224 - setEditFields(skipUnwrap.conditionItemRefs, editEntryIdData);  
225 - setEditFields(skipUnwrap.actionItemRefs, getMasterDeviceList);  
226 - setEditAlarmConfig(skipUnwrap.actionItemRefs, editAlarmConfigData);  
227 - });  
228 -  
229 - const map = {  
230 - ANY_TIME: 0,  
231 - SPECIFIC_TIME: 1,  
232 - CUSTOM: 2,  
233 - };  
234 - // 回显触发器数据---此处是个闭包!  
235 - triggers.forEach((trigger, index) => {  
236 - nextTick(async () => {  
237 - const selectProductId = ref('');  
238 - // 回显启用规则  
239 - unref(skipUnwrap.triggerItemRefs)[index].currentIndex =  
240 - map[trigger.triggerCondition.schedule.type];  
241 - unref(skipUnwrap.triggerItemRefs)[index].scheduleData = trigger.triggerCondition.schedule;  
242 - unref(skipUnwrap.triggerItemRefs)[index].isUpdate = true;  
243 - unref(skipUnwrap.triggerItemRefs)[index].alarmScheduleRef.scheduleData =  
244 - trigger.triggerCondition.schedule;  
245 - unref(skipUnwrap.triggerItemRefs)[index].setFieldsFormValueFun({  
246 - triggered: trigger?.triggerCondition?.condition?.spec?.type,  
247 - device: trigger?.entityType,  
248 - deviceType: trigger?.deviceType,  
249 - triggerType: trigger?.triggerType,  
250 - type1: trigger?.triggerCondition?.condition?.condition[0]?.key?.type,  
251 - type2: trigger?.triggerCondition?.condition?.condition[0]?.key?.key,  
252 - operationType: trigger?.triggerCondition?.condition?.condition[0]?.valueType,  
253 - detail: trigger?.triggerCondition?.alarmDetails,  
254 - entityId: trigger?.entityId,  
255 - deviceProfileId: trigger?.deviceProfileId,  
256 - replaceValue: trigger?.triggerCondition?.condition?.spec?.predicate?.defaultValue,  
257 - time: trigger?.triggerCondition?.condition?.spec?.predicate?.defaultValue,  
258 - timeUnit: trigger?.triggerCondition?.condition?.spec?.unit,  
259 - });  
260 - if (trigger.deviceProfileId != undefined) {  
261 - selectProductId.value = trigger?.deviceProfileId;  
262 - }  
263 - //fengtao-把设备id回传给子组件  
264 - unref(skipUnwrap.triggerItemRefs)[index].updateFieldAttributeFunc(selectProductId.value);  
265 - //fengtao  
266 - // 设置值operationType  
267 - unref(skipUnwrap.triggerItemRefs)[index].operationType =  
268 - trigger.triggerCondition?.condition.condition[0].valueType;  
269 -  
270 - const ConditionScreeningForm = await unref(skipUnwrap.triggerItemRefs)[  
271 - index  
272 - ].getRefItemConditionScreeningRefs();  
273 -  
274 - // 设置对应条件筛选的个数  
275 - unref(skipUnwrap.triggerItemRefs)[index].setConditionScreeningList([  
276 - ...new Array(trigger.triggerCondition.condition.condition.length).keys(),  
277 - ]);  
278 - // 操作符类型 NUMERIC|String|Boolean|DATE_TIME  
279 - const valueType = trigger.triggerCondition?.condition.condition[0].valueType;  
280 -  
281 - // 循环设置条件筛选值。TODO:此处设置顺序有问题  
282 - nextTick(() => {  
283 - const richTextList = [];  
284 - trigger.triggerCondition.condition.condition.forEach((item, index) => {  
285 - const formItem = {  
286 - operation: item.predicate.operation,  
287 - value:  
288 - valueType === 'DATE_TIME'  
289 - ? formatToDateTime(  
290 - Number(item.predicate.value.defaultValue),  
291 - 'YYYY-MM-DD HH:mm:ss'  
292 - )  
293 - : String(item.predicate.value.defaultValue),  
294 - ignoreCase: item.valueType === 'STRING' ? item.predicate.ignoreCase : undefined,  
295 - };  
296 - richTextList.push({  
297 - // 查询中文操作符  
298 - operation: findOperation(valueType, item.predicate.operation).label,  
299 - value:  
300 - valueType === 'DATE_TIME'  
301 - ? formatToDateTime(  
302 - Number(item.predicate.value.defaultValue),  
303 - 'YYYY-MM-DD HH:mm:ss'  
304 - )  
305 - : String(item.predicate.value.defaultValue),  
306 - attribute: trigger.triggerCondition?.condition.condition[0]?.key?.key,  
307 - });  
308 - ConditionScreeningForm.value[index].setFieldsValue(formItem);  
309 - });  
310 - unref(skipUnwrap.triggerItemRefs)[index].setRichText(richTextList);  
311 - });  
312 - });  
313 - });  
314 -  
315 - doConditions.forEach((condition, index) => {  
316 - nextTick(async () => {  
317 - const selectProductId = ref('');  
318 - // 回显启用规则  
319 - unref(skipUnwrap.conditionItemRefs)[index].currentIndex =  
320 - map[condition.triggerCondition.schedule.type];  
321 - unref(skipUnwrap.conditionItemRefs)[index].scheduleData =  
322 - condition.triggerCondition.schedule;  
323 - unref(skipUnwrap.conditionItemRefs)[index].isUpdate = true;  
324 - unref(skipUnwrap.conditionItemRefs)[index].alarmScheduleRef.scheduleData =  
325 - condition.triggerCondition.schedule;  
326 - unref(skipUnwrap.conditionItemRefs)[index].setFieldsFormValueFun({  
327 - triggered: condition?.triggerCondition?.condition?.spec?.type,  
328 - device: condition?.entityType,  
329 - triggerType: condition?.triggerType,  
330 - type1: condition?.triggerCondition?.condition?.condition[0]?.key?.type,  
331 - type2: condition?.triggerCondition?.condition?.condition[0]?.key?.key,  
332 - operationType: condition?.triggerCondition?.condition?.condition[0]?.valueType,  
333 - detail: condition?.triggerCondition?.alarmDetails,  
334 - entityId: condition?.entityId,  
335 - deviceProfileId: condition?.deviceProfileId,  
336 - deviceType: condition?.deviceType,  
337 - replaceValue: condition?.triggerCondition?.condition?.spec?.predicate?.defaultValue,  
338 - time: condition?.triggerCondition?.condition?.spec?.predicate?.defaultValue,  
339 - timeUnit: condition?.triggerCondition?.condition?.spec?.unit,  
340 - });  
341 - if (condition?.deviceProfileId != undefined) {  
342 - selectProductId.value = condition?.deviceProfileId;  
343 - }  
344 - //fengtao-把设备id回传给子组件  
345 - unref(skipUnwrap.conditionItemRefs)[index].updateFieldAttributeFunc(  
346 - selectProductId.value  
347 - );  
348 - //fengtao  
349 - // 设置值operationType  
350 - unref(skipUnwrap.conditionItemRefs)[index].operationType =  
351 - condition.triggerCondition?.condition.condition[0].valueType;  
352 -  
353 - const ConditionScreeningForm = await unref(skipUnwrap.conditionItemRefs)[  
354 - index  
355 - ].getRefItemConditionScreeningRefs();  
356 -  
357 - // 设置对应条件筛选的个数  
358 - unref(skipUnwrap.conditionItemRefs)[index].setConditionScreeningList([  
359 - ...new Array(condition.triggerCondition.condition.condition.length).keys(),  
360 - ]);  
361 - // 操作符类型 NUMERIC|String|Boolean|DATorganizeImportsE_TIME  
362 - const valueType = condition.triggerCondition?.condition.condition[0].valueType;  
363 -  
364 - // 循环设置条件筛选值。TODO:此处设置顺序有问题  
365 - nextTick(() => {  
366 - const richTextList = [];  
367 - condition.triggerCondition.condition.condition.forEach((item, index) => {  
368 - const formItem = {  
369 - operation: item.predicate.operation,  
370 - value:  
371 - valueType === 'DATE_TIME'  
372 - ? formatToDateTime(  
373 - Number(item.predicate.value.defaultValue),  
374 - 'YYYY-MM-DD HH:mm:ss'  
375 - )  
376 - : String(item.predicate.value.defaultValue),  
377 - ignoreCase: item.valueType === 'STRING' ? item.predicate.ignoreCase : undefined,  
378 - };  
379 - richTextList.push({  
380 - // 查询中文操作符  
381 - operation: findOperation(valueType, item.predicate.operation).label,  
382 - value:  
383 - valueType === 'DATE_TIME'  
384 - ? formatToDateTime(  
385 - Number(item.predicate.value.defaultValue),  
386 - 'YYYY-MM-DD HH:mm:ss'  
387 - )  
388 - : String(item.predicate.value.defaultValue),  
389 - attribute: condition.triggerCondition?.condition.condition[0]?.key?.key,  
390 - });  
391 - ConditionScreeningForm.value[index].setFieldsValue(formItem);  
392 - });  
393 - unref(skipUnwrap.conditionItemRefs)[index].setRichText(richTextList);  
394 - });  
395 - });  
396 - });  
397 - doActions.forEach((action, index) => {  
398 - nextTick(() => {  
399 - const selectProductId = ref('');  
400 - // 设置执行动作外层值  
401 - unref(skipUnwrap.actionItemRefs)[index].setFieldsFormValueFun({  
402 - ...action,  
403 - outTarget: action.outTarget,  
404 - device: action.entityType,  
405 - deviceId: action.deviceId,  
406 - alarm_config: action.alarmProfileId,  
407 - alarm_level: action.doContext.alarmLevel,  
408 - });  
409 - // 如果是设备输出设置脚本值  
410 - if (action.outTarget === 'DEVICE_OUT') {  
411 - if (action.commandType.toString() === '0') {  
412 - unref(skipUnwrap.actionItemRefs)[index].setJsonValue(action.doContext.params);  
413 - }  
414 - }  
415 - // 清除告警有值?{数组}  
416 - if (action?.doContext?.clearRule?.length) {  
417 - unref(skipUnwrap.actionItemRefs)[index].checked = true;  
418 - // 生成对应清除告警的数组长度  
419 - unref(skipUnwrap.actionItemRefs)[index].clearRuleList = [  
420 - ...new Array(action?.doContext?.clearRule?.length).keys(),  
421 - ];  
422 - // 推迟执行时机-DOM渲染完毕在执行  
423 - nextTick(async () => {  
424 - unref(skipUnwrap.actionItemRefs)[index].refItem.clearRuleRefs.value.map(  
425 - (item, index) => {  
426 - // 回显启用规则  
427 - item.currentIndex =  
428 - map[action.doContext.clearRule[index].triggerCondition.schedule.type];  
429 - item.scheduleData = action.doContext.clearRule[index].triggerCondition.schedule;  
430 - item.isUpdate = true;  
431 - item.alarmScheduleRef.scheduleData =  
432 - action.doContext.clearRule[index].triggerCondition.schedule;  
433 - item.setFieldsFormValueFun({  
434 - triggered:  
435 - action.doContext.clearRule[index].triggerCondition.condition.spec.type,  
436 - device: action.doContext.clearRule[index].entityType,  
437 - deviceType: action.doContext.clearRule[index].deviceType,  
438 - triggerType: action.doContext.clearRule[index].triggerType,  
439 - type1:  
440 - action.doContext.clearRule[index].triggerCondition.condition.condition[0].key  
441 - .type,  
442 - type2:  
443 - action.doContext.clearRule[index].triggerCondition.condition.condition[0].key  
444 - .key,  
445 - operationType:  
446 - action.doContext.clearRule[index].triggerCondition.condition.condition[0]  
447 - .valueType,  
448 - detail: action.doContext.clearRule[index].triggerCondition.alarmDetails,  
449 - entityId: action.doContext.clearRule[index].entityId,  
450 - deviceProfileId: action.doContext.clearRule[index]?.deviceProfileId,  
451 - replaceValue:  
452 - action.doContext.clearRule[index].triggerCondition.condition.spec.predicate  
453 - .defaultValue,  
454 - time: action.doContext.clearRule[index].triggerCondition.condition.spec  
455 - .predicate.defaultValue,  
456 - timeUnit:  
457 - action.doContext.clearRule[index].triggerCondition.condition.spec.unit,  
458 - });  
459 - if (action.doContext.clearRule[index]?.deviceProfileId != undefined) {  
460 - selectProductId.value = action.doContext.clearRule[index]?.deviceProfileId;  
461 - }  
462 - //fengtao-把设备id回传给子组件  
463 - item.updateFieldAttributeFunc(selectProductId.value);  
464 - item.updateFieldDeviceId(deviceList.value, provideOrgid.value, isUpdate.value);  
465 - //fengtao  
466 - // 单独设置operationType值 操作符类型 NUMERIC|String|Boolean|DATE_TIME  
467 - item.operationType =  
468 - action.doContext.clearRule[  
469 - index  
470 - ].triggerCondition.condition.condition[0].valueType;  
471 - }  
472 - );  
473 -  
474 - const ConditionScreeningForm = await unref(skipUnwrap.actionItemRefs)[  
475 - index  
476 - ].getRefItemConditionScreeningRefs();  
477 -  
478 - // 循环设置条件筛选值。TODO:此处设置顺序有问题  
479 - action.doContext.clearRule.map((rule, ruleIndex) => {  
480 - // 生成对应条件筛选的数组个数  
481 - unref(skipUnwrap.actionItemRefs)[index].setConditionScreeningList([  
482 - ...new Array(  
483 - action.doContext.clearRule[  
484 - ruleIndex  
485 - ].triggerCondition.condition.condition.length  
486 - ).keys(),  
487 - ]);  
488 - nextTick(() => {  
489 - const richTextList = [];  
490 - rule.triggerCondition.condition.condition.forEach((item, conditionIndex) => {  
491 - //TODO-fengtao之前是Number( item.predicate.value.defaultValue)-发现回显是Invalide Time  
492 - const formItem = {  
493 - operation: item.predicate.operation,  
494 - value:  
495 - item.valueType === 'DATE_TIME'  
496 - ? formatToDateTime(  
497 - Number(item.predicate.value.defaultValue),  
498 - 'YYYY-MM-DD HH:mm:ss'  
499 - )  
500 - : String(item.predicate.value.defaultValue),  
501 - ignoreCase:  
502 - item.valueType === 'STRING' ? item.predicate.ignoreCase : undefined,  
503 - };  
504 - //TODO-fengtao之前是Number( item.predicate.value.defaultValue)-发现回显是Invalide Time  
505 - richTextList.push({  
506 - // 查询中文操作符  
507 - operation: findOperation(item.valueType, item.predicate.operation).label,  
508 - value:  
509 - item.valueType === 'DATE_TIME'  
510 - ? formatToDateTime(  
511 - item.predicate.value.defaultValue,  
512 - 'YYYY-MM-DD HH:mm:ss'  
513 - )  
514 - : String(item.predicate.value.defaultValue),  
515 - attribute: item?.key?.key,  
516 - });  
517 - //TODO-fengtao之前是Number( item.predicate.value.defaultValue)-发现回显是Invalide Time  
518 - ConditionScreeningForm[ruleIndex].value[conditionIndex].setFieldsValue(  
519 - formItem  
520 - );  
521 - });  
522 - unref(skipUnwrap.actionItemRefs)[index].setRichText(richTextList, ruleIndex);  
523 - });  
524 - });  
525 - });  
526 - }  
527 - nextTick(() => {  
528 - setEditFields(skipUnwrap.actionItemRefs, editEntryIdData);  
529 - });  
530 - });  
531 - });  
532 - }  
533 - if (unref(isUpdate) === 3) isView.value = false;  
534 - setDrawerProps({  
535 - showFooter: unref(isView),  
536 - loading: false,  
537 - });  
538 -  
539 - if (isUpdate.value == 3) {  
540 - setProps({ disabled: true });  
541 - updateSchema({  
542 - field: 'organizationId',  
543 - componentProps: { apiTreeSelectProps: { disabled: true } },  
544 - });  
545 - await nextTick();  
546 - unref(skipUnwrap.triggerItemRefs)?.forEach((item) => {  
547 - item.setDisabledProps({ disabled: true });  
548 - });  
549 -  
550 - unref(skipUnwrap.conditionItemRefs)?.forEach((item) => {  
551 - item.setDisabledProps({ disabled: true });  
552 - });  
553 -  
554 - unref(skipUnwrap.actionItemRefs)?.forEach((item) => {  
555 - item.setDisabledProps({ disabled: true });  
556 - });  
557 - } else {  
558 - updateSchema({  
559 - field: 'organizationId',  
560 - componentProps: { apiTreeSelectProps: { disabled: false } },  
561 - });  
562 - unref(skipUnwrap.triggerItemRefs)?.forEach((item) => {  
563 - item.setCancelDisabled();  
564 - });  
565 - unref(skipUnwrap.conditionItemRefs)?.forEach((item) => {  
566 - item.setCancelDisabled();  
567 - });  
568 - }  
569 - });  
570 -  
571 - // 设置设备的options  
572 - const setEditFields = (linkAge, deviceList) => {  
573 - unref(linkAge).map((item) => {  
574 - // TODO-fengtao  
575 - item.updateFieldDeviceId(deviceList, orgId, isUpdate);  
576 - // TODO-fengtao  
577 - });  
578 - };  
579 - // 设置告警配置options  
580 - const setEditAlarmConfig = (linkAge, alarmConfigList) => {  
581 - unref(linkAge).map((item) => {  
582 - item.updateEditFieldAlarmConfig(alarmConfigList);  
583 - });  
584 - };  
585 - // 监听组织变化更新设备列表  
586 - const deviceList = ref([]);  
587 - const getMasterDeviceList = ref([]);  
588 - const orgId = ref('');  
589 - const alarmConfigList = ref([]);  
590 - //FT add 2022-10-27  
591 - const addOrgId = ref('');  
592 - //FT add 2022-10-27  
593 - watch(organizationIdRef, async (newValue: string) => {  
594 - if (!newValue) return;  
595 - //FT add 2022-10-27  
596 - addOrgId.value = newValue;  
597 - //FT add 2022-10-27  
598 - const { items = [] } = await screenLinkPageByDeptIdGetDevice({ organizationId: newValue });  
599 - //TODO fengtao  
600 - // getMasterDeviceList.value = await byOrganizationIdGetMasterDevice(newValue);  
601 - //TODO fengtao  
602 - deviceList.value = (items || []).map((item) => ({  
603 - ...item,  
604 - label: item.name,  
605 - value: item.tbDeviceId,  
606 - }));  
607 - //TODO fengtao  
608 - orgId.value = newValue;  
609 - //TODO fengtao  
610 - setFields(skipUnwrap.triggerItemRefs, true);  
611 - setFields(skipUnwrap.conditionItemRefs, true);  
612 - setFields(skipUnwrap.actionItemRefs, true);  
613 - const data = await getOrganizationAlarmConfig({ organizationId: newValue });  
614 - alarmConfigList.value = data.map((item) => ({  
615 - label: item.name,  
616 - value: item.id,  
617 - status: item.status,  
618 - }));  
619 - setEditAlarmConfig(skipUnwrap.actionItemRefs, alarmConfigList);  
620 - // setFields(skipUnwrap.actionItemRefs, true);  
621 - // console.log(unref(organizationIdRef));  
622 - // setAlarmConfig(skipUnwrap.actionItemRefs, true);  
623 - });  
624 - //FT add 2022-10-27  
625 - const handleDynamicChangeAlarmConfig = async () => {  
626 - const data = await getOrganizationAlarmConfig({  
627 - organizationId: isUpdate.value ? provideOrgid.value : addOrgId.value,  
628 - });  
629 - const res = data.map((item) => ({ label: item.name, value: item.id, status: item.status }));  
630 - isUpdate.value ? (editAlarmConfigData.value = res) : (alarmConfigList.value = res);  
631 - unref(skipUnwrap.actionItemRefs)?.map((item: any) => {  
632 - if (isUpdate.value) {  
633 - item.updateEditFieldAlarmConfig(editAlarmConfigData);  
634 - } else {  
635 - item.updateFieldAlarmConfig(alarmConfigList);  
636 - }  
637 - });  
638 - };  
639 - //FT add 2022-10-27  
640 - // 根据上面组织变化动态改变触发器,执行条件,执行动作的设备值  
641 - function setFields(linkAge, isOrganizationChange = false) {  
642 - unref(linkAge).map((item) => {  
643 - isOrganizationChange && item.resetFieldsValueFunc();  
644 - //TODO fengtao  
645 - item.updateFieldDeviceId(deviceList, orgId, isUpdate);  
646 - if (isUpdate.value) {  
647 - item.updateEditFieldAlarmConfig(editAlarmConfigData);  
648 - } else {  
649 - item.updateFieldAlarmConfig(alarmConfigList);  
650 - }  
651 - //TODO fengtao  
652 - });  
653 - }  
654 - // function setAlarmConfig(linkAge, isOrganizationChange = false) {  
655 - // unref(linkAge).map((item) => {  
656 - // isOrganizationChange && item.resetFieldsValueFunc();  
657 - // item.updateFieldAlarmConfig(alarmConfigList);  
658 - // });  
659 - // }  
660 -  
661 - const isEmptyThrowError = (obj) => {  
662 - if (isEmpty(obj)) {  
663 - createMessage.error('请选择组织');  
664 - throw Error('请选择组织');  
665 - }  
666 - if (!Reflect.get(obj, 'organizationId')) {  
667 - createMessage.error('请选择组织');  
668 - throw Error('请选择组织');  
669 - }  
670 - };  
671 -  
672 - // 添加触发器  
673 - const addTrigger = () => {  
674 - unref(triggerData).push(Date.now());  
675 - nextTick(() => {  
676 - setFields(skipUnwrap.triggerItemRefs);  
677 - });  
678 - isEmptyThrowError(getFieldsValue());  
679 - };  
680 - // 添加执行条件  
681 - const addCondition = () => {  
682 - unref(conditionData).push(Date.now());  
683 - nextTick(() => {  
684 - setFields(skipUnwrap.conditionItemRefs);  
685 - });  
686 - isEmptyThrowError(getFieldsValue());  
687 - };  
688 - // 添加执行动作  
689 - const addAction = () => {  
690 - unref(actionData).push(Date.now());  
691 - nextTick(() => {  
692 - setFields(skipUnwrap.actionItemRefs);  
693 - });  
694 - isEmptyThrowError(getFieldsValue());  
695 - };  
696 -  
697 - /**  
698 - * 获取触发器、执行条件、执行动作表单值--多个  
699 - */  
700 - const getFormValueFunc = () => {  
701 - getTriggerFormValue.value = unref(skipUnwrap.triggerItemRefs)?.map((item) =>  
702 - genTriggerOrConditionData(item.getFieldsValueFunc())  
703 - );  
704 - getConditionFormValue.value = unref(skipUnwrap.conditionItemRefs)?.map((item) =>  
705 - genTriggerOrConditionData(item.getFieldsValueFunc())  
706 - );  
707 - getActionFormValue.value = unref(skipUnwrap.actionItemRefs)?.map((item) =>  
708 - genActionData(item.getFieldsValueFunc())  
709 - );  
710 - };  
711 - const handleSubmit = async () => {  
712 - let basicFormValue = await validate();  
713 - if (!basicFormValue) return;  
714 - for (const item of unref(skipUnwrap.actionItemRefs)) {  
715 - const valid = await item.validateForm();  
716 - if (!valid) return;  
717 - }  
718 - try {  
719 - setDrawerProps({ confirmLoading: true });  
720 - getFormValueFunc();  
721 - //新增的代码2022-11-22  
722 - if (unref(getTriggerFormValue).length === 0 && unref(getConditionFormValue).length === 0) {  
723 - createMessage.error('请添加触发器或者执行条件');  
724 - throw '请添加触发器或者执行条件';  
725 - }  
726 -  
727 - // return;  
728 - //新增的代码2022-11-22  
729 - const postAddOrEditData = {  
730 - ...basicFormValue,  
731 - triggers: !unref(getTriggerFormValue).length ? null : unref(getTriggerFormValue),  
732 - doConditions: !unref(getConditionFormValue).length ? null : unref(getConditionFormValue),  
733 - doActions: unref(getActionFormValue).flat(),  
734 - id: unref(id),  
735 - tenantId: unref(tenantId),  
736 - };  
737 - //FT change  
738 - let mustTriggerCondition = false;  
739 - let mustCondition = false;  
740 - if (postAddOrEditData?.triggers !== null && postAddOrEditData?.triggers.length > 0) {  
741 - postAddOrEditData?.triggers.some((s) => {  
742 - if (s.triggerCondition?.condition?.condition == undefined) {  
743 - mustTriggerCondition = true;  
744 - }  
745 - });  
746 - } else {  
747 - mustTriggerCondition = false;  
748 - }  
749 - if (postAddOrEditData?.doConditions !== null && postAddOrEditData?.doConditions.length > 0) {  
750 - postAddOrEditData?.doConditions.some((s) => {  
751 - if (s.triggerCondition?.condition?.condition == undefined) {  
752 - mustCondition = true;  
753 - }  
754 - });  
755 - } else {  
756 - mustCondition = false;  
757 - }  
758 - if (mustTriggerCondition) return;  
759 - if (mustCondition) return;  
760 - //FT change  
761 - await screenLinkPageAddApi(postAddOrEditData, unref(isUpdate));  
762 - createMessage.success(`${unref(isUpdate) ? '编辑' : '新增'}成功`);  
763 - closeDrawer();  
764 - handleClose();  
765 - emit('success');  
766 - } finally {  
767 - setDrawerProps({ confirmLoading: false });  
768 - }  
769 - };  
770 - // 删除  
771 - const deleteTriggerOrCondition = ({ index, title }) => {  
772 - if (title === '触发器') {  
773 - unref(triggerData).splice(index, 1);  
774 - } else if (title === '执行条件') {  
775 - unref(conditionData).splice(index, 1);  
776 - }  
777 - };  
778 - const deleteAction = (actionIndex) => {  
779 - unref(actionData).splice(actionIndex, 1);  
780 - unref(arr).splice(actionIndex, 1);  
781 - };  
782 - const arr = ref([]);  
783 - const getActionFormArr = () => {  
784 - arr.value = unref(skipUnwrap.actionItemRefs).map((item) => item.getFieldsValue());  
785 - };  
786 - const handleClose = () => {  
787 - id.value = undefined;  
788 - tenantId.value = undefined;  
789 - organizationIdRef.value = undefined;  
790 - isView.value = true;  
791 - getTriggerFormValue.value = [];  
792 - getConditionFormValue.value = [];  
793 - getActionFormValue.value = [];  
794 - triggerData.value = [];  
795 - conditionData.value = [];  
796 - actionData.value = [];  
797 - unref(skipUnwrap.triggerItemRefs).map((item) => {  
798 - item.resetFieldsValueFunc();  
799 - });  
800 - unref(skipUnwrap.conditionItemRefs).map((item) => {  
801 - item.resetFieldsValueFunc();  
802 - });  
803 - unref(skipUnwrap.actionItemRefs).map((item) => {  
804 - item.resetFieldsValueFunc();  
805 - });  
806 - window.localStorage.removeItem('isViewDisabledBtn');  
807 - // window.localStorage.setItem('isViewDisabledBtn', 'no')  
808 - };  
809 -</script>  
810 -  
811 -<style lang="less" scoped>  
812 - //TODO-fengtao  
813 - ///移除选择框默认样式(24px)否则超出默认宽度会造成页面样式错乱  
814 - :deep(.ant-select-selector) {  
815 - padding-right: 0 !important;  
816 - }  
817 -  
818 - :deep(.ant-select-selection-overflow) {  
819 - max-width: 10vw !important;  
820 - }  
821 -  
822 - //TODO-fengtao  
823 -  
824 - label.validate-dot::before {  
825 - display: inline-block;  
826 - color: #ff4d4f;  
827 - font-size: 14px;  
828 - font-family: SimSun, sans-serif;  
829 - line-height: 1;  
830 - content: '*';  
831 - }  
832 -</style>  
  1 +import { OperationType } from './type';
  2 +import { FormSchema } from '/@/components/Form';
  3 +import {
  4 + BooleanOperationEnum,
  5 + BooleanOperationNameEnum,
  6 + BooleanOperationValueEnum,
  7 + BooleanOperationValueNameEnum,
  8 + NumberOperationEnum,
  9 + NumberOperationNameEnum,
  10 + StringOperationEnum,
  11 + StringOperationNameEnum,
  12 + TriggerValueTypeEnum,
  13 +} from '/@/enums/linkedgeEnum';
  14 +
  15 +export enum FormFieldsEnum {
  16 + CONDITION_OPERATION = 'operation',
  17 + CONDITION_VALUE = 'defaultValue',
  18 + CONDITION_VALUE_IGNORE_CASE = 'ignoreCase',
  19 +}
  20 +
  21 +export enum FormFieldsNameEnum {
  22 + CONDITION_OPERATION = '执行操作',
  23 + CONDITION_VALUE = '操作值',
  24 + CONDITION_VALUE_IGNORE_CASE = '忽略大小写',
  25 +}
  26 +
  27 +interface GetFormSchemasParamsType {
  28 + triggerType: TriggerValueTypeEnum;
  29 +}
  30 +
  31 +export interface ConditionFormRecordType {
  32 + [FormFieldsEnum.CONDITION_OPERATION]: OperationType;
  33 + [FormFieldsEnum.CONDITION_VALUE]: any;
  34 + [FormFieldsEnum.CONDITION_VALUE_IGNORE_CASE]?: boolean;
  35 +}
  36 +
  37 +export const getOperationMap = (type: TriggerValueTypeEnum) => {
  38 + const keyMap = {
  39 + [TriggerValueTypeEnum.NUMERIC]: { value: NumberOperationEnum, label: NumberOperationNameEnum },
  40 + [TriggerValueTypeEnum.BOOLEAN]: {
  41 + value: BooleanOperationEnum,
  42 + label: BooleanOperationNameEnum,
  43 + },
  44 + [TriggerValueTypeEnum.STRING]: { value: StringOperationEnum, label: StringOperationNameEnum },
  45 + [TriggerValueTypeEnum.DATE_TIME]: {
  46 + value: NumberOperationEnum,
  47 + label: NumberOperationNameEnum,
  48 + },
  49 + };
  50 +
  51 + return keyMap[type];
  52 +};
  53 +
  54 +const getOperation = (type: TriggerValueTypeEnum) => {
  55 + const res = getOperationMap(type);
  56 +
  57 + const { label, value } = res;
  58 + return Object.keys(value).map((value) => ({ label: label[value], value }));
  59 +};
  60 +
  61 +const getOperationValueFormSchemas = (triggerType: TriggerValueTypeEnum): FormSchema[] => {
  62 + const getNumberSchemas = (): FormSchema[] => {
  63 + return [
  64 + {
  65 + field: FormFieldsEnum.CONDITION_VALUE,
  66 + label: FormFieldsNameEnum.CONDITION_VALUE,
  67 + component: 'InputNumber',
  68 + required: true,
  69 + componentProps: () => {
  70 + return {
  71 + placeholder: `请输入${FormFieldsNameEnum.CONDITION_VALUE}`,
  72 + };
  73 + },
  74 + },
  75 + ];
  76 + };
  77 +
  78 + const getBoolSchemas = (): FormSchema[] => {
  79 + return [
  80 + {
  81 + field: FormFieldsEnum.CONDITION_VALUE,
  82 + label: FormFieldsNameEnum.CONDITION_VALUE,
  83 + component: 'Select',
  84 + required: true,
  85 + componentProps: () => {
  86 + return {
  87 + options: [
  88 + { label: BooleanOperationValueNameEnum.TRUE, value: BooleanOperationValueEnum.TRUE },
  89 + {
  90 + label: BooleanOperationValueNameEnum.FALSE,
  91 + value: BooleanOperationValueEnum.FALSE,
  92 + },
  93 + ],
  94 + placeholder: `请输入${FormFieldsNameEnum.CONDITION_VALUE}`,
  95 + };
  96 + },
  97 + },
  98 + ];
  99 + };
  100 +
  101 + const getStringSchemas = (): FormSchema[] => {
  102 + return [
  103 + {
  104 + field: FormFieldsEnum.CONDITION_VALUE,
  105 + label: FormFieldsNameEnum.CONDITION_VALUE,
  106 + component: 'Input',
  107 + required: true,
  108 + componentProps: () => {
  109 + return {
  110 + placeholder: `请输入${FormFieldsNameEnum.CONDITION_VALUE}`,
  111 + };
  112 + },
  113 + },
  114 + {
  115 + field: FormFieldsEnum.CONDITION_VALUE_IGNORE_CASE,
  116 + label: ' ',
  117 + component: 'Checkbox',
  118 + renderComponentContent: () => ({
  119 + default: () => FormFieldsNameEnum.CONDITION_VALUE_IGNORE_CASE,
  120 + }),
  121 + },
  122 + ];
  123 + };
  124 +
  125 + const getDateSchemas = (): FormSchema[] => {
  126 + return [
  127 + {
  128 + field: FormFieldsEnum.CONDITION_VALUE,
  129 + label: FormFieldsNameEnum.CONDITION_VALUE,
  130 + component: 'DatePicker',
  131 + required: true,
  132 + componentProps: () => {
  133 + return {
  134 + showTime: true,
  135 + placeholder: `请输入${FormFieldsNameEnum.CONDITION_VALUE}`,
  136 + };
  137 + },
  138 + },
  139 + ];
  140 + };
  141 +
  142 + const schemasMap = {
  143 + [TriggerValueTypeEnum.BOOLEAN]: getBoolSchemas,
  144 + [TriggerValueTypeEnum.DATE_TIME]: getDateSchemas,
  145 + [TriggerValueTypeEnum.NUMERIC]: getNumberSchemas,
  146 + [TriggerValueTypeEnum.STRING]: getStringSchemas,
  147 + };
  148 +
  149 + return schemasMap[triggerType]?.() || [];
  150 +};
  151 +
  152 +export const getFormSchemas = ({ triggerType }: GetFormSchemasParamsType): FormSchema[] => {
  153 + return [
  154 + {
  155 + field: FormFieldsEnum.CONDITION_OPERATION,
  156 + label: FormFieldsNameEnum.CONDITION_OPERATION,
  157 + component: 'Select',
  158 + required: true,
  159 + componentProps: () => {
  160 + return {
  161 + options: getOperation(triggerType),
  162 + placeholder: `请选择${FormFieldsNameEnum.CONDITION_OPERATION}`,
  163 + };
  164 + },
  165 + },
  166 + ...getOperationValueFormSchemas(triggerType),
  167 + ];
  168 +};
  1 +import { ConditionListItemType } from './type';
  2 +import { buildUUID } from '/@/utils/uuid';
  3 +
  4 +export { default as ConditionFilter } from './index.vue';
  5 +
  6 +export enum CheckStatusEnum {
  7 + NO_VALIDATE = 'NO_VALIDATE',
  8 + VALIDATE_SUCCESS = 'VALIDATE_SUCCESS',
  9 + VALIDATE_FAILED = 'VALIDATE_FAILED',
  10 +}
  11 +
  12 +export enum CheckStatusColorEnum {
  13 + NO_VALIDATE,
  14 + VALIDATE_SUCCESS = '!text-green-600',
  15 + VALIDATE_FAILED = '!text-red-600',
  16 +}
  17 +
  18 +export enum CheckStatusIconEnum {
  19 + NO_VALIDATE = '',
  20 + VALIDATE_SUCCESS = 'ant-design:check-outlined',
  21 + VALIDATE_FAILED = 'ant-design:close-outlined',
  22 +}
  23 +
  24 +export function getNewConditionFilterItem(
  25 + checkStatus = CheckStatusEnum.NO_VALIDATE
  26 +): ConditionListItemType {
  27 + return {
  28 + key: buildUUID(),
  29 + checkStatus,
  30 + };
  31 +}
  1 +<script setup lang="ts">
  2 + import { ComponentPublicInstance, computed, ref, unref, watch } from 'vue';
  3 + import { Card, Tag, Button, Tooltip } from 'ant-design-vue';
  4 + import { Icon } from '/@/components/Icon';
  5 + import { getFormSchemas, FormFieldsEnum } from './config';
  6 + import { CollapseContainer } from '/@/components/Container';
  7 + import { BasicForm, FormActionType } from '/@/components/Form';
  8 + import { TriggerValueTypeEnum } from '/@/enums/linkedgeEnum';
  9 + import { ConditionFilterProps, ConditionListItemType } from './type';
  10 + import { ConditionMessageItemType, useConditionFilterMessage } from './useConditionFilterMessage';
  11 + import { useConditionData } from './useConditionData';
  12 + import {
  13 + CheckStatusEnum,
  14 + CheckStatusIconEnum,
  15 + CheckStatusColorEnum,
  16 + getNewConditionFilterItem,
  17 + } from '.';
  18 + import { useSceneLinkageDrawerContext } from '../SceneLinkageDrawer/sceneLinkageDrawerContext';
  19 +
  20 + const { disabledDrawer } = useSceneLinkageDrawerContext();
  21 +
  22 + const props = withDefaults(defineProps<ConditionFilterProps>(), {
  23 + triggerType: TriggerValueTypeEnum.BOOLEAN,
  24 + });
  25 +
  26 + const getSchemas = computed(() => {
  27 + const { triggerType } = props;
  28 + return getFormSchemas({ triggerType });
  29 + });
  30 +
  31 + const conditionListElRef = ref<ConditionListItemType[]>([getNewConditionFilterItem()]);
  32 +
  33 + const getConditionFormValues = (): Record<FormFieldsEnum, string>[] => {
  34 + return unref(conditionListElRef).map(
  35 + (conditionForm) => conditionForm.ref?.getFieldsValue() as Record<FormFieldsEnum, string>
  36 + );
  37 + };
  38 +
  39 + const conditionMessageList = ref<ConditionMessageItemType[]>([]);
  40 +
  41 + const handleConditionFormValueChange = () => {
  42 + const condition = getConditionFormValues();
  43 + const { triggerType } = props;
  44 + const message = useConditionFilterMessage({ triggerType, condition });
  45 + conditionMessageList.value = message;
  46 + };
  47 +
  48 + const setConditionRef = (
  49 + conditionItem: ConditionListItemType,
  50 + el: Element | ComponentPublicInstance | null
  51 + ) => {
  52 + conditionItem.ref = el as unknown as FormActionType;
  53 + };
  54 +
  55 + const handleDelete = (conditionItem: ConditionListItemType) => {
  56 + const { key } = conditionItem;
  57 +
  58 + const index = unref(conditionListElRef).findIndex((item) => item.key === key);
  59 +
  60 + ~index && unref(conditionListElRef).splice(index, 1);
  61 +
  62 + handleConditionFormValueChange();
  63 + };
  64 +
  65 + const handleAdd = () => {
  66 + conditionListElRef.value.push(getNewConditionFilterItem());
  67 + };
  68 +
  69 + watch(
  70 + () => props.triggerType,
  71 + () => {
  72 + conditionListElRef.value = [getNewConditionFilterItem()];
  73 + conditionMessageList.value = [];
  74 + }
  75 + );
  76 +
  77 + const { getFieldsValue, setFieldsValue, validate } = useConditionData(
  78 + props,
  79 + conditionListElRef,
  80 + handleConditionFormValueChange
  81 + );
  82 +
  83 + const handelValidate = async () => {
  84 + await validate?.();
  85 + };
  86 +
  87 + defineExpose({ getFieldsValue, setFieldsValue, validate });
  88 +</script>
  89 +
  90 +<template>
  91 + <Card class="condition-filter-card !border-dashed !border-2 !border-gray-200 !rounded-lg">
  92 + <CollapseContainer class="rounded-lg" :default-expand="!disabledDrawer">
  93 + <template #title>
  94 + <section class="flex !p-2">
  95 + <div class="w-20 flex-grow-0 flex-shrink-0"> 条件筛选 </div>
  96 + <div class="flex flex-wrap gap-1 mr-4">
  97 + <template v-for="(item, index) in conditionMessageList" :key="index">
  98 + <Tag class="!rounded-md !m-0 !text-sky-700"> {{ objectModel?.name }} </Tag>
  99 + <span>{{ item.operation }}</span>
  100 + <Tag class="!rounded-md !m-0 !text-orange-300">
  101 + {{ item.defaultValue }}
  102 + </Tag>
  103 + <span v-if="conditionMessageList.length - 1 !== index">和</span>
  104 + </template>
  105 + </div>
  106 + </section>
  107 + </template>
  108 + <!-- -->
  109 + <main
  110 + class="flex gap-4 items-center mb-2"
  111 + v-for="(conditionItem, index) in conditionListElRef"
  112 + :key="conditionItem.key"
  113 + >
  114 + <section
  115 + class="border-2 border-dashed border-gray-200 rounded-lg px-4 py-2 flex-1 flex items-center"
  116 + >
  117 + <BasicForm
  118 + class="flex-auto"
  119 + :ref="(el) => setConditionRef(conditionItem, el)"
  120 + :show-action-button-group="false"
  121 + layout="horizontal"
  122 + :disabled="disabledDrawer"
  123 + :label-width="80"
  124 + :schemas="getSchemas"
  125 + :base-col-props="{ span: 24 / getSchemas.length, xl: 8, lg: 12, sm: 24 }"
  126 + @field-value-change="handleConditionFormValueChange"
  127 + />
  128 + <Icon
  129 + :style="{
  130 + opacity: conditionItem.checkStatus !== CheckStatusEnum.NO_VALIDATE ? 100 : 0,
  131 + }"
  132 + :icon="CheckStatusIconEnum[conditionItem.checkStatus!]"
  133 + :size="20"
  134 + :class="CheckStatusColorEnum[conditionItem.checkStatus!]"
  135 + class="transition-opacity duration-500 ease-linear"
  136 + />
  137 + </section>
  138 + <span
  139 + class="w-5"
  140 + :style="{ visibility: conditionListElRef.length - 1 !== index ? 'visible' : 'hidden' }"
  141 + >
  142 +
  143 + </span>
  144 + <Tooltip title="删除">
  145 + <Icon
  146 + v-if="!disabledDrawer"
  147 + icon="fluent:delete-off-20-regular"
  148 + size="24"
  149 + class="cursor-pointer"
  150 + @click="handleDelete(conditionItem)"
  151 + />
  152 + </Tooltip>
  153 + </main>
  154 + <section class="flex mt-4 items-center justify-between">
  155 + <Button v-if="!disabledDrawer" type="primary" @click="handleAdd">
  156 + <Icon icon="ant-design:plus-outlined" />
  157 + 新增条件筛选
  158 + </Button>
  159 + <Button v-if="!disabledDrawer" type="primary" @click="handelValidate">添加</Button>
  160 + </section>
  161 + </CollapseContainer>
  162 + </Card>
  163 +</template>
  164 +
  165 +<style lang="less" scoped>
  166 + .condition-filter-card {
  167 + :deep(.ant-card-body) {
  168 + @apply p-3;
  169 +
  170 + .vben-collapse-container__header {
  171 + height: fit-content;
  172 + }
  173 +
  174 + .ant-calendar-picker {
  175 + min-width: auto !important;
  176 + width: 100%;
  177 + }
  178 +
  179 + .ant-input-number {
  180 + min-width: auto !important;
  181 + }
  182 + }
  183 + }
  184 +</style>
  185 +./type
  1 +import { CheckStatusEnum } from '.';
  2 +import { ScheduleType } from '../EnablingRule/type';
  3 +import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel';
  4 +import { FormActionType } from '/@/components/Form';
  5 +import {
  6 + BooleanOperationEnum,
  7 + DeviceTriggerTypeEum,
  8 + FlipFlopTypeEnum,
  9 + NumberOperationEnum,
  10 + StringOperationNameEnum,
  11 + TriggerTypeEnum,
  12 + TriggerValueTypeEnum,
  13 +} from '/@/enums/linkedgeEnum';
  14 +
  15 +export interface ConditionListItemType {
  16 + key: string;
  17 + ref?: FormActionType;
  18 + checkStatus?: CheckStatusEnum;
  19 +}
  20 +
  21 +export interface ConditionFilterProps {
  22 + triggerType?: TriggerValueTypeEnum;
  23 + objectModel?: DeviceModelOfMatterAttrs;
  24 + conditionType?: DeviceTriggerTypeEum;
  25 +}
  26 +
  27 +export interface TriggerCondition {
  28 + condition: ConditionType;
  29 + alarmDetails?: Nullable<any>;
  30 + dashboardId?: Nullable<any>;
  31 + schedule: ScheduleType;
  32 + triggerType?: TriggerTypeEnum;
  33 +}
  34 +
  35 +export interface ConditionType {
  36 + condition: ConditionItemType[];
  37 + spec: { type: FlipFlopTypeEnum; unit?: string; predicate?: { defaultValue: any } };
  38 +}
  39 +
  40 +export type OperationType = NumberOperationEnum | StringOperationNameEnum | BooleanOperationEnum;
  41 +
  42 +export interface ConditionItemType {
  43 + key: { type: DeviceTriggerTypeEum; key: string };
  44 + predicate: {
  45 + type: TriggerValueTypeEnum;
  46 + operation: OperationType;
  47 + ignoreCase?: boolean;
  48 + value: {
  49 + defaultValue: number | string | boolean;
  50 + userValue?: Nullable<number | string | boolean>;
  51 + dynamicValue?: Nullable<number | string | boolean>;
  52 + };
  53 + };
  54 + valueType: TriggerValueTypeEnum;
  55 + value?: Nullable<any>;
  56 +}
  1 +import { Ref, nextTick, unref } from 'vue';
  2 +import { ConditionFilterProps, ConditionItemType, ConditionListItemType } from './type';
  3 +import { DefineComponentsBasicExpose } from '/#/utils';
  4 +import { DeviceTriggerTypeEum, TriggerValueTypeEnum } from '/@/enums/linkedgeEnum';
  5 +import { ConditionFormRecordType, FormFieldsEnum } from './config';
  6 +import { CheckStatusEnum, getNewConditionFilterItem } from '.';
  7 +import { dateUtil } from '/@/utils/dateUtil';
  8 +
  9 +function getPredicateType(triggerType: TriggerValueTypeEnum) {
  10 + const map = {
  11 + [TriggerValueTypeEnum.BOOLEAN]: TriggerValueTypeEnum.BOOLEAN,
  12 + [TriggerValueTypeEnum.STRING]: TriggerValueTypeEnum.STRING,
  13 + [TriggerValueTypeEnum.NUMERIC]: TriggerValueTypeEnum.NUMERIC,
  14 + [TriggerValueTypeEnum.DATE_TIME]: TriggerValueTypeEnum.NUMERIC,
  15 + };
  16 +
  17 + return map[triggerType];
  18 +}
  19 +
  20 +export function useConditionData(
  21 + props: ConditionFilterProps,
  22 + conditionItemRef: Ref<ConditionListItemType[]>,
  23 + handleConditionFormValueChange: Fn
  24 +): DefineComponentsBasicExpose<ConditionItemType[]> {
  25 + const createConditionItem = ({
  26 + operation,
  27 + defaultValue,
  28 + ignoreCase,
  29 + }: ConditionFormRecordType): ConditionItemType => {
  30 + const { triggerType, objectModel } = props;
  31 + const { identifier } = objectModel || {};
  32 + return {
  33 + key: { key: identifier!, type: DeviceTriggerTypeEum.TIME_SERIES },
  34 + predicate: {
  35 + ...(ignoreCase ? { ignoreCase } : {}),
  36 + operation,
  37 + type: getPredicateType(triggerType!),
  38 + value: {
  39 + defaultValue:
  40 + triggerType === TriggerValueTypeEnum.DATE_TIME
  41 + ? Number(dateUtil(defaultValue).format('x'))
  42 + : defaultValue,
  43 + },
  44 + },
  45 + valueType: triggerType!,
  46 + };
  47 + };
  48 +
  49 + const getFieldsValue = (): ConditionItemType[] => {
  50 + return unref(conditionItemRef).map((conditionItem) => {
  51 + const value = conditionItem.ref?.getFieldsValue() as ConditionFormRecordType;
  52 + return createConditionItem(value);
  53 + });
  54 + };
  55 +
  56 + const setFieldsValue = (condition: ConditionItemType[]) => {
  57 + conditionItemRef.value = Array.from({ length: condition.length }, () =>
  58 + getNewConditionFilterItem(CheckStatusEnum.VALIDATE_SUCCESS)
  59 + );
  60 +
  61 + nextTick(() => {
  62 + unref(conditionItemRef).forEach((conditionItem, index) => {
  63 + const result = condition[index] || {};
  64 +
  65 + const { operation, value, ignoreCase, type } = result.predicate || {};
  66 +
  67 + conditionItem.ref?.setFieldsValue({
  68 + [FormFieldsEnum.CONDITION_OPERATION]: operation,
  69 + [FormFieldsEnum.CONDITION_VALUE]:
  70 + type === TriggerValueTypeEnum.BOOLEAN
  71 + ? value.defaultValue.toString()
  72 + : value.defaultValue,
  73 + [FormFieldsEnum.CONDITION_VALUE_IGNORE_CASE]: ignoreCase,
  74 + });
  75 + });
  76 + handleConditionFormValueChange();
  77 + });
  78 + };
  79 +
  80 + const validate = async () => {
  81 + for (const conditionItem of unref(conditionItemRef)) {
  82 + conditionItem.checkStatus = CheckStatusEnum.VALIDATE_FAILED;
  83 + await conditionItem.ref?.validate();
  84 + conditionItem.checkStatus = CheckStatusEnum.VALIDATE_SUCCESS;
  85 + }
  86 + };
  87 + return {
  88 + getFieldsValue,
  89 + setFieldsValue,
  90 + validate,
  91 + };
  92 +}
  1 +import { FormFieldsEnum, getOperationMap } from './config';
  2 +import {
  3 + BooleanOperationValueEnum,
  4 + BooleanOperationValueNameEnum,
  5 + TriggerValueTypeEnum,
  6 +} from '/@/enums/linkedgeEnum';
  7 +
  8 +export type ConditionMessageItemType = Record<
  9 + Exclude<FormFieldsEnum, FormFieldsEnum.CONDITION_VALUE_IGNORE_CASE>,
  10 + string
  11 +>;
  12 +
  13 +interface UseConditionFilterMessageParamsType {
  14 + triggerType: TriggerValueTypeEnum;
  15 + condition: ConditionMessageItemType[];
  16 +}
  17 +
  18 +export function useConditionFilterMessage({
  19 + triggerType,
  20 + condition,
  21 +}: UseConditionFilterMessageParamsType) {
  22 + const { label } = getOperationMap(triggerType);
  23 + const list: ConditionMessageItemType[] = [];
  24 + for (const item of condition) {
  25 + let { defaultValue, operation } = item;
  26 + if (!defaultValue || !operation) continue;
  27 + operation = label[operation];
  28 + if (triggerType === TriggerValueTypeEnum.BOOLEAN)
  29 + defaultValue =
  30 + defaultValue === BooleanOperationValueEnum.TRUE
  31 + ? BooleanOperationValueNameEnum.TRUE
  32 + : BooleanOperationValueNameEnum.FALSE;
  33 + list.push({ operation, defaultValue });
  34 + }
  35 + return list;
  36 +}
  1 +import TimeRangePicker from './TimeRangePicker.vue';
  2 +import { WeekName } from './config';
  3 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  4 +
  5 +enum FormFieldsEnum {
  6 + DAY_OF_WEEK = 'dayOfWeek',
  7 + ENABLED = 'enabled',
  8 + ENDS_ON = 'endsOn',
  9 + STARTS_ON = 'startsOn',
  10 + TIME_RANGE = 'timeRange',
  11 +}
  12 +
  13 +export interface RuleFormRecordItem {
  14 + [FormFieldsEnum.DAY_OF_WEEK]: number;
  15 + [FormFieldsEnum.ENABLED]: boolean;
  16 + [FormFieldsEnum.TIME_RANGE]: {
  17 + [FormFieldsEnum.STARTS_ON]: number;
  18 + [FormFieldsEnum.ENDS_ON]: number;
  19 + };
  20 +}
  21 +
  22 +useComponentRegister('TimeRangePicker', TimeRangePicker);
  23 +
  24 +export const getFormSchemas = (index: number): FormSchema[] => {
  25 + return [
  26 + {
  27 + field: FormFieldsEnum.DAY_OF_WEEK,
  28 + label: '',
  29 + component: 'InputNumber',
  30 + defaultValue: index + 1,
  31 + ifShow: false,
  32 + },
  33 + {
  34 + field: FormFieldsEnum.ENABLED,
  35 + label: '',
  36 + component: 'Checkbox',
  37 + defaultValue: false,
  38 + renderComponentContent: () => ({
  39 + default: () => `星期${WeekName[index]}`,
  40 + }),
  41 + colProps: { span: 5 },
  42 + },
  43 + {
  44 + field: FormFieldsEnum.TIME_RANGE,
  45 + label: '',
  46 + changeEvent: 'update:value',
  47 + valueField: 'value',
  48 + component: 'TimeRangePicker',
  49 + dynamicDisabled: ({ model }) => !model[FormFieldsEnum.ENABLED],
  50 + colProps: { span: 16 },
  51 + defaultValue: { startsOn: 0, endsOn: 0 },
  52 + dynamicRules: ({ model }) => {
  53 + return [
  54 + {
  55 + required: model[FormFieldsEnum.ENABLED],
  56 + validator(_rule, value: Record<'startsOn' | 'endsOn', number>, _callback) {
  57 + if (!model[FormFieldsEnum.ENABLED]) return Promise.resolve();
  58 + if (!value?.endsOn) return Promise.reject('请选择结束时间');
  59 + if (!value?.startsOn) return Promise.reject('请选择开始时间');
  60 + return Promise.resolve();
  61 + },
  62 + },
  63 + ];
  64 + },
  65 + },
  66 + ];
  67 +};
  1 +<script setup lang="ts">
  2 + import { CustomRuleItemType } from './type';
  3 + import { ComponentPublicInstance, nextTick, ref, unref, watch } from 'vue';
  4 + import { BasicForm, FormActionType } from '/@/components/Form';
  5 + import { getFormSchemas, RuleFormRecordItem } from './CustomRule.config';
  6 + import { buildUUID } from '/@/utils/uuid';
  7 + import { DefineComponentsBasicExpose } from '/#/utils';
  8 +
  9 + interface CustomRuleItemRefType {
  10 + key: string;
  11 + ref?: FormActionType;
  12 + value?: CustomRuleItemType;
  13 + }
  14 +
  15 + const props = withDefaults(
  16 + defineProps<{
  17 + value?: CustomRuleItemType[];
  18 + }>(),
  19 + {
  20 + value: () => [],
  21 + }
  22 + );
  23 +
  24 + const customRuleListRef = ref<CustomRuleItemRefType[]>(
  25 + Array.from({ length: 7 }, () => ({ key: buildUUID() }))
  26 + );
  27 +
  28 + const setRuleItemRef = (
  29 + el: Nullable<Element | ComponentPublicInstance>,
  30 + ruleItem: CustomRuleItemRefType
  31 + ) => {
  32 + ruleItem.ref = el as unknown as FormActionType;
  33 + };
  34 +
  35 + const validate = async () => {
  36 + for (const item of unref(customRuleListRef)) {
  37 + await item.ref?.validate?.();
  38 + }
  39 + };
  40 +
  41 + const getFieldsValue = (): CustomRuleItemType[] => {
  42 + return unref(customRuleListRef).map((item) => {
  43 + const { dayOfWeek, enabled, timeRange } = item.ref?.getFieldsValue() as RuleFormRecordItem;
  44 + return {
  45 + dayOfWeek,
  46 + enabled,
  47 + ...timeRange,
  48 + };
  49 + });
  50 + };
  51 +
  52 + const setFieldsValue = async (ruleList: CustomRuleItemType[]) => {
  53 + if (!ruleList.length) return;
  54 + await nextTick();
  55 +
  56 + ruleList.forEach((value, index) => {
  57 + const refEl = unref(customRuleListRef)[index];
  58 + refEl.ref?.setFieldsValue({
  59 + ...value,
  60 + timeRange: {
  61 + startsOn: value.startsOn,
  62 + endsOn: value.endsOn,
  63 + },
  64 + } as RuleFormRecordItem);
  65 + });
  66 + };
  67 +
  68 + watch(
  69 + () => props.value,
  70 + () => {
  71 + setFieldsValue(props.value);
  72 + },
  73 + { immediate: true }
  74 + );
  75 +
  76 + defineExpose<DefineComponentsBasicExpose<CustomRuleItemType[]>>({
  77 + getFieldsValue,
  78 + setFieldsValue,
  79 + validate,
  80 + });
  81 +</script>
  82 +
  83 +<template>
  84 + <section>
  85 + <template v-for="(item, index) in customRuleListRef" :key="index">
  86 + <BasicForm
  87 + :ref="(el) => setRuleItemRef(el, item)"
  88 + :show-action-button-group="false"
  89 + :schemas="getFormSchemas(index)"
  90 + class="h-14"
  91 + />
  92 + </template>
  93 + </section>
  94 +</template>
  1 +<script setup lang="ts">
  2 + import { TimePicker } from 'ant-design-vue';
  3 + import { Moment } from 'moment';
  4 + import { computed, unref } from 'vue';
  5 + import { dateUtil } from '/@/utils/dateUtil';
  6 +
  7 + interface TimeRangePickerProps {
  8 + timestampToNumber?: boolean;
  9 + value?: Record<string, number>;
  10 + startValue?: number;
  11 + endValue?: number;
  12 + startField?: string;
  13 + endField?: string;
  14 + disabled?: boolean;
  15 + }
  16 +
  17 + enum TimePickerTypeEnum {
  18 + START = 'START',
  19 + END = 'END',
  20 + }
  21 +
  22 + const emit = defineEmits(['update:value', 'update:startValue', 'update:endValue', 'change']);
  23 +
  24 + const props = withDefaults(defineProps<TimeRangePickerProps>(), {
  25 + value: () => ({}),
  26 + startField: 'startsOn',
  27 + endField: 'endsOn',
  28 + });
  29 +
  30 + const getTimeMoment = (value: number) => {
  31 + value = value / 1000;
  32 + const hour = Math.floor(value / 60 / 60);
  33 + const minute = Math.floor((value - hour * 60 * 60) / 60);
  34 + const second = value - hour * 60 * 60 - minute * 60;
  35 +
  36 + return dateUtil({ hour, minute, second });
  37 + };
  38 +
  39 + const getRangeValue = computed(() => {
  40 + const { value, startValue, endValue, startField, endField } = props;
  41 +
  42 + let startTs = startValue || value[startField];
  43 +
  44 + let endTs = endValue || value[endField];
  45 +
  46 + return {
  47 + startTs: startTs ? getTimeMoment(startTs) : null,
  48 + endTs: endTs ? getTimeMoment(endTs) : null,
  49 + };
  50 + });
  51 +
  52 + const getTimestamp = (moment: Moment | null) => {
  53 + if (!moment) return null;
  54 + const { hours, minutes, seconds } = moment.toObject();
  55 + return (hours * 60 * 60 + minutes * 60 + seconds) * 1000;
  56 + };
  57 +
  58 + const handleTimeChange = (time: Moment, type: TimePickerTypeEnum) => {
  59 + const { value, startField, endField } = props;
  60 + const { startTs, endTs } = unref(getRangeValue);
  61 +
  62 + const rangeValue = {
  63 + [startField]: getTimestamp(startTs),
  64 + [endField]: getTimestamp(endTs),
  65 + };
  66 +
  67 + Reflect.set(
  68 + rangeValue,
  69 + type === TimePickerTypeEnum.START ? startField : endField,
  70 + getTimestamp(time)
  71 + );
  72 +
  73 + if (
  74 + rangeValue[startField] &&
  75 + rangeValue[endField] &&
  76 + rangeValue[startField]! > rangeValue[endField]!
  77 + ) {
  78 + Reflect.set(rangeValue, type === TimePickerTypeEnum.START ? startField : endField, null);
  79 + }
  80 +
  81 + emit('update:startValue', rangeValue[startField]);
  82 + emit('update:endValue', rangeValue[endField]);
  83 + emit('update:value', rangeValue);
  84 + emit('change', value);
  85 + };
  86 +
  87 + const handleBasicDisabledHours = (type: TimePickerTypeEnum) => {
  88 + return () => {
  89 + const { startTs, endTs } = unref(getRangeValue);
  90 +
  91 + if (type === TimePickerTypeEnum.START && !endTs) return [];
  92 + if (type === TimePickerTypeEnum.END && !startTs) return [];
  93 +
  94 + const selectedHour = type === TimePickerTypeEnum.END ? startTs!.hour() : endTs!.hour();
  95 +
  96 + return type === TimePickerTypeEnum.START
  97 + ? Array.from({ length: 24 - selectedHour }, (_, index) => selectedHour + index + 1)
  98 + : Array.from({ length: selectedHour }, (_, index) => index);
  99 + };
  100 + };
  101 +
  102 + const handleBasicDisabledMinutes = (type: TimePickerTypeEnum) => {
  103 + return (currentSelectedHour: number) => {
  104 + const { startTs, endTs } = unref(getRangeValue);
  105 +
  106 + if (type === TimePickerTypeEnum.START && !endTs) return [];
  107 + if (type === TimePickerTypeEnum.END && !startTs) return [];
  108 +
  109 + const selectedTime = type === TimePickerTypeEnum.END ? startTs : endTs;
  110 + const selectedHour = selectedTime!.hour();
  111 + const selectedMinute = selectedTime!.minute();
  112 +
  113 + if (type == TimePickerTypeEnum.START) {
  114 + return selectedHour > currentSelectedHour
  115 + ? []
  116 + : Array.from({ length: 60 - selectedMinute }, (_, index) => selectedMinute + index + 1);
  117 + } else {
  118 + return selectedHour < currentSelectedHour
  119 + ? []
  120 + : Array.from({ length: selectedMinute }, (_, index) => index);
  121 + }
  122 + };
  123 + };
  124 +
  125 + const handleBasicDisabledSeconds = (type: TimePickerTypeEnum) => {
  126 + return (currentSelectedHour: number, currentSelectedMinute: number) => {
  127 + const { startTs, endTs } = unref(getRangeValue);
  128 +
  129 + if (type === TimePickerTypeEnum.START && !endTs) return [];
  130 + if (type === TimePickerTypeEnum.END && !startTs) return [];
  131 +
  132 + const selectedTime = type === TimePickerTypeEnum.END ? startTs : endTs;
  133 + const selectedHour = selectedTime!.hour();
  134 + const selectedMinute = selectedTime!.minute();
  135 + const selectedSeconds = selectedTime!.seconds();
  136 +
  137 + if (type === TimePickerTypeEnum.START) {
  138 + return selectedHour >= currentSelectedHour && selectedMinute > currentSelectedMinute
  139 + ? []
  140 + : Array.from({ length: 60 - selectedSeconds }, (_, index) => selectedSeconds + index + 1);
  141 + } else {
  142 + return selectedHour <= currentSelectedHour && selectedMinute < currentSelectedMinute
  143 + ? []
  144 + : Array.from({ length: selectedSeconds }, (_, index) => index);
  145 + }
  146 + };
  147 + };
  148 +</script>
  149 +
  150 +<template>
  151 + <main class="time-range-picker flex items-center">
  152 + <TimePicker
  153 + :value="getRangeValue.startTs"
  154 + format="HH:mm"
  155 + :disabled="disabled"
  156 + @change="(time) => handleTimeChange(time, TimePickerTypeEnum.START)"
  157 + :disabled-hours="handleBasicDisabledHours(TimePickerTypeEnum.START)"
  158 + :disabled-minutes="handleBasicDisabledMinutes(TimePickerTypeEnum.START)"
  159 + :disabled-seconds="handleBasicDisabledSeconds(TimePickerTypeEnum.START)"
  160 + />
  161 + <slot v-if="$slots?.separator" name="separator"></slot>
  162 + <span v-else class="mx-4">~</span>
  163 + <TimePicker
  164 + :value="getRangeValue.endTs"
  165 + format="HH:mm"
  166 + :disabled="disabled"
  167 + @change="(time) => handleTimeChange(time, TimePickerTypeEnum.END)"
  168 + :disabled-hours="handleBasicDisabledHours(TimePickerTypeEnum.END)"
  169 + :disabled-minutes="handleBasicDisabledMinutes(TimePickerTypeEnum.END)"
  170 + :disabled-seconds="handleBasicDisabledSeconds(TimePickerTypeEnum.END)"
  171 + />
  172 + </main>
  173 +</template>
  174 +
  175 +<style scoped lang="less">
  176 + .time-range-picker {
  177 + :deep(.ant-time-picker) {
  178 + width: auto;
  179 + }
  180 + }
  181 +</style>
  1 +import TimeRangePicker from './TimeRangePicker.vue';
  2 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  3 +import { ScheduleTypeEnum, ScheduleTypeNameEnum } from '/@/enums/linkedgeEnum';
  4 +
  5 +export enum FormFieldsEnum {
  6 + SCHEDULE_TYPE = 'type',
  7 + TIMEZONE = 'timezone',
  8 + STARTS_ON = 'startsOn',
  9 + ENDS_ON = 'endsOn',
  10 + DAYS_OF_WEEK = 'daysOfWeek',
  11 + SCHEDULE_ITEMS = 'items',
  12 + TIME_RANGE = 'timeRange',
  13 +}
  14 +
  15 +export enum FormFieldsNameEnum {
  16 + SCHEDULE_TYPE = '启用类型',
  17 + TIMEZONE = '时区',
  18 + DAYS_OF_WEEK = '启用日',
  19 + TIME_RANGE = '启用时间',
  20 +}
  21 +
  22 +export interface EnableRuleFormType {
  23 + [FormFieldsEnum.DAYS_OF_WEEK]: number[];
  24 + [FormFieldsEnum.SCHEDULE_TYPE]: ScheduleTypeEnum;
  25 + [FormFieldsEnum.TIMEZONE]: string;
  26 + [FormFieldsEnum.TIME_RANGE]: {
  27 + [FormFieldsEnum.STARTS_ON]: number;
  28 + [FormFieldsEnum.ENDS_ON]: number;
  29 + };
  30 +}
  31 +
  32 +useComponentRegister('TimeRangePicker', TimeRangePicker);
  33 +
  34 +export const WeekName = ['一', '二', '三', '四', '五', '六', '日'];
  35 +
  36 +enum TimezoneEnum {
  37 + SHANG_HAI = 'Asia/Shanghai',
  38 +}
  39 +
  40 +export const getFormSchemas = (): FormSchema[] => {
  41 + return [
  42 + {
  43 + field: FormFieldsEnum.SCHEDULE_TYPE,
  44 + label: FormFieldsNameEnum.SCHEDULE_TYPE,
  45 + component: 'Select',
  46 + required: true,
  47 + defaultValue: ScheduleTypeEnum.ANY_TIME,
  48 + componentProps: () => {
  49 + return {
  50 + options: Object.keys(ScheduleTypeEnum).map((value) => ({
  51 + label: ScheduleTypeNameEnum[value],
  52 + value,
  53 + })),
  54 + allowClear: false,
  55 + };
  56 + },
  57 + },
  58 + {
  59 + field: FormFieldsEnum.TIMEZONE,
  60 + label: FormFieldsNameEnum.TIMEZONE,
  61 + component: 'Select',
  62 + required: true,
  63 + defaultValue: TimezoneEnum.SHANG_HAI,
  64 + ifShow: ({ model }) => model[FormFieldsEnum.SCHEDULE_TYPE] !== ScheduleTypeEnum.ANY_TIME,
  65 + componentProps: () => {
  66 + return {
  67 + options: [{ label: TimezoneEnum.SHANG_HAI, value: TimezoneEnum.SHANG_HAI }],
  68 + allowClear: false,
  69 + };
  70 + },
  71 + },
  72 + {
  73 + field: FormFieldsEnum.SCHEDULE_ITEMS,
  74 + label: '规则',
  75 + component: 'Input',
  76 + ifShow: ({ model }) => model[FormFieldsEnum.SCHEDULE_TYPE] === ScheduleTypeEnum.CUSTOM,
  77 + slot: 'custom',
  78 + },
  79 + {
  80 + field: FormFieldsEnum.DAYS_OF_WEEK,
  81 + label: FormFieldsNameEnum.DAYS_OF_WEEK,
  82 + component: 'CheckboxGroup',
  83 + ifShow: ({ model }) => model[FormFieldsEnum.SCHEDULE_TYPE] === ScheduleTypeEnum.SPECIFIC_TIME,
  84 + rules: [{ required: true, type: 'array' }],
  85 + componentProps: () => {
  86 + return {
  87 + options: Array.from({ length: 7 }, (_, index) => ({
  88 + label: `星期${WeekName[index]}`,
  89 + value: index + 1,
  90 + })),
  91 + };
  92 + },
  93 + },
  94 + {
  95 + field: FormFieldsEnum.TIME_RANGE,
  96 + label: FormFieldsNameEnum.TIME_RANGE,
  97 + component: 'TimeRangePicker',
  98 + changeEvent: 'update:value',
  99 + valueField: 'value',
  100 + ifShow: ({ model }) => model[FormFieldsEnum.SCHEDULE_TYPE] === ScheduleTypeEnum.SPECIFIC_TIME,
  101 + rules: [
  102 + {
  103 + required: true,
  104 + validator(_rule, value: Record<'startsOn' | 'endsOn', number>, _callback) {
  105 + if (!value?.endsOn) return Promise.reject('请选择结束时间');
  106 + if (!value?.startsOn) return Promise.reject('请选择开始时间');
  107 + return Promise.resolve();
  108 + },
  109 + },
  110 + ],
  111 + },
  112 + ];
  113 +};
  1 +export { default as EnablingRule } from './index.vue';
  1 +<script setup lang="ts">
  2 + import { BasicModal, useModalInner } from '/@/components/Modal';
  3 + import { BasicForm, useForm } from '/@/components/Form';
  4 + import { EnableRuleFormType, getFormSchemas } from './config';
  5 + import { nextTick, ref, unref } from 'vue';
  6 + import CustomRule from './CustomRule.vue';
  7 + import { ScheduleTypeEnum } from '/@/enums/linkedgeEnum';
  8 + import { EnableRuleFormModalParamsType, ScheduleType } from './type';
  9 + import { useSceneLinkageDrawerContext } from '../SceneLinkageDrawer/sceneLinkageDrawerContext';
  10 +
  11 + const { disabledDrawer } = useSceneLinkageDrawerContext();
  12 +
  13 + const emit = defineEmits(['register', 'settingComplete']);
  14 +
  15 + const scheduleItemKey = ref<string>();
  16 + const [registerModal, { closeModal }] = useModalInner(
  17 + ({ record }: EnableRuleFormModalParamsType) => {
  18 + resetFields();
  19 + const { type, schedule, key } = record;
  20 + scheduleItemKey.value = key;
  21 + handleSetFieldsValue({ ...schedule, type });
  22 + clearValidate();
  23 + }
  24 + );
  25 +
  26 + const [register, { getFieldsValue, validate, setFieldsValue, resetFields, clearValidate }] =
  27 + useForm({
  28 + schemas: getFormSchemas(),
  29 + showActionButtonGroup: false,
  30 + layout: 'vertical',
  31 + });
  32 +
  33 + const customRuleElRef = ref<Nullable<InstanceType<typeof CustomRule>>>();
  34 +
  35 + const handleGetFieldsValue = async (): Promise<ScheduleType> => {
  36 + await unref(customRuleElRef)?.validate?.();
  37 + await validate();
  38 + const { type, timeRange, timezone, daysOfWeek } = getFieldsValue() as EnableRuleFormType;
  39 + const customValue = unref(customRuleElRef)?.getFieldsValue();
  40 +
  41 + if (type === ScheduleTypeEnum.ANY_TIME) return { type };
  42 +
  43 + if (type === ScheduleTypeEnum.SPECIFIC_TIME)
  44 + return {
  45 + type,
  46 + timezone,
  47 + daysOfWeek,
  48 + ...timeRange,
  49 + };
  50 +
  51 + return {
  52 + type,
  53 + timezone,
  54 + items: customValue,
  55 + };
  56 + };
  57 +
  58 + const handleSetFieldsValue = async (schedule: ScheduleType) => {
  59 + const { type, startsOn, endsOn, timezone, daysOfWeek } = schedule;
  60 +
  61 + if (type === ScheduleTypeEnum.ANY_TIME) return setFieldsValue({ type });
  62 +
  63 + if (type === ScheduleTypeEnum.SPECIFIC_TIME)
  64 + return setFieldsValue({ type, timezone, daysOfWeek, timeRange: { startsOn, endsOn } });
  65 +
  66 + setFieldsValue(schedule);
  67 + await nextTick();
  68 + unref(customRuleElRef)?.setFieldsValue(schedule.items || []);
  69 + };
  70 +
  71 + const handleOk = async () => {
  72 + const result = await handleGetFieldsValue();
  73 + emit('settingComplete', result, unref(scheduleItemKey));
  74 + closeModal();
  75 + };
  76 +</script>
  77 +
  78 +<template>
  79 + <BasicModal
  80 + @register="registerModal"
  81 + title="启用规则"
  82 + :width="600"
  83 + @ok="handleOk"
  84 + :cancel-text="disabledDrawer ? '关闭' : '取消'"
  85 + :show-ok-btn="!disabledDrawer"
  86 + >
  87 + <BasicForm @register="register" :disabled="disabledDrawer">
  88 + <template #custom="{ model, field }">
  89 + <CustomRule ref="customRuleElRef" v-model:value="model[field]" />
  90 + </template>
  91 + </BasicForm>
  92 + </BasicModal>
  93 +</template>
  1 +import { ScheduleTypeEnum } from '/@/enums/linkedgeEnum';
  2 +
  3 +export interface CustomRuleItemType {
  4 + dayOfWeek: number;
  5 + enabled: boolean;
  6 + endsOn: number;
  7 + startsOn: number;
  8 +}
  9 +
  10 +export interface ScheduleType {
  11 + type: ScheduleTypeEnum;
  12 + timezone?: string;
  13 + items?: CustomRuleItemType[];
  14 + daysOfWeek?: number[];
  15 + endsOn?: number;
  16 + startsOn?: number;
  17 +}
  18 +
  19 +interface EnableRuleFormModalParams {
  20 + type: ScheduleTypeEnum;
  21 + schedule: ScheduleType;
  22 + key: string;
  23 +}
  24 +
  25 +export type EnableRuleFormModalParamsType = ModalParamsType<EnableRuleFormModalParams>;
  1 +<script setup lang="ts">
  2 + import { ApiSelect } from '/@/components/Form';
  3 + import { Divider } from 'ant-design-vue';
  4 + import { Icon } from '/@/components/Icon';
  5 + import { VNode } from 'vue';
  6 + import AlarmConfigDrawer from '/@/views/alarm/config/ContactDrawer.vue';
  7 + import { useDrawer } from '/@/components/Drawer';
  8 +
  9 + const [registerAlarmContactDrawer, { openDrawer }] = useDrawer();
  10 +
  11 + const Options = (props: { menuNode: VNode }) => {
  12 + return props.menuNode;
  13 + };
  14 +
  15 + const handleOpenAlarmConfig = () => {
  16 + openDrawer();
  17 + };
  18 +
  19 + const handleSuccess = () => {};
  20 +</script>
  21 +
  22 +<template>
  23 + <section>
  24 + <ApiSelect v-bind="$attrs">
  25 + <template #dropdownRender="{ menuNode }">
  26 + <Options :menuNode="menuNode" />
  27 + <Divider class="!my-2" />
  28 + <div class="cursor-pointer text-gray-500" @click="handleOpenAlarmConfig">
  29 + <Icon icon="ant-design:plus-outlined" class="mx-2" />
  30 + <span>新增告警配置</span>
  31 + </div>
  32 + </template>
  33 + </ApiSelect>
  34 + <AlarmConfigDrawer
  35 + @register="registerAlarmContactDrawer"
  36 + default-enable
  37 + @success="handleSuccess"
  38 + />
  39 + </section>
  40 +</template>
  1 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  2 +import {
  3 + CommandTypeEnum,
  4 + CommandTypeNameEnum,
  5 + ExecutionActionEnum,
  6 + ExecutionActionNameEnum,
  7 + TriggerEntityTypeEnum,
  8 + TriggerEntityTypeNameEnum,
  9 +} from '/@/enums/linkedgeEnum';
  10 +import AlarmProfileSelect from './AlarmProfileSelect.vue';
  11 +import {
  12 + byOrganizationIdGetMasterDevice,
  13 + getOrganizationAlarmConfig,
  14 +} from '/@/api/ruleengine/ruleengineApi';
  15 +import { useSceneLinkageDrawerContext } from '../SceneLinkageDrawer/sceneLinkageDrawerContext';
  16 +import { Ref, h, toRaw, unref } from 'vue';
  17 +import { AlarmLevelEnum, AlarmLevelNameEnum } from '/@/enums/alarmEnum';
  18 +import Icon from '/@/components/Icon';
  19 +import { Tooltip } from 'ant-design-vue';
  20 +import { findDictItemByCode } from '/@/api/system/dict';
  21 +import { DictEnum } from '/@/enums/dictEnum';
  22 +import { getDeviceProfile } from '/@/api/alarm/position';
  23 +import { createPickerSearch } from '/@/utils/pickerSearch';
  24 +import { ServiceCallTypeEnum, ServiceCallTypeNameEnum } from '/@/enums/toolEnum';
  25 +import { DeviceTypeEnum } from '../../../dataFlow/cpns/config';
  26 +import { getModelServices } from '/@/api/device/modelOfMatter';
  27 +import { ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  28 +import { DeviceProfileModel } from '/@/api/device/model/deviceModel';
  29 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  30 +import { JSONEditor } from '/@/components/CodeEditor';
  31 +import { useJsonParse } from '/@/hooks/business/useJsonParse';
  32 +import { validateTCPCustomCommand } from '/@/components/Form/src/externalCompns/components/ThingsModelForm';
  33 +
  34 +export enum FormFieldsEnum {
  35 + OUT_TARGET = 'outTarget',
  36 + ALARM_PROFILED = 'alarmProfileId',
  37 + ALARM_LEVEL = 'alarmLevel',
  38 + DEVICE_TYPE = 'deviceType',
  39 + DEVICE_PROFILE_ID = 'deviceProfileId',
  40 + ENTITY_TYPE = 'entityType',
  41 + ENTITY_ID = 'entityId',
  42 + COMMAND_TYPE = 'commandType',
  43 + CALL_TYPE = 'callType',
  44 + CALL_SERVICE_IDENTIFIER = 'callServiceIdentifier',
  45 +
  46 + TCP_CUSTOM_COMMAND = 'tcpCustomCommand',
  47 + CUSTOM_COMMAND = 'customCommand',
  48 + CALL_SERVICE = 'callService',
  49 + SERVICE_COMMAND = 'serviceCommand',
  50 + SERVICE_ID = 'serviceId',
  51 + TRANSPORT_TYPE = 'transportType',
  52 + ENABLE_CLEAR_RULE = 'enableClearRule',
  53 + CLEAR_RULE = 'clearRule',
  54 +}
  55 +
  56 +export enum FormFieldsNameEnum {
  57 + OUT_TARGET = '执行动作',
  58 + ALARM_PROFILED = '告警配置',
  59 + ALARM_LEVEL = '告警等级',
  60 + DEVICE_TYPE = '设备类型',
  61 + DEVICE_PROFILE_ID = '产品',
  62 + ENTITY_TYPE = '触发设备类型',
  63 + ENTITY_ID = '设备',
  64 + COMMAND_TYPE = '类型',
  65 + CALL_TYPE = '命令下发类型',
  66 + CALL_SERVICE_IDENTIFIER = '服务',
  67 +
  68 + ENABLE_CLEAR_RULE = '清除告警',
  69 + TCP_CUSTOM_COMMAND = '自定义命令',
  70 + CUSTOM_COMMAND = '自定义命令',
  71 +}
  72 +
  73 +useComponentRegister('AlarmProfileSelect', AlarmProfileSelect);
  74 +useComponentRegister('JSONEditor', JSONEditor);
  75 +
  76 +export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => {
  77 + const { organizationId } = useSceneLinkageDrawerContext();
  78 + return [
  79 + {
  80 + field: FormFieldsEnum.OUT_TARGET,
  81 + label: '',
  82 + component: 'Select',
  83 + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.OUT_TARGET}` }],
  84 + defaultValue: ExecutionActionEnum.DEVICE_OUT,
  85 + componentProps: () => {
  86 + return {
  87 + options: [
  88 + { label: ExecutionActionNameEnum.DEVICE_OUT, value: ExecutionActionEnum.DEVICE_OUT },
  89 + {
  90 + label: ExecutionActionNameEnum.MSG_NOTIFY,
  91 + value: ExecutionActionEnum.MSG_NOTIFY,
  92 + disabled: unref(hasAlarmNotify),
  93 + },
  94 + ],
  95 + labelField: 'name',
  96 + valueField: 'id',
  97 + placeholder: `请选择${FormFieldsNameEnum.OUT_TARGET}`,
  98 + onChange(value: ExecutionActionEnum) {
  99 + hasAlarmNotify.value = value === ExecutionActionEnum.MSG_NOTIFY;
  100 + },
  101 + };
  102 + },
  103 + },
  104 + {
  105 + field: FormFieldsEnum.ALARM_PROFILED,
  106 + label: '',
  107 + component: 'AlarmProfileSelect',
  108 + rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }],
  109 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY,
  110 + componentProps: () => {
  111 + return {
  112 + api: async () => {
  113 + if (!unref(organizationId)) return [];
  114 + return await getOrganizationAlarmConfig({ organizationId: unref(organizationId) });
  115 + },
  116 + labelField: 'name',
  117 + valueField: 'id',
  118 + placeholder: `请选择${FormFieldsNameEnum.ALARM_PROFILED}`,
  119 + };
  120 + },
  121 + },
  122 + {
  123 + field: FormFieldsEnum.ALARM_LEVEL,
  124 + label: '',
  125 + component: 'Select',
  126 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY,
  127 + rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_LEVEL}` }],
  128 + componentProps: () => {
  129 + return {
  130 + options: Object.keys(AlarmLevelEnum).map((value) => ({
  131 + label: AlarmLevelNameEnum[value],
  132 + value,
  133 + })),
  134 + placeholder: `请选择${FormFieldsNameEnum.ALARM_LEVEL}`,
  135 + };
  136 + },
  137 + },
  138 + {
  139 + field: FormFieldsEnum.ENABLE_CLEAR_RULE,
  140 + label: '',
  141 + component: 'Checkbox',
  142 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY,
  143 + renderComponentContent: () => ({
  144 + default: () => [
  145 + h('span', FormFieldsNameEnum.ENABLE_CLEAR_RULE),
  146 + h(Tooltip, { title: '清除告警与触发器一一对应' }, () =>
  147 + h(Icon, {
  148 + icon: 'ant-design:question-circle-outlined',
  149 + class: ' cursor-pointer ml-1',
  150 + })
  151 + ),
  152 + ],
  153 + }),
  154 + },
  155 + {
  156 + field: FormFieldsEnum.CLEAR_RULE,
  157 + label: '',
  158 + component: 'Input',
  159 + colProps: { span: 24, xxl: 24, xl: 24, lg: 24, md: 24, sm: 24, xs: 24 },
  160 + slot: 'alarmClearRule',
  161 + ifShow: ({ model }) =>
  162 + model[FormFieldsEnum.ENABLE_CLEAR_RULE] &&
  163 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY,
  164 + },
  165 + {
  166 + field: FormFieldsEnum.DEVICE_TYPE,
  167 + label: '',
  168 + component: 'ApiSelect',
  169 + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.DEVICE_TYPE}` }],
  170 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT,
  171 + defaultValue: DeviceTypeEnum.SENSOR,
  172 + componentProps: ({ formActionType }) => {
  173 + return {
  174 + api: findDictItemByCode,
  175 + params: {
  176 + dictCode: DictEnum.DEVICE_TYPE,
  177 + },
  178 + labelField: 'itemText',
  179 + valueField: 'itemValue',
  180 + placeholder: `请选择${FormFieldsNameEnum.DEVICE_TYPE}`,
  181 + onChange() {
  182 + const { setFieldsValue } = formActionType;
  183 + setFieldsValue({
  184 + [FormFieldsEnum.DEVICE_PROFILE_ID]: null,
  185 + [FormFieldsEnum.ENTITY_ID]: [],
  186 + [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: null,
  187 + [FormFieldsEnum.SERVICE_COMMAND]: null,
  188 + [FormFieldsEnum.CALL_SERVICE]: null,
  189 + [FormFieldsEnum.TRANSPORT_TYPE]: null,
  190 + });
  191 + },
  192 + };
  193 + },
  194 + },
  195 + {
  196 + field: FormFieldsEnum.TRANSPORT_TYPE,
  197 + label: '传输协议',
  198 + component: 'Input',
  199 + ifShow: false,
  200 + },
  201 + {
  202 + field: FormFieldsEnum.DEVICE_PROFILE_ID,
  203 + label: '',
  204 + component: 'ApiSelect',
  205 + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.DEVICE_PROFILE_ID}` }],
  206 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT,
  207 + componentProps: ({ formModel, formActionType }) => {
  208 + const deviceType = formModel[FormFieldsEnum.DEVICE_TYPE];
  209 + const { setFieldsValue } = formActionType;
  210 + return {
  211 + api: async () => {
  212 + return await getDeviceProfile(deviceType);
  213 + },
  214 + labelField: 'name',
  215 + valueField: 'id',
  216 + placeholder: `请选择${FormFieldsNameEnum.DEVICE_PROFILE_ID}`,
  217 + ...createPickerSearch(),
  218 + onChange(_value: string, option: DeviceProfileModel) {
  219 + setFieldsValue({
  220 + [FormFieldsEnum.ENTITY_ID]: [],
  221 + [FormFieldsEnum.TRANSPORT_TYPE]: option?.transportType,
  222 + [FormFieldsEnum.SERVICE_ID]: null,
  223 + [FormFieldsEnum.CALL_SERVICE]: null,
  224 + [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: null,
  225 + [FormFieldsEnum.SERVICE_COMMAND]: null,
  226 + });
  227 + },
  228 + onOptionsChange(options: (DeviceProfileModel & Record<'label' | 'value', string>)[]) {
  229 + const deviceProfileId = formModel[FormFieldsEnum.DEVICE_PROFILE_ID];
  230 + const res = options.find((item) => item.value === deviceProfileId);
  231 + res &&
  232 + setFieldsValue({
  233 + [FormFieldsEnum.TRANSPORT_TYPE]: res.transportType,
  234 + });
  235 + },
  236 + };
  237 + },
  238 + },
  239 + {
  240 + field: FormFieldsEnum.ENTITY_TYPE,
  241 + label: '',
  242 + component: 'Select',
  243 + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.ENTITY_TYPE}` }],
  244 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT,
  245 + componentProps: () => {
  246 + return {
  247 + options: Object.keys(TriggerEntityTypeEnum).map((value) => ({
  248 + label: TriggerEntityTypeNameEnum[value],
  249 + value,
  250 + })),
  251 + placeholder: `请选择${FormFieldsNameEnum.ENTITY_TYPE}`,
  252 + };
  253 + },
  254 + },
  255 + {
  256 + field: FormFieldsEnum.ENTITY_ID,
  257 + label: '',
  258 + component: 'ApiSelect',
  259 + rules: [{ required: true, type: 'array', message: `请选择${FormFieldsNameEnum.ENTITY_ID}` }],
  260 + ifShow: ({ model }) =>
  261 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT &&
  262 + model[FormFieldsEnum.ENTITY_TYPE] === TriggerEntityTypeEnum.PART,
  263 + componentProps: ({ formModel }) => {
  264 + return {
  265 + api: async (params: Record<'organizationId' | 'deviceProfileId', string>) => {
  266 + if (params.deviceProfileId && params.organizationId) {
  267 + const result = await byOrganizationIdGetMasterDevice(params);
  268 + if (result) {
  269 + return result.map((item) => ({
  270 + ...item,
  271 + label: item.alias || item.name,
  272 + value: item.tbDeviceId,
  273 + }));
  274 + }
  275 + }
  276 + return [];
  277 + },
  278 + mode: 'multiple',
  279 + maxTagCount: 3,
  280 + params: {
  281 + organizationId: unref(organizationId),
  282 + deviceProfileId: formModel[FormFieldsEnum.DEVICE_PROFILE_ID],
  283 + },
  284 + placeholder: `请选择${FormFieldsNameEnum.ENTITY_ID}`,
  285 + ...createPickerSearch(),
  286 + };
  287 + },
  288 + },
  289 + {
  290 + field: FormFieldsEnum.COMMAND_TYPE,
  291 + label: '',
  292 + component: 'Select',
  293 + rules: [
  294 + { required: true, message: `请选择${FormFieldsNameEnum.COMMAND_TYPE}`, type: 'number' },
  295 + ],
  296 + defaultValue: CommandTypeEnum.CUSTOM,
  297 + ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT,
  298 + componentProps: ({ formActionType }) => {
  299 + return {
  300 + options: [
  301 + { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM },
  302 + { label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE },
  303 + ],
  304 + placeholder: `请选择${FormFieldsNameEnum.COMMAND_TYPE}`,
  305 + onChange() {
  306 + const { setFieldsValue } = formActionType;
  307 + setFieldsValue({
  308 + [FormFieldsEnum.CUSTOM_COMMAND]: null,
  309 + [FormFieldsEnum.TCP_CUSTOM_COMMAND]: null,
  310 + [FormFieldsEnum.CALL_SERVICE]: null,
  311 + [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: null,
  312 + [FormFieldsEnum.SERVICE_COMMAND]: null,
  313 + });
  314 + },
  315 + };
  316 + },
  317 + },
  318 + {
  319 + field: FormFieldsEnum.CALL_TYPE,
  320 + label: '',
  321 + component: 'Select',
  322 + defaultValue: ServiceCallTypeEnum.ASYNC,
  323 + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.CALL_TYPE}` }],
  324 + ifShow: ({ model }) =>
  325 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT &&
  326 + model[FormFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
  327 + componentProps: () => {
  328 + return {
  329 + options: [
  330 + { label: ServiceCallTypeNameEnum.ASYNC, value: ServiceCallTypeEnum.ASYNC },
  331 + { label: ServiceCallTypeNameEnum.SYNC, value: ServiceCallTypeEnum.SYNC },
  332 + ],
  333 + placeholder: `请选择${FormFieldsNameEnum.CALL_TYPE}`,
  334 + };
  335 + },
  336 + },
  337 + {
  338 + field: FormFieldsEnum.SERVICE_ID,
  339 + label: '',
  340 + component: 'ApiSelect',
  341 + rules: [{ required: true, message: `请选择${FormFieldsNameEnum.CALL_SERVICE_IDENTIFIER}` }],
  342 + ifShow: ({ model }) =>
  343 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT &&
  344 + model[FormFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.SERVICE,
  345 + componentProps: ({ formModel, formActionType }) => {
  346 + return {
  347 + api: async () => {
  348 + const deviceProfileId = formModel[FormFieldsEnum.DEVICE_PROFILE_ID];
  349 + if (!deviceProfileId) return [];
  350 + return await getModelServices({ deviceProfileId });
  351 + },
  352 + labelField: 'functionName',
  353 + valueField: 'id',
  354 + placeholder: `请选择${FormFieldsNameEnum.CALL_SERVICE_IDENTIFIER}`,
  355 + onChange(
  356 + value: string,
  357 + option: ModelOfMatterParams & Record<'label' | 'value' | 'id', string>
  358 + ) {
  359 + const { setFieldsValue } = formActionType;
  360 + setFieldsValue({
  361 + [FormFieldsEnum.CALL_TYPE]: value ? option.callType : null,
  362 + [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: value ? option.identifier : null,
  363 + [FormFieldsEnum.CALL_SERVICE]: value
  364 + ? { ...toRaw(option), id: option?.value, functionName: option?.label }
  365 + : null,
  366 + [FormFieldsEnum.SERVICE_COMMAND]: {},
  367 + });
  368 + },
  369 + onOptionsChange(options: (StructJSON & Record<'label' | 'value' | 'id', string>)[]) {
  370 + const { setFieldsValue } = formActionType;
  371 + const serviceId = formModel[FormFieldsEnum.SERVICE_ID];
  372 + const res = options.find((item) => item.value === serviceId);
  373 + res &&
  374 + setFieldsValue({
  375 + [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: res.identifier,
  376 + [FormFieldsEnum.CALL_SERVICE]: {
  377 + ...toRaw(res),
  378 + id: res?.value,
  379 + functionName: res?.label,
  380 + },
  381 + });
  382 + },
  383 + };
  384 + },
  385 + },
  386 + {
  387 + field: FormFieldsEnum.CALL_SERVICE_IDENTIFIER,
  388 + label: '服务标识符',
  389 + component: 'Input',
  390 + ifShow: false,
  391 + },
  392 + {
  393 + field: FormFieldsEnum.CALL_SERVICE,
  394 + label: '服务详情',
  395 + component: 'Input',
  396 + ifShow: false,
  397 + },
  398 + {
  399 + field: FormFieldsEnum.SERVICE_COMMAND,
  400 + label: '',
  401 + component: 'Input',
  402 + colProps: { span: 24, xxl: 24, xl: 24, lg: 24, md: 24, sm: 24, xs: 24 },
  403 + ifShow: ({ model }) =>
  404 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT &&
  405 + model[FormFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.SERVICE &&
  406 + model[FormFieldsEnum.CALL_SERVICE],
  407 + colSlot: 'serviceCommand',
  408 + },
  409 + {
  410 + field: FormFieldsEnum.CUSTOM_COMMAND,
  411 + label: '',
  412 + component: 'JSONEditor',
  413 + valueField: 'value',
  414 + changeEvent: 'update:value',
  415 + colProps: { span: 24, xxl: 24, xl: 24, lg: 24, md: 24, sm: 24, xs: 24 },
  416 + rules: [
  417 + {
  418 + required: true,
  419 + validator(_rule, value: string) {
  420 + const { flag } = useJsonParse(value);
  421 + return flag ? Promise.resolve() : Promise.reject('请检查自定义命令');
  422 + },
  423 + },
  424 + ],
  425 + ifShow: ({ model }) =>
  426 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT &&
  427 + model[FormFieldsEnum.TRANSPORT_TYPE] !== TransportTypeEnum.TCP &&
  428 + model[FormFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
  429 + componentProps: () => {
  430 + return {
  431 + title: FormFieldsNameEnum.CUSTOM_COMMAND,
  432 + };
  433 + },
  434 + },
  435 + {
  436 + field: FormFieldsEnum.TCP_CUSTOM_COMMAND,
  437 + label: FormFieldsNameEnum.TCP_CUSTOM_COMMAND,
  438 + component: 'Input',
  439 + colProps: { span: 24, xxl: 24, xl: 24, lg: 24, md: 24, sm: 24, xs: 24 },
  440 + rules: [
  441 + { required: true, message: `请输入${FormFieldsNameEnum.TCP_CUSTOM_COMMAND}` },
  442 + { validator: validateTCPCustomCommand },
  443 + ],
  444 + ifShow: ({ model }) =>
  445 + model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT &&
  446 + model[FormFieldsEnum.TRANSPORT_TYPE] === TransportTypeEnum.TCP &&
  447 + model[FormFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
  448 + componentProps: () => {
  449 + return {
  450 + placeholder: `请输入${FormFieldsNameEnum.TCP_CUSTOM_COMMAND}`,
  451 + };
  452 + },
  453 + // labelWidth: 120,
  454 + },
  455 + ];
  456 +};
  1 +import { ExecutionActionListRefItemType } from './type';
  2 +import { buildUUID } from '/@/utils/uuid';
  3 +
  4 +export { default as ExecutionAction } from './index.vue';
  5 +
  6 +export function createNewExecutionActionItem(): ExecutionActionListRefItemType {
  7 + return { key: buildUUID() };
  8 +}
  1 +<script setup lang="ts">
  2 + import { CollapseContainer } from '/@/components/Container';
  3 + import { Icon } from '/@/components/Icon';
  4 + import { Tooltip, Button } from 'ant-design-vue';
  5 + import { BasicForm, FormActionType, ThingsModelForm } from '/@/components/Form';
  6 + import { getFormSchemas, FormFieldsEnum } from './config';
  7 + import { FlipFlop } from '../FlipFlop';
  8 + import { ComponentPublicInstance, ref, unref } from 'vue';
  9 + import { ExecutionActionListRefItemType } from './type';
  10 + import { useExecutionActionData } from './useExecutionActionData';
  11 + import { createNewExecutionActionItem } from '.';
  12 + import { useSceneLinkageDrawerContext } from '../SceneLinkageDrawer/sceneLinkageDrawerContext';
  13 +
  14 + const { disabledDrawer } = useSceneLinkageDrawerContext();
  15 +
  16 + const hasAlarmNotify = ref(false);
  17 +
  18 + const formSchemas = getFormSchemas(hasAlarmNotify);
  19 +
  20 + const executionActionListRef = ref<ExecutionActionListRefItemType[]>([
  21 + createNewExecutionActionItem(),
  22 + ]);
  23 +
  24 + const handleAdd = () => {
  25 + executionActionListRef.value.push(createNewExecutionActionItem());
  26 + };
  27 +
  28 + const handleDelete = (executionActionItem: ExecutionActionListRefItemType) => {
  29 + const index = unref(executionActionListRef).findIndex(
  30 + (item) => item.key === executionActionItem.key
  31 + );
  32 + ~index && unref(executionActionListRef).splice(index, 1);
  33 + };
  34 +
  35 + const setExecutionActionRef = (
  36 + el: Nullable<Element | ComponentPublicInstance>,
  37 + executionActionItem: ExecutionActionListRefItemType
  38 + ) => {
  39 + executionActionItem.ref = el as unknown as FormActionType;
  40 + };
  41 +
  42 + const setAlarmClearRuleRef = (
  43 + el: Nullable<Element | ComponentPublicInstance>,
  44 + executionActionItem: ExecutionActionListRefItemType
  45 + ) => {
  46 + executionActionItem.alarmClearRuleElRef = el as unknown as InstanceType<typeof FlipFlop>;
  47 + };
  48 +
  49 + const handleClearRuleDelete = (_executionActionItem: ExecutionActionListRefItemType) => {
  50 + //
  51 + };
  52 +
  53 + const setThingsModelFormRef = (
  54 + el: Nullable<Element | ComponentPublicInstance>,
  55 + executionActionItem: ExecutionActionListRefItemType
  56 + ) => {
  57 + executionActionItem.thingsModelFormRefl = el as unknown as InstanceType<typeof ThingsModelForm>;
  58 + };
  59 +
  60 + const { getFieldsValue, setFieldsValue, validate, resetFieldsValue } = useExecutionActionData(
  61 + executionActionListRef,
  62 + hasAlarmNotify
  63 + );
  64 +
  65 + defineExpose({
  66 + getFieldsValue,
  67 + setFieldsValue,
  68 + validate,
  69 + resetFieldsValue,
  70 + });
  71 +</script>
  72 +
  73 +<template>
  74 + <section>
  75 + <CollapseContainer
  76 + class="mb-4"
  77 + v-for="(executionActionItem, index) in executionActionListRef"
  78 + :title="`执行动作${index + 1}`"
  79 + :key="executionActionItem.key"
  80 + >
  81 + <template #action>
  82 + <Tooltip title="删除">
  83 + <Icon
  84 + v-if="!disabledDrawer"
  85 + class="ml-2 cursor-pointer"
  86 + icon="fluent:delete-off-20-regular"
  87 + size="20"
  88 + @click="handleDelete(executionActionItem)"
  89 + />
  90 + </Tooltip>
  91 + </template>
  92 + <BasicForm
  93 + :ref="(el) => setExecutionActionRef(el, executionActionItem)"
  94 + :disabled="disabledDrawer"
  95 + :show-action-button-group="false"
  96 + :baseColProps="{ span: 6, xxl: 6, xl: 8, lg: 12, sm: 24 }"
  97 + :schemas="formSchemas"
  98 + >
  99 + <template #alarmClearRule>
  100 + <FlipFlop
  101 + :ref="(el) => setAlarmClearRuleRef(el, executionActionItem)"
  102 + :panel-title="(index) => `清除告警${index}`"
  103 + :disabled="disabledDrawer"
  104 + addButtonName="新增清除告警"
  105 + @delete="handleClearRuleDelete(executionActionItem)"
  106 + />
  107 + </template>
  108 +
  109 + <template #serviceCommand="{ field, model }">
  110 + <ThingsModelForm
  111 + :ref="(el) => setThingsModelFormRef(el, executionActionItem)"
  112 + :value="model[field]"
  113 + :identifier="model[FormFieldsEnum.CALL_SERVICE_IDENTIFIER]"
  114 + :input-data="model[FormFieldsEnum.CALL_SERVICE]?.functionJson?.inputData"
  115 + :title="model[FormFieldsEnum.CALL_SERVICE]?.functionName"
  116 + :disabled="disabledDrawer"
  117 + :transport-type="model[FormFieldsEnum.TRANSPORT_TYPE]"
  118 + />
  119 + </template>
  120 + </BasicForm>
  121 + </CollapseContainer>
  122 + <Button v-if="!disabledDrawer" class="w-full" type="primary" @click="handleAdd">
  123 + <Icon icon="ant-design:plus-outlined" />
  124 + 执行动作
  125 + </Button>
  126 + </section>
  127 +</template>
  1 +export { default as ExecutionAction } from './index.vue';
  2 +import { FlipFlop } from '../FlipFlop';
  3 +import { FormFieldsEnum } from './config';
  4 +import { FlipFlopConditionType } from '../FlipFlop/types';
  5 +import { DeviceModelOfMatterAttrs, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  6 +import { FormActionType, ThingsModelForm } from '/@/components/Form';
  7 +import { AlarmLevelEnum } from '/@/enums/alarmEnum';
  8 +import { CommandTypeEnum, ExecutionActionEnum, TriggerEntityTypeEnum } from '/@/enums/linkedgeEnum';
  9 +import { ServiceCallTypeEnum } from '/@/enums/toolEnum';
  10 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  11 +
  12 +export interface ExecutionActionListRefItemType {
  13 + key: string;
  14 + alarmClearRuleElRef?: InstanceType<typeof FlipFlop>;
  15 + ref?: FormActionType;
  16 + thingsModelFormRefl?: InstanceType<typeof ThingsModelForm>;
  17 +}
  18 +
  19 +export interface ExecutionActionDataItemType {
  20 + callType: ServiceCallTypeEnum;
  21 + commandType: CommandTypeEnum;
  22 + deviceProfileId: string;
  23 + deviceType: DeviceTypeEnum;
  24 + entityType: TriggerEntityTypeEnum;
  25 + outTarget: ExecutionActionEnum;
  26 + deviceId?: string[];
  27 + thingsModelId?: string;
  28 + doContext: DoContextType | DoContextAlarmType;
  29 + alarmProfileId: string;
  30 +}
  31 +
  32 +export interface DoContextType {
  33 + method?: string;
  34 + additionalInfo?: { cmdType: string | number };
  35 + params?: string | Recordable;
  36 +}
  37 +
  38 +export interface DoContextAlarmType {
  39 + alarmLevel: AlarmLevelEnum;
  40 + clearRule: FlipFlopConditionType[];
  41 +}
  42 +
  43 +export interface ExecutionActionFormItemRecordType {
  44 + [FormFieldsEnum.ALARM_LEVEL]?: AlarmLevelEnum;
  45 + [FormFieldsEnum.ALARM_PROFILED]?: string;
  46 + [FormFieldsEnum.CALL_TYPE]?: ServiceCallTypeEnum;
  47 + [FormFieldsEnum.COMMAND_TYPE]?: CommandTypeEnum;
  48 + [FormFieldsEnum.DEVICE_TYPE]?: DeviceTypeEnum;
  49 + [FormFieldsEnum.DEVICE_PROFILE_ID]?: string;
  50 + [FormFieldsEnum.OUT_TARGET]?: ExecutionActionEnum;
  51 + [FormFieldsEnum.TCP_CUSTOM_COMMAND]?: string;
  52 + [FormFieldsEnum.CUSTOM_COMMAND]?: string;
  53 + [FormFieldsEnum.TRANSPORT_TYPE]?: TransportTypeEnum;
  54 + [FormFieldsEnum.ENTITY_TYPE]?: TriggerEntityTypeEnum;
  55 + [FormFieldsEnum.ENTITY_ID]?: string[];
  56 + [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]?: string;
  57 + [FormFieldsEnum.CALL_SERVICE]?: DeviceModelOfMatterAttrs;
  58 + [FormFieldsEnum.SERVICE_ID]?: string;
  59 +}
  1 +import { Ref, unref, nextTick, toRaw } from 'vue';
  2 +import { DefineComponentsBasicExpose } from '/#/utils';
  3 +import {
  4 + DoContextAlarmType,
  5 + DoContextType,
  6 + ExecutionActionDataItemType,
  7 + ExecutionActionFormItemRecordType,
  8 + ExecutionActionListRefItemType,
  9 +} from './type';
  10 +import { CommandTypeEnum, ExecutionActionEnum } from '/@/enums/linkedgeEnum';
  11 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  12 +import { useJsonParse } from '/@/hooks/business/useJsonParse';
  13 +import { createNewExecutionActionItem } from '.';
  14 +import { FormFieldsEnum } from './config';
  15 +import { isObject, isString } from '/@/utils/is';
  16 +
  17 +export function useExecutionActionData(
  18 + executionActionListRef: Ref<ExecutionActionListRefItemType[]>,
  19 + hasAlarmNotify: Ref<boolean>
  20 +): DefineComponentsBasicExpose<ExecutionActionDataItemType[]> {
  21 + const getExecutionActionItemData = (executionActionItem: ExecutionActionListRefItemType) => {
  22 + const { ref, alarmClearRuleElRef, thingsModelFormRefl } = executionActionItem;
  23 + const {
  24 + callType,
  25 + commandType,
  26 + deviceType,
  27 + deviceProfileId,
  28 + entityType,
  29 + entityId,
  30 + outTarget,
  31 + transportType,
  32 + alarmLevel,
  33 + callServiceIdentifier,
  34 + serviceId,
  35 + tcpCustomCommand,
  36 + customCommand,
  37 + alarmProfileId,
  38 + } = (ref?.getFieldsValue?.() || {}) as Required<ExecutionActionFormItemRecordType>;
  39 +
  40 + const thingsModelFormRecord = thingsModelFormRefl?.getFieldsValue?.();
  41 +
  42 + const clearRule = alarmClearRuleElRef?.getFieldsValue();
  43 +
  44 + const record: ExecutionActionDataItemType = {
  45 + callType,
  46 + commandType,
  47 + alarmProfileId,
  48 + deviceType,
  49 + deviceProfileId,
  50 + entityType,
  51 + deviceId: entityId,
  52 + outTarget,
  53 + thingsModelId: serviceId,
  54 + doContext: {},
  55 + };
  56 +
  57 + if (outTarget === ExecutionActionEnum.DEVICE_OUT) {
  58 + record.doContext = {
  59 + method: 'methodThingskit',
  60 + additionalInfo: { cmdType: commandType },
  61 + params:
  62 + commandType === CommandTypeEnum.CUSTOM
  63 + ? transportType === TransportTypeEnum.TCP
  64 + ? tcpCustomCommand
  65 + : useJsonParse(customCommand).value
  66 + : {
  67 + [callServiceIdentifier]: thingsModelFormRecord,
  68 + },
  69 + };
  70 + } else {
  71 + record.doContext = {
  72 + alarmLevel,
  73 + clearRule: clearRule || [],
  74 + };
  75 + }
  76 +
  77 + return record;
  78 + };
  79 +
  80 + const getFieldsValue = () => {
  81 + const dataList: ExecutionActionDataItemType[] = [];
  82 + for (const executionActionItem of unref(executionActionListRef)) {
  83 + dataList.push(getExecutionActionItemData(executionActionItem));
  84 + }
  85 +
  86 + return dataList;
  87 + };
  88 +
  89 + const setFieldsValue = (executionActionData: ExecutionActionDataItemType[]) => {
  90 + executionActionListRef.value = Array.from({ length: executionActionData.length }, () =>
  91 + createNewExecutionActionItem()
  92 + );
  93 +
  94 + nextTick(() => {
  95 + unref(executionActionListRef).forEach((executionActionItem, index) => {
  96 + const result = executionActionData[index];
  97 +
  98 + const { doContext, outTarget, commandType } = result;
  99 +
  100 + const { thingsModelId, deviceId } = result;
  101 +
  102 + const record = {
  103 + ...result,
  104 + [FormFieldsEnum.SERVICE_ID]: thingsModelId,
  105 + [FormFieldsEnum.ENTITY_ID]: deviceId,
  106 + };
  107 +
  108 + if (outTarget === ExecutionActionEnum.MSG_NOTIFY) {
  109 + const { alarmLevel, clearRule } = doContext as DoContextAlarmType;
  110 + Object.assign(record, {
  111 + [FormFieldsEnum.ALARM_LEVEL]: alarmLevel,
  112 + [FormFieldsEnum.ENABLE_CLEAR_RULE]: !!(clearRule && clearRule.length),
  113 + });
  114 + hasAlarmNotify.value = true;
  115 + }
  116 +
  117 + if (outTarget == ExecutionActionEnum.DEVICE_OUT && (doContext as DoContextType)?.params) {
  118 + const { params } = doContext as DoContextType;
  119 + Object.assign(record, {
  120 + [isString(params) ? FormFieldsEnum.TCP_CUSTOM_COMMAND : FormFieldsEnum.CUSTOM_COMMAND]:
  121 + params,
  122 + });
  123 + }
  124 +
  125 + if (
  126 + outTarget === ExecutionActionEnum.DEVICE_OUT &&
  127 + commandType === CommandTypeEnum.SERVICE
  128 + ) {
  129 + const { params } = doContext as DoContextType;
  130 + if (isObject(params)) {
  131 + const [identifier] = Object.keys(params);
  132 + const result = params[identifier];
  133 + Object.assign(record, { [FormFieldsEnum.SERVICE_COMMAND]: toRaw(result || {}) });
  134 + executionActionItem.thingsModelFormRefl?.setFieldsValue(result || {});
  135 + }
  136 + }
  137 +
  138 + executionActionItem.ref?.setFieldsValue(record);
  139 +
  140 + if (
  141 + outTarget === ExecutionActionEnum.MSG_NOTIFY &&
  142 + (doContext as DoContextAlarmType).clearRule &&
  143 + (doContext as DoContextAlarmType).clearRule.length
  144 + ) {
  145 + nextTick(() => {
  146 + executionActionItem.alarmClearRuleElRef?.setFieldsValue(
  147 + (result.doContext as DoContextAlarmType).clearRule
  148 + );
  149 + });
  150 + }
  151 + });
  152 + });
  153 + };
  154 +
  155 + const validate = async () => {
  156 + for (const executionActionItem of unref(executionActionListRef)) {
  157 + const { ref, alarmClearRuleElRef, thingsModelFormRefl } = executionActionItem;
  158 + await ref?.validate?.();
  159 + await alarmClearRuleElRef?.validate?.();
  160 + await thingsModelFormRefl?.validate?.();
  161 + }
  162 + };
  163 +
  164 + const resetFieldsValue = () => {
  165 + executionActionListRef.value = [createNewExecutionActionItem()];
  166 + hasAlarmNotify.value = false;
  167 + };
  168 +
  169 + return {
  170 + getFieldsValue,
  171 + setFieldsValue,
  172 + validate,
  173 + resetFieldsValue,
  174 + };
  175 +}
  1 +<script setup lang="ts">
  2 + import { InputGroup, InputNumber, Select } from 'ant-design-vue';
  3 + import { TriggerUnitEnum } from '/@/enums/linkedgeEnum';
  4 +
  5 + withDefaults(
  6 + defineProps<{
  7 + value?: number;
  8 + unit?: TriggerUnitEnum;
  9 + unitOptions: Record<'label' | 'value', any>[];
  10 + disabled?: boolean;
  11 + }>(),
  12 + {
  13 + value: undefined,
  14 + unit: TriggerUnitEnum.SECONDS,
  15 + unitOptions: () => [],
  16 + }
  17 + );
  18 + const emit = defineEmits(['unitChange', 'update:unit', 'update:value']);
  19 +
  20 + const handleUnitChange = (value: TriggerUnitEnum) => {
  21 + emit('update:unit', value);
  22 + emit('unitChange', value);
  23 + };
  24 +</script>
  25 +
  26 +<template>
  27 + <InputGroup class="!flex" compact>
  28 + <InputNumber
  29 + :disabled="disabled"
  30 + :value="value"
  31 + :min="0"
  32 + @change="$emit('update:value', $event)"
  33 + v-bind="$attrs"
  34 + />
  35 + <Select
  36 + :disabled="disabled"
  37 + :value="unit"
  38 + :options="unitOptions"
  39 + :allow-clear="false"
  40 + @change="handleUnitChange"
  41 + />
  42 + </InputGroup>
  43 +</template>
  1 +import { unref } from 'vue';
  2 +import { useSceneLinkageDrawerContext } from '../SceneLinkageDrawer/sceneLinkageDrawerContext';
  3 +import { getDeviceProfile } from '/@/api/alarm/position';
  4 +import { byOrganizationIdGetMasterDevice } from '/@/api/ruleengine/ruleengineApi';
  5 +import { findDictItemByCode } from '/@/api/system/dict';
  6 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  7 +import { DictEnum } from '/@/enums/dictEnum';
  8 +import { createPickerSearch } from '/@/utils/pickerSearch';
  9 +import { DeviceTypeEnum } from '../../../dataFlow/cpns/config';
  10 +import { getDeviceAttributes } from '/@/api/dataBoard';
  11 +import { DeviceAttributeParams } from '/@/api/dataBoard/model';
  12 +import {
  13 + TriggerValueTypeEnum,
  14 + TriggerValueTypeNameEnum,
  15 + TriggerEntityTypeEnum,
  16 + TriggerEntityTypeNameEnum,
  17 + TriggerTypeEnum,
  18 + TriggerTypeNameEnum,
  19 + DeviceTriggerTypeEum,
  20 + DeviceTriggerTypeNameEum,
  21 + FlipFlopTypeEnum,
  22 + FlipFlopTypeNameEnum,
  23 + TriggerUnitEnum,
  24 + TriggerUnitNameEnum,
  25 +} from '/@/enums/linkedgeEnum';
  26 +import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel';
  27 +import TriggerDurationInput from './TriggerDurationInput.vue';
  28 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  29 +
  30 +export enum FormFieldEnum {
  31 + FLIP_FLOP_TYPE = 'flipFlopType',
  32 + DEVICE_TYPE = 'deviceType',
  33 + DEVICE_PROFILE_ID = 'deviceProfileId',
  34 + ENTITY_TYPE = 'entityType',
  35 + ENTITY_ID = 'entityId',
  36 + TRIGGER_TYPE = 'triggerType',
  37 + CONDITION_TYPE = 'conditionType',
  38 + CONDITION_KEY = 'conditionKey',
  39 + CONDITION_VALUE_TYPE = 'conditionValueType',
  40 + TRIGGER_DURATION_UNIT = 'triggerDurationUnit',
  41 + TRIGGER_DURATION_VALUE = 'triggerDurationValue',
  42 + TRIGGER_REPEAT_COUNT = 'triggerRepeatCount',
  43 +
  44 + CONDITION_KEY_DETAIL = 'conditionKeyDetail',
  45 +}
  46 +
  47 +export enum FormFieldNameEnum {
  48 + FLIP_FLOP_TYPE = '触发类型',
  49 + DEVICE_TYPE = '设备类型',
  50 + DEVICE_PROFILE_ID = '产品',
  51 + ENTITY_TYPE = '触发设备类型',
  52 + ENTITY_ID = '设备',
  53 + TRIGGER_TYPE = '触发方式',
  54 + CONDITION_TYPE = '触发条件',
  55 + CONDITION_KEY = '属性',
  56 + CONDITION_VALUE_TYPE = '比较类型',
  57 + TRIGGER_DURATION_VALUE = '持续时长',
  58 + TRIGGER_REPEAT_COUNT = '重复次数',
  59 +}
  60 +
  61 +useComponentRegister('TriggerDurationInput', TriggerDurationInput);
  62 +
  63 +function getTriggerValueTypeByThingsModelType(type: DataTypeEnum) {
  64 + const map = {
  65 + [DataTypeEnum.BOOL]: TriggerValueTypeEnum.BOOLEAN,
  66 + [DataTypeEnum.NUMBER_DOUBLE]: TriggerValueTypeEnum.NUMERIC,
  67 + [DataTypeEnum.NUMBER_INT]: TriggerValueTypeEnum.NUMERIC,
  68 + [DataTypeEnum.STRING]: TriggerValueTypeEnum.STRING,
  69 + [DataTypeEnum.STRUCT]: TriggerValueTypeEnum.STRING,
  70 + };
  71 +
  72 + return map[type];
  73 +}
  74 +
  75 +export const getFormSchemas = (): FormSchema[] => {
  76 + const { organizationId } = useSceneLinkageDrawerContext();
  77 +
  78 + return [
  79 + {
  80 + field: FormFieldEnum.FLIP_FLOP_TYPE,
  81 + label: '',
  82 + component: 'Select',
  83 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.FLIP_FLOP_TYPE}` }],
  84 + defaultValue: FlipFlopTypeEnum.SIMPLE,
  85 + componentProps: () => {
  86 + return {
  87 + options: Object.keys(FlipFlopTypeEnum).map((value) => ({
  88 + label: FlipFlopTypeNameEnum[value],
  89 + value,
  90 + })),
  91 + placeholder: `请选择${FormFieldNameEnum.FLIP_FLOP_TYPE}`,
  92 + };
  93 + },
  94 + },
  95 + {
  96 + field: FormFieldEnum.DEVICE_TYPE,
  97 + label: '',
  98 + component: 'ApiSelect',
  99 + defaultValue: DeviceTypeEnum.SENSOR,
  100 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.DEVICE_TYPE}` }],
  101 + componentProps: ({ formActionType }) => {
  102 + const { setFieldsValue } = formActionType;
  103 + return {
  104 + api: findDictItemByCode,
  105 + params: {
  106 + dictCode: DictEnum.DEVICE_TYPE,
  107 + },
  108 + labelField: 'itemText',
  109 + valueField: 'itemValue',
  110 + placeholder: `请选择${FormFieldNameEnum.DEVICE_TYPE}`,
  111 + onChange() {
  112 + setFieldsValue({
  113 + [FormFieldEnum.DEVICE_PROFILE_ID]: null,
  114 + [FormFieldEnum.ENTITY_ID]: [],
  115 + [FormFieldEnum.CONDITION_KEY]: null,
  116 + });
  117 + },
  118 + };
  119 + },
  120 + },
  121 + {
  122 + field: FormFieldEnum.DEVICE_PROFILE_ID,
  123 + label: '',
  124 + component: 'ApiSelect',
  125 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.DEVICE_PROFILE_ID}` }],
  126 + componentProps: ({ formModel, formActionType }) => {
  127 + const deviceType = formModel[FormFieldEnum.DEVICE_TYPE];
  128 + const { setFieldsValue } = formActionType;
  129 + return {
  130 + api: async () => {
  131 + return await getDeviceProfile(deviceType);
  132 + },
  133 + labelField: 'name',
  134 + valueField: 'id',
  135 + placeholder: `请选择${FormFieldNameEnum.DEVICE_PROFILE_ID}`,
  136 + ...createPickerSearch(),
  137 + onChange() {
  138 + setFieldsValue({
  139 + [FormFieldEnum.ENTITY_ID]: [],
  140 + [FormFieldEnum.CONDITION_KEY]: null,
  141 + });
  142 + },
  143 + };
  144 + },
  145 + },
  146 + {
  147 + field: FormFieldEnum.ENTITY_TYPE,
  148 + label: '',
  149 + component: 'Select',
  150 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.ENTITY_TYPE}` }],
  151 + componentProps: () => {
  152 + return {
  153 + options: Object.keys(TriggerEntityTypeEnum).map((value) => ({
  154 + label: TriggerEntityTypeNameEnum[value],
  155 + value,
  156 + })),
  157 + placeholder: `请选择${FormFieldNameEnum.ENTITY_TYPE}`,
  158 + };
  159 + },
  160 + },
  161 + {
  162 + field: FormFieldEnum.TRIGGER_DURATION_UNIT,
  163 + label: '',
  164 + component: 'Input',
  165 + ifShow: false,
  166 + defaultValue: TriggerUnitEnum.SECONDS,
  167 + },
  168 + {
  169 + field: FormFieldEnum.TRIGGER_DURATION_VALUE,
  170 + label: '',
  171 + component: 'TriggerDurationInput',
  172 + rules: [
  173 + {
  174 + required: true,
  175 + message: `请输入${FormFieldNameEnum.TRIGGER_DURATION_VALUE}`,
  176 + type: 'number',
  177 + },
  178 + ],
  179 + changeEvent: 'update:value',
  180 + ifShow: ({ model }) => model[FormFieldEnum.FLIP_FLOP_TYPE] === FlipFlopTypeEnum.DURATION,
  181 + componentProps: ({ formActionType, formModel }) => {
  182 + const { setFieldsValue } = formActionType;
  183 + return {
  184 + placeholder: `请输入${FormFieldNameEnum.TRIGGER_DURATION_VALUE}`,
  185 + unit: formModel[FormFieldEnum.TRIGGER_DURATION_UNIT],
  186 + unitOptions: Object.values(TriggerUnitEnum).map((value) => ({
  187 + label: TriggerUnitNameEnum[value],
  188 + value,
  189 + })),
  190 + onUnitChange(value: TriggerUnitEnum) {
  191 + setFieldsValue({ [FormFieldEnum.TRIGGER_DURATION_UNIT]: value });
  192 + },
  193 + };
  194 + },
  195 + },
  196 + {
  197 + field: FormFieldEnum.TRIGGER_REPEAT_COUNT,
  198 + label: '',
  199 + component: 'InputNumber',
  200 + rules: [{ required: true, message: `请输入${FormFieldNameEnum.TRIGGER_REPEAT_COUNT}` }],
  201 + ifShow: ({ model }) => model[FormFieldEnum.FLIP_FLOP_TYPE] === FlipFlopTypeEnum.REPEATING,
  202 + componentProps: {
  203 + placeholder: `请输入${FormFieldNameEnum.TRIGGER_REPEAT_COUNT}`,
  204 + min: 0,
  205 + },
  206 + },
  207 + {
  208 + field: FormFieldEnum.ENTITY_ID,
  209 + label: '',
  210 + component: 'ApiSelect',
  211 + rules: [{ required: true, type: 'array', message: `请选择${FormFieldNameEnum.ENTITY_ID}` }],
  212 + ifShow: ({ model }) => model[FormFieldEnum.ENTITY_TYPE] === TriggerEntityTypeEnum.PART,
  213 + componentProps: ({ formModel }) => {
  214 + return {
  215 + api: async (params: Record<'organizationId' | 'deviceProfileId', string>) => {
  216 + if (params.deviceProfileId && params.organizationId) {
  217 + const result = await byOrganizationIdGetMasterDevice(params);
  218 + if (result) {
  219 + return result.map((item) => ({
  220 + ...item,
  221 + label: item.alias || item.name,
  222 + value: item.tbDeviceId,
  223 + }));
  224 + }
  225 + }
  226 + return [];
  227 + },
  228 + mode: 'multiple',
  229 + maxTagCount: 3,
  230 + params: {
  231 + organizationId: unref(organizationId),
  232 + deviceProfileId: formModel[FormFieldEnum.DEVICE_PROFILE_ID],
  233 + },
  234 + placeholder: `请选择${FormFieldNameEnum.ENTITY_ID}`,
  235 + ...createPickerSearch(),
  236 + };
  237 + },
  238 + },
  239 + {
  240 + field: FormFieldEnum.TRIGGER_TYPE,
  241 + label: '',
  242 + component: 'Select',
  243 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.TRIGGER_TYPE}` }],
  244 + defaultValue: TriggerTypeEnum.DEVICE_TRIGGER,
  245 + componentProps: () => {
  246 + return {
  247 + options: [
  248 + { label: TriggerTypeNameEnum.DEVICE_TRIGGER, value: TriggerTypeEnum.DEVICE_TRIGGER },
  249 + ],
  250 + };
  251 + },
  252 + },
  253 + {
  254 + field: FormFieldEnum.CONDITION_TYPE,
  255 + label: '',
  256 + component: 'Select',
  257 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.CONDITION_TYPE}` }],
  258 + defaultValue: DeviceTriggerTypeEum.TIME_SERIES,
  259 + componentProps: () => {
  260 + return {
  261 + options: [
  262 + {
  263 + label: DeviceTriggerTypeNameEum.TIME_SERIES,
  264 + value: DeviceTriggerTypeEum.TIME_SERIES,
  265 + },
  266 + ],
  267 + };
  268 + },
  269 + },
  270 + {
  271 + field: FormFieldEnum.CONDITION_KEY_DETAIL,
  272 + label: '',
  273 + component: 'Input',
  274 + ifShow: false,
  275 + },
  276 + {
  277 + field: FormFieldEnum.CONDITION_KEY,
  278 + label: '',
  279 + component: 'ApiSelect',
  280 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.CONDITION_KEY}` }],
  281 + componentProps: ({ formModel, formActionType }) => {
  282 + const { setFieldsValue } = formActionType;
  283 + return {
  284 + api: async (params: DeviceAttributeParams) => {
  285 + if (params.deviceProfileId) {
  286 + return await getDeviceAttributes(params);
  287 + }
  288 + return [];
  289 + },
  290 + labelField: 'name',
  291 + valueField: 'identifier',
  292 + params: {
  293 + deviceProfileId: formModel[FormFieldEnum.DEVICE_PROFILE_ID],
  294 + },
  295 + placeholder: `请选择${FormFieldNameEnum.CONDITION_KEY}`,
  296 + onChange(
  297 + value: string,
  298 + option: DeviceModelOfMatterAttrs & Record<'label' | 'value', string>
  299 + ) {
  300 + setFieldsValue({
  301 + [FormFieldEnum.CONDITION_KEY_DETAIL]: value
  302 + ? { ...option, identifier: option?.value, name: option?.label }
  303 + : {},
  304 + [FormFieldEnum.CONDITION_VALUE_TYPE]:
  305 + value && option?.detail?.dataType?.type
  306 + ? getTriggerValueTypeByThingsModelType(option.detail.dataType?.type)
  307 + : null,
  308 + });
  309 + },
  310 + onOptionsChange(
  311 + options: (DeviceModelOfMatterAttrs & Record<'label' | 'value', string>)[]
  312 + ) {
  313 + const conditionKey = formModel[FormFieldEnum.CONDITION_KEY];
  314 + const res = options.find((item) => item.value === conditionKey);
  315 + res &&
  316 + setFieldsValue({
  317 + [FormFieldEnum.CONDITION_KEY_DETAIL]: {
  318 + ...res,
  319 + identifier: res?.value,
  320 + name: res?.label,
  321 + },
  322 + });
  323 + },
  324 + };
  325 + },
  326 + },
  327 + {
  328 + field: FormFieldEnum.CONDITION_VALUE_TYPE,
  329 + label: '',
  330 + component: 'Select',
  331 + rules: [{ required: true, message: `请选择${FormFieldNameEnum.CONDITION_VALUE_TYPE}` }],
  332 + componentProps: () => {
  333 + return {
  334 + options: Object.keys(TriggerValueTypeEnum).map((value) => ({
  335 + label: TriggerValueTypeNameEnum[value],
  336 + value,
  337 + })),
  338 + placeholder: `请选择${FormFieldNameEnum.CONDITION_VALUE_TYPE}`,
  339 + };
  340 + },
  341 + },
  342 + {
  343 + field: 'conditionFilter',
  344 + label: '',
  345 + component: 'Input',
  346 + colProps: { span: 24, xxl: 24, xl: 24, lg: 24, md: 24, sm: 24, xs: 24 },
  347 + ifShow: ({ model }) =>
  348 + model[FormFieldEnum.CONDITION_KEY] && model[FormFieldEnum.CONDITION_VALUE_TYPE],
  349 + colSlot: 'conditionFilter',
  350 + },
  351 + ];
  352 +};
  1 +import { FlipFlopItemType } from './types';
  2 +import { ScheduleTypeEnum } from '/@/enums/linkedgeEnum';
  3 +import { buildUUID } from '/@/utils/uuid';
  4 +
  5 +export { default as FlipFlop } from './index.vue';
  6 +
  7 +export function createNewFlipFlopItem(): FlipFlopItemType {
  8 + return { key: buildUUID(), schedule: { type: ScheduleTypeEnum.ANY_TIME } };
  9 +}
  1 +<script setup lang="ts">
  2 + import { ComponentPublicInstance, ref, unref, watch } from 'vue';
  3 + import { Tooltip, Button } from 'ant-design-vue';
  4 + import { CollapseContainer } from '/@/components/Container';
  5 + import { Icon } from '/@/components/Icon';
  6 + import { FlipFlopItemType, ScheduleOptionItemType } from './types';
  7 + import { BasicForm, FormActionType } from '/@/components/Form';
  8 + import { getFormSchemas, FormFieldEnum } from './config';
  9 + import { useSceneLinkageDrawerContext } from '../SceneLinkageDrawer/sceneLinkageDrawerContext';
  10 + import { ScheduleTypeEnum, ScheduleTypeNameEnum } from '/@/enums/linkedgeEnum';
  11 + import { ConditionFilter } from '../ConditionFilter';
  12 + import { EnablingRule } from '../EnablingRule';
  13 + import { useModal } from '/@/components/Modal';
  14 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  15 + import { EnableRuleFormModalParamsType, ScheduleType } from '../EnablingRule/type';
  16 + import { useFlipFlopData } from './useFlipFlopData';
  17 + import { createNewFlipFlopItem } from '.';
  18 +
  19 + const { disabledDrawer } = useSceneLinkageDrawerContext();
  20 +
  21 + const emit = defineEmits(['delete']);
  22 +
  23 + const props = withDefaults(
  24 + defineProps<{
  25 + addButtonName?: string;
  26 + defaultNull?: boolean;
  27 + panelTitle?: (index: number) => string;
  28 + showAddButton?: boolean;
  29 + disabled?: boolean;
  30 + }>(),
  31 + {
  32 + addButtonName: '触发器 (OR)',
  33 + defaultNull: false,
  34 + panelTitle: (index: number) => `触发器${index}`,
  35 + showAddButton: true,
  36 + disabled: false,
  37 + }
  38 + );
  39 +
  40 + const formSchemas = getFormSchemas();
  41 +
  42 + const [registerModal, { openModal }] = useModal();
  43 +
  44 + const handleOpenModal = (type: ScheduleTypeEnum, flipFlopItem: FlipFlopItemType) => {
  45 + if (unref(disabledDrawer) && type !== flipFlopItem.schedule.type) return;
  46 +
  47 + if (type === ScheduleTypeEnum.ANY_TIME) {
  48 + flipFlopItem.schedule = { type };
  49 + return;
  50 + }
  51 +
  52 + openModal(true, {
  53 + mode: DataActionModeEnum.CREATE,
  54 + record: {
  55 + type,
  56 + schedule: flipFlopItem.schedule,
  57 + key: flipFlopItem.key,
  58 + },
  59 + } as EnableRuleFormModalParamsType);
  60 + };
  61 +
  62 + const handleRuleSetting = (params: ScheduleType, key: string) => {
  63 + const index = unref(flipFlopListElRef).findIndex((flipFlopItem) => flipFlopItem.key === key);
  64 +
  65 + ~index && (unref(flipFlopListElRef)[index].schedule = params);
  66 + };
  67 +
  68 + const scheduleOptions: ScheduleOptionItemType[] = Object.keys(ScheduleTypeEnum).map((value) => ({
  69 + label: ScheduleTypeNameEnum[value],
  70 + value: value as ScheduleTypeEnum,
  71 + }));
  72 +
  73 + const { organizationId } = useSceneLinkageDrawerContext();
  74 +
  75 + const flipFlopListElRef = ref<FlipFlopItemType[]>(
  76 + props.defaultNull ? [] : [createNewFlipFlopItem()]
  77 + );
  78 +
  79 + /**
  80 + * @description on organization change
  81 + */
  82 + watch(organizationId, () => {
  83 + unref(flipFlopListElRef).forEach((flipFlopItem) =>
  84 + flipFlopItem.ref?.setFieldsValue({ [FormFieldEnum.ENTITY_ID]: [] })
  85 + );
  86 + });
  87 +
  88 + const setFlopFlipFormRef = (
  89 + flipFlopItem: FlipFlopItemType,
  90 + el: Nullable<Element | ComponentPublicInstance>
  91 + ) => {
  92 + flipFlopItem.ref = el as unknown as FormActionType;
  93 + };
  94 +
  95 + const setFlopFlipConditionRef = (
  96 + flipFlopItem: FlipFlopItemType,
  97 + el: Nullable<Element | ComponentPublicInstance>
  98 + ) => {
  99 + flipFlopItem.conditionRef = el as unknown as InstanceType<typeof ConditionFilter>;
  100 + };
  101 +
  102 + const handleDelete = (flipFlopItem: FlipFlopItemType) => {
  103 + const index = unref(flipFlopListElRef).findIndex((item) => item.key === flipFlopItem.key);
  104 + ~index && unref(flipFlopListElRef).splice(index, 1);
  105 + emit('delete');
  106 + };
  107 +
  108 + const handleAdd = () => {
  109 + flipFlopListElRef.value.push(createNewFlipFlopItem());
  110 + };
  111 +
  112 + const { getFieldsValue, setFieldsValue, validate, resetFieldsValue } =
  113 + useFlipFlopData(flipFlopListElRef);
  114 +
  115 + defineExpose({ getFieldsValue, setFieldsValue, validate, resetFieldsValue });
  116 +</script>
  117 +
  118 +<template>
  119 + <section>
  120 + <CollapseContainer
  121 + v-for="(flipFlopItem, index) in flipFlopListElRef"
  122 + :key="flipFlopItem.key"
  123 + :title="panelTitle(index + 1)"
  124 + class="mb-4"
  125 + >
  126 + <template #action>
  127 + <header class="flex items-center justify-between">
  128 + <div class="flex items-center">
  129 + <div class="flex">
  130 + <span class="mr-2">启用规则:</span>
  131 + <div
  132 + class="px-1 cursor-pointer"
  133 + v-for="schedule in scheduleOptions"
  134 + :key="schedule.value"
  135 + type="text"
  136 + :class="flipFlopItem.schedule.type === schedule.value ? 'text-blue-400' : ''"
  137 + @click="handleOpenModal(schedule.value, flipFlopItem)"
  138 + >
  139 + {{ schedule.label }}
  140 + </div>
  141 + </div>
  142 + <Tooltip title="删除">
  143 + <Icon
  144 + v-if="!disabledDrawer"
  145 + class="ml-2 cursor-pointer"
  146 + icon="fluent:delete-off-20-regular"
  147 + size="20"
  148 + @click="handleDelete(flipFlopItem)"
  149 + />
  150 + </Tooltip>
  151 + </div>
  152 + </header>
  153 + </template>
  154 + <BasicForm
  155 + :ref="(el) => setFlopFlipFormRef(flipFlopItem, el)"
  156 + :key="flipFlopItem.key"
  157 + class="trigger-form"
  158 + layout="horizontal"
  159 + :disabled="disabledDrawer"
  160 + :schemas="formSchemas"
  161 + :showActionButtonGroup="false"
  162 + :baseColProps="{ span: 6, xxl: 6, xl: 8, lg: 12, sm: 24 }"
  163 + >
  164 + <template #conditionFilter="{ model }">
  165 + <ConditionFilter
  166 + :ref="(el) => setFlopFlipConditionRef(flipFlopItem, el)"
  167 + :triggerType="model[FormFieldEnum.CONDITION_VALUE_TYPE]"
  168 + :object-model="model[FormFieldEnum.CONDITION_KEY_DETAIL]"
  169 + :condition-type="model[FormFieldEnum.CONDITION_TYPE]"
  170 + />
  171 + </template>
  172 + </BasicForm>
  173 + </CollapseContainer>
  174 + <Button
  175 + v-if="showAddButton && !disabledDrawer"
  176 + type="primary"
  177 + class="w-full"
  178 + @click="handleAdd"
  179 + >
  180 + <Icon icon="ant-design:plus-outlined" />
  181 + {{ addButtonName }}
  182 + </Button>
  183 +
  184 + <EnablingRule @register="registerModal" @setting-complete="handleRuleSetting" />
  185 + </section>
  186 +</template>
  187 +
  188 +<style lang="less" scoped>
  189 + .trigger-form {
  190 + :deep(.ant-form-item-control-input-content) {
  191 + width: 100%;
  192 +
  193 + > div > div {
  194 + width: 100%;
  195 + }
  196 +
  197 + .ant-input-number {
  198 + width: 100% !important;
  199 + }
  200 + // .ant-col {
  201 + // .ant-row{
  202 + // .ant-col {
  203 + // .
  204 + // }
  205 + // }
  206 + // }
  207 + }
  208 + }
  209 +</style>
  1 +import { ConditionFilter } from '../ConditionFilter';
  2 +import { TriggerCondition } from '../ConditionFilter/type';
  3 +import { ScheduleType } from '../EnablingRule/type';
  4 +import { FormFieldEnum } from './config';
  5 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  6 +import { FormActionType } from '/@/components/Form';
  7 +import {
  8 + DeviceTriggerTypeEum,
  9 + FlipFlopTypeEnum,
  10 + ScheduleTypeEnum,
  11 + TriggerEntityTypeEnum,
  12 + TriggerTypeEnum,
  13 + TriggerUnitEnum,
  14 + TriggerValueTypeNameEnum,
  15 +} from '/@/enums/linkedgeEnum';
  16 +
  17 +export interface ScheduleOptionItemType {
  18 + label: string;
  19 + value: ScheduleTypeEnum;
  20 +}
  21 +
  22 +export interface FlipFlopItemType {
  23 + key: string;
  24 + ref?: FormActionType;
  25 + schedule: ScheduleType;
  26 + conditionRef?: InstanceType<typeof ConditionFilter>;
  27 +}
  28 +
  29 +export interface FlipFlopConditionType {
  30 + deviceProfileId: string;
  31 + deviceType: DeviceTypeEnum;
  32 + entityId: string[];
  33 + entityType: TriggerEntityTypeEnum;
  34 + triggerCondition: TriggerCondition;
  35 + triggerType: TriggerTypeEnum;
  36 +}
  37 +
  38 +export interface FlipFlopFormRecordType {
  39 + [FormFieldEnum.FLIP_FLOP_TYPE]: FlipFlopTypeEnum;
  40 + [FormFieldEnum.DEVICE_TYPE]: DeviceTypeEnum;
  41 + [FormFieldEnum.DEVICE_PROFILE_ID]: string;
  42 + [FormFieldEnum.ENTITY_TYPE]: TriggerEntityTypeEnum;
  43 + [FormFieldEnum.ENTITY_ID]: string[];
  44 + [FormFieldEnum.TRIGGER_TYPE]: TriggerTypeEnum;
  45 + [FormFieldEnum.CONDITION_TYPE]: DeviceTriggerTypeEum;
  46 + [FormFieldEnum.CONDITION_KEY]: string;
  47 + [FormFieldEnum.CONDITION_VALUE_TYPE]: TriggerValueTypeNameEnum;
  48 + [FormFieldEnum.TRIGGER_DURATION_VALUE]: number;
  49 + [FormFieldEnum.TRIGGER_REPEAT_COUNT]: number;
  50 + [FormFieldEnum.TRIGGER_DURATION_UNIT]: TriggerUnitEnum;
  51 +}
  1 +import { Ref, nextTick, toRaw, unref } from 'vue';
  2 +import { FlipFlopConditionType, FlipFlopFormRecordType, FlipFlopItemType } from './types';
  3 +import { DefineComponentsBasicExpose } from '/#/utils';
  4 +import { FormFieldEnum } from './config';
  5 +import { FlipFlopTypeEnum } from '/@/enums/linkedgeEnum';
  6 +import { createNewFlipFlopItem } from '.';
  7 +
  8 +export function useFlipFlopData(
  9 + flipFlopListRef: Ref<FlipFlopItemType[]>
  10 +): DefineComponentsBasicExpose<FlipFlopConditionType[]> {
  11 + const crateFlipFlopCondition = (flipFlopItem: FlipFlopItemType): FlipFlopConditionType => {
  12 + const {
  13 + deviceProfileId,
  14 + deviceType,
  15 + entityId,
  16 + entityType,
  17 + triggerType,
  18 + flipFlopType,
  19 + triggerDurationUnit,
  20 + triggerRepeatCount,
  21 + triggerDurationValue,
  22 + } = flipFlopItem.ref?.getFieldsValue() as FlipFlopFormRecordType;
  23 +
  24 + const condition = flipFlopItem.conditionRef?.getFieldsValue() || [];
  25 +
  26 + return {
  27 + deviceProfileId,
  28 + deviceType,
  29 + entityId,
  30 + entityType,
  31 + triggerType,
  32 + triggerCondition: {
  33 + condition: {
  34 + condition,
  35 + spec: {
  36 + type: flipFlopType,
  37 + ...(flipFlopType !== FlipFlopTypeEnum.SIMPLE
  38 + ? {
  39 + unit: triggerDurationUnit,
  40 + predicate: {
  41 + defaultValue:
  42 + flipFlopType === FlipFlopTypeEnum.DURATION
  43 + ? triggerDurationValue
  44 + : triggerRepeatCount,
  45 + },
  46 + }
  47 + : {}),
  48 + },
  49 + },
  50 + schedule: toRaw(unref(flipFlopItem.schedule)),
  51 + },
  52 + };
  53 + };
  54 +
  55 + const getFieldsValue = () => {
  56 + return unref(flipFlopListRef).map((flipFlopItem) => crateFlipFlopCondition(flipFlopItem));
  57 + };
  58 +
  59 + const setFieldsValue = (flipFlopData: FlipFlopConditionType[]) => {
  60 + flipFlopListRef.value = Array.from({ length: flipFlopData.length }, () =>
  61 + createNewFlipFlopItem()
  62 + );
  63 +
  64 + nextTick(() => {
  65 + unref(flipFlopListRef).forEach((item, index) => {
  66 + const data = flipFlopData[index];
  67 + const condition = data.triggerCondition.condition;
  68 + const [firstItem] = condition.condition;
  69 + const { key, valueType } = firstItem;
  70 + const { type, unit, predicate } = condition.spec;
  71 + const { defaultValue } = predicate || {};
  72 +
  73 + item.ref?.setFieldsValue({
  74 + ...data,
  75 + [FormFieldEnum.FLIP_FLOP_TYPE]: type,
  76 + [FormFieldEnum.CONDITION_KEY]: key.key,
  77 + [FormFieldEnum.CONDITION_VALUE_TYPE]: valueType,
  78 + [FormFieldEnum.TRIGGER_DURATION_UNIT]: unit,
  79 + [FormFieldEnum.TRIGGER_DURATION_VALUE]:
  80 + type === FlipFlopTypeEnum.DURATION ? defaultValue : null,
  81 + [FormFieldEnum.TRIGGER_REPEAT_COUNT]:
  82 + type === FlipFlopTypeEnum.REPEATING ? defaultValue : null,
  83 + });
  84 +
  85 + const {
  86 + daysOfWeek,
  87 + startsOn = 0,
  88 + endsOn = 0,
  89 + timezone,
  90 + type: scheduleType,
  91 + items,
  92 + } = data.triggerCondition.schedule;
  93 + item.schedule = {
  94 + type: scheduleType,
  95 + timezone,
  96 + daysOfWeek,
  97 + startsOn,
  98 + endsOn,
  99 + items,
  100 + };
  101 +
  102 + nextTick(() => {
  103 + item.conditionRef?.setFieldsValue(condition.condition);
  104 + });
  105 + });
  106 + });
  107 + };
  108 +
  109 + const validate = async () => {
  110 + for (const flipFlopItem of unref(flipFlopListRef)) {
  111 + await flipFlopItem.ref?.validate();
  112 + await flipFlopItem.conditionRef?.validate?.();
  113 + }
  114 + };
  115 +
  116 + const resetFieldsValue = (defaultNull?: boolean) => {
  117 + flipFlopListRef.value = defaultNull ? [] : [createNewFlipFlopItem()];
  118 + };
  119 +
  120 + return {
  121 + getFieldsValue,
  122 + setFieldsValue,
  123 + validate,
  124 + resetFieldsValue,
  125 + };
  126 +}
  1 +import { FormSchema, useComponentRegister } from '/@/components/Form';
  2 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  3 +
  4 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  5 +
  6 +export enum FormFieldsEnum {
  7 + NAME = 'name',
  8 + ORGANIZATION_ID = 'organizationId',
  9 + DESCRIPTION = 'description',
  10 +}
  11 +export enum FormFieldsNameEnum {
  12 + NAME = '场景联动名称',
  13 + ORGANIZATION_ID = '所属组织',
  14 + DESCRIPTION = '描述',
  15 +}
  16 +export const getFormSchemas = (
  17 + onOrgTreeSelectChange: Fn,
  18 + onOrgTreeSelectOptionsChange: Fn
  19 +): FormSchema[] => {
  20 + return [
  21 + {
  22 + field: FormFieldsEnum.NAME,
  23 + component: 'Input',
  24 + label: FormFieldsNameEnum.NAME,
  25 + required: true,
  26 + componentProps: {
  27 + placeholder: `请输入${FormFieldsNameEnum.NAME}`,
  28 + },
  29 + },
  30 + {
  31 + field: FormFieldsEnum.ORGANIZATION_ID,
  32 + component: 'OrgTreeSelect',
  33 + label: FormFieldsNameEnum.ORGANIZATION_ID,
  34 + required: true,
  35 + componentProps: {
  36 + placeholder: `请输入${FormFieldsNameEnum.NAME}`,
  37 + onChange: onOrgTreeSelectChange,
  38 + onOptionsChange: onOrgTreeSelectOptionsChange,
  39 + },
  40 + },
  41 + {
  42 + field: FormFieldsEnum.DESCRIPTION,
  43 + component: 'InputTextArea',
  44 + label: FormFieldsNameEnum.DESCRIPTION,
  45 + componentProps: {
  46 + placeholder: `请输入${FormFieldsNameEnum.DESCRIPTION}`,
  47 + },
  48 + },
  49 + {
  50 + field: 'flipFlop',
  51 + component: 'Input',
  52 + label: '',
  53 + slot: 'flipFlop',
  54 + },
  55 + {
  56 + field: 'executionCondition',
  57 + component: 'Input',
  58 + label: '',
  59 + slot: 'executionCondition',
  60 + },
  61 + {
  62 + field: 'executionAction',
  63 + component: 'Input',
  64 + label: '',
  65 + slot: 'executionAction',
  66 + },
  67 + ];
  68 +};
  1 +export { default as SceneLinkageDrawer } from './index.vue';
  1 +<script setup lang="ts">
  2 + import { computed, ref, unref } from 'vue';
  3 + import { SceneLinkageDrawerDataType } from './type';
  4 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  5 + import { DataActionModeEnum, DataActionModeNameEnum } from '/@/enums/toolEnum';
  6 + import { BasicForm, useForm } from '/@/components/Form';
  7 + import { getFormSchemas, FormFieldsEnum } from './config';
  8 + import { createSceneLinkageDrawerContext } from './sceneLinkageDrawerContext';
  9 + import { FlipFlop } from '../FlipFlop';
  10 + import { Divider, Tooltip } from 'ant-design-vue';
  11 + import { Icon } from '/@/components/Icon';
  12 + import { ExecutionAction } from '../ExecutionAction';
  13 + import { useSceneLinkageData } from './useSceneLinkedgeData';
  14 + import { screenLinkPageAddApi } from '/@/api/ruleengine/ruleengineApi';
  15 + import { useMessage } from '/@/hooks/web/useMessage';
  16 + import { OrganizationListItem } from '/@/api/system/model/systemModel';
  17 +
  18 + const emit = defineEmits(['success', 'register']);
  19 +
  20 + const drawerMode = ref(DataActionModeEnum.CREATE);
  21 +
  22 + const getDrawTitle = computed(() => `${DataActionModeNameEnum[unref(drawerMode)]}场景联动`);
  23 +
  24 + const disabledDrawer = ref(false);
  25 +
  26 + const currentUpdateLinkedgeId = ref<string>();
  27 +
  28 + const [register, { setDrawerProps, closeDrawer }] = useDrawerInner(
  29 + (data: SceneLinkageDrawerDataType) => {
  30 + const { mode, record } = data;
  31 + drawerMode.value = mode;
  32 + disabledDrawer.value = mode === DataActionModeEnum.READ;
  33 + resetFieldsValue?.();
  34 +
  35 + if (mode === DataActionModeEnum.UPDATE || mode === DataActionModeEnum.READ) {
  36 + currentUpdateLinkedgeId.value = record.id;
  37 + setDrawerProps({ loading: true });
  38 + handleOnOrganizationChange(data.record.organizationId);
  39 + setTimeout(() => {
  40 + setFieldsValue(record);
  41 + setDrawerProps({ loading: false });
  42 + }, 300);
  43 + }
  44 +
  45 + setDrawerProps({
  46 + showOkBtn: !unref(disabledDrawer),
  47 + cancelText: unref(disabledDrawer) ? '关闭' : '取消',
  48 + });
  49 + }
  50 + );
  51 +
  52 + const [registerForm, basicFormActionType] = useForm({
  53 + schemas: getFormSchemas(handleOnOrganizationChange, handleOnOrganizationOptionsChange),
  54 + layout: 'horizontal',
  55 + labelWidth: 120,
  56 + showActionButtonGroup: false,
  57 + });
  58 +
  59 + const organizationId = ref<string>();
  60 +
  61 + function handleOnOrganizationChange(id: string) {
  62 + organizationId.value = id;
  63 + }
  64 +
  65 + function handleOnOrganizationOptionsChange(options: OrganizationListItem[]) {
  66 + if (unref(drawerMode) === DataActionModeEnum.CREATE) {
  67 + const [firsetItem] = options;
  68 + organizationId.value = firsetItem?.id;
  69 + basicFormActionType.setFieldsValue({ [FormFieldsEnum.ORGANIZATION_ID]: firsetItem?.id });
  70 + }
  71 + }
  72 +
  73 + createSceneLinkageDrawerContext({
  74 + organizationId: computed(() => unref(organizationId)),
  75 + disabledDrawer: computed(() => unref(disabledDrawer)),
  76 + });
  77 +
  78 + const flipFlopElRef = ref<InstanceType<typeof FlipFlop>>();
  79 +
  80 + const executionConditionElRef = ref<InstanceType<typeof FlipFlop>>();
  81 +
  82 + const executionActionElRef = ref<InstanceType<typeof ExecutionAction>>();
  83 +
  84 + const { getFieldsValue, setFieldsValue, validate, resetFieldsValue } = useSceneLinkageData({
  85 + flipFlopElRef,
  86 + executionActionElRef,
  87 + executionConditionElRef,
  88 + basicFormActionType,
  89 + });
  90 +
  91 + const { createMessage } = useMessage();
  92 + const handleSave = async () => {
  93 + await validate?.();
  94 + setDrawerProps({ loading: true, confirmLoading: true });
  95 + try {
  96 + const value = getFieldsValue();
  97 +
  98 + if (unref(drawerMode) === DataActionModeEnum.UPDATE) {
  99 + Reflect.set(value, 'id', unref(currentUpdateLinkedgeId));
  100 + }
  101 +
  102 + await screenLinkPageAddApi(value, unref(drawerMode) === DataActionModeEnum.UPDATE);
  103 + createMessage.success(`${DataActionModeNameEnum[unref(drawerMode)]}成功`);
  104 + closeDrawer();
  105 + emit('success');
  106 + } finally {
  107 + setDrawerProps({ loading: false, confirmLoading: false });
  108 + }
  109 + };
  110 +</script>
  111 +
  112 +<template>
  113 + <BasicDrawer
  114 + @register="register"
  115 + :title="getDrawTitle"
  116 + width="50%"
  117 + @ok="handleSave"
  118 + show-cancel-btn
  119 + show-footer
  120 + >
  121 + <BasicForm @register="registerForm" :disabled="disabledDrawer">
  122 + <template #flipFlop>
  123 + <Divider orientation="left">
  124 + <Tooltip>
  125 + <template #title> 触发器不可为空,消息只要满足触发条件中任意一个即可触发。 </template>
  126 + <label><span class="text-red-400 mr-1">*</span>触发器</label>
  127 + <Icon icon="ant-design:question-circle-outlined" class="ml-2" />
  128 + </Tooltip>
  129 + </Divider>
  130 + <FlipFlop ref="flipFlopElRef" />
  131 + </template>
  132 + <template #executionCondition>
  133 + <Divider orientation="left">
  134 + <Tooltip>
  135 + <template #title> 执行条件可为空,消息需要满足所有执行条件才会被处理。 </template>
  136 + <label>执行条件</label>
  137 + <Icon icon="ant-design:question-circle-outlined" class="ml-2" />
  138 + </Tooltip>
  139 + </Divider>
  140 + <FlipFlop
  141 + ref="executionConditionElRef"
  142 + addButtonName="执行条件 (AND)"
  143 + default-null
  144 + :panel-title="(index) => `执行条件${index}`"
  145 + />
  146 + </template>
  147 + <template #executionAction>
  148 + <Divider orientation="left">
  149 + <Tooltip>
  150 + <template #title>
  151 + 触发器和执行条件都满足时,场景联动会做什么,例如:设备联动、告警通知等。
  152 + </template>
  153 + <label><span class="text-red-400 mr-1">*</span>执行动作</label>
  154 + <Icon icon="ant-design:question-circle-outlined" class="ml-2" />
  155 + </Tooltip>
  156 + </Divider>
  157 + <ExecutionAction ref="executionActionElRef" />
  158 + </template>
  159 + </BasicForm>
  160 + </BasicDrawer>
  161 +</template>
  1 +import type { ComputedRef, InjectionKey } from 'vue';
  2 +import { createContext, useContext } from '/@/hooks/core/useContext';
  3 +
  4 +export interface SceneLinkageDrawerContextProps {
  5 + organizationId: ComputedRef<string | undefined>;
  6 + disabledDrawer: ComputedRef<boolean>;
  7 +}
  8 +
  9 +const key: InjectionKey<SceneLinkageDrawerContextProps> = Symbol('scene-linkage-drawer-context');
  10 +
  11 +export function createSceneLinkageDrawerContext(context: SceneLinkageDrawerContextProps) {
  12 + return createContext<SceneLinkageDrawerContextProps>(context, key, { native: true });
  13 +}
  14 +
  15 +export function useSceneLinkageDrawerContext() {
  16 + return useContext<SceneLinkageDrawerContextProps>(key);
  17 +}
  1 +import { ExecutionActionDataItemType } from '../ExecutionAction/type';
  2 +import { FlipFlopConditionType } from '../FlipFlop/types';
  3 +import { FormFieldsEnum } from './config';
  4 +export type SceneLinkageDrawerDataType = ModalParamsType<SceneLinkageDataType>;
  5 +
  6 +export interface SceneLinkageFormRecordType {
  7 + [FormFieldsEnum.NAME]: string;
  8 + [FormFieldsEnum.ORGANIZATION_ID]: string;
  9 + [FormFieldsEnum.DESCRIPTION]: string;
  10 +}
  11 +
  12 +export interface SceneLinkageDataType {
  13 + id?: string;
  14 + doActions: ExecutionActionDataItemType[];
  15 + triggers: FlipFlopConditionType[];
  16 + doConditions: FlipFlopConditionType[];
  17 + name: string;
  18 + description?: string;
  19 + organizationId: string;
  20 +}
  1 +import { Ref, unref } from 'vue';
  2 +import { ExecutionAction } from '../ExecutionAction';
  3 +import { FlipFlop } from '../FlipFlop';
  4 +import { DefineComponentsBasicExpose } from '/#/utils';
  5 +import { SceneLinkageDataType, SceneLinkageFormRecordType } from './type';
  6 +import { FormActionType } from '/@/components/Form';
  7 +
  8 +interface UseSceneLinkageDataParamsType {
  9 + flipFlopElRef: Ref<InstanceType<typeof FlipFlop> | undefined>;
  10 + executionConditionElRef: Ref<InstanceType<typeof FlipFlop> | undefined>;
  11 + executionActionElRef: Ref<InstanceType<typeof ExecutionAction> | undefined>;
  12 + basicFormActionType: FormActionType;
  13 +}
  14 +
  15 +export function useSceneLinkageData({
  16 + flipFlopElRef,
  17 + executionActionElRef,
  18 + executionConditionElRef,
  19 + basicFormActionType,
  20 +}: UseSceneLinkageDataParamsType): DefineComponentsBasicExpose<SceneLinkageDataType> {
  21 + const getFieldsValue = () => {
  22 + const doActions = unref(executionActionElRef)?.getFieldsValue() || [];
  23 + const doConditions = unref(executionConditionElRef)?.getFieldsValue() || [];
  24 + const triggers = unref(flipFlopElRef)?.getFieldsValue() || [];
  25 + const { name, description, organizationId } =
  26 + basicFormActionType.getFieldsValue() as SceneLinkageFormRecordType;
  27 +
  28 + return {
  29 + name,
  30 + description,
  31 + organizationId,
  32 + doActions,
  33 + doConditions,
  34 + triggers,
  35 + };
  36 + };
  37 +
  38 + const setFieldsValue = (record: SceneLinkageDataType) => {
  39 + const { doActions, doConditions, triggers, name, description, organizationId } = record;
  40 +
  41 + basicFormActionType.setFieldsValue({ name, description, organizationId });
  42 +
  43 + unref(flipFlopElRef)?.setFieldsValue(triggers);
  44 +
  45 + unref(executionConditionElRef)?.setFieldsValue(doConditions);
  46 +
  47 + unref(executionActionElRef)?.setFieldsValue(doActions);
  48 + };
  49 +
  50 + const validate = async () => {
  51 + await basicFormActionType.validate?.();
  52 + await unref(flipFlopElRef)?.validate?.();
  53 + await unref(executionConditionElRef)?.validate?.();
  54 + await unref(executionActionElRef)?.validate?.();
  55 + };
  56 +
  57 + const resetFieldsValue = () => {
  58 + basicFormActionType.resetFields();
  59 + unref(flipFlopElRef)?.resetFieldsValue?.();
  60 + unref(executionConditionElRef)?.resetFieldsValue?.(true);
  61 + unref(executionActionElRef)?.resetFieldsValue?.();
  62 + };
  63 +
  64 + return {
  65 + getFieldsValue,
  66 + setFieldsValue,
  67 + validate,
  68 + resetFieldsValue,
  69 + };
  70 +}
1 import { formatToDateTime } from '/@/utils/dateUtil'; 1 import { formatToDateTime } from '/@/utils/dateUtil';
2 -import { Number_Operation, String_Operation, Boolean_Operation } from '/@/enums/operationEnum'; 2 +import {
  3 + NumberOperationEnum,
  4 + StringOperationEnum,
  5 + BooleanOperationEnum,
  6 +} from '../../../../enums/linkedgeEnum';
3 7
4 // 生成触发器或执行条件JSON数据 8 // 生成触发器或执行条件JSON数据
5 export const genTriggerOrConditionData = (triggerData) => { 9 export const genTriggerOrConditionData = (triggerData) => {
@@ -164,26 +168,26 @@ export const genActionData = (actionData) => { @@ -164,26 +168,26 @@ export const genActionData = (actionData) => {
164 }; 168 };
165 169
166 export const operationNumber_OR_TIME = [ 170 export const operationNumber_OR_TIME = [
167 - { label: '等于', value: Number_Operation.EQUAL, symbol: '=' },  
168 - { label: '不等于', value: Number_Operation.NOT_EQUAL, symbol: '!=' },  
169 - { label: '小于', value: Number_Operation.LESS, symbol: '<' },  
170 - { label: '小于等于', value: Number_Operation.LESS_OR_EQUAL, symbol: '<=' },  
171 - { label: '大于', value: Number_Operation.GREATER, symbol: '>' },  
172 - { label: '大于等于', value: Number_Operation.GREATER_OR_EQUAL, symbol: '>=' }, 171 + { label: '等于', value: NumberOperationEnum.EQUAL, symbol: '=' },
  172 + { label: '不等于', value: NumberOperationEnum.NOT_EQUAL, symbol: '!=' },
  173 + { label: '小于', value: NumberOperationEnum.LESS, symbol: '<' },
  174 + { label: '小于等于', value: NumberOperationEnum.LESS_OR_EQUAL, symbol: '<=' },
  175 + { label: '大于', value: NumberOperationEnum.GREATER, symbol: '>' },
  176 + { label: '大于等于', value: NumberOperationEnum.GREATER_OR_EQUAL, symbol: '>=' },
173 ]; 177 ];
174 178
175 export const operationString = [ 179 export const operationString = [
176 - { label: '等于', value: String_Operation.EQUAL, symbol: '=' },  
177 - { label: '不等于', value: String_Operation.NOT_EQUAL, symbol: '!=' },  
178 - { label: '开始于', value: String_Operation.BEGAN_IN, symbol: '开始于' },  
179 - { label: '结束于', value: String_Operation.END_IN, symbol: '结束于' },  
180 - { label: '包含', value: String_Operation.INCLUDE, symbol: '包含' },  
181 - { label: '不包含', value: String_Operation.NOT_INCLUDE, symbol: '不包含' }, 180 + { label: '等于', value: StringOperationEnum.EQUAL, symbol: '=' },
  181 + { label: '不等于', value: StringOperationEnum.NOT_EQUAL, symbol: '!=' },
  182 + { label: '开始于', value: StringOperationEnum.STARTS_WITH, symbol: '开始于' },
  183 + { label: '结束于', value: StringOperationEnum.ENDS_WITH, symbol: '结束于' },
  184 + { label: '包含', value: StringOperationEnum.CONTAINS, symbol: '包含' },
  185 + { label: '不包含', value: StringOperationEnum.NOT_CONTAINS, symbol: '不包含' },
182 ]; 186 ];
183 187
184 export const operationBoolean = [ 188 export const operationBoolean = [
185 - { label: '等于', value: Boolean_Operation.EQUAL, symbol: '=' },  
186 - { label: '不等于', value: Boolean_Operation.NOT_EQUAL, symbol: '!=' }, 189 + { label: '等于', value: BooleanOperationEnum.EQUAL, symbol: '=' },
  190 + { label: '不等于', value: BooleanOperationEnum.NOT_EQUAL, symbol: '!=' },
187 ]; 191 ];
188 192
189 export const scheduleOptions = [ 193 export const scheduleOptions = [
@@ -376,7 +380,8 @@ export function isType(operationType) { @@ -376,7 +380,8 @@ export function isType(operationType) {
376 } 380 }
377 export function conditionPreView(data, operationType) { 381 export function conditionPreView(data, operationType) {
378 if (operationType === 'NUMERIC' || operationType === 'DATE_TIME') { 382 if (operationType === 'NUMERIC' || operationType === 'DATE_TIME') {
379 - const { EQUAL, NOT_EQUAL, LESS, LESS_OR_EQUAL, GREATER, GREATER_OR_EQUAL } = Number_Operation; 383 + const { EQUAL, NOT_EQUAL, LESS, LESS_OR_EQUAL, GREATER, GREATER_OR_EQUAL } =
  384 + NumberOperationEnum;
380 return data.map((item) => { 385 return data.map((item) => {
381 return { 386 return {
382 operation: 387 operation:
@@ -398,7 +403,14 @@ export function conditionPreView(data, operationType) { @@ -398,7 +403,14 @@ export function conditionPreView(data, operationType) {
398 }; 403 };
399 }); 404 });
400 } else if (operationType === 'STRING') { 405 } else if (operationType === 'STRING') {
401 - const { EQUAL, NOT_EQUAL, BEGAN_IN, END_IN, INCLUDE, NOT_INCLUDE } = String_Operation; 406 + const {
  407 + EQUAL,
  408 + NOT_EQUAL,
  409 + STARTS_WITH: BEGAN_IN,
  410 + ENDS_WITH: END_IN,
  411 + CONTAINS: INCLUDE,
  412 + NOT_CONTAINS: NOT_INCLUDE,
  413 + } = StringOperationEnum;
402 return data.map((item) => { 414 return data.map((item) => {
403 return { 415 return {
404 operation: 416 operation:
@@ -420,7 +432,7 @@ export function conditionPreView(data, operationType) { @@ -420,7 +432,7 @@ export function conditionPreView(data, operationType) {
420 }; 432 };
421 }); 433 });
422 } else if (operationType === 'BOOLEAN') { 434 } else if (operationType === 'BOOLEAN') {
423 - const { EQUAL, NOT_EQUAL } = Boolean_Operation; 435 + const { EQUAL, NOT_EQUAL } = BooleanOperationEnum;
424 return data.map((item) => { 436 return data.map((item) => {
425 return { 437 return {
426 operation: 438 operation:
1 -<template>  
2 - <BasicModal  
3 - v-bind="$attrs"  
4 - :width="600"  
5 - :title="isUpdateFlag ? '编辑' + title : '新增' + title"  
6 - centered  
7 - @register="registerModal"  
8 - @ok="handleOk"  
9 - @cancel="handleCancel"  
10 - >  
11 - <BasicForm @register="registerForm" ref="basicFormRef">  
12 - <template #customEnable>  
13 - <template v-for="(item, optionIndex) in options" :key="item.flag">  
14 - <div :class="optionIndex >= 1 ? 'mt-4' : ''" class="flex">  
15 - <div class="ml-4 mr-4 flex items-center">  
16 - <Checkbox v-model:checked="item.enabled">星期{{ item.flag }}</Checkbox>  
17 - </div>  
18 - <TimePicker  
19 - placeholder="开始时间"  
20 - v-model:value="item.startsOn"  
21 - value-format="x"  
22 - format="HH:mm"  
23 - :disabled="!item.enabled"  
24 - />  
25 - <span class="ml-4 mr-4 flex items-center">~</span>  
26 - <TimePicker  
27 - @change="handleBlur(item.startsOn, item.endsOn)"  
28 - placeholder="结束时间"  
29 - v-model:value="item.endsOn"  
30 - value-format="x"  
31 - format="HH:mm"  
32 - :disabled="!item.enabled"  
33 - />  
34 - </div>  
35 - </template>  
36 - </template>  
37 - <template #timing>  
38 - <TimePicker  
39 - placeholder="开始时间"  
40 - v-model:value="timeState.startsOn"  
41 - value-format="x"  
42 - format="HH:mm"  
43 - />  
44 - <span class="ml-4 mr-4">~</span>  
45 - <TimePicker  
46 - @change="handleTimeBlur(timeState.startsOn, timeState.endsOn)"  
47 - placeholder="结束时间"  
48 - v-model:value="timeState.endsOn"  
49 - value-format="x"  
50 - format="HH:mm"  
51 - />  
52 - </template>  
53 - </BasicForm>  
54 - </BasicModal>  
55 -</template>  
56 -  
57 -<script lang="ts" setup>  
58 - import { reactive, ref, watch, nextTick } from 'vue';  
59 - import { useModalInner, BasicModal } from '/@/components/Modal';  
60 - import { BasicForm, useForm } from '/@/components/Form';  
61 - import { alarmScheduleSchemas } from '../config/config.data';  
62 - import { Checkbox, TimePicker } from 'ant-design-vue';  
63 - import { useMessage } from '/@/hooks/web/useMessage';  
64 -  
65 - const emit = defineEmits(['register', 'cancel']);  
66 - const title = ref('');  
67 - const isUpdateFlag = ref(false);  
68 - const { createMessage } = useMessage();  
69 - const [registerForm, { setFieldsValue, getFieldsValue }] = useForm({  
70 - showActionButtonGroup: false,  
71 - schemas: alarmScheduleSchemas,  
72 - });  
73 - const timeState = reactive({  
74 - startsOn: null,  
75 - endsOn: null,  
76 - });  
77 -  
78 - const options = ref([  
79 - {  
80 - enabled: false,  
81 - dayOfWeek: 1,  
82 - flag: '一',  
83 - endsOn: null,  
84 - startsOn: null,  
85 - },  
86 - {  
87 - enabled: false,  
88 - dayOfWeek: 2,  
89 - flag: '二',  
90 - endsOn: null,  
91 - startsOn: null,  
92 - },  
93 - {  
94 - enabled: false,  
95 - dayOfWeek: 3,  
96 - flag: '三',  
97 - endsOn: null,  
98 - startsOn: null,  
99 - },  
100 - {  
101 - enabled: false,  
102 - dayOfWeek: 4,  
103 - flag: '四',  
104 - endsOn: null,  
105 - startsOn: null,  
106 - },  
107 - {  
108 - enabled: false,  
109 - dayOfWeek: 5,  
110 - flag: '五',  
111 - endsOn: null,  
112 - startsOn: null,  
113 - },  
114 - {  
115 - enabled: false,  
116 - dayOfWeek: 6,  
117 - flag: '六',  
118 - endsOn: null,  
119 - startsOn: null,  
120 - },  
121 - {  
122 - enabled: false,  
123 - dayOfWeek: 7,  
124 - flag: '日',  
125 - endsOn: null,  
126 - startsOn: null,  
127 - },  
128 - ]);  
129 - const basicFormRef = ref<InstanceType<typeof BasicForm>>();  
130 - let index = ref(null);  
131 - watch(  
132 - options,  
133 - (newValue) => {  
134 - const arr = [];  
135 - for (let item of newValue) {  
136 - if (item.enabled && item.startsOn && item.endsOn) {  
137 - arr.push(true);  
138 - } else if ((!item.enabled && item.startsOn && item.endsOn) || item.enabled) {  
139 - arr.push(false);  
140 - }  
141 - }  
142 - const flag = arr.length ? !arr.every((item) => item) : true;  
143 - nextTick(() => {  
144 - setModalProps({  
145 - okButtonProps: {  
146 - disabled: flag,  
147 - },  
148 - });  
149 - });  
150 - },  
151 - {  
152 - deep: true,  
153 - }  
154 - );  
155 -  
156 - const [registerModal, { closeModal, setModalProps }] = useModalInner((data) => {  
157 - watch([timeState, basicFormRef.value.formModel], ([timeState, formModel]) => {  
158 - setModalProps({  
159 - okButtonProps: {  
160 - disabled:  
161 - timeState.startsOn === null ||  
162 - timeState.endsOn === null ||  
163 - !formModel.daysOfWeek?.length,  
164 - },  
165 - });  
166 - watch(  
167 - () => formModel.schedule,  
168 - () => {  
169 - timeState.startsOn = null;  
170 - timeState.endsOn = null;  
171 - }  
172 - );  
173 - });  
174 - const { value, currentIndex, isUpdate, scheduleData } = data;  
175 - isUpdateFlag.value = isUpdate;  
176 - if (value === 'SPECIFIC_TIME') {  
177 - title.value = '定时启用';  
178 - } else {  
179 - title.value = '自定义启用';  
180 - }  
181 -  
182 - index.value = currentIndex;  
183 - const dayZenoTime = Math.round(new Date(new Date().toLocaleDateString()).getTime());  
184 - // 编辑  
185 - setFieldsValue({  
186 - schedule: value,  
187 - });  
188 - if (isUpdate) {  
189 - nextTick(() => {  
190 - // 回显定时启用  
191 - if (scheduleData.type === 'SPECIFIC_TIME') {  
192 - setFieldsValue({  
193 - daysOfWeek: scheduleData.daysOfWeek,  
194 - });  
195 - timeState.startsOn = scheduleData.startsOn + dayZenoTime + '';  
196 - timeState.endsOn = scheduleData.endsOn + dayZenoTime + '';  
197 - }  
198 - // 回显自定义启用  
199 - if (scheduleData.type === 'CUSTOM') {  
200 - for (let [index, item] of scheduleData?.items.entries()) {  
201 - if (item.enabled) {  
202 - options.value[index].enabled = item.enabled;  
203 - options.value[index].startsOn = item.startsOn + dayZenoTime + '';  
204 - options.value[index].endsOn = item.endsOn + dayZenoTime + '';  
205 - }  
206 - }  
207 - }  
208 - });  
209 - }  
210 - });  
211 - const scheduleData = ref({  
212 - type: 'ANY_TIME',  
213 - });  
214 - const handleBlur = (eS, eE) => {  
215 - if (eS > eE) {  
216 - return createMessage.warn('开始时间不能大于结束时间');  
217 - }  
218 - };  
219 - const handleTimeBlur = (eS, eE) => {  
220 - if (eS > eE) {  
221 - return createMessage.warn('开始时间不能大于结束时间');  
222 - }  
223 - };  
224 - const handleOk = () => {  
225 - const { schedule: type, timezone, daysOfWeek } = getFieldsValue();  
226 - // 获取当天0时时间戳  
227 - const dayZenoTime = Math.round(new Date(new Date().toLocaleDateString()).getTime());  
228 - if (type === 'CUSTOM') {  
229 - const items = options.value.map((item) => {  
230 - return {  
231 - startsOn: item.startsOn ? item.startsOn - dayZenoTime : 0,  
232 - endsOn: item.endsOn ? item.endsOn - dayZenoTime : 0,  
233 - dayOfWeek: item.dayOfWeek,  
234 - enabled: item.enabled,  
235 - };  
236 - });  
237 - scheduleData.value = {  
238 - type,  
239 - timezone,  
240 - items,  
241 - };  
242 - } else if (type === 'SPECIFIC_TIME') {  
243 - scheduleData.value = {  
244 - type,  
245 - timezone,  
246 - daysOfWeek,  
247 - startsOn: timeState.startsOn - dayZenoTime,  
248 - endsOn: timeState.endsOn - dayZenoTime,  
249 - };  
250 - }  
251 - closeModal();  
252 - };  
253 - const handleCancel = () => {  
254 - emit('cancel', index.value);  
255 - };  
256 -  
257 - defineExpose({  
258 - scheduleData,  
259 - });  
260 -</script>  
261 -  
262 -<style lang="less" scoped>  
263 - :deep(.ant-time-picker) {  
264 - width: 12rem;  
265 - }  
266 -</style>  
1 -<template>  
2 - <div>  
3 - <CollapseContainer style="background-color: #f2f2f2" title="清除告警" :canExpan="false">  
4 - <template #action>  
5 - <div class="flex">  
6 - <div class="flex">  
7 - <span class="mr-2">启用规则:</span>  
8 - <template v-for="(item, scheduleIndex) in scheduleOptions" :key="item.label">  
9 - <div  
10 - :class="{ 'ml-4': scheduleIndex >= 1, active: scheduleIndex === currentIndex }"  
11 - class="cursor-pointer"  
12 - @click="handleScheduleChange(item.value)"  
13 - >{{ item.label }}</div  
14 - >  
15 - </template>  
16 - </div>  
17 - <Tooltip title="移除" class="ml-4">  
18 - <Icon  
19 - icon="fluent:delete-off-20-regular"  
20 - size="20"  
21 - class="mr-2 cursor-pointer"  
22 - @click="handleDelete(index)"  
23 - v-if="clearRuleList.length > 1"  
24 - />  
25 - </Tooltip>  
26 - </div>  
27 - </template>  
28 - <BasicForm @register="registerForm">  
29 - <template #operationType="{ model, field }">  
30 - <Select  
31 - :options="options"  
32 - :disabled="disabled"  
33 - v-model:value="model[field]"  
34 - @change="operationType = model[field]"  
35 - placeholder="请选择比较类型"  
36 - allowClear  
37 - />  
38 - </template>  
39 - <template #time="{ model, field }">  
40 - <Input v-model:value="model[field]" placeholder="请输入持续时间">  
41 - <template #addonAfter>  
42 - <Select  
43 - :disabled="disabled"  
44 - v-model:value="model[`timeUnit`]"  
45 - :options="timeUnitOptions"  
46 - style="width: 60px"  
47 - />  
48 - </template>  
49 - </Input>  
50 - </template>  
51 - </BasicForm>  
52 - <Card size="small" :bordered="false" style="border: 2px dashed #d9d9d9" v-if="operationType">  
53 - <ConditionScreening  
54 - :childGetFieldsValue="childGetFieldsValue"  
55 - ref="conditionScreeningRef"  
56 - />  
57 - </Card>  
58 - </CollapseContainer>  
59 - <AlarmSchedule ref="alarmScheduleRef" @register="registerModal" @cancel="handleCancel" />  
60 - </div>  
61 -</template>  
62 -<script lang="ts" setup>  
63 - import { ref, provide, nextTick, unref } from 'vue';  
64 - import { CollapseContainer } from '/@/components/Container/index';  
65 - import { BasicForm, useForm } from '/@/components/Form/index';  
66 - import { Card, Select, Input, Tooltip } from 'ant-design-vue';  
67 - import { trigger_condition_schema, TOption } from '../config/config.data';  
68 - import { getAttribute } from '/@/api/ruleengine/ruleengineApi';  
69 - import ConditionScreening from './ConditionScreening.vue';  
70 - import { scheduleOptions, timeUnitOptions, options } from '../config/formatData';  
71 - import { Icon } from '/@/components/Icon';  
72 - import AlarmSchedule from './AlarmSchedule.vue';  
73 - import { useModal } from '/@/components/Modal';  
74 - import { cloneDeep } from 'lodash-es';  
75 - import useCommonFun from '../hooks/useCommonFun';  
76 -  
77 - const { useByProductGetAttribute } = useCommonFun();  
78 - defineProps({  
79 - index: {  
80 - type: Number,  
81 - required: true,  
82 - },  
83 - clearRuleList: {  
84 - type: Array,  
85 - default: () => [],  
86 - },  
87 - });  
88 - const emit = defineEmits(['delete']);  
89 - const isUpdate = ref(false);  
90 - const conditionScreeningRef = ref();  
91 - const [registerForm, { resetFields, getFieldsValue, updateSchema, setFieldsValue, setProps }] =  
92 - useForm({  
93 - //TODO-wenwei-修复  
94 - schemas: cloneDeep(trigger_condition_schema),  
95 - //TODO-wenwei-修复  
96 - showActionButtonGroup: false,  
97 - });  
98 -  
99 - const alarmScheduleRef = ref<InstanceType<typeof AlarmSchedule>>();  
100 - const getFieldsValueFunc = () => {  
101 - const predicate = conditionScreeningRef?.value?.refItem?.conditionScreeningRefs?.value?.map(  
102 - (item) => item.getFieldsValue()  
103 - );  
104 - return { ...getFieldsValue(), predicate, schedule: alarmScheduleRef.value.scheduleData };  
105 - };  
106 -  
107 - const updateFieldDeviceId = () => {  
108 - // console.log(deviceList);  
109 - // console.log(isUpdate);  
110 - };  
111 -  
112 - const resetFieldsValueFunc = () => resetFields();  
113 - // 回显数据函数  
114 - const setFieldsFormValueFun = (fieldsValue) => {  
115 - setFieldsValue(fieldsValue);  
116 - };  
117 - const updateFieldAttributeFunc = async (e) => {  
118 - const res = await getAttribute(e);  
119 - const options = ref<TOption[]>([]);  
120 - useByProductGetAttribute(res, updateSchema, options);  
121 - };  
122 - //TODO-fengtao  
123 - const schedule = ref('ANY_TIME');  
124 - const operationType = ref<string>('');  
125 - provide('operationType', operationType);  
126 -  
127 - const handleDelete = (index: number) => {  
128 - emit('delete', index);  
129 - };  
130 -  
131 - // 子组件获取父组件的值  
132 - const childGetFieldsValue = () => getFieldsValue();  
133 -  
134 - // 获取conditionScreeningForm的组件  
135 - const getRefItemConditionScreeningRefs = async () => {  
136 - await nextTick();  
137 - return conditionScreeningRef.value.refItem.conditionScreeningRefs;  
138 - };  
139 -  
140 - const setConditionScreeningList = (list) => {  
141 - conditionScreeningRef.value.conditionScreeningList = list;  
142 - };  
143 - const setRichText = (list) => {  
144 - conditionScreeningRef.value.otherAttribute = list;  
145 - };  
146 -  
147 - const currentIndex = ref(0);  
148 - const [registerModal, { openModal }] = useModal();  
149 - const handleScheduleChange = (value) => {  
150 - if (unref(disabled)) return;  
151 - const index = scheduleOptions.findIndex((item) => item.value === value);  
152 - // 报警日程弹窗  
153 - if (index !== 0) {  
154 - openModal(true, {  
155 - isUpdate: isUpdate.value,  
156 - value,  
157 - currentIndex: currentIndex.value,  
158 - scheduleData,  
159 - });  
160 - } else {  
161 - alarmScheduleRef.value.scheduleData = {  
162 - type: value,  
163 - };  
164 - }  
165 - currentIndex.value = index;  
166 - };  
167 - const handleCancel = (index) => {  
168 - currentIndex.value = index;  
169 - };  
170 - const scheduleData = ref(null);  
171 -  
172 - const disabled = ref<boolean>(false);  
173 - const setDisabledProps = (value) => {  
174 - setProps(value);  
175 - disabled.value = true;  
176 - };  
177 -  
178 - const setCancelDisabled = () => {  
179 - disabled.value = false;  
180 - };  
181 - defineExpose({  
182 - getFieldsValue,  
183 - updateFieldDeviceId,  
184 - resetFieldsValueFunc,  
185 - setFieldsFormValueFun,  
186 - childGetFieldsValue,  
187 - conditionScreeningRef,  
188 - schedule,  
189 - operationType,  
190 - getFieldsValueFunc,  
191 - getRefItemConditionScreeningRefs,  
192 - setConditionScreeningList,  
193 - setRichText,  
194 - currentIndex,  
195 - scheduleData,  
196 - isUpdate,  
197 - alarmScheduleRef,  
198 - updateFieldAttributeFunc,  
199 - setDisabledProps,  
200 - setCancelDisabled,  
201 - });  
202 -</script>  
203 -<style>  
204 - .active {  
205 - color: #377dff;  
206 - }  
207 -</style>  
1 -<template>  
2 - <div>  
3 - <CollapseContainer ref="collapseContainerRef" @expand="handleExpand">  
4 - <template #title>  
5 - <div>条件筛选</div>  
6 - <RichText :otherAttribute="otherAttribute" @resetFilter="resetFilter" />  
7 - </template>  
8 -  
9 - <template v-for="(item, index) in conditionScreeningList" :key="item">  
10 - <ConditionScreeningForm  
11 - :conditionScreeningList="conditionScreeningList"  
12 - :ref="refItem.conditionScreeningRefs"  
13 - :index="index"  
14 - @deleteConditionForm="deleteConditionForm"  
15 - />  
16 - </template>  
17 - </CollapseContainer>  
18 -  
19 - <div class="flex justify-between">  
20 - <a-button  
21 - :disabled="isViewDisabledBtn == 'isView' ? true : false"  
22 - type="primary"  
23 - class="mt-4 ml-2"  
24 - @click="addConditionForm"  
25 - >新增条件筛选</a-button  
26 - >  
27 - <a-button  
28 - :disabled="isViewDisabledBtn == 'isView' ? true : false"  
29 - type="primary"  
30 - class="mt-4 mr-2"  
31 - @click="preView"  
32 - v-if="isPreview"  
33 - >保存</a-button  
34 - >  
35 - </div>  
36 - </div>  
37 -</template>  
38 -  
39 -<script lang="ts" setup>  
40 - import { unref, ref } from 'vue';  
41 - import ConditionScreeningForm from './ConditionScreeningForm.vue';  
42 - import { conditionPreView } from '../config/formatData.ts';  
43 - import { CollapseContainer } from '/@/components/Container/index';  
44 - import RichText from './RichText.vue';  
45 - const props = defineProps({  
46 - childGetFieldsValue: {  
47 - type: Function,  
48 - required: true,  
49 - },  
50 - });  
51 - const refItem = {  
52 - conditionScreeningRefs: ref([]),  
53 - };  
54 - const isViewDisabledBtn = window.localStorage.getItem('isViewDisabledBtn');  
55 - const isPreview = ref(true);  
56 - const collapseContainerRef = ref();  
57 - const conditionScreeningList = ref([Date.now()]);  
58 - const addConditionForm = () => {  
59 - if (!unref(isPreview)) {  
60 - collapseContainerRef.value.handleExpand();  
61 - }  
62 - unref(conditionScreeningList).push(Date.now());  
63 - const lastIndex = refItem.conditionScreeningRefs.value.length - 1;  
64 - refItem.conditionScreeningRefs.value[lastIndex]?.appendSchemaByField(  
65 - {  
66 - field: 'AND',  
67 - label: '和',  
68 - component: 'Input',  
69 - slot: 'and',  
70 - // labelWidth: 50,  
71 - colProps: { span: 3 },  
72 - },  
73 - 'value'  
74 - );  
75 - };  
76 - const handleExpand = (show) => {  
77 - isPreview.value = show;  
78 - };  
79 -  
80 - const otherAttribute = ref([]);  
81 - // 预览条件筛选结果  
82 - const preView = async () => {  
83 - const attributes = [];  
84 - const fieldsValue = props.childGetFieldsValue();  
85 - for (let i = 0; i < unref(refItem.conditionScreeningRefs).length; i++) {  
86 - const valid = await unref(refItem.conditionScreeningRefs)[i].validate();  
87 - if (!valid) return;  
88 - attributes.push({  
89 - ...unref(refItem.conditionScreeningRefs)[i].getFieldsValue(),  
90 - attribute: fieldsValue.type2,  
91 - });  
92 - }  
93 - otherAttribute.value = conditionPreView(attributes, fieldsValue.operationType);  
94 - // collapseContainerRef.value.handleExpand();  
95 - collapseContainerRef.value.show = true;  
96 - };  
97 -  
98 - //ft add 表单数据置空  
99 - const resetConditionScreenForm = () => {  
100 - conditionScreeningList.value = [];  
101 - resetFilter();  
102 - for (let i = 0; i < unref(refItem.conditionScreeningRefs).length; i++) {  
103 - unref(refItem.conditionScreeningRefs)[i]?.resetConditionScreenForm();  
104 - }  
105 - };  
106 - //ft add 表单数据置空  
107 -  
108 - const resetFilter = () => {  
109 - otherAttribute.value = [];  
110 - };  
111 - const deleteConditionForm = (index) => {  
112 - unref(conditionScreeningList).splice(index, 1);  
113 - const lastIndex = refItem.conditionScreeningRefs.value.length - 2;  
114 - refItem.conditionScreeningRefs.value[lastIndex]?.removeSchemaByFiled('AND');  
115 - };  
116 -  
117 - defineExpose({  
118 - refItem,  
119 - conditionScreeningList,  
120 - otherAttribute,  
121 - resetConditionScreenForm,  
122 - });  
123 -</script>  
124 -  
125 -<style lang="less" scoped>  
126 - :deep(.vben-collapse-container__header) {  
127 - overflow: hidden !important;  
128 - overflow-y: scroll !important;  
129 - white-space: pre !important;  
130 - height: 93px !important;  
131 - }  
132 -</style>  
1 -<template>  
2 - <div class="flex" style="align-items: center">  
3 - <BasicForm @register="registerFormCondition" class="basicStyle" />  
4 - <Tooltip title="移除" class="ml-4">  
5 - <Icon  
6 - icon="fluent:delete-off-20-regular"  
7 - size="20"  
8 - class="mr-2 cursor-pointer"  
9 - @click="deleteConditionForm(index)"  
10 - />  
11 - </Tooltip>  
12 - </div>  
13 -</template>  
14 -  
15 -<script lang="ts" setup>  
16 - import { watch, ref, onMounted, inject } from 'vue';  
17 - import { BasicForm, useForm } from '/@/components/Form/index';  
18 - import { Icon } from '/@/components/Icon';  
19 - import { Tooltip } from 'ant-design-vue';  
20 - import { isType } from '../config/formatData';  
21 - defineProps({  
22 - index: {  
23 - type: Number,  
24 - required: true,  
25 - },  
26 - conditionScreeningList: {  
27 - type: Array,  
28 - default: () => [],  
29 - },  
30 - });  
31 - const emit = defineEmits(['deleteConditionForm']);  
32 - const operationType = inject('operationType');  
33 - let schemas = ref([]);  
34 - const isViewDisabledBtn = window.localStorage.getItem('isViewDisabledBtn');  
35 - onMounted(() => {  
36 - schemas.value = isType(operationType.value);  
37 -  
38 - if (isViewDisabledBtn == 'isView') setProps({ disabled: true });  
39 - });  
40 - watch(operationType, (newValue) => {  
41 - schemas.value = isType(newValue);  
42 - resetFields();  
43 - });  
44 - const [  
45 - registerFormCondition,  
46 - {  
47 - resetFields,  
48 - appendSchemaByField,  
49 - removeSchemaByFiled,  
50 - getFieldsValue,  
51 - setFieldsValue,  
52 - validate,  
53 - setProps,  
54 - },  
55 - ] = useForm({  
56 - showActionButtonGroup: false,  
57 - labelWidth: 100,  
58 - compact: true,  
59 - schemas,  
60 - });  
61 - const deleteConditionForm = (index: number) => {  
62 - if (isViewDisabledBtn == 'isView') return;  
63 - emit('deleteConditionForm', index);  
64 - };  
65 - // ft add  
66 - const resetConditionScreenForm = () => resetFields();  
67 - // ft add  
68 - defineExpose({  
69 - appendSchemaByField,  
70 - removeSchemaByFiled,  
71 - getFieldsValue,  
72 - setFieldsValue,  
73 - validate,  
74 - resetConditionScreenForm,  
75 - });  
76 -</script>  
77 -  
78 -<style lang="less">  
79 - .basicStyle {  
80 - margin-top: 0.5rem;  
81 - width: 80%;  
82 - border: 1px dashed #d9d9d9;  
83 - padding: 1rem 0 0.5rem;  
84 - border-radius: 0.25rem;  
85 - }  
86 -</style>  
1 -<template>  
2 - <div  
3 - style="width: 100%; flex-wrap: wrap"  
4 - v-if="otherAttribute.length > 0 && otherAttribute[0].value && otherAttribute[0].operation"  
5 - class="flex"  
6 - >  
7 - <template v-for="(item, index) in otherAttribute" :key="item.value">  
8 - <div class="flex" v-if="item?.value && item?.attribute">  
9 - <div class="text mr-2" style="color: #305680">{{ item.attribute }}</div>  
10 - {{ item.operation }}  
11 - <div class="text ml-2" style="color: #ff8c68">{{ item.value }}</div>  
12 - <span v-if="otherAttribute[index + 1]">和</span>  
13 - </div>  
14 - </template>  
15 - </div>  
16 -</template>  
17 -  
18 -<script lang="ts" setup>  
19 - import { watch, inject } from 'vue';  
20 - defineProps({  
21 - otherAttribute: {  
22 - type: Array,  
23 - default: () => [],  
24 - },  
25 - });  
26 - const emit = defineEmits(['resetFilter']);  
27 - const operationType = inject('operationType');  
28 - watch(operationType, () => {  
29 - emit('resetFilter');  
30 - });  
31 -</script>  
32 -  
33 -<style scoped>  
34 - .text {  
35 - border: 1px solid #d2d2d2;  
36 - border-radius: 5px;  
37 - padding: 0 5px;  
38 - margin: 0 5px;  
39 - }  
40 -</style>  
1 -<template>  
2 - <div>  
3 - <CollapseContainer :title="`${title} ${index + 1}`">  
4 - <template #action>  
5 - <div class="flex">  
6 - <div class="flex">  
7 - <span class="mr-2">启用规则:</span>  
8 - <template v-for="(item, scheduleIndex) in scheduleOptions" :key="item.label">  
9 - <div  
10 - :class="{ 'ml-4': scheduleIndex >= 1, active: scheduleIndex === currentIndex }"  
11 - class="cursor-pointer !p-0"  
12 - :disabled="disabled"  
13 - @click="handleScheduleChange(item.value)"  
14 - >{{ item.label }}</div  
15 - >  
16 - </template>  
17 - </div>  
18 - <Tooltip title="移除" class="ml-4">  
19 - <Icon  
20 - icon="fluent:delete-off-20-regular"  
21 - size="20"  
22 - class="mr-2 cursor-pointer"  
23 - @click="handleDelete({ index, title })"  
24 - />  
25 - </Tooltip>  
26 - </div>  
27 - </template>  
28 - <BasicForm @register="registerForm">  
29 - <template #operationType="{ model, field }">  
30 - <Select  
31 - :options="options"  
32 - :disabled="disabled"  
33 - v-model:value="model[field]"  
34 - :getPopupContainer="(triggerNode) => triggerNode.parentNode"  
35 - @change="operationType = model[field]"  
36 - placeholder="请选择比较类型"  
37 - allowClear  
38 - />  
39 - </template>  
40 - <template #time="{ model, field }">  
41 - <Input v-model:value="model[field]" placeholder="请输入持续时间">  
42 - <template #addonAfter>  
43 - <Select  
44 - :disabled="disabled"  
45 - v-model:value="model[`timeUnit`]"  
46 - :options="timeUnitOptions"  
47 - style="width: 60px"  
48 - />  
49 - </template>  
50 - </Input>  
51 - </template>  
52 - </BasicForm>  
53 - <Card size="small" :bordered="false" style="border: 2px dashed #d9d9d9" v-if="operationType">  
54 - <ConditionScreening  
55 - :childGetFieldsValue="childGetFieldsValue"  
56 - ref="conditionScreeningRef"  
57 - />  
58 - </Card>  
59 - </CollapseContainer>  
60 - <AlarmSchedule ref="alarmScheduleRef" @register="registerModal" @cancel="handleCancel" />  
61 - </div>  
62 -</template>  
63 -<script lang="ts" setup>  
64 - import { Card, Input, Select, Tooltip } from 'ant-design-vue';  
65 - import { cloneDeep } from 'lodash-es';  
66 - import { nextTick, provide, ref } from 'vue';  
67 - import { TOption, trigger_condition_schema } from '../config/config.data';  
68 - import { options, scheduleOptions, timeUnitOptions } from '../config/formatData';  
69 - import useCommonFun from '../hooks/useCommonFun';  
70 - import AlarmSchedule from './AlarmSchedule.vue';  
71 - import ConditionScreening from './ConditionScreening.vue';  
72 - import { getAttribute } from '/@/api/ruleengine/ruleengineApi';  
73 - import { CollapseContainer } from '/@/components/Container/index';  
74 - import { BasicForm, useForm } from '/@/components/Form/index';  
75 - import { Icon } from '/@/components/Icon';  
76 - import { useModal } from '/@/components/Modal';  
77 - import { useMessage } from '/@/hooks/web/useMessage';  
78 - import { unref } from 'vue';  
79 -  
80 - const { useByProductGetAttribute } = useCommonFun();  
81 - defineProps({  
82 - title: {  
83 - type: String,  
84 - required: true,  
85 - },  
86 - index: {  
87 - type: Number,  
88 - required: true,  
89 - },  
90 - provideOrgid: {  
91 - type: String,  
92 - default: '',  
93 - },  
94 - });  
95 - const emit = defineEmits(['delete']);  
96 - const { createMessage } = useMessage();  
97 -  
98 - const isUpdate = ref(false);  
99 - const conditionScreeningRef = ref();  
100 - const [registerForm, { resetFields, getFieldsValue, updateSchema, setFieldsValue, setProps }] =  
101 - useForm({  
102 - //TODO-wenwei-修复  
103 - schemas: cloneDeep(trigger_condition_schema),  
104 - //TODO-wenwei-修复  
105 - showActionButtonGroup: false,  
106 - });  
107 -  
108 - const alarmScheduleRef = ref<InstanceType<typeof AlarmSchedule>>();  
109 -  
110 - //ft-add  
111 - const resetConditionForm = () => {  
112 - conditionScreeningRef?.value?.resetConditionScreenForm();  
113 - };  
114 - //ft-add  
115 -  
116 - const getFieldsValueFunc = () => {  
117 - const predicate = conditionScreeningRef?.value?.refItem?.conditionScreeningRefs?.value?.map(  
118 - (item) => item.getFieldsValue()  
119 - );  
120 - //TODO-fengtao-设备、属性、条件筛选验证  
121 - const validate = getFieldsValue();  
122 - if (validate.deviceProfileId === undefined || !validate.deviceProfileId) {  
123 - createMessage.error('请选择产品');  
124 - throw '请选择产品';  
125 - }  
126 - if (validate.triggerType == undefined) return createMessage.error('请选择设备触发方式');  
127 - if (validate.type1 == undefined) return createMessage.error('请选择属性触发方式');  
128 - if (!validate.deviceType) return createMessage.error('请选择设备类型');  
129 - if (validate.device == undefined) return createMessage.error('请选择设备');  
130 - if (validate.device == 'PART') {  
131 - if (validate.entityId == undefined) return createMessage.error('请选择设备');  
132 - }  
133 - if (validate.type2 == '' || validate.type2 == null) return createMessage.error('请选择属性');  
134 - if (predicate == undefined) return createMessage.error('请填写条件筛选');  
135 - let predicateIsRequired = false;  
136 - if (predicate) {  
137 - predicate.some((f) => {  
138 - let arr = Object.keys(f);  
139 - if (arr.length == 0) {  
140 - predicateIsRequired = true;  
141 - }  
142 - if (f.operation == undefined) {  
143 - predicateIsRequired = true;  
144 - }  
145 - if (f.value == undefined) {  
146 - predicateIsRequired = true;  
147 - }  
148 - });  
149 - }  
150 - if (predicateIsRequired) return createMessage.error('请填写条件筛选');  
151 - //TODO-fengtao-设备、属性、条件筛选验证  
152 - return { ...getFieldsValue(), predicate, schedule: alarmScheduleRef.value?.scheduleData };  
153 - };  
154 -  
155 - //TODO-fengtao  
156 - const updateFieldDeviceId = (_deviceList: any[], _, _isUpdate) => {  
157 - // console.log(deviceList);  
158 - // console.log(isUpdate);  
159 - };  
160 - //TODO-fengtao  
161 - const resetFieldsValueFunc = () => {  
162 - resetConditionForm();  
163 - resetFields();  
164 - };  
165 - // 回显数据函数  
166 - const setFieldsFormValueFun = (fieldsValue) => {  
167 - setFieldsValue(fieldsValue);  
168 - };  
169 - //TODO-fengtao  
170 - const updateFieldAttributeFunc = async (e) => {  
171 - const res = await getAttribute(e);  
172 - const options = ref<TOption[]>([]);  
173 - useByProductGetAttribute(res, updateSchema, options);  
174 - };  
175 - //TODO-fengtao  
176 - const handleDelete = (params: { index: number; title: string }) => {  
177 - if (unref(disabled)) return;  
178 - emit('delete', params);  
179 - };  
180 - const operationType = ref<string>('');  
181 - provide('operationType', operationType);  
182 -  
183 - // 子组件获取父组件的值  
184 - const childGetFieldsValue = () => getFieldsValue();  
185 -  
186 - // 获取conditionScreeningForm的组件  
187 - const getRefItemConditionScreeningRefs = async () => {  
188 - await nextTick();  
189 - return conditionScreeningRef.value.refItem.conditionScreeningRefs;  
190 - };  
191 -  
192 - const setConditionScreeningList = (list) => {  
193 - conditionScreeningRef.value.conditionScreeningList = list;  
194 - };  
195 - const setRichText = (list) => {  
196 - conditionScreeningRef.value.otherAttribute = list;  
197 - };  
198 -  
199 - const [registerModal, { openModal }] = useModal();  
200 - const currentIndex = ref(0);  
201 - const handleScheduleChange = (value) => {  
202 - if (unref(disabled)) return;  
203 - const index = scheduleOptions.findIndex((item) => item.value === value);  
204 - // 报警日程弹窗  
205 - if (index !== 0) {  
206 - openModal(true, {  
207 - isUpdate: isUpdate.value,  
208 - value,  
209 - currentIndex: currentIndex.value,  
210 - scheduleData,  
211 - });  
212 - } else {  
213 - alarmScheduleRef.value.scheduleData = {  
214 - type: value,  
215 - };  
216 - }  
217 - currentIndex.value = index;  
218 - };  
219 - const handleCancel = (index) => {  
220 - currentIndex.value = index;  
221 - };  
222 - const scheduleData = ref(null);  
223 - //FT add 2022-10-27  
224 - const updateFieldAlarmConfig = () => {  
225 - //什么也不做  
226 - // console.log(alarmConfigList);  
227 - };  
228 - //FT add 2022-10-27  
229 - const updateEditFieldAlarmConfig = () => {  
230 - //什么也不做  
231 - // console.log(alarmConfigList);  
232 - };  
233 -  
234 - const disabled = ref<boolean>(false);  
235 - const setDisabledProps = (value) => {  
236 - setProps(value);  
237 - disabled.value = true;  
238 - };  
239 -  
240 - const setCancelDisabled = () => {  
241 - disabled.value = false;  
242 - };  
243 -  
244 - defineExpose({  
245 - getFieldsValueFunc,  
246 - updateFieldDeviceId,  
247 - resetFieldsValueFunc,  
248 - setFieldsFormValueFun,  
249 - childGetFieldsValue,  
250 - operationType,  
251 - getRefItemConditionScreeningRefs,  
252 - setConditionScreeningList,  
253 - setRichText,  
254 - currentIndex,  
255 - scheduleData,  
256 - isUpdate,  
257 - alarmScheduleRef,  
258 - updateFieldAttributeFunc,  
259 - updateFieldAlarmConfig,  
260 - updateEditFieldAlarmConfig,  
261 - setDisabledProps,  
262 - setCancelDisabled,  
263 - });  
264 -</script>  
265 -  
266 -<style lang="less" scoped>  
267 - ///移除选择框默认样式(24px)否则超出默认宽度会造成页面样式错乱  
268 - :deep(.ant-select-selector) {  
269 - padding-right: 0 !important;  
270 - }  
271 -</style>  
272 -  
273 -<style>  
274 - .active {  
275 - color: #377dff;  
276 - }  
277 -</style>  
1 -<template>  
2 - <CollapseContainer :title="`执行动作 ${actionIndex + 1}`">  
3 - <template #action>  
4 - <Tooltip title="移除" v-if="actionData.length > 1">  
5 - <Icon  
6 - icon="fluent:delete-off-20-regular"  
7 - size="20"  
8 - class="mr-2 cursor-pointer"  
9 - @click="handleDelete(actionIndex)"  
10 - />  
11 - </Tooltip>  
12 - </template>  
13 - <BasicForm @register="registerAction">  
14 - <template #outTarget="{ model, field }">  
15 - <Select  
16 - :disabled="disabled"  
17 - :options="options"  
18 - v-model:value="model[field]"  
19 - @change="changeOutTarget"  
20 - @dropdown-visible-change="handleDropdownVisibleChange"  
21 - placeholder="请选择执行动作"  
22 - allowClear  
23 - />  
24 - </template>  
25 - <template #alarmConfigSlot="{ model, field }">  
26 - <a-select  
27 - allowClear  
28 - :disabled="disabled"  
29 - placeholder="请选择告警配置"  
30 - v-model:value="model[field]"  
31 - style="width: 205px; margin-left: 10px"  
32 - :options="alarmConfigOptions.map((item) => ({ value: item.value, label: item.label }))"  
33 - >  
34 - <template #dropdownRender="{ menuNode: menu }">  
35 - <v-nodes :vnodes="menu" />  
36 - <a-divider style="margin: 4px 0" />  
37 - <div @click="handleOpenAlarmConfig" style="padding: 4px 0; cursor: pointer">  
38 - <plus-outlined />  
39 - 新增告警配置  
40 - </div>  
41 - </template>  
42 - </a-select>  
43 - </template>  
44 - <template #doContext="{ model, field }">  
45 - <div v-if="model['transportType'] === TransportTypeEnum.TCP">  
46 - <Input v-model:value="model[field]" :disabled="disabled" placeholder="请输入自定义命令" />  
47 - </div>  
48 - <div  
49 - v-show="model['transportType'] !== TransportTypeEnum.TCP"  
50 - class="flex"  
51 - style="align-items: center"  
52 - >  
53 - <div v-show="!disabled" ref="jsoneditorRef" style="height: 100%; width: 100%"></div>  
54 -  
55 - <a-textarea  
56 - v-show="disabled"  
57 - :disabled="true"  
58 - :value="JSON.stringify(disabledValue)"  
59 - :rows="4"  
60 - />  
61 -  
62 - <a-button style="margin: -5px 0" type="text" @click="handlePremitter">格式化</a-button>  
63 - <Tooltip  
64 - :title="  
65 - JSON.stringify({  
66 - method: 'setDOValue',  
67 - params: { devID: '492S211218028819', data: { DO1: 1 } },  
68 - })  
69 - "  
70 - class="ml-2"  
71 - >  
72 - <QuestionCircleOutlined style="font-size: 1rem" />  
73 - </Tooltip>  
74 - </div>  
75 - </template>  
76 - <template #clearAlarm>  
77 - <Checkbox v-model:checked="checked" :disabled="disabled" @change="handleCheckedChange">  
78 - 清除告警  
79 - </Checkbox>  
80 - <Tooltip title="清除告警与触发器一一对应">  
81 - <QuestionCircleOutlined />  
82 - </Tooltip>  
83 - </template>  
84 - </BasicForm>  
85 - <template v-for="(item, index) in clearRuleList" :key="item">  
86 - <Card  
87 - :bordered="false"  
88 - style="border: 2px dashed #d9d9d9; margin-top: 1rem"  
89 - :bodyStyle="{ padding: 0 }"  
90 - >  
91 - <ClearAlarm  
92 - :clearRuleList="clearRuleList"  
93 - :index="index"  
94 - :ref="refItem.clearRuleRefs"  
95 - @delete="deleteClearRule"  
96 - />  
97 - </Card>  
98 - </template>  
99 -  
100 - <a-button v-if="checked && isAddClearRule" class="mt-4" type="primary" @click="addClearRule"  
101 - >新增清除告警</a-button  
102 - >  
103 - </CollapseContainer>  
104 - <AlarmConfigDrawer  
105 - :defaultEnable="true"  
106 - @register="registerAlarmContactDrawer"  
107 - @success="handleSuccess"  
108 - />  
109 -</template>  
110 -  
111 -<script lang="ts">  
112 - import { defineComponent } from 'vue';  
113 - import { isNullOrUnDef, isNumber, isObject, isString } from '/@/utils/is';  
114 - import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';  
115 - export default defineComponent({  
116 - components: {  
117 - VNodes: (_, { attrs }) => {  
118 - return attrs.vnodes;  
119 - },  
120 - Input,  
121 - },  
122 - inheritAttrs: false,  
123 - });  
124 -</script>  
125 -<script lang="ts" setup>  
126 - import { ref, onMounted, nextTick, unref, computed, provide } from 'vue';  
127 - import { CollapseContainer } from '/@/components/Container/index';  
128 - import { BasicForm, useForm } from '/@/components/Form/index';  
129 - import { Tooltip, Select, Checkbox, Card, Input, message } from 'ant-design-vue';  
130 - import { Icon } from '/@/components/Icon';  
131 - import { actionSchema, CommandTypeEnum } from '../config/config.data';  
132 - import jsoneditor from 'jsoneditor';  
133 - import 'jsoneditor/dist/jsoneditor.min.css';  
134 - import { QuestionCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';  
135 - import ClearAlarm from './ClearAlarm.vue';  
136 - import { useMessage } from '/@/hooks/web/useMessage';  
137 - import { useDrawer } from '/@/components/Drawer';  
138 - import AlarmConfigDrawer from '/@/views/alarm/config/ContactDrawer.vue';  
139 -  
140 - const props = defineProps({  
141 - actionIndex: {  
142 - type: Number,  
143 - required: true,  
144 - },  
145 - actionData: {  
146 - type: Array,  
147 - default: () => [],  
148 - },  
149 - triggerData: {  
150 - type: Array,  
151 - default: () => [],  
152 - },  
153 - deviceList: {  
154 - type: Array,  
155 - required: true,  
156 - },  
157 - arr: {  
158 - type: Array,  
159 - default: () => [],  
160 - },  
161 - provideOrgid: {  
162 - type: String,  
163 - default: '',  
164 - },  
165 - });  
166 - //新增代码2022-11-23  
167 - const handlePremitter = () => {  
168 - const value = unref(jsonInstance).get();  
169 - if (!value) return;  
170 - return unref(jsonInstance).set(value);  
171 - };  
172 - //新增代码2022-11-23  
173 - const alarmConfigOptions: any = ref([]);  
174 - const [registerAlarmContactDrawer, { openDrawer }] = useDrawer();  
175 - async function handleSuccess() {  
176 - emit('dynamicChangeAlarmConfig');  
177 - }  
178 - // 新增或编辑  
179 - const handleOpenAlarmConfig = () => {  
180 - openDrawer(true, {  
181 - isUpdate: false,  
182 - });  
183 - };  
184 - const isAddClearRule = computed(() => clearRuleList.value.length < props.triggerData.length);  
185 - const refItem = {  
186 - clearRuleRefs: ref([]),  
187 - };  
188 - const { createMessage } = useMessage();  
189 -  
190 - const emit = defineEmits(['deleteAction', 'getActionFormArr', 'dynamicChangeAlarmConfig']);  
191 - const options = computed(() => {  
192 - return [  
193 - { label: '设备输出', value: 'DEVICE_OUT' },  
194 - {  
195 - label: '告警输出',  
196 - value: 'MSG_NOTIFY',  
197 - disabled:  
198 - props.arr.filter((item) => item.outTarget === 'MSG_NOTIFY').length ||  
199 - !props.triggerData.length,  
200 - },  
201 - ];  
202 - });  
203 -  
204 - const changeOutTarget = (e) => {  
205 - if (!e) validateFields(['outTarget']);  
206 - else clearValidate('outTarget');  
207 - emit('getActionFormArr');  
208 - };  
209 - const [  
210 - registerAction,  
211 - {  
212 - getFieldsValue,  
213 - resetFields,  
214 - setFieldsValue,  
215 - validate,  
216 - clearValidate,  
217 - validateFields,  
218 - setProps,  
219 - },  
220 - ] = useForm({  
221 - schemas: actionSchema,  
222 - showActionButtonGroup: false,  
223 - });  
224 -  
225 - // 获取整个执行动作表单值  
226 - const getFieldsValueFunc = () => {  
227 - const clearRule = refItem.clearRuleRefs.value.map((item) => {  
228 - const predicate = item.conditionScreeningRef?.refItem?.conditionScreeningRefs?.value?.map(  
229 - (item) => item.getFieldsValue()  
230 - );  
231 - return {  
232 - ...item.getFieldsValue(),  
233 - predicate,  
234 - schedule: item.alarmScheduleRef.scheduleData,  
235 - };  
236 - });  
237 - //TODO-fengtao-清除告警验证  
238 - let productIdIsRequired = false;  
239 - let deviceIdIsRequired = false;  
240 - let attrIsRequired = false;  
241 - let predicateIsRequired = false;  
242 - let predicateDoubleIsRequired = false;  
243 - let triggerTypeIsRequired = false;  
244 - let type1IsRequired = false;  
245 - if (clearRule) {  
246 - clearRule.some((s) => {  
247 - if (s.device == 'PART') {  
248 - if (s.entityId == undefined) {  
249 - deviceIdIsRequired = true;  
250 - }  
251 - }  
252 - if (s.deviceProfileId === undefined) {  
253 - productIdIsRequired = true;  
254 - }  
255 - if (s.type2 == '' || s.type2 == null) {  
256 - attrIsRequired = true;  
257 - }  
258 - if (s.type1 == '' || s.type1 == null) {  
259 - type1IsRequired = true;  
260 - }  
261 - if (s.triggerType == '' || s.triggerType == null || s.triggerType == undefined) {  
262 - triggerTypeIsRequired = true;  
263 - }  
264 - if (s.predicate == undefined) {  
265 - predicateIsRequired = true;  
266 - }  
267 - if (s?.predicate) {  
268 - s?.predicate.some((f) => {  
269 - let arr = Object.keys(f);  
270 - if (arr.length == 0) {  
271 - predicateDoubleIsRequired = true;  
272 - }  
273 - if (f.operation == undefined) {  
274 - predicateDoubleIsRequired = true;  
275 - }  
276 - if (f.value == undefined) {  
277 - predicateDoubleIsRequired = true;  
278 - }  
279 - });  
280 - }  
281 - });  
282 - }  
283 - if (productIdIsRequired) return createMessage.error('请选择产品');  
284 - if (deviceIdIsRequired) return createMessage.error('请选择设备');  
285 - if (attrIsRequired) return createMessage.error('请选择属性');  
286 - if (predicateIsRequired) return createMessage.error('请填写条件筛选');  
287 - if (predicateDoubleIsRequired) return createMessage.error('请填写条件筛选');  
288 - if (triggerTypeIsRequired) return createMessage.error('请选择设备触发方式');  
289 - if (type1IsRequired) return createMessage.error('请选择属性触发方式');  
290 - //TODO-fengtao-清除告警验证  
291 - //TODO-fengtao-设备验证  
292 - const validate = getFieldsValue();  
293 - //ft-add-2022-11-22  
294 - const type = validate?.outTarget;  
295 - if (type === 'DEVICE_OUT') {  
296 - if (!validate.deviceType) return createMessage.error('请选择类型');  
297 - if (!validate.deviceProfileId) return createMessage.error('请选择产品');  
298 - if (validate.device == 'PART' && validate.deviceId == undefined)  
299 - return createMessage.error('请选择设备');  
300 - }  
301 - if (type === 'MSG_NOTIFY') {  
302 - if (!validate.alarm_config) return createMessage.error('请选择告警配置');  
303 - if (!validate.alarm_level) return createMessage.error('请选择告警等级');  
304 - }  
305 - //ft-add-2022-11-22  
306 - //TODO-fengtao-设备验证  
307 - const value = getFieldsValue();  
308 - const isTCPTransportType = value.transportType === TransportTypeEnum.TCP;  
309 - const doContext = unref(jsonInstance)?.get() || {};  
310 - const serviceInputValue = Reflect.get(value, 'serviceInputValue') || {};  
311 -  
312 - const getServiceIdentifier = Reflect.get(value, 'serviceIdentifier');  
313 -  
314 - const isCustomCommand = Number(value.commandType) === CommandTypeEnum.CUSTOM;  
315 -  
316 - const thingsModelKeys = Reflect.get(value, 'thingsModelKeys') || [];  
317 -  
318 - if (!isCustomCommand) {  
319 - for (const key of thingsModelKeys) {  
320 - if (isNullOrUnDef((serviceInputValue || {})[key]) || '') {  
321 - message.warning('服务命令为必填项');  
322 - throw '';  
323 - }  
324 - }  
325 - }  
326 -  
327 - return {  
328 - ...value,  
329 - ...(isCustomCommand  
330 - ? { doContext: isTCPTransportType ? value.doContext : doContext }  
331 - : {  
332 - doContext: isTCPTransportType  
333 - ? value.serviceIdentifier  
334 - : { [getServiceIdentifier]: serviceInputValue },  
335 - }),  
336 - clearRule,  
337 - };  
338 - };  
339 -  
340 - const setFieldsFormValueFun = (fieldsValue) => {  
341 - const doContext = Reflect.get(fieldsValue, 'doContext');  
342 - const commandType = Reflect.get(fieldsValue, 'commandType');  
343 - const getServiceIdentifier = Object.keys(doContext?.params || {})[0];  
344 -  
345 - setFieldsValue({  
346 - ...fieldsValue,  
347 - ...(isNumber(fieldsValue.commandType)  
348 - ? { commandType: String(fieldsValue.commandType) }  
349 - : {}),  
350 - ...(commandType === CommandTypeEnum.SERVICE  
351 - ? {  
352 - serviceInputValue: isObject(doContext.params)  
353 - ? doContext?.params?.[getServiceIdentifier] || {}  
354 - : {},  
355 - tcpServiceCommand: isString(doContext.params) ? doContext.params : '',  
356 - }  
357 - : { doContext: doContext.params }),  
358 - });  
359 - };  
360 - //ft-add  
361 - const resetConditionForm = () => {};  
362 -  
363 - //ft-add  
364 - const resetFieldsValueFunc = () => {  
365 - resetFields();  
366 - resetConditionForm();  
367 - };  
368 -  
369 - const orgIdItem = ref('');  
370 - const isUpdateItem = ref('');  
371 - const deviceListItem = ref([]);  
372 -  
373 - //update 2022-11-28  
374 - const updateFieldDeviceId = (deviceList, orgId, isUpdate) => {  
375 - if (isUpdate.value) {  
376 - orgIdItem.value = orgId.value;  
377 - }  
378 - orgIdItem.value = orgId.value;  
379 - isUpdateItem.value = isUpdate.value;  
380 - deviceListItem.value = deviceList.value;  
381 - };  
382 - //FT add 2022-10-27  
383 - const updateEditFieldAlarmConfig = (alarmConfigList) => {  
384 - alarmConfigOptions.value = alarmConfigList.value.filter((item) => item.status);  
385 - };  
386 - const updateFieldAlarmConfig = (alarmConfigList) => {  
387 - alarmConfigOptions.value = alarmConfigList.value.filter((item) => item.status);  
388 - };  
389 - //FT add 2022-10-27  
390 -  
391 - const checked = ref(false);  
392 - const validateForm = () => {  
393 - return validate();  
394 - };  
395 -  
396 - const handleDelete = (actionIndex) => {  
397 - if (unref(disabled)) return;  
398 - emit('deleteAction', actionIndex);  
399 - };  
400 -  
401 - const clearRuleList = ref([]);  
402 - const handleCheckedChange = (e: Event) => {  
403 - if (!e.target.checked) {  
404 - clearRuleList.value = [];  
405 - } else {  
406 - addClearRule();  
407 - }  
408 - };  
409 - const addClearRule = () => {  
410 - unref(clearRuleList).push(Date.now());  
411 - nextTick(() => {  
412 - setFields(refItem.clearRuleRefs);  
413 - });  
414 - };  
415 -  
416 - function setFields(refs) {  
417 - unref(refs).map((item) => {  
418 - item.updateFieldDeviceId(  
419 - deviceListItem.value,  
420 - props.provideOrgid || orgIdItem.value,  
421 - isUpdateItem.value  
422 - );  
423 - });  
424 - }  
425 - const deleteClearRule = (index) => {  
426 - unref(clearRuleList).splice(index, 1);  
427 - };  
428 -  
429 - // json 以及初始化JSON  
430 - const jsoneditorRef = ref();  
431 - const jsonValue = ref({});  
432 - const jsonInstance = ref();  
433 -  
434 - const disabledValue = ref(); //查看时使用表单全部禁用使用这个来获取表单数据  
435 - onMounted(() => {  
436 - nextTick(() => {  
437 - let options = {  
438 - mode: 'code',  
439 - mainMenuBar: false,  
440 - statusBar: false,  
441 - };  
442 - let editor = new jsoneditor(jsoneditorRef.value, options);  
443 - editor.set(jsonValue.value);  
444 - jsonInstance.value = editor;  
445 - });  
446 - });  
447 -  
448 - const getJsonValue = () => unref(jsonInstance).get();  
449 - const setJsonValue = (Json) => {  
450 - nextTick(() => {  
451 - disabledValue.value = Json;  
452 - unref(jsonInstance).set(Json);  
453 - });  
454 - };  
455 -  
456 - const operationType = ref<string>('');  
457 - provide('operationType', operationType);  
458 -  
459 - const getRefItemConditionScreeningRefs = async () => {  
460 - await nextTick();  
461 - return refItem.clearRuleRefs.value.map(  
462 - (item) => item.conditionScreeningRef.refItem.conditionScreeningRefs  
463 - );  
464 - };  
465 - const setConditionScreeningList = (list) => {  
466 - refItem.clearRuleRefs.value.map((item) => {  
467 - item.conditionScreeningRef.conditionScreeningList = list;  
468 - });  
469 - };  
470 -  
471 - const setRichText = (list, index) => {  
472 - refItem.clearRuleRefs.value[index].conditionScreeningRef.otherAttribute = list;  
473 - };  
474 -  
475 - const handleDropdownVisibleChange = () => {  
476 - emit('getActionFormArr');  
477 - };  
478 -  
479 - const disabled = ref<boolean>(false);  
480 - const setDisabledProps = async (value) => {  
481 - setProps(value);  
482 - disabled.value = true;  
483 - await nextTick();  
484 - unref(refItem.clearRuleRefs)?.forEach((item) => {  
485 - item.setDisabledProps(value);  
486 - });  
487 - };  
488 -  
489 - const setCancelDisabled = () => {  
490 - disabled.value = false;  
491 - unref(refItem.clearRuleRefs)?.forEach((item) => {  
492 - item.setCancelDisabled();  
493 - });  
494 - };  
495 -  
496 - defineExpose({  
497 - getFieldsValue,  
498 - getFieldsValueFunc,  
499 - setFieldsFormValueFun,  
500 - resetFieldsValueFunc,  
501 - updateFieldDeviceId,  
502 - updateFieldAlarmConfig,  
503 - validateForm,  
504 - getJsonValue,  
505 - setJsonValue,  
506 - jsonInstance,  
507 - updateEditFieldAlarmConfig,  
508 - checked,  
509 - getRefItemConditionScreeningRefs,  
510 - setConditionScreeningList,  
511 - setRichText,  
512 - refItem,  
513 - clearRuleList,  
514 - resetConditionForm,  
515 - handleDropdownVisibleChange,  
516 - setDisabledProps,  
517 - setCancelDisabled,  
518 - });  
519 -</script>  
520 -  
521 -<style>  
522 - .jsoneditor {  
523 - border: none;  
524 - }  
525 -</style>  
@@ -67,11 +67,11 @@ @@ -67,11 +67,11 @@
67 </Tag> 67 </Tag>
68 </template> 68 </template>
69 </BasicTable> 69 </BasicTable>
70 - <SceneLinkAgeDrawer @register="registerDrawer" @success="handleSuccess" /> 70 + <SceneLinkageDrawer @register="registerDrawer" @success="handleSuccess" />
71 </div> 71 </div>
72 </template> 72 </template>
73 <script lang="ts" setup> 73 <script lang="ts" setup>
74 - import { nextTick } from 'vue'; 74 + import { nextTick, toRaw, unref } from 'vue';
75 import { BasicTable, useTable, TableAction } from '/@/components/Table'; 75 import { BasicTable, useTable, TableAction } from '/@/components/Table';
76 import { useDrawer } from '/@/components/Drawer'; 76 import { useDrawer } from '/@/components/Drawer';
77 import { 77 import {
@@ -84,10 +84,12 @@ @@ -84,10 +84,12 @@
84 import { columns, searchFormSchema } from './config/config.data'; 84 import { columns, searchFormSchema } from './config/config.data';
85 import { USER_INFO_KEY } from '/@/enums/cacheEnum'; 85 import { USER_INFO_KEY } from '/@/enums/cacheEnum';
86 import { getAuthCache } from '/@/utils/auth'; 86 import { getAuthCache } from '/@/utils/auth';
87 - import SceneLinkAgeDrawer from './SceneLinkAgeDrawer.vue'; 87 + import { SceneLinkageDrawer } from './components/SceneLinkageDrawer';
88 import { useMessage } from '/@/hooks/web/useMessage'; 88 import { useMessage } from '/@/hooks/web/useMessage';
89 import { Authority } from '/@/components/Authority'; 89 import { Authority } from '/@/components/Authority';
90 import { usePermission } from '/@/hooks/web/usePermission'; 90 import { usePermission } from '/@/hooks/web/usePermission';
  91 + import { SceneLinkageDrawerDataType } from './components/SceneLinkageDrawer/type';
  92 + import { DataActionModeEnum } from '/@/enums/toolEnum';
91 93
92 const userInfo: any = getAuthCache(USER_INFO_KEY); 94 const userInfo: any = getAuthCache(USER_INFO_KEY);
93 const userId = userInfo.userId; 95 const userId = userInfo.userId;
@@ -129,25 +131,22 @@ @@ -129,25 +131,22 @@
129 }); 131 });
130 132
131 function handleAdd() { 133 function handleAdd() {
132 - window.localStorage.setItem('isViewDisabledBtn', 'noView');  
133 openDrawer(true, { 134 openDrawer(true, {
134 - isUpdate: false,  
135 - }); 135 + mode: DataActionModeEnum.CREATE,
  136 + } as SceneLinkageDrawerDataType);
136 } 137 }
137 138
138 function handleEdit(record: Recordable) { 139 function handleEdit(record: Recordable) {
139 - window.localStorage.setItem('isViewDisabledBtn', 'noView');  
140 openDrawer(true, { 140 openDrawer(true, {
141 - record,  
142 - isUpdate: true,  
143 - }); 141 + mode: DataActionModeEnum.UPDATE,
  142 + record: toRaw(unref(record)),
  143 + } as SceneLinkageDrawerDataType);
144 } 144 }
145 function handleView(record: Recordable) { 145 function handleView(record: Recordable) {
146 - window.localStorage.setItem('isViewDisabledBtn', 'isView');  
147 openDrawer(true, { 146 openDrawer(true, {
  147 + mode: DataActionModeEnum.READ,
148 record, 148 record,
149 - isUpdate: 3,  
150 - }); 149 + } as SceneLinkageDrawerDataType);
151 } 150 }
152 function handleSuccess() { 151 function handleSuccess() {
153 reload(); 152 reload();
@@ -121,11 +121,11 @@ @@ -121,11 +121,11 @@
121 const handleNewRecord = () => { 121 const handleNewRecord = () => {
122 const { componentKey } = unref(selectWidgetKeys); 122 const { componentKey } = unref(selectWidgetKeys);
123 if (componentKey === 'HumidityComponent2' && unref(dataSource).length >= 6) { 123 if (componentKey === 'HumidityComponent2' && unref(dataSource).length >= 6) {
124 - createMessage.warning('绑定的数据源不能超过6条~'); 124 + createMessage.warning('绑定的数据源不能超过6条');
125 return; 125 return;
126 } 126 }
127 if (unref(dataSource).length >= DATA_SOURCE_LIMIT_NUMBER) { 127 if (unref(dataSource).length >= DATA_SOURCE_LIMIT_NUMBER) {
128 - createMessage.warning('绑定的数据源不能超过10条~'); 128 + createMessage.warning('绑定的数据源不能超过10条');
129 return; 129 return;
130 } 130 }
131 dataSource.value.push(genNewDataSourceItem()); 131 dataSource.value.push(genNewDataSourceItem());
@@ -246,7 +246,7 @@ @@ -246,7 +246,7 @@
246 ? await updateDataComponent(value) 246 ? await updateDataComponent(value)
247 : await addDataComponent(value); 247 : await addDataComponent(value);
248 createMessage.success( 248 createMessage.success(
249 - `${unref(currentMode) === DataActionModeEnum.UPDATE ? '编辑' : '新增'}成功~` 249 + `${unref(currentMode) === DataActionModeEnum.UPDATE ? '编辑' : '新增'}成功`
250 ); 250 );
251 closeModal(); 251 closeModal();
252 emit('ok'); 252 emit('ok');
@@ -9,13 +9,13 @@ import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; @@ -9,13 +9,13 @@ import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
9 import { getModelServices } from '/@/api/device/modelOfMatter'; 9 import { getModelServices } from '/@/api/device/modelOfMatter';
10 import { findDictItemByCode } from '/@/api/system/dict'; 10 import { findDictItemByCode } from '/@/api/system/dict';
11 import { FormSchema, useComponentRegister } from '/@/components/Form'; 11 import { FormSchema, useComponentRegister } from '/@/components/Form';
12 -import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';  
13 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; 12 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
14 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const'; 13 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
15 import { CommandTypeEnum } from '/@/views/rule/linkedge/config/config.data'; 14 import { CommandTypeEnum } from '/@/views/rule/linkedge/config/config.data';
16 import { DataActionModeEnum } from '/@/enums/toolEnum'; 15 import { DataActionModeEnum } from '/@/enums/toolEnum';
17 import { TaskTypeEnum } from '/@/views/task/center/config'; 16 import { TaskTypeEnum } from '/@/views/task/center/config';
18 import { createPickerSearch } from '/@/utils/pickerSearch'; 17 import { createPickerSearch } from '/@/utils/pickerSearch';
  18 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
19 19
20 useComponentRegister('OrgTreeSelect', OrgTreeSelect); 20 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
21 21
@@ -362,7 +362,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => { @@ -362,7 +362,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
362 (isControlComponent(category!) && 362 (isControlComponent(category!) &&
363 unref(selectWidgetKeys).componentKey !== 'LateralNumericalControl') || 363 unref(selectWidgetKeys).componentKey !== 'LateralNumericalControl') ||
364 isBooleanComponent(unref(selectWidgetKeys)) 364 isBooleanComponent(unref(selectWidgetKeys))
365 - ? DataTypeEnum.IS_BOOL 365 + ? DataTypeEnum.BOOL
366 : undefined, 366 : undefined,
367 }); 367 });
368 368
@@ -60,7 +60,10 @@ @@ -60,7 +60,10 @@
60 event: DataActionModeEnum.DELETE, 60 event: DataActionModeEnum.DELETE,
61 icon: 'ant-design:delete-outlined', 61 icon: 'ant-design:delete-outlined',
62 auth: VisualComponentPermission.DELETE, 62 auth: VisualComponentPermission.DELETE,
63 - onClick: handleDelete, 63 + popconfirm: {
  64 + title: '是否确认删除操作?',
  65 + onConfirm: handleDelete.bind(null),
  66 + },
64 }, 67 },
65 ]); 68 ]);
66 69
@@ -71,7 +74,7 @@ @@ -71,7 +74,7 @@
71 async function handleDelete() { 74 async function handleDelete() {
72 try { 75 try {
73 await deleteDataComponent({ dataBoardId: unref(boardId), ids: [props.sourceInfo.id] }); 76 await deleteDataComponent({ dataBoardId: unref(boardId), ids: [props.sourceInfo.id] });
74 - createMessage.success('删除成功~'); 77 + createMessage.success('删除成功');
75 emit('ok'); 78 emit('ok');
76 } catch (error) {} 79 } catch (error) {}
77 } 80 }
@@ -132,7 +135,7 @@ @@ -132,7 +135,7 @@
132 layout: layout as Layout, 135 layout: layout as Layout,
133 }, 136 },
134 }); 137 });
135 - createMessage.success('复制成功~'); 138 + createMessage.success('复制成功');
136 emit('ok'); 139 emit('ok');
137 } catch (error) { 140 } catch (error) {
138 throw error; 141 throw error;
@@ -10,3 +10,10 @@ export interface ModalParamsType<T = Recordable> { @@ -10,3 +10,10 @@ export interface ModalParamsType<T = Recordable> {
10 record: T; 10 record: T;
11 [key: string]: any; 11 [key: string]: any;
12 } 12 }
  13 +
  14 +export interface DefineComponentsBasicExpose<T = Recordable> {
  15 + getFieldsValue: () => T;
  16 + setFieldsValue: (value: T) => any;
  17 + validate?: () => Promise<any>;
  18 + resetFieldsValue?: (...args) => any;
  19 +}