Showing
18 changed files
with
391 additions
and
219 deletions
... | ... | @@ -21,7 +21,7 @@ VITE_PROXY = [["/api","http://222.180.200.114:48080/api"],["/thingskit-drawio"," |
21 | 21 | # 实时数据的ws地址 |
22 | 22 | # VITE_WEB_SOCKET = ws://localhost:8080/api/ws/plugins/telemetry?token= |
23 | 23 | # VITE_WEB_SOCKET = ws://44.99.141.212:8080/api/ws/plugins/telemetry?token= |
24 | -VITE_WEB_SOCKET = ws://dev.thingskit.com/api/ws/plugins/telemetry?token= | |
24 | +VITE_WEB_SOCKET = ws://222.180.200.114:48080/api/ws/plugins/telemetry?token= | |
25 | 25 | # VITE_WEB_SOCKET = ws://121.37.251.8:8080/api/ws/plugins/telemetry?token= |
26 | 26 | |
27 | 27 | # Delete console | ... | ... |
1 | 1 | import { defHttp } from '/@/utils/http/axios'; |
2 | 2 | import { |
3 | 3 | DeviceModel, |
4 | + DeviceModelOfMatterAttrs, | |
4 | 5 | DeviceProfileModel, |
5 | 6 | DeviceProfileQueryParam, |
6 | 7 | DeviceQueryParam, |
... | ... | @@ -24,6 +25,8 @@ enum DeviceManagerApi { |
24 | 25 | |
25 | 26 | DEVICE_CREDENTIALS = '/device/credentials', |
26 | 27 | COMMAND_ISSUANCE = '/rpc', |
28 | + | |
29 | + DEVICE_ATTR = '/device/attributes', | |
27 | 30 | } |
28 | 31 | |
29 | 32 | export const devicePage = (params: DeviceQueryParam) => { |
... | ... | @@ -224,3 +227,9 @@ export const commandIssuanceApi = (type, tbDeviceId, data) => { |
224 | 227 | } |
225 | 228 | ); |
226 | 229 | }; |
230 | + | |
231 | +export const getDeviceAttrs = (profileId: string, id: string) => { | |
232 | + return defHttp.get<DeviceModelOfMatterAttrs[]>({ | |
233 | + url: `${DeviceManagerApi.DEVICE_ATTR}/${profileId}/${id}`, | |
234 | + }); | |
235 | +}; | ... | ... |
1 | +import { StructJSON } from './modelOfMatterModel'; | |
1 | 2 | import { BasicPageParams } from '/@/api/model/baseModel'; |
2 | 3 | import { AlarmStatus } from '/@/views/alarm/log/config/detail.config'; |
3 | 4 | export enum DeviceState { |
... | ... | @@ -85,3 +86,9 @@ export interface DeviceRecord { |
85 | 86 | type: string; |
86 | 87 | default: boolean; |
87 | 88 | } |
89 | + | |
90 | +export interface DeviceModelOfMatterAttrs { | |
91 | + name: string; | |
92 | + identifier: string; | |
93 | + detail: StructJSON; | |
94 | +} | ... | ... |
... | ... | @@ -15,9 +15,23 @@ export interface Specs { |
15 | 15 | }; |
16 | 16 | } |
17 | 17 | |
18 | -export interface FunctionJson { | |
18 | +export interface DataType { | |
19 | 19 | type: string; |
20 | - specs?: Partial<Specs> | ModelOfMatterParams[]; | |
20 | + specs?: Partial<Specs> | StructJSON[]; | |
21 | +} | |
22 | + | |
23 | +export interface StructJSON { | |
24 | + functionName?: string; | |
25 | + identifier?: string; | |
26 | + remark?: string; | |
27 | + dataType?: DataType; | |
28 | +} | |
29 | + | |
30 | +export interface FunctionJson { | |
31 | + dataType?: DataType | DataType[]; | |
32 | + inputData?: DataType[]; | |
33 | + outputData?: DataType[]; | |
34 | + serviceCommand?: string; | |
21 | 35 | } |
22 | 36 | |
23 | 37 | export interface ModelOfMatterParams { |
... | ... | @@ -28,6 +42,9 @@ export interface ModelOfMatterParams { |
28 | 42 | identifier: string; |
29 | 43 | remark: string; |
30 | 44 | id?: string; |
45 | + callType?: string; | |
46 | + eventType?: string; | |
47 | + accessMode?: string; | |
31 | 48 | } |
32 | 49 | |
33 | 50 | export interface GetModelTslParams { | ... | ... |
... | ... | @@ -7,10 +7,12 @@ |
7 | 7 | import { BasicForm, useForm } from '/@/components/Form'; |
8 | 8 | import { formSchemas } from './config'; |
9 | 9 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
10 | - import { OpenModalMode, OpenModalParams, StructRecord } from './type'; | |
10 | + import { OpenModalMode, OpenModalParams } from './type'; | |
11 | 11 | import { ref, unref } from 'vue'; |
12 | - import { transformFormValue } from './util'; | |
12 | + import { transfromToStructJSON } from './util'; | |
13 | 13 | import { cloneDeep } from 'lodash-es'; |
14 | + import { DataType, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
15 | + import { isArray } from '/@/utils/is'; | |
14 | 16 | |
15 | 17 | const modalReceiveRecord = ref<OpenModalParams>({ |
16 | 18 | mode: OpenModalMode.CREATE, |
... | ... | @@ -32,17 +34,14 @@ |
32 | 34 | const [registerModal, { closeModal }] = useModalInner((record: OpenModalParams) => { |
33 | 35 | modalReceiveRecord.value = record; |
34 | 36 | const data = record.record || {}; |
35 | - const { functionJson = {} } = data! as StructRecord; | |
36 | - const { specs = {} } = functionJson as StructRecord['functionJson']; | |
37 | + const { dataType = {} } = data! as StructJSON; | |
38 | + const { specs = {}, type } = dataType as DataType; | |
39 | + | |
37 | 40 | if (record.record) { |
38 | 41 | const value = { |
42 | + type, | |
39 | 43 | ...data, |
40 | - ...functionJson, | |
41 | - ...specs, | |
42 | - valueRange: { | |
43 | - min: specs.min, | |
44 | - max: specs.max, | |
45 | - }, | |
44 | + ...(isArray(specs) ? { specs } : { ...specs }), | |
46 | 45 | }; |
47 | 46 | |
48 | 47 | setFieldsValue(value); |
... | ... | @@ -52,9 +51,9 @@ |
52 | 51 | const handleSubmit = async () => { |
53 | 52 | try { |
54 | 53 | const _value = await validate(); |
55 | - let value = transformFormValue(_value); | |
56 | - value = { | |
57 | - ...value, | |
54 | + let structJSON = transfromToStructJSON(_value); | |
55 | + const value = { | |
56 | + ...structJSON, | |
58 | 57 | ...(unref(modalReceiveRecord)?.record?.id |
59 | 58 | ? { id: unref(modalReceiveRecord)?.record?.id } |
60 | 59 | : {}), | ... | ... |
1 | 1 | import { h } from 'vue'; |
2 | +import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
2 | 3 | import { findDictItemByCode } from '/@/api/system/dict'; |
3 | 4 | import { FormSchema } from '/@/components/Table'; |
4 | 5 | import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; |
... | ... | @@ -11,6 +12,23 @@ export enum DateTypeEnum { |
11 | 12 | IS_BOOL = 'BOOL', |
12 | 13 | } |
13 | 14 | |
15 | +const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => { | |
16 | + value = value || {}; | |
17 | + const { min, max } = value; | |
18 | + if (min >= max) { | |
19 | + return Promise.reject('最大值小于最小值'); | |
20 | + } | |
21 | + return Promise.resolve(); | |
22 | +}; | |
23 | + | |
24 | +const validateJSON = (_rule, value: ModelOfMatterParams[], _callback) => { | |
25 | + console.log('validate json', value); | |
26 | + if (value.length) { | |
27 | + return Promise.resolve(); | |
28 | + } | |
29 | + return Promise.reject('JSON对象不能为空'); | |
30 | +}; | |
31 | + | |
14 | 32 | export const formSchemas: FormSchema[] = [ |
15 | 33 | { |
16 | 34 | field: FormField.FUNCTION_NAME, |
... | ... | @@ -70,6 +88,7 @@ export const formSchemas: FormSchema[] = [ |
70 | 88 | ifShow: ({ values }) => |
71 | 89 | values[FormField.TYPE] === DateTypeEnum.IS_NUMBER_INT || |
72 | 90 | values[FormField.TYPE] === DateTypeEnum.IS_NUMBER_DOUBLE, |
91 | + rules: [{ validator: validateValueRange }], | |
73 | 92 | }, |
74 | 93 | { |
75 | 94 | field: FormField.STEP, |
... | ... | @@ -81,10 +100,29 @@ export const formSchemas: FormSchema[] = [ |
81 | 100 | componentProps: { |
82 | 101 | maxLength: 255, |
83 | 102 | placeholder: '请输入步长', |
103 | + min: 1, | |
104 | + formatter: (value: number | string) => { | |
105 | + return value ? Math.floor(Number(value)) : value; | |
106 | + }, | |
84 | 107 | }, |
85 | 108 | ifShow: ({ values }) => |
86 | 109 | values[FormField.TYPE] === DateTypeEnum.IS_NUMBER_INT || |
87 | 110 | values[FormField.TYPE] === DateTypeEnum.IS_NUMBER_DOUBLE, |
111 | + dynamicRules: ({ model }) => { | |
112 | + const valueRange = model[FormField.VALUE_RANGE] || {}; | |
113 | + const { min = 0, max = 0 } = valueRange; | |
114 | + const step = model[FormField.STEP]; | |
115 | + return [ | |
116 | + { | |
117 | + validator: () => { | |
118 | + if (step > max - min) { | |
119 | + return Promise.reject('步长不能大于取值范围的差值'); | |
120 | + } | |
121 | + return Promise.resolve(); | |
122 | + }, | |
123 | + }, | |
124 | + ]; | |
125 | + }, | |
88 | 126 | }, |
89 | 127 | { |
90 | 128 | field: FormField.UNIT_NAME, |
... | ... | @@ -140,6 +178,18 @@ export const formSchemas: FormSchema[] = [ |
140 | 178 | placeholder: '如:关', |
141 | 179 | }, |
142 | 180 | ifShow: ({ values }) => values[FormField.TYPE] === DateTypeEnum.IS_BOOL, |
181 | + dynamicRules: ({ model }) => { | |
182 | + const close = model[FormField.BOOL_CLOSE]; | |
183 | + const open = model[FormField.BOOL_OPEN]; | |
184 | + return [ | |
185 | + { | |
186 | + validator() { | |
187 | + if (open === close) return Promise.reject('布尔值不能相同'); | |
188 | + return Promise.resolve(); | |
189 | + }, | |
190 | + }, | |
191 | + ]; | |
192 | + }, | |
143 | 193 | }, |
144 | 194 | { |
145 | 195 | field: FormField.BOOL_OPEN, |
... | ... | @@ -153,6 +203,18 @@ export const formSchemas: FormSchema[] = [ |
153 | 203 | placeholder: '如:开', |
154 | 204 | }, |
155 | 205 | ifShow: ({ values }) => values[FormField.TYPE] === DateTypeEnum.IS_BOOL, |
206 | + dynamicRules: ({ model }) => { | |
207 | + const close = model[FormField.BOOL_CLOSE]; | |
208 | + const open = model[FormField.BOOL_OPEN]; | |
209 | + return [ | |
210 | + { | |
211 | + validator() { | |
212 | + if (open === close) return Promise.reject('布尔值不能相同'); | |
213 | + return Promise.resolve(); | |
214 | + }, | |
215 | + }, | |
216 | + ]; | |
217 | + }, | |
156 | 218 | }, |
157 | 219 | { |
158 | 220 | field: FormField.LENGTH, |
... | ... | @@ -174,14 +236,14 @@ export const formSchemas: FormSchema[] = [ |
174 | 236 | ifShow: ({ values }) => values[FormField.TYPE] === DateTypeEnum.IS_STRING, |
175 | 237 | }, |
176 | 238 | { |
177 | - field: FormField.R_W_FLAG, | |
239 | + field: FormField.ACCESS_MODE, | |
178 | 240 | component: 'ApiRadioGroup', |
179 | 241 | label: '读写类型', |
180 | 242 | required: true, |
181 | 243 | colProps: { |
182 | 244 | span: 24, |
183 | 245 | }, |
184 | - defaultValue: 'READ_ONLY', | |
246 | + defaultValue: 'r', | |
185 | 247 | componentProps: { |
186 | 248 | placeholder: '请选择读写类型', |
187 | 249 | api: findDictItemByCode, |
... | ... | @@ -200,15 +262,7 @@ export const formSchemas: FormSchema[] = [ |
200 | 262 | changeEvent: 'update:value', |
201 | 263 | colProps: { span: 24 }, |
202 | 264 | ifShow: ({ values }) => values[FormField.TYPE] === DateTypeEnum.IS_STRUCT, |
203 | - rules: [ | |
204 | - { | |
205 | - required: true, | |
206 | - validator(_rule, value, _callback) { | |
207 | - console.log(value); | |
208 | - return Promise.resolve(); | |
209 | - }, | |
210 | - }, | |
211 | - ], | |
265 | + rules: [{ required: true, validator: validateJSON }], | |
212 | 266 | }, |
213 | 267 | { |
214 | 268 | field: FormField.REFARK, | ... | ... |
1 | 1 | import { DateTypeEnum } from './config'; |
2 | -import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
2 | +import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
3 | 3 | import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; |
4 | 4 | |
5 | 5 | export enum OpenModalMode { |
... | ... | @@ -22,6 +22,6 @@ export interface StructFormValue |
22 | 22 | [FormField.STRUCT]: StructRecord[]; |
23 | 23 | } |
24 | 24 | |
25 | -export interface StructRecord extends ModelOfMatterParams { | |
25 | +export interface StructRecord extends StructJSON { | |
26 | 26 | id: string; |
27 | 27 | } | ... | ... |
1 | 1 | import { DateTypeEnum } from './config'; |
2 | 2 | import { StructFormValue } from './type'; |
3 | -import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
4 | -import { useMessage } from '/@/hooks/web/useMessage'; | |
5 | -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; | |
3 | +import { DataType, ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
6 | 4 | |
7 | -export const validateValueRangeAndStep = (min: number, step: number, max: number) => { | |
8 | - const { createMessage } = useMessage(); | |
9 | - if (min > max) { | |
10 | - createMessage.error('最大值必须大于最小值,整数型不能有小数位,单精度有效位为7,双精度为16'); | |
11 | - throw '最大值必须大于最小值,整数型不能有小数位,单精度有效位为7,双精度为16'; | |
12 | - } | |
13 | - | |
14 | - if (step > max - min) { | |
15 | - createMessage.error('步长不能大于取值范围的差值'); | |
16 | - throw '步长不能大于取值范围的差值'; | |
17 | - } | |
18 | -}; | |
19 | - | |
20 | -export const validateValueBool = (boolClose, boolOpen) => { | |
21 | - const { createMessage } = useMessage(); | |
22 | - | |
23 | - if (boolClose == boolOpen) { | |
24 | - createMessage.error('布尔值不能相同'); | |
25 | - throw '布尔值不能相同'; | |
26 | - } | |
27 | -}; | |
28 | - | |
29 | -export const validateValueStruct = (data: []) => { | |
30 | - const { createMessage } = useMessage(); | |
31 | - | |
32 | - if (data.length === 0) { | |
33 | - createMessage.error('struct不能为空'); | |
34 | - throw 'struct不能为空'; | |
35 | - } | |
36 | -}; | |
37 | - | |
38 | -export function transformFormValue(value: StructFormValue): Partial<ModelOfMatterParams> { | |
5 | +export function transfromToStructJSON(value: StructFormValue): StructJSON { | |
39 | 6 | const { |
40 | 7 | type, |
41 | 8 | valueRange, |
... | ... | @@ -49,32 +16,28 @@ export function transformFormValue(value: StructFormValue): Partial<ModelOfMatte |
49 | 16 | identifier, |
50 | 17 | remark, |
51 | 18 | specs, |
19 | + assessMode, | |
52 | 20 | } = value; |
53 | - const { min, max } = valueRange! || {}; | |
54 | - const basic = { functionName, identifier, remark }; | |
55 | - let functionJson = {} as unknown as ModelOfMatterParams['functionJson']; | |
21 | + const basic = { functionName, identifier, remark, assessMode }; | |
22 | + let dataType = {} as unknown as DataType; | |
56 | 23 | |
57 | - console.log(value); | |
58 | 24 | switch (type) { |
59 | 25 | case DateTypeEnum.IS_NUMBER_INT: |
60 | - validateValueRangeAndStep(Number(min), Number(step), Number(max)); | |
61 | - functionJson = { | |
26 | + dataType = { | |
62 | 27 | type, |
63 | - specs: { max, min, valueRange, step, unit, unitName }, | |
28 | + specs: { valueRange, step, unit, unitName }, | |
64 | 29 | }; |
65 | 30 | break; |
66 | 31 | |
67 | 32 | case DateTypeEnum.IS_NUMBER_DOUBLE: |
68 | - validateValueRangeAndStep(Number(min), Number(step), Number(max)); | |
69 | - functionJson = { | |
33 | + dataType = { | |
70 | 34 | type, |
71 | - specs: { max, min, valueRange, step, unit, unitName }, | |
35 | + specs: { valueRange, step, unit, unitName }, | |
72 | 36 | }; |
73 | 37 | break; |
74 | 38 | |
75 | 39 | case DateTypeEnum.IS_BOOL: |
76 | - validateValueBool(Number(boolClose), Number(boolOpen)); | |
77 | - functionJson = { | |
40 | + dataType = { | |
78 | 41 | type, |
79 | 42 | specs: { |
80 | 43 | boolOpen: boolOpen, |
... | ... | @@ -84,22 +47,18 @@ export function transformFormValue(value: StructFormValue): Partial<ModelOfMatte |
84 | 47 | break; |
85 | 48 | |
86 | 49 | case DateTypeEnum.IS_STRING: |
87 | - functionJson = { | |
50 | + dataType = { | |
88 | 51 | type, |
89 | 52 | specs: { length }, |
90 | 53 | }; |
91 | 54 | break; |
92 | 55 | |
93 | 56 | case DateTypeEnum.IS_STRUCT: |
94 | - functionJson = { | |
57 | + dataType = { | |
95 | 58 | type, |
96 | 59 | specs: specs! as unknown as ModelOfMatterParams[], |
97 | 60 | }; |
98 | 61 | break; |
99 | 62 | } |
100 | - return { | |
101 | - ...basic, | |
102 | - functionType: FunctionType.PROPERTIES, | |
103 | - functionJson, | |
104 | - }; | |
63 | + return { ...basic, dataType }; | |
105 | 64 | } | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | - import { nextTick, reactive, ref, unref } from 'vue'; | |
2 | + import { nextTick, onUnmounted, reactive, ref, unref } from 'vue'; | |
3 | 3 | import { List, Button, Tooltip, Card } from 'ant-design-vue'; |
4 | 4 | import { PageWrapper } from '/@/components/Page'; |
5 | 5 | import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons-vue'; |
... | ... | @@ -13,6 +13,11 @@ |
13 | 13 | import { BasicForm, useForm } from '/@/components/Form'; |
14 | 14 | import HistoryData from './HistoryData.vue'; |
15 | 15 | import { BasicModal, useModal } from '/@/components/Modal'; |
16 | + import { getDeviceAttrs } from '/@/api/device/deviceManager'; | |
17 | + import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel'; | |
18 | + import { computed } from '@vue/reactivity'; | |
19 | + import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
20 | + import { isObject } from '/@/utils/is'; | |
16 | 21 | |
17 | 22 | interface ReceiveMessage { |
18 | 23 | data: { |
... | ... | @@ -27,7 +32,7 @@ |
27 | 32 | } |
28 | 33 | |
29 | 34 | const props = defineProps<{ |
30 | - deviceDetail: Record<'tbDeviceId', string>; | |
35 | + deviceDetail: Record<'tbDeviceId' | 'profileId' | 'id', string>; | |
31 | 36 | }>(); |
32 | 37 | |
33 | 38 | const grid = { |
... | ... | @@ -43,16 +48,21 @@ |
43 | 48 | originData: [] as DataSource[], |
44 | 49 | dataSource: [] as DataSource[], |
45 | 50 | message: {} as ReceiveMessage['data'], |
46 | - sendValue: { | |
51 | + attrKeys: [] as DeviceModelOfMatterAttrs[], | |
52 | + }); | |
53 | + | |
54 | + const getSendValue = computed(() => { | |
55 | + return { | |
47 | 56 | tsSubCmds: [ |
48 | 57 | { |
49 | 58 | entityType: 'DEVICE', |
50 | - entityId: props.deviceDetail!.tbDeviceId || 'd2526c70-60a9-11ed-ba9c-6b98bfcc8255', | |
59 | + entityId: props.deviceDetail!.tbDeviceId, | |
51 | 60 | scope: 'LATEST_TELEMETRY', |
52 | 61 | cmdId: 1, |
62 | + keys: socketInfo.attrKeys.map((item) => item.identifier).join(','), | |
53 | 63 | }, |
54 | 64 | ], |
55 | - }, | |
65 | + }; | |
56 | 66 | }); |
57 | 67 | |
58 | 68 | const [registerForm, { getFieldsValue }] = useForm({ |
... | ... | @@ -114,9 +124,23 @@ |
114 | 124 | |
115 | 125 | const { createMessage } = useMessage(); |
116 | 126 | |
127 | + const getUnit = (record: StructJSON) => { | |
128 | + const { dataType } = record; | |
129 | + const { specs } = dataType! || {}; | |
130 | + | |
131 | + return isObject(specs) | |
132 | + ? (specs as Specs).unitName && (specs as Specs).unit | |
133 | + ? `${(specs as Specs).unitName}/${(specs as Specs).unit}` | |
134 | + : '' | |
135 | + : ''; | |
136 | + }; | |
137 | + | |
117 | 138 | const { send, close, data } = useWebSocket(socketInfo.origin, { |
118 | - onConnected() { | |
119 | - send(JSON.stringify(socketInfo.sendValue)); | |
139 | + async onConnected() { | |
140 | + const { id, profileId } = props.deviceDetail; | |
141 | + const value = await getDeviceAttrs(profileId, id); | |
142 | + socketInfo.attrKeys = value; | |
143 | + send(JSON.stringify(unref(getSendValue))); | |
120 | 144 | }, |
121 | 145 | async onMessage() { |
122 | 146 | try { |
... | ... | @@ -128,11 +152,11 @@ |
128 | 152 | socketInfo.message[key] = value.data[key]; |
129 | 153 | }); |
130 | 154 | |
131 | - const allKeys = Object.keys(socketInfo.message); | |
132 | - | |
133 | - socketInfo.originData = socketInfo.dataSource = allKeys.map((key) => { | |
155 | + socketInfo.originData = socketInfo.dataSource = socketInfo.attrKeys.map((item) => { | |
156 | + const { identifier: key, name, detail } = item; | |
157 | + const unit = getUnit(detail); | |
134 | 158 | const [time, value] = socketInfo.message[key].at(0) || []; |
135 | - return { key, value, time }; | |
159 | + return { key, value, time, name, unit }; | |
136 | 160 | }); |
137 | 161 | |
138 | 162 | await nextTick(); |
... | ... | @@ -141,7 +165,6 @@ |
141 | 165 | } catch (error) {} |
142 | 166 | }, |
143 | 167 | onDisconnected() { |
144 | - console.log('断开连接了'); | |
145 | 168 | close(); |
146 | 169 | }, |
147 | 170 | onError() { |
... | ... | @@ -154,6 +177,8 @@ |
154 | 177 | socketInfo.attr = key; |
155 | 178 | openModal(true); |
156 | 179 | }; |
180 | + | |
181 | + onUnmounted(() => close()); | |
157 | 182 | </script> |
158 | 183 | |
159 | 184 | <template> |
... | ... | @@ -198,16 +223,17 @@ |
198 | 223 | <List.Item> |
199 | 224 | <Card class="shadow-md"> |
200 | 225 | <template #title> |
201 | - <span class="text-base font-normal">{{ item.key }}</span> | |
226 | + <span class="text-base font-normal">{{ item.name }}</span> | |
202 | 227 | </template> |
203 | 228 | <template #extra> |
204 | 229 | <Button type="link" class="!p-0" @click="handleShowDetail(item)">历史数据</Button> |
205 | 230 | </template> |
206 | - <section> | |
207 | - <div class="flex font-bold text-lg mb-4"> | |
208 | - <div>{{ item.value }}</div> | |
231 | + <section class="min-h-16 flex flex-col justify-between"> | |
232 | + <div class="flex font-bold text-lg mb-4 gap-2"> | |
233 | + <div>{{ item.value || '--' }}</div> | |
234 | + <div>{{ item.unit }}</div> | |
209 | 235 | </div> |
210 | - <div class="text-dark-800 text-xs">{{ formatToDateTime(item.time) }}</div> | |
236 | + <div class="text-dark-800 text-xs">{{ formatToDateTime(item.time) || '--' }}</div> | |
211 | 237 | </section> |
212 | 238 | </Card> |
213 | 239 | </List.Item> | ... | ... |
... | ... | @@ -32,7 +32,11 @@ |
32 | 32 | /> |
33 | 33 | </Tabs> |
34 | 34 | <Attribute v-show="activeKey === FunctionType.PROPERTIES" ref="AttrRef" /> |
35 | - <Service v-show="activeKey === FunctionType.SERVICE" ref="ServiceRef" /> | |
35 | + <Service | |
36 | + v-show="activeKey === FunctionType.SERVICE" | |
37 | + :record="$props.record" | |
38 | + ref="ServiceRef" | |
39 | + /> | |
36 | 40 | <Events v-show="activeKey === FunctionType.EVENTS" ref="EventsRef" /> |
37 | 41 | </div> |
38 | 42 | </BasicModal> |
... | ... | @@ -51,7 +55,6 @@ |
51 | 55 | import { useMessage } from '/@/hooks/web/useMessage'; |
52 | 56 | import { OpenModelMode, OpenModelOfMatterModelParams } from './types/index'; |
53 | 57 | import { FunctionType } from './cpns/config'; |
54 | - import { StructRecord } from '/@/components/Form/src/externalCompns/components/StructForm/type'; | |
55 | 58 | |
56 | 59 | const emit = defineEmits(['register', 'success']); |
57 | 60 | |
... | ... | @@ -75,9 +78,9 @@ |
75 | 78 | const functionType = ref<FunctionType>(); |
76 | 79 | const { createMessage } = useMessage(); |
77 | 80 | |
78 | - const setAttrFormData = (data: StructRecord) => AttrRef.value?.setFormData(data); | |
79 | - const setServiceFormData = (data) => ServiceRef.value?.setFormData(data); | |
80 | - const setEventsFormData = (data) => EventsRef.value?.setFormData(data); | |
81 | + const setAttrFormData = (data: ModelOfMatterParams) => AttrRef.value?.setFormData(data); | |
82 | + const setServiceFormData = (data: ModelOfMatterParams) => ServiceRef.value?.setFormData(data); | |
83 | + const setEventsFormData = (data: ModelOfMatterParams) => EventsRef.value?.setFormData(data); | |
81 | 84 | |
82 | 85 | const enums = { |
83 | 86 | [FunctionType.PROPERTIES]: setAttrFormData, |
... | ... | @@ -85,7 +88,7 @@ |
85 | 88 | [FunctionType.EVENTS]: setEventsFormData, |
86 | 89 | }; |
87 | 90 | |
88 | - function setFormData(type: FunctionType, value: StructRecord) { | |
91 | + function setFormData(type: FunctionType, value: ModelOfMatterParams) { | |
89 | 92 | const setFn = enums[type]; |
90 | 93 | setFn(value); |
91 | 94 | } |
... | ... | @@ -98,7 +101,7 @@ |
98 | 101 | if (record) { |
99 | 102 | functionType.value = data.record.functionType; |
100 | 103 | activeKey.value = data.record.functionType; |
101 | - setFormData(record.functionType, record as unknown as StructRecord); | |
104 | + setFormData(record.functionType, record as unknown as ModelOfMatterParams); | |
102 | 105 | } |
103 | 106 | if (unref(openModalMode) === OpenModelMode.VIEW) { |
104 | 107 | setModalProps({ showOkBtn: false, showCancelBtn: false, title: '查看物模型' }); |
... | ... | @@ -133,7 +136,7 @@ |
133 | 136 | } else if (activeKey.value == FunctionType.SERVICE) { |
134 | 137 | params = (await ServiceRef.value?.getFormData()) || {}; |
135 | 138 | } else { |
136 | - params = await EventsRef.value?.getFormData(); | |
139 | + params = (await EventsRef.value?.getFormData()) || {}; | |
137 | 140 | } |
138 | 141 | params.deviceProfileId = props.record.id; |
139 | 142 | if (unref(openModalMode) === OpenModelMode.CREATE) { | ... | ... |
1 | 1 | <template> |
2 | - <div> | |
3 | - <BasicModal | |
4 | - title="物模型TSL" | |
5 | - :maskClosable="false" | |
6 | - v-bind="$attrs" | |
7 | - width="55rem" | |
8 | - @register="register" | |
9 | - @ok="handleSubmit" | |
10 | - @cancel="handleCancel" | |
11 | - > | |
12 | - <TslContent ref="TslConRef" /> | |
13 | - </BasicModal> | |
14 | - </div> | |
2 | + <BasicModal | |
3 | + title="物模型TSL" | |
4 | + :maskClosable="false" | |
5 | + v-bind="$attrs" | |
6 | + width="55rem" | |
7 | + @register="register" | |
8 | + @ok="handleSubmit" | |
9 | + @cancel="handleCancel" | |
10 | + > | |
11 | + <TslContent :record="$props.record" ref="TslConRef" /> | |
12 | + </BasicModal> | |
15 | 13 | </template> |
16 | 14 | <script lang="ts" setup> |
17 | 15 | import { ref } from 'vue'; |
18 | 16 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
19 | - // import { useMessage } from '/@/hooks/web/useMessage'; | |
20 | 17 | import TslContent from './cpns/TslContent.vue'; |
18 | + import { DeviceRecord } from '/@/api/device/model/deviceModel'; | |
21 | 19 | |
22 | 20 | defineEmits(['register']); |
23 | - // const { createMessage } = useMessage(); | |
21 | + | |
22 | + defineProps<{ | |
23 | + record: DeviceRecord; | |
24 | + }>(); | |
25 | + | |
24 | 26 | const TslConRef = ref<InstanceType<typeof TslContent>>(); |
25 | 27 | const isUpdate = ref(false); |
26 | 28 | |
27 | 29 | const [register, { closeModal, setModalProps }] = useModalInner(async (data) => { |
30 | + // const record = await getModelTsl({ | |
31 | + // functionType: FunctionType.PROPERTIES, | |
32 | + // deviceProfileId: props.record.id, | |
33 | + // }); | |
28 | 34 | setModalProps({ confirmLoading: true }); |
29 | 35 | isUpdate.value = data.isUpdate; |
30 | 36 | setModalProps({ confirmLoading: false }); |
31 | 37 | // const jsCode = TslConRef?.value.aceEditor.getValue(); |
32 | 38 | // TslConRef?.value.aceEditor.setValue(jsCode); |
33 | 39 | }); |
40 | + | |
34 | 41 | const handleCancel = () => { |
35 | 42 | TslConRef.value?.resetFormData(); |
36 | 43 | closeModal(); | ... | ... |
... | ... | @@ -3,14 +3,12 @@ |
3 | 3 | </template> |
4 | 4 | <script lang="ts" setup> |
5 | 5 | import { BasicForm, useForm } from '/@/components/Form'; |
6 | - import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
6 | + import { DataType, ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
7 | 7 | import { formSchemas } from '/@/components/Form/src/externalCompns/components/StructForm/config'; |
8 | - import { transformFormValue } from '/@/components/Form/src/externalCompns/components/StructForm/util'; | |
9 | - import { | |
10 | - StructFormValue, | |
11 | - StructRecord, | |
12 | - } from '/@/components/Form/src/externalCompns/components/StructForm/type'; | |
13 | - import { isObject } from 'lodash'; | |
8 | + import { StructFormValue } from '/@/components/Form/src/externalCompns/components/StructForm/type'; | |
9 | + import { transfromToStructJSON } from '/@/components/Form/src/externalCompns/components/StructForm/util'; | |
10 | + import { FunctionType } from './config'; | |
11 | + import { isArray } from 'lodash'; | |
14 | 12 | |
15 | 13 | const [register, { validate, resetFields, setFieldsValue }] = useForm({ |
16 | 14 | labelWidth: 100, |
... | ... | @@ -26,7 +24,18 @@ |
26 | 24 | async function getFormData(): Promise<Partial<ModelOfMatterParams>> { |
27 | 25 | const _values = (await validate()) as StructFormValue; |
28 | 26 | if (!_values) return {}; |
29 | - let value = transformFormValue(_values); | |
27 | + const { functionName, remark, identifier, accessMode } = _values; | |
28 | + const structJSON = transfromToStructJSON(_values); | |
29 | + const value = { | |
30 | + functionName, | |
31 | + functionType: FunctionType.PROPERTIES, | |
32 | + remark, | |
33 | + identifier, | |
34 | + accessMode, | |
35 | + functionJson: { | |
36 | + dataType: structJSON.dataType, | |
37 | + }, | |
38 | + } as ModelOfMatterParams; | |
30 | 39 | return value; |
31 | 40 | } |
32 | 41 | |
... | ... | @@ -34,14 +43,17 @@ |
34 | 43 | resetFields(); |
35 | 44 | }; |
36 | 45 | |
37 | - const setFormData = (record: StructRecord) => { | |
38 | - const { functionJson } = record as StructRecord; | |
39 | - const { specs = {} } = functionJson || ({} as StructRecord['functionJson']); | |
46 | + const setFormData = (record: ModelOfMatterParams) => { | |
47 | + const { functionJson } = record; | |
48 | + const { dataType = {} } = functionJson!; | |
49 | + | |
50 | + const { specs } = dataType! as DataType; | |
40 | 51 | |
41 | 52 | const value = { |
42 | 53 | ...record, |
43 | 54 | ...functionJson, |
44 | - ...(isObject(specs) ? specs : {}), | |
55 | + ...dataType, | |
56 | + ...(isArray(specs) ? specs : { ...specs }), | |
45 | 57 | }; |
46 | 58 | |
47 | 59 | setFieldsValue(value); | ... | ... |
... | ... | @@ -3,10 +3,11 @@ |
3 | 3 | </template> |
4 | 4 | <script lang="ts" setup> |
5 | 5 | import { BasicForm, useForm } from '/@/components/Form'; |
6 | - import { eventSchemas } from './config'; | |
6 | + import { eventSchemas, FunctionType } from './config'; | |
7 | 7 | import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; |
8 | + import { StructFormValue } from '/@/components/Form/src/externalCompns/components/StructForm/type'; | |
8 | 9 | |
9 | - const [register, { validate, resetFields }] = useForm({ | |
10 | + const [register, { validate, resetFields, setFieldsValue }] = useForm({ | |
10 | 11 | labelWidth: 100, |
11 | 12 | schemas: eventSchemas, |
12 | 13 | actionColOptions: { |
... | ... | @@ -18,12 +19,35 @@ |
18 | 19 | }); |
19 | 20 | |
20 | 21 | //回显数据 |
21 | - const setFormData = () => {}; | |
22 | + const setFormData = (record: ModelOfMatterParams) => { | |
23 | + const { functionJson = {}, functionName, identifier, remark, eventType } = record; | |
24 | + const { outputData } = functionJson; | |
25 | + const value = { | |
26 | + functionName, | |
27 | + identifier, | |
28 | + remark, | |
29 | + outputData, | |
30 | + eventType, | |
31 | + }; | |
32 | + setFieldsValue(value); | |
33 | + }; | |
22 | 34 | |
23 | 35 | async function getFormData() { |
24 | - const values = (await validate()) as ModelOfMatterParams; | |
25 | - if (!values) return; | |
26 | - console.log(values); | |
36 | + const _values = (await validate()) as StructFormValue; | |
37 | + const { functionName, remark, identifier, outputData, eventType } = _values; | |
38 | + if (!_values) return {} as unknown as ModelOfMatterParams; | |
39 | + const value = { | |
40 | + functionName, | |
41 | + identifier, | |
42 | + remark, | |
43 | + functionType: FunctionType.EVENTS, | |
44 | + eventType, | |
45 | + functionJson: { | |
46 | + outputData, | |
47 | + }, | |
48 | + } as ModelOfMatterParams; | |
49 | + | |
50 | + return value; | |
27 | 51 | } |
28 | 52 | |
29 | 53 | //清空数据 | ... | ... |
... | ... | @@ -4,11 +4,18 @@ |
4 | 4 | <script lang="ts" setup> |
5 | 5 | import { BasicForm, useForm } from '/@/components/Form'; |
6 | 6 | import { serviceSchemas } from './config'; |
7 | - import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
8 | 7 | import { FunctionType } from './config'; |
9 | - const [register, { validate, resetFields }] = useForm({ | |
8 | + import { StructFormValue } from '/@/components/Form/src/externalCompns/components/StructForm/type'; | |
9 | + import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
10 | + import { DeviceRecord } from '/@/api/device/model/deviceModel'; | |
11 | + | |
12 | + const props = defineProps<{ | |
13 | + record: DeviceRecord; | |
14 | + }>(); | |
15 | + | |
16 | + const [register, { validate, resetFields, setFieldsValue }] = useForm({ | |
10 | 17 | labelWidth: 100, |
11 | - schemas: serviceSchemas, | |
18 | + schemas: serviceSchemas(props.record.transportType === 'TCP'), | |
12 | 19 | actionColOptions: { |
13 | 20 | span: 14, |
14 | 21 | }, |
... | ... | @@ -18,15 +25,41 @@ |
18 | 25 | }); |
19 | 26 | |
20 | 27 | //回显数据 |
21 | - const setFormData = () => {}; | |
28 | + const setFormData = (record: ModelOfMatterParams) => { | |
29 | + const { functionJson = {}, functionName, identifier, remark, callType } = record; | |
30 | + const { inputData, outputData, serviceCommand } = functionJson; | |
31 | + const value = { | |
32 | + functionName, | |
33 | + identifier, | |
34 | + remark, | |
35 | + inputData, | |
36 | + outputData, | |
37 | + serviceCommand, | |
38 | + callType, | |
39 | + }; | |
40 | + setFieldsValue(value); | |
41 | + }; | |
22 | 42 | |
23 | 43 | //获取数据 |
24 | 44 | async function getFormData() { |
25 | - const values = (await validate()) as ModelOfMatterParams; | |
26 | - if (!values) return; | |
27 | - values.functionType = FunctionType.SERVICE; | |
28 | - console.log(values); | |
29 | - return values; | |
45 | + const _values = (await validate()) as StructFormValue; | |
46 | + const { functionName, remark, identifier, inputData, outputData, serviceCommand, callType } = | |
47 | + _values; | |
48 | + if (!_values) return {}; | |
49 | + const value = { | |
50 | + functionName, | |
51 | + identifier, | |
52 | + remark, | |
53 | + functionType: FunctionType.SERVICE, | |
54 | + callType, | |
55 | + functionJson: { | |
56 | + inputData, | |
57 | + outputData, | |
58 | + serviceCommand, | |
59 | + }, | |
60 | + } as ModelOfMatterParams; | |
61 | + | |
62 | + return value; | |
30 | 63 | } |
31 | 64 | |
32 | 65 | //清空数据 | ... | ... |
... | ... | @@ -3,11 +3,11 @@ |
3 | 3 | <div> |
4 | 4 | <Typography> |
5 | 5 | <TypographyParagraph> |
6 | - <blockquote style="background: #f2f2f2">{{ useBlockContent }}</blockquote> | |
6 | + <blockquote class="bg-gray-50">{{ useBlockContent }}</blockquote> | |
7 | 7 | </TypographyParagraph> |
8 | 8 | </Typography> |
9 | 9 | </div> |
10 | - <div style="display: flex; justify-content: space-between; align-items: center"> | |
10 | + <div class="flex justify-between items-center"> | |
11 | 11 | <div>模型内容</div> |
12 | 12 | <div> |
13 | 13 | <Button @click="handlePremitter"> | ... | ... |
... | ... | @@ -17,12 +17,13 @@ export enum FormField { |
17 | 17 | BOOL_CLOSE = 'boolClose', |
18 | 18 | BOOL_OPEN = 'boolOpen', |
19 | 19 | LENGTH = 'length', |
20 | - R_W_FLAG = 'rwFlag', | |
21 | 20 | SPECS_LIST = 'specs', |
22 | 21 | CALL_TYPE = 'callType', |
23 | 22 | INPUT_PARAM = 'inputData', |
24 | 23 | OUTPUT_PARAM = 'outputData', |
25 | 24 | EVENT_TYPE = 'eventType', |
25 | + SERVICE_COMMAND = 'serviceCommand', | |
26 | + ACCESS_MODE = 'accessMode', | |
26 | 27 | |
27 | 28 | STRUCT = 'struct', |
28 | 29 | } |
... | ... | @@ -33,6 +34,11 @@ export enum FunctionType { |
33 | 34 | SERVICE = 'services', |
34 | 35 | } |
35 | 36 | |
37 | +export enum AssessMode { | |
38 | + READ = 'r', | |
39 | + WRITE = 'w', | |
40 | +} | |
41 | + | |
36 | 42 | /** |
37 | 43 | * 新增参数 动态显示表单 |
38 | 44 | */ |
... | ... | @@ -82,79 +88,92 @@ export const defaultTslContent = { |
82 | 88 | ], |
83 | 89 | }; |
84 | 90 | |
85 | -export const serviceSchemas: FormSchema[] = [ | |
86 | - { | |
87 | - field: FormField.FUNCTION_NAME, | |
88 | - label: '功能名称', | |
89 | - required: true, | |
90 | - component: 'Input', | |
91 | - colProps: { | |
92 | - span: 18, | |
91 | +export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => { | |
92 | + return [ | |
93 | + { | |
94 | + field: FormField.FUNCTION_NAME, | |
95 | + label: '功能名称', | |
96 | + required: true, | |
97 | + component: 'Input', | |
98 | + colProps: { | |
99 | + span: 18, | |
100 | + }, | |
101 | + componentProps: { | |
102 | + maxLength: 255, | |
103 | + placeholder: '请输入功能名称', | |
104 | + }, | |
93 | 105 | }, |
94 | - componentProps: { | |
95 | - maxLength: 255, | |
96 | - placeholder: '请输入功能名称', | |
106 | + { | |
107 | + field: FormField.IDENTIFIER, | |
108 | + label: '标识符', | |
109 | + required: true, | |
110 | + component: 'Input', | |
111 | + colProps: { | |
112 | + span: 18, | |
113 | + }, | |
114 | + componentProps: { | |
115 | + maxLength: 255, | |
116 | + placeholder: '请输入标识符', | |
117 | + }, | |
97 | 118 | }, |
98 | - }, | |
99 | - { | |
100 | - field: FormField.IDENTIFIER, | |
101 | - label: '标识符', | |
102 | - required: true, | |
103 | - component: 'Input', | |
104 | - colProps: { | |
105 | - span: 18, | |
119 | + { | |
120 | + field: FormField.CALL_TYPE, | |
121 | + component: 'ApiRadioGroup', | |
122 | + label: '调用方式', | |
123 | + required: true, | |
124 | + colProps: { | |
125 | + span: 24, | |
126 | + }, | |
127 | + defaultValue: 'ASYNC', | |
128 | + componentProps: { | |
129 | + placeholder: '请选择调用方式', | |
130 | + api: findDictItemByCode, | |
131 | + params: { | |
132 | + dictCode: 'call_mode', | |
133 | + }, | |
134 | + labelField: 'itemText', | |
135 | + valueField: 'itemValue', | |
136 | + }, | |
106 | 137 | }, |
107 | - componentProps: { | |
108 | - maxLength: 255, | |
109 | - placeholder: '请输入标识符', | |
138 | + { | |
139 | + field: FormField.INPUT_PARAM, | |
140 | + label: '输入参数', | |
141 | + component: 'StructForm', | |
142 | + valueField: 'value', | |
143 | + changeEvent: 'update:value', | |
144 | + ifShow: !tcpDeviceFlag, | |
145 | + colProps: { span: 24 }, | |
110 | 146 | }, |
111 | - }, | |
112 | - { | |
113 | - field: FormField.CALL_TYPE, | |
114 | - component: 'ApiRadioGroup', | |
115 | - label: '调用方式', | |
116 | - required: true, | |
117 | - colProps: { | |
118 | - span: 24, | |
147 | + { | |
148 | + field: FormField.OUTPUT_PARAM, | |
149 | + label: '输出参数', | |
150 | + component: 'StructForm', | |
151 | + valueField: 'value', | |
152 | + changeEvent: 'update:value', | |
153 | + ifShow: !tcpDeviceFlag, | |
154 | + colProps: { span: 24 }, | |
119 | 155 | }, |
120 | - defaultValue: 'ASYNC', | |
121 | - componentProps: { | |
122 | - placeholder: '请选择调用方式', | |
123 | - api: findDictItemByCode, | |
124 | - params: { | |
125 | - dictCode: 'call_mode', | |
156 | + { | |
157 | + field: FormField.SERVICE_COMMAND, | |
158 | + label: '服务命令', | |
159 | + component: 'Input', | |
160 | + ifShow: tcpDeviceFlag, | |
161 | + componentProps: { | |
162 | + placeholder: '请输入服务命令', | |
126 | 163 | }, |
127 | - labelField: 'itemText', | |
128 | - valueField: 'itemValue', | |
129 | 164 | }, |
130 | - }, | |
131 | - { | |
132 | - field: FormField.INPUT_PARAM, | |
133 | - label: '输入参数', | |
134 | - component: 'StructForm', | |
135 | - valueField: 'value', | |
136 | - changeEvent: 'update:value', | |
137 | - colProps: { span: 24 }, | |
138 | - }, | |
139 | - { | |
140 | - field: FormField.OUTPUT_PARAM, | |
141 | - label: '输出参数', | |
142 | - component: 'StructForm', | |
143 | - valueField: 'value', | |
144 | - changeEvent: 'update:value', | |
145 | - colProps: { span: 24 }, | |
146 | - }, | |
147 | - { | |
148 | - field: FormField.REFARK, | |
149 | - label: '备注', | |
150 | - component: 'InputTextArea', | |
151 | - componentProps: { | |
152 | - rows: 6, | |
153 | - maxLength: 100, | |
154 | - placeholder: '请输入描述', | |
165 | + { | |
166 | + field: FormField.REFARK, | |
167 | + label: '备注', | |
168 | + component: 'InputTextArea', | |
169 | + componentProps: { | |
170 | + rows: 6, | |
171 | + maxLength: 100, | |
172 | + placeholder: '请输入描述', | |
173 | + }, | |
155 | 174 | }, |
156 | - }, | |
157 | -]; | |
175 | + ]; | |
176 | +}; | |
158 | 177 | |
159 | 178 | export const eventSchemas: FormSchema[] = [ |
160 | 179 | { |
... | ... | @@ -191,7 +210,7 @@ export const eventSchemas: FormSchema[] = [ |
191 | 210 | colProps: { |
192 | 211 | span: 24, |
193 | 212 | }, |
194 | - defaultValue: 'INFO_EVENT_TYPE', | |
213 | + defaultValue: 'INFO', | |
195 | 214 | componentProps: { |
196 | 215 | placeholder: '请选择事件类型', |
197 | 216 | api: findDictItemByCode, | ... | ... |
... | ... | @@ -18,6 +18,7 @@ |
18 | 18 | const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({ |
19 | 19 | columns, |
20 | 20 | title: '包仓库', |
21 | + bordered: true, | |
21 | 22 | api: async (params: GetOtaPackagesParams) => { |
22 | 23 | const data = await getOtaPackagesList({ |
23 | 24 | ...params, | ... | ... |