Commit 087e8308f9ae929ccd13d0a971efdeb03ac0fb73
Merge branch 'feat/device-ota-package' into 'main_dev'
Feat/device ota package See merge request yunteng/thingskit-front!1221
Showing
13 changed files
with
223 additions
and
289 deletions
... | ... | @@ -17,7 +17,7 @@ enum Api { |
17 | 17 | CREATE_OTA_PACKAGES = '/otaPackage', |
18 | 18 | UPLOAD_OTA_PACKAGES = '/otaPackage', |
19 | 19 | DELETE_OTA_PACKAGES = '/otaPackage', |
20 | - GET_DEVICE_PROFILE_INFO_DEFAULT = '/deviceProfileInfo/default', | |
20 | + GET_DEVICE_PROFILE_INFO_DEFAULT = '/deviceProfileInfo', | |
21 | 21 | GET_OTA_PACKAGE_INFO = '/otaPackage/info', |
22 | 22 | DOWNLOAD_PACKAGE = '/otaPackage', |
23 | 23 | |
... | ... | @@ -75,13 +75,13 @@ export const uploadOtaPackages = (params: UploadOtaPackagesParams) => { |
75 | 75 | }; |
76 | 76 | |
77 | 77 | /** |
78 | - * @description 获取设备默认信息 | |
78 | + * @description 获取设备信息 | |
79 | 79 | * @returns |
80 | 80 | */ |
81 | -export const getDefaultDeviceProfile = () => { | |
81 | +export const getDeviceProfileInfo = (id: string) => { | |
82 | 82 | return defHttp.get<DefaultDeviceProfileInfo>( |
83 | 83 | { |
84 | - url: Api.GET_DEVICE_PROFILE_INFO_DEFAULT, | |
84 | + url: `${Api.GET_DEVICE_PROFILE_INFO_DEFAULT}/${id}`, | |
85 | 85 | }, |
86 | 86 | { |
87 | 87 | joinPrefix: false, | ... | ... |
src/components/Form/src/components/ApiQuerySelect.vue
deleted
100644 → 0
1 | -<script lang="ts"> | |
2 | - export type OptionsItem = { label: string; value: string; disabled?: boolean }; | |
3 | - export interface OnChangeHookParams { | |
4 | - options: Ref<OptionsItem[]>; | |
5 | - } | |
6 | -</script> | |
7 | - | |
8 | -<script lang="ts" setup> | |
9 | - import { ref, watchEffect, computed, unref, watch, Ref } from 'vue'; | |
10 | - import { Select } from 'ant-design-vue'; | |
11 | - import { isFunction } from '/@/utils/is'; | |
12 | - import { useRuleFormItem } from '/@/hooks/component/useFormItem'; | |
13 | - import { get, omit } from 'lodash-es'; | |
14 | - import { LoadingOutlined } from '@ant-design/icons-vue'; | |
15 | - import { useI18n } from '/@/hooks/web/useI18n'; | |
16 | - import { useDebounceFn } from '@vueuse/shared'; | |
17 | - | |
18 | - const emit = defineEmits(['options-change', 'change']); | |
19 | - const props = withDefaults( | |
20 | - defineProps<{ | |
21 | - value?: Recordable | number | string; | |
22 | - numberToString?: boolean; | |
23 | - api?: (arg?: Recordable) => Promise<Recordable>; | |
24 | - queryApi?: (value?: any) => Promise<Recordable>; | |
25 | - params?: Recordable | ((searchText?: string) => Recordable); | |
26 | - resultField?: string; | |
27 | - labelField?: string; | |
28 | - valueField?: string; | |
29 | - immediate?: boolean; | |
30 | - queryEmptyDataAgin?: boolean; | |
31 | - }>(), | |
32 | - { | |
33 | - resultField: '', | |
34 | - labelField: 'label', | |
35 | - valueField: 'value', | |
36 | - searchField: 'text', | |
37 | - immediate: true, | |
38 | - queryEmptyDataAgin: true, | |
39 | - } | |
40 | - ); | |
41 | - | |
42 | - const selectOption = ref<OptionsItem>(); | |
43 | - const options = ref<OptionsItem[]>([]); | |
44 | - const loading = ref(false); | |
45 | - const isFirstLoad = ref(true); | |
46 | - const emitData = ref<any[]>([]); | |
47 | - const { t } = useI18n(); | |
48 | - | |
49 | - // Embedded in the form, just use the hook binding to perform form verification | |
50 | - const [state] = useRuleFormItem(props, 'value', 'change', emitData); | |
51 | - | |
52 | - const getOptions = computed(() => { | |
53 | - const { labelField, valueField = 'value', numberToString } = props; | |
54 | - const _options = unref(options); | |
55 | - | |
56 | - if ( | |
57 | - unref(selectOption) && | |
58 | - !_options.find((item) => get(item, valueField) === get(unref(selectOption), valueField)) | |
59 | - ) { | |
60 | - _options.push(unref(selectOption)!); | |
61 | - } | |
62 | - return _options.reduce((prev, next: Recordable) => { | |
63 | - if (next) { | |
64 | - const value = get(next, valueField); | |
65 | - const label = get(next, labelField); | |
66 | - prev.push({ | |
67 | - ...omit(next, [labelField, valueField]), | |
68 | - label, | |
69 | - value: numberToString ? `${value}` : value, | |
70 | - }); | |
71 | - } | |
72 | - return prev; | |
73 | - }, [] as OptionsItem[]); | |
74 | - }); | |
75 | - | |
76 | - watchEffect(() => { | |
77 | - props.immediate && fetch(); | |
78 | - }); | |
79 | - | |
80 | - watch( | |
81 | - () => props.params, | |
82 | - () => { | |
83 | - !unref(isFirstLoad) && fetch(); | |
84 | - }, | |
85 | - { deep: true } | |
86 | - ); | |
87 | - | |
88 | - watch( | |
89 | - () => props.value, | |
90 | - async (target) => { | |
91 | - if (target && props.queryApi && isFunction(props.queryApi)) { | |
92 | - if (unref(getOptions).find((item) => item.value === target)) return; | |
93 | - const detail = await props.queryApi(target); | |
94 | - if ( | |
95 | - unref(options).find( | |
96 | - (item) => get(item, props.valueField) === get(detail, props.valueField) | |
97 | - ) | |
98 | - ) | |
99 | - return; | |
100 | - | |
101 | - selectOption.value = detail as OptionsItem; | |
102 | - } | |
103 | - }, | |
104 | - { | |
105 | - immediate: true, | |
106 | - } | |
107 | - ); | |
108 | - | |
109 | - async function fetch(searchText?: string) { | |
110 | - const api = props.api; | |
111 | - if (!api || !isFunction(api)) return; | |
112 | - options.value = []; | |
113 | - try { | |
114 | - loading.value = true; | |
115 | - const params = | |
116 | - props.params && isFunction(props.params) ? props.params(searchText) : props.params; | |
117 | - | |
118 | - const res = await api(params); | |
119 | - | |
120 | - if (Array.isArray(res)) { | |
121 | - options.value = res; | |
122 | - emitChange(); | |
123 | - return; | |
124 | - } | |
125 | - | |
126 | - if (props.resultField) { | |
127 | - options.value = get(res, props.resultField) || []; | |
128 | - } | |
129 | - emitChange(); | |
130 | - } catch (error) { | |
131 | - console.warn(error); | |
132 | - } finally { | |
133 | - loading.value = false; | |
134 | - } | |
135 | - } | |
136 | - | |
137 | - async function handleFetch() { | |
138 | - const { immediate } = props; | |
139 | - if (!immediate && unref(isFirstLoad)) { | |
140 | - await fetch(); | |
141 | - isFirstLoad.value = false; | |
142 | - } | |
143 | - } | |
144 | - | |
145 | - function emitChange() { | |
146 | - emit('options-change', unref(getOptions)); | |
147 | - } | |
148 | - | |
149 | - function handleChange(value: string, ...args) { | |
150 | - emitData.value = args; | |
151 | - if (!value && props.queryEmptyDataAgin) fetch(); | |
152 | - } | |
153 | - | |
154 | - const debounceSearchFunction = useDebounceFn(fetch, 300); | |
155 | -</script> | |
156 | - | |
157 | -<template> | |
158 | - <Select | |
159 | - v-bind="$attrs" | |
160 | - show-search | |
161 | - @dropdownVisibleChange="handleFetch" | |
162 | - @change="handleChange" | |
163 | - :options="getOptions" | |
164 | - :filter-option="false" | |
165 | - @search="debounceSearchFunction" | |
166 | - v-model:value="state" | |
167 | - > | |
168 | - <template #[item]="data" v-for="item in Object.keys($slots)"> | |
169 | - <slot :name="item" v-bind="data || {}"></slot> | |
170 | - </template> | |
171 | - <template #suffixIcon v-if="loading"> | |
172 | - <LoadingOutlined spin /> | |
173 | - </template> | |
174 | - <template #notFoundContent v-if="loading"> | |
175 | - <span> | |
176 | - <LoadingOutlined spin class="mr-1" /> | |
177 | - {{ t('component.form.apiSelectNotFound') }} | |
178 | - </span> | |
179 | - </template> | |
180 | - </Select> | |
181 | -</template> |
... | ... | @@ -10,7 +10,6 @@ |
10 | 10 | import { Select } from 'ant-design-vue'; |
11 | 11 | import { isFunction } from '/@/utils/is'; |
12 | 12 | import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
13 | - import { useAttrs } from '/@/hooks/core/useAttrs'; | |
14 | 13 | import { get, omit } from 'lodash-es'; |
15 | 14 | import { LoadingOutlined } from '@ant-design/icons-vue'; |
16 | 15 | import { useI18n } from '/@/hooks/web/useI18n'; |
... | ... | @@ -21,17 +20,14 @@ |
21 | 20 | defineProps<{ |
22 | 21 | value?: Recordable | number | string; |
23 | 22 | numberToString?: boolean; |
24 | - api?: (arg?: Recordable) => Promise<OptionsItem[]>; | |
25 | - searchApi?: (arg?: Recordable) => Promise<OptionsItem[]>; | |
26 | - params?: Recordable; | |
23 | + api?: (arg?: Recordable) => Promise<Recordable>; | |
24 | + queryApi?: (value?: any) => Promise<Recordable>; | |
25 | + params?: Recordable | ((searchText?: string) => Recordable); | |
27 | 26 | resultField?: string; |
28 | 27 | labelField?: string; |
29 | 28 | valueField?: string; |
30 | 29 | immediate?: boolean; |
31 | - searchField?: string; | |
32 | 30 | queryEmptyDataAgin?: boolean; |
33 | - onChangeHook?: ({ options }: OnChangeHookParams) => void; | |
34 | - dropdownVisibleChangeHook?: ({ options }: OnChangeHookParams) => void; | |
35 | 31 | }>(), |
36 | 32 | { |
37 | 33 | resultField: '', |
... | ... | @@ -42,11 +38,12 @@ |
42 | 38 | queryEmptyDataAgin: true, |
43 | 39 | } |
44 | 40 | ); |
41 | + | |
42 | + const selectOption = ref<OptionsItem>(); | |
45 | 43 | const options = ref<OptionsItem[]>([]); |
46 | 44 | const loading = ref(false); |
47 | 45 | const isFirstLoad = ref(true); |
48 | 46 | const emitData = ref<any[]>([]); |
49 | - const attrs = useAttrs(); | |
50 | 47 | const { t } = useI18n(); |
51 | 48 | |
52 | 49 | // Embedded in the form, just use the hook binding to perform form verification |
... | ... | @@ -54,7 +51,15 @@ |
54 | 51 | |
55 | 52 | const getOptions = computed(() => { |
56 | 53 | const { labelField, valueField = 'value', numberToString } = props; |
57 | - return unref(options).reduce((prev, next: Recordable) => { | |
54 | + const _options = unref(options); | |
55 | + | |
56 | + if ( | |
57 | + unref(selectOption) && | |
58 | + !_options.find((item) => get(item, valueField) === get(unref(selectOption), valueField)) | |
59 | + ) { | |
60 | + _options.push(unref(selectOption)!); | |
61 | + } | |
62 | + return _options.reduce((prev, next: Recordable) => { | |
58 | 63 | if (next) { |
59 | 64 | const value = get(next, valueField); |
60 | 65 | const label = get(next, labelField); |
... | ... | @@ -68,15 +73,6 @@ |
68 | 73 | }, [] as OptionsItem[]); |
69 | 74 | }); |
70 | 75 | |
71 | - const getBindProps = computed(() => { | |
72 | - const { searchApi } = props; | |
73 | - return { | |
74 | - ...attrs, | |
75 | - showSearch: true, | |
76 | - filterOption: !searchApi, | |
77 | - }; | |
78 | - }); | |
79 | - | |
80 | 76 | watchEffect(() => { |
81 | 77 | props.immediate && fetch(); |
82 | 78 | }); |
... | ... | @@ -89,18 +85,45 @@ |
89 | 85 | { deep: true } |
90 | 86 | ); |
91 | 87 | |
92 | - async function fetch() { | |
88 | + watch( | |
89 | + () => props.value, | |
90 | + async (target) => { | |
91 | + if (target && props.queryApi && isFunction(props.queryApi)) { | |
92 | + if (unref(getOptions).find((item) => item.value === target)) return; | |
93 | + const detail = await props.queryApi(target); | |
94 | + if ( | |
95 | + !detail || | |
96 | + unref(options).find( | |
97 | + (item) => get(item, props.valueField) === get(detail, props.valueField) | |
98 | + ) | |
99 | + ) | |
100 | + return; | |
101 | + | |
102 | + selectOption.value = detail as OptionsItem; | |
103 | + } | |
104 | + }, | |
105 | + { | |
106 | + immediate: true, | |
107 | + } | |
108 | + ); | |
109 | + | |
110 | + async function fetch(searchText?: string) { | |
93 | 111 | const api = props.api; |
94 | 112 | if (!api || !isFunction(api)) return; |
95 | 113 | options.value = []; |
96 | 114 | try { |
97 | 115 | loading.value = true; |
98 | - const res = await api(props.params); | |
116 | + const params = | |
117 | + props.params && isFunction(props.params) ? props.params(searchText) : props.params; | |
118 | + | |
119 | + const res = await api(params); | |
120 | + | |
99 | 121 | if (Array.isArray(res)) { |
100 | 122 | options.value = res; |
101 | 123 | emitChange(); |
102 | 124 | return; |
103 | 125 | } |
126 | + | |
104 | 127 | if (props.resultField) { |
105 | 128 | options.value = get(res, props.resultField) || []; |
106 | 129 | } |
... | ... | @@ -113,14 +136,11 @@ |
113 | 136 | } |
114 | 137 | |
115 | 138 | async function handleFetch() { |
116 | - const { immediate, dropdownVisibleChangeHook } = props; | |
139 | + const { immediate } = props; | |
117 | 140 | if (!immediate && unref(isFirstLoad)) { |
118 | 141 | await fetch(); |
119 | 142 | isFirstLoad.value = false; |
120 | 143 | } |
121 | - if (dropdownVisibleChangeHook && isFunction(dropdownVisibleChangeHook)) { | |
122 | - dropdownVisibleChangeHook({ options }); | |
123 | - } | |
124 | 144 | } |
125 | 145 | |
126 | 146 | function emitChange() { |
... | ... | @@ -129,47 +149,20 @@ |
129 | 149 | |
130 | 150 | function handleChange(value: string, ...args) { |
131 | 151 | emitData.value = args; |
132 | - if (!value && props.queryEmptyDataAgin) handleSearch(); | |
133 | - const { onChangeHook } = props; | |
134 | - if (!onChangeHook && !isFunction(onChangeHook)) return; | |
135 | - onChangeHook({ options }); | |
152 | + if (!value && props.queryEmptyDataAgin) fetch(); | |
136 | 153 | } |
137 | 154 | |
138 | - const debounceSearchFunction = useDebounceFn(handleSearch, 300); | |
139 | - async function handleSearch(params?: string) { | |
140 | - let { searchApi, api, searchField } = props; | |
141 | - if (!searchApi || !isFunction(searchApi)) { | |
142 | - if (!api || !isFunction(api)) return; | |
143 | - searchApi = api; | |
144 | - } | |
145 | - options.value = []; | |
146 | - try { | |
147 | - loading.value = true; | |
148 | - const res = await searchApi({ ...props.params, [searchField]: params }); | |
149 | - if (Array.isArray(res)) { | |
150 | - options.value = res; | |
151 | - emitChange(); | |
152 | - return; | |
153 | - } | |
154 | - if (props.resultField) { | |
155 | - options.value = get(res, props.resultField) || []; | |
156 | - } | |
157 | - emitChange(); | |
158 | - } catch (error) { | |
159 | - // eslint-disable-next-line no-console | |
160 | - console.warn(error); | |
161 | - } finally { | |
162 | - loading.value = false; | |
163 | - } | |
164 | - } | |
155 | + const debounceSearchFunction = useDebounceFn(fetch, 300); | |
165 | 156 | </script> |
166 | 157 | |
167 | 158 | <template> |
168 | 159 | <Select |
160 | + v-bind="$attrs" | |
161 | + show-search | |
169 | 162 | @dropdownVisibleChange="handleFetch" |
170 | - v-bind="getBindProps" | |
171 | 163 | @change="handleChange" |
172 | 164 | :options="getOptions" |
165 | + :filter-option="false" | |
173 | 166 | @search="debounceSearchFunction" |
174 | 167 | v-model:value="state" |
175 | 168 | > | ... | ... |
... | ... | @@ -13,7 +13,6 @@ import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; |
13 | 13 | import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; |
14 | 14 | import { HexInput, InputTypeEnum } from '../../profiles/components/ObjectModelForm/HexInput'; |
15 | 15 | import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; |
16 | -import ApiQuerySelectVue from '/@/components/Form/src/components/ApiQuerySelect.vue'; | |
17 | 16 | import { getDeviceProfileOtaPackages, getOtaPackageInfo } from '/@/api/ota'; |
18 | 17 | import { QueryDeviceProfileOtaPackagesType } from '/@/api/ota/model'; |
19 | 18 | import { OTAPackageType } from '/@/enums/otaEnum'; |
... | ... | @@ -22,7 +21,6 @@ useComponentRegister('JSONEditor', JSONEditor); |
22 | 21 | useComponentRegister('LockControlGroup', LockControlGroup); |
23 | 22 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
24 | 23 | useComponentRegister('HexInput', HexInput); |
25 | -useComponentRegister('ApiQuerySelect', ApiQuerySelectVue); | |
26 | 24 | |
27 | 25 | export enum TypeEnum { |
28 | 26 | IS_GATEWAY = 'GATEWAY', |
... | ... | @@ -382,7 +380,7 @@ export const step1Schemas: FormSchema[] = [ |
382 | 380 | { |
383 | 381 | field: 'firmwareId', |
384 | 382 | label: '分配的固件', |
385 | - component: 'ApiQuerySelect', | |
383 | + component: 'ApiSearchSelect', | |
386 | 384 | ifShow: ({ model }) => model?.isUpdate, |
387 | 385 | componentProps: ({ formModel }) => { |
388 | 386 | return { |
... | ... | @@ -390,7 +388,10 @@ export const step1Schemas: FormSchema[] = [ |
390 | 388 | api: async (params: QueryDeviceProfileOtaPackagesType) => { |
391 | 389 | if (!params.deviceProfileId) return []; |
392 | 390 | const result = await getDeviceProfileOtaPackages(params); |
393 | - return result.data.map((item) => ({ label: item.title, value: item.id.id })); | |
391 | + return result.data.map((item) => ({ | |
392 | + label: `${item.title}(${item.version})`, | |
393 | + value: item.id.id, | |
394 | + })); | |
394 | 395 | }, |
395 | 396 | params: (textSearch: string) => { |
396 | 397 | return { |
... | ... | @@ -403,7 +404,7 @@ export const step1Schemas: FormSchema[] = [ |
403 | 404 | }, |
404 | 405 | queryApi: async (id: string) => { |
405 | 406 | const result = await getOtaPackageInfo(id); |
406 | - return { label: result.title, value: result.id.id }; | |
407 | + return { label: `${result.title}(${result.version})`, value: result.id.id }; | |
407 | 408 | }, |
408 | 409 | }; |
409 | 410 | }, |
... | ... | @@ -411,7 +412,7 @@ export const step1Schemas: FormSchema[] = [ |
411 | 412 | { |
412 | 413 | field: 'softwareId', |
413 | 414 | label: '分配的软件', |
414 | - component: 'ApiQuerySelect', | |
415 | + component: 'ApiSearchSelect', | |
415 | 416 | ifShow: ({ model }) => model?.isUpdate, |
416 | 417 | componentProps: ({ formModel }) => { |
417 | 418 | return { |
... | ... | @@ -419,7 +420,10 @@ export const step1Schemas: FormSchema[] = [ |
419 | 420 | api: async (params: QueryDeviceProfileOtaPackagesType) => { |
420 | 421 | if (!params.deviceProfileId) return []; |
421 | 422 | const result = await getDeviceProfileOtaPackages(params); |
422 | - return result.data.map((item) => ({ label: item.title, value: item.id.id })); | |
423 | + return result.data.map((item) => ({ | |
424 | + label: `${item.title}(${item.version})`, | |
425 | + value: item.id.id, | |
426 | + })); | |
423 | 427 | }, |
424 | 428 | params: (textSearch: string) => { |
425 | 429 | return { |
... | ... | @@ -432,7 +436,7 @@ export const step1Schemas: FormSchema[] = [ |
432 | 436 | }, |
433 | 437 | queryApi: async (id: string) => { |
434 | 438 | const result = await getOtaPackageInfo(id); |
435 | - return { label: result.title, value: result.id.id }; | |
439 | + return { label: `${result.title}(${result.version})`, value: result.id.id }; | |
436 | 440 | }, |
437 | 441 | }; |
438 | 442 | }, | ... | ... |
... | ... | @@ -16,6 +16,9 @@ import { useMessage } from '/@/hooks/web/useMessage'; |
16 | 16 | import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; |
17 | 17 | import { createImgPreview } from '/@/components/Preview'; |
18 | 18 | import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; |
19 | +import { getDeviceProfileOtaPackages, getOtaPackageInfo } from '/@/api/ota'; | |
20 | +import { QueryDeviceProfileOtaPackagesType } from '/@/api/ota/model'; | |
21 | +import { OTAPackageType } from '/@/enums/otaEnum'; | |
19 | 22 | |
20 | 23 | export enum Mode { |
21 | 24 | CARD = 'card', |
... | ... | @@ -305,7 +308,84 @@ export const step1Schemas: FormSchema[] = [ |
305 | 308 | resultField: 'items', |
306 | 309 | }, |
307 | 310 | }, |
308 | - | |
311 | + { | |
312 | + field: 'isUpdate', | |
313 | + component: 'Switch', | |
314 | + label: '', | |
315 | + ifShow: false, | |
316 | + }, | |
317 | + { | |
318 | + field: 'profileId', | |
319 | + component: 'Input', | |
320 | + label: '', | |
321 | + ifShow: false, | |
322 | + }, | |
323 | + { | |
324 | + field: 'firmwareId', | |
325 | + label: '分配的固件', | |
326 | + component: 'ApiSearchSelect', | |
327 | + colProps: { span: 14 }, | |
328 | + ifShow: ({ model }) => model?.isUpdate, | |
329 | + componentProps: ({ formModel }) => { | |
330 | + return { | |
331 | + placeholder: '请选择分配的固件', | |
332 | + api: async (params: QueryDeviceProfileOtaPackagesType) => { | |
333 | + if (!params.deviceProfileId) return []; | |
334 | + const result = await getDeviceProfileOtaPackages(params); | |
335 | + return result.data.map((item) => ({ | |
336 | + label: `${item.title}(${item.version})`, | |
337 | + value: item.id.id, | |
338 | + })); | |
339 | + }, | |
340 | + params: (textSearch: string) => { | |
341 | + return { | |
342 | + textSearch, | |
343 | + page: 0, | |
344 | + type: OTAPackageType.FIRMWARE, | |
345 | + pageSize: 10, | |
346 | + deviceProfileId: formModel?.profileId, | |
347 | + }; | |
348 | + }, | |
349 | + queryApi: async (id: string) => { | |
350 | + const result = await getOtaPackageInfo(id); | |
351 | + return { label: `${result.title}(${result.version})`, value: result.id.id }; | |
352 | + }, | |
353 | + }; | |
354 | + }, | |
355 | + }, | |
356 | + { | |
357 | + field: 'softwareId', | |
358 | + label: '分配的软件', | |
359 | + component: 'ApiSearchSelect', | |
360 | + colProps: { span: 14 }, | |
361 | + ifShow: ({ model }) => model?.isUpdate, | |
362 | + componentProps: ({ formModel }) => { | |
363 | + return { | |
364 | + placeholder: '请选择分配的软件', | |
365 | + api: async (params: QueryDeviceProfileOtaPackagesType) => { | |
366 | + if (!params.deviceProfileId) return []; | |
367 | + const result = await getDeviceProfileOtaPackages(params); | |
368 | + return result.data.map((item) => ({ | |
369 | + label: `${item.title}(${item.version})`, | |
370 | + value: item.id.id, | |
371 | + })); | |
372 | + }, | |
373 | + params: (textSearch: string) => { | |
374 | + return { | |
375 | + textSearch, | |
376 | + page: 0, | |
377 | + type: OTAPackageType.SOFTWARE, | |
378 | + pageSize: 10, | |
379 | + deviceProfileId: formModel?.profileId, | |
380 | + }; | |
381 | + }, | |
382 | + queryApi: async (id: string) => { | |
383 | + const result = await getOtaPackageInfo(id); | |
384 | + return { label: `${result.title}(${result.version})`, value: result.id.id }; | |
385 | + }, | |
386 | + }; | |
387 | + }, | |
388 | + }, | |
309 | 389 | { |
310 | 390 | label: '描述', |
311 | 391 | field: 'description', | ... | ... |
... | ... | @@ -90,7 +90,12 @@ |
90 | 90 | // 不能把image字段回显进去,页面会显示大量警告 |
91 | 91 | const { image, ...params } = v; |
92 | 92 | imageUrl.value = image; |
93 | - setFieldsValue({ ...params, category: params?.categoryId ? 1 : 2 }); | |
93 | + setFieldsValue({ | |
94 | + ...params, | |
95 | + category: params?.categoryId ? 1 : 2, | |
96 | + isUpdate: true, | |
97 | + profileId: v?.tbProfileId, | |
98 | + }); | |
94 | 99 | }; |
95 | 100 | //获取数据 |
96 | 101 | async function getFormData() { | ... | ... |
... | ... | @@ -55,9 +55,12 @@ |
55 | 55 | try { |
56 | 56 | setLoading(true); |
57 | 57 | const { id } = await createOtaPackage(value); |
58 | - const { isURL } = value; | |
59 | - if (!isURL) { | |
58 | + const { url } = value; | |
59 | + if (!url) { | |
60 | 60 | await handleUploadFile(value, id.id); |
61 | + } else { | |
62 | + closeModal(); | |
63 | + emit('update:list'); | |
61 | 64 | } |
62 | 65 | } catch (error) { |
63 | 66 | } finally { |
... | ... | @@ -69,7 +72,10 @@ |
69 | 72 | try { |
70 | 73 | await validate(); |
71 | 74 | const value = getFieldsValue(); |
72 | - value[PackageField.DEVICE_PROFILE_INFO] = JSON.parse(value[PackageField.DEVICE_PROFILE_INFO]); | |
75 | + value[PackageField.DEVICE_PROFILE_INFO] = { | |
76 | + id: value[PackageField.DEVICE_PROFILE_INFO], | |
77 | + entityType: 'DEVICE_PROFILE', | |
78 | + }; | |
73 | 79 | value[PackageField.ADDITIONAL_INFO] = { |
74 | 80 | [PackageField.DESCRIPTION]: value[PackageField.DESCRIPTION], |
75 | 81 | }; | ... | ... |
... | ... | @@ -71,9 +71,7 @@ export const columns: BasicColumn[] = [ |
71 | 71 | { |
72 | 72 | title: '校验和', |
73 | 73 | dataIndex: PackageField.CHECK_SUM, |
74 | - format(text, record) { | |
75 | - return text ? `${record[PackageField.CHECK_SUM_ALG]}: ${text.slice(0, 11)}` : ''; | |
76 | - }, | |
74 | + slots: { customRender: PackageField.CHECK_SUM }, | |
77 | 75 | width: 120, |
78 | 76 | }, |
79 | 77 | { | ... | ... |
1 | -import { getDefaultDeviceProfile, getDeviceProfileInfos } from '/@/api/ota'; | |
2 | -import { Id } from '/@/api/ota/model'; | |
1 | +import { getDeviceProfileInfo, getDeviceProfileInfos } from '/@/api/ota'; | |
2 | + | |
3 | 3 | import { FormSchema } from '/@/components/Form'; |
4 | 4 | import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; |
5 | 5 | |
... | ... | @@ -109,29 +109,36 @@ export const formSchema: FormSchema[] = [ |
109 | 109 | label: '所属产品', |
110 | 110 | component: 'ApiSearchSelect', |
111 | 111 | helpMessage: ['上传的包仅适用于具有所选配置文件的设备'], |
112 | - defaultValue: 'default', | |
113 | 112 | rules: [{ required: true, message: '所属产品为必填项' }], |
114 | - componentProps: ({ formActionType }) => { | |
115 | - const { setFieldsValue } = formActionType; | |
113 | + defaultValue: 'default', | |
114 | + componentProps: ({ formModel, formActionType }) => { | |
116 | 115 | return { |
117 | 116 | placeholder: '请选择所属产品', |
118 | 117 | showSearch: true, |
119 | - resultField: 'data', | |
120 | 118 | labelField: 'name', |
121 | 119 | valueField: 'id', |
122 | - api: async () => { | |
123 | - const data = await getDefaultDeviceProfile(); | |
124 | - data.id = JSON.stringify(data.id) as unknown as Id; | |
125 | - setFieldsValue({ [PackageField.DEVICE_PROFILE_INFO]: data.id }); | |
126 | - return { data: [data] }; | |
127 | - }, | |
128 | - searchApi: async (params: Recordable) => { | |
129 | - const data = await getDeviceProfileInfos({ textSearch: params.text }); | |
130 | - data.data = data.data.map((item) => ({ | |
120 | + api: async (params: Recordable) => { | |
121 | + const data = await getDeviceProfileInfos(params); | |
122 | + return data.data.map((item) => ({ | |
131 | 123 | ...item, |
132 | - id: JSON.stringify(item.id) as unknown as Id, | |
124 | + id: item.id.id, | |
133 | 125 | })); |
134 | - return data; | |
126 | + }, | |
127 | + params: (textSearch: string) => { | |
128 | + return { | |
129 | + page: 0, | |
130 | + pageSize: 10, | |
131 | + textSearch, | |
132 | + }; | |
133 | + }, | |
134 | + queryApi: async (id = 'default') => { | |
135 | + const data = await getDeviceProfileInfo(id); | |
136 | + | |
137 | + if (formModel[PackageField.DEVICE_PROFILE_INFO] === 'default' && id === 'default') { | |
138 | + formActionType.setFieldsValue({ [PackageField.DEVICE_PROFILE_INFO]: data.id.id }); | |
139 | + } | |
140 | + | |
141 | + return { ...data, id: data.id.id }; | |
135 | 142 | }, |
136 | 143 | }; |
137 | 144 | }, | ... | ... |
... | ... | @@ -11,9 +11,11 @@ |
11 | 11 | import { useDrawer } from '/@/components/Drawer'; |
12 | 12 | import { useMessage } from '/@/hooks/web/useMessage'; |
13 | 13 | import { useDownload } from './hook/useDownload'; |
14 | - import { computed } from 'vue'; | |
14 | + import { computed, unref } from 'vue'; | |
15 | 15 | import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm'; |
16 | 16 | import { Authority } from '/@/components/Authority'; |
17 | + import Icon from '/@/components/Icon'; | |
18 | + import { useClipboard } from '@vueuse/core'; | |
17 | 19 | |
18 | 20 | const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({ |
19 | 21 | columns, |
... | ... | @@ -100,6 +102,13 @@ |
100 | 102 | reload(); |
101 | 103 | } catch (error) {} |
102 | 104 | }; |
105 | + | |
106 | + const { copy, copied } = useClipboard({ legacy: true }); | |
107 | + const handleCopy = (event: MouseEvent, text: string) => { | |
108 | + event.stopPropagation(); | |
109 | + copy(text); | |
110 | + unref(copied) && createMessage.success('复制成功'); | |
111 | + }; | |
103 | 112 | </script> |
104 | 113 | |
105 | 114 | <template> |
... | ... | @@ -115,6 +124,15 @@ |
115 | 124 | </Button> |
116 | 125 | </Authority> |
117 | 126 | </template> |
127 | + <template #checksum="{ text }"> | |
128 | + <div | |
129 | + class="cursor-pointer flex gap-2 items-center" | |
130 | + @click="(event) => handleCopy(event, text)" | |
131 | + > | |
132 | + <div class="flex-auto truncate">{{ text }}</div> | |
133 | + <Icon v-if="text" icon="ant-design:copy-outlined" /> | |
134 | + </div> | |
135 | + </template> | |
118 | 136 | <template #action="{ record }"> |
119 | 137 | <TableAction |
120 | 138 | @click.stop | ... | ... |
... | ... | @@ -17,21 +17,17 @@ const { t } = useI18n(); |
17 | 17 | |
18 | 18 | import { |
19 | 19 | getEntityDevice, |
20 | - getEntityAssets, | |
21 | - getEntityViews, | |
22 | 20 | getEntityTenant, |
23 | 21 | getEntityCustomer, |
24 | 22 | getEntityUser, |
25 | - getEntityDashboard, | |
26 | - getEntityEdge, | |
27 | 23 | } from '/@/api/ruleChainDesigner'; |
28 | 24 | import { useUserStore } from '/@/store/modules/user'; |
29 | 25 | |
30 | 26 | export const getEntityIdSelect = (type: EntityTypeEnum) => { |
31 | 27 | const method = { |
32 | 28 | [EntityTypeEnum.DEVICE]: getEntityDevice, |
33 | - [EntityTypeEnum.ASSET]: getEntityAssets, | |
34 | - [EntityTypeEnum.ENTITY_VIEW]: getEntityViews, | |
29 | + // [EntityTypeEnum.ASSET]: getEntityAssets, | |
30 | + // [EntityTypeEnum.ENTITY_VIEW]: getEntityViews, | |
35 | 31 | [EntityTypeEnum.TENANT]: async () => { |
36 | 32 | const userInfo = useUserStore(); |
37 | 33 | const params = { tenantId: userInfo.getUserInfo.tenantId! }; |
... | ... | @@ -40,8 +36,8 @@ export const getEntityIdSelect = (type: EntityTypeEnum) => { |
40 | 36 | }, |
41 | 37 | [EntityTypeEnum.CUSTOMER]: getEntityCustomer, |
42 | 38 | [EntityTypeEnum.USER]: getEntityUser, |
43 | - [EntityTypeEnum.DASHBOARD]: getEntityDashboard, | |
44 | - [EntityTypeEnum.EDGE]: getEntityEdge, | |
39 | + // [EntityTypeEnum.DASHBOARD]: getEntityDashboard, | |
40 | + // [EntityTypeEnum.EDGE]: getEntityEdge, | |
45 | 41 | }; |
46 | 42 | |
47 | 43 | const params: Recordable = { | ... | ... |
1 | 1 | import { RouteLocationNormalizedLoaded } from 'vue-router'; |
2 | 2 | import { RuleChainFieldsEnum, RuleChainFieldsNameEnum } from '../../../enum/formField/flow'; |
3 | -import { getRuleChains } from '/@/api/ruleDesigner'; | |
3 | +import { getRuleChainDetail, getRuleChains } from '/@/api/ruleDesigner'; | |
4 | 4 | import { FormSchema } from '/@/components/Form'; |
5 | 5 | import { useI18n } from '/@/hooks/web/useI18n'; |
6 | 6 | |
... | ... | @@ -31,13 +31,20 @@ export const getFormSchemas = (route: RouteLocationNormalizedLoaded): FormSchema |
31 | 31 | return { |
32 | 32 | placeholder: '请选择规则链', |
33 | 33 | showSearch: true, |
34 | - params: { | |
35 | - pageSize: 50, | |
36 | - page: 0, | |
37 | - type: 'CORE', | |
34 | + params: (textSearch: string) => { | |
35 | + return { | |
36 | + pageSize: 10, | |
37 | + page: 0, | |
38 | + type: 'CORE', | |
39 | + textSearch, | |
40 | + }; | |
38 | 41 | }, |
39 | 42 | api: (params: Recordable) => fetch(params, ruleChainId), |
40 | - searchApi: (params: Recordable) => fetch(params, ruleChainId), | |
43 | + queryApi: async (id: string) => { | |
44 | + if (!id) return; | |
45 | + const data = await getRuleChainDetail(id); | |
46 | + return { label: data.name, value: data.id.id }; | |
47 | + }, | |
41 | 48 | getPopupContainer: () => document.body, |
42 | 49 | }; |
43 | 50 | }, | ... | ... |