Commit 087e8308f9ae929ccd13d0a971efdeb03ac0fb73

Authored by xp.Huang
2 parents be641638 d83f9576

Merge branch 'feat/device-ota-package' into 'main_dev'

Feat/device ota package

See merge request yunteng/thingskit-front!1221
@@ -17,7 +17,7 @@ enum Api { @@ -17,7 +17,7 @@ enum Api {
17 CREATE_OTA_PACKAGES = '/otaPackage', 17 CREATE_OTA_PACKAGES = '/otaPackage',
18 UPLOAD_OTA_PACKAGES = '/otaPackage', 18 UPLOAD_OTA_PACKAGES = '/otaPackage',
19 DELETE_OTA_PACKAGES = '/otaPackage', 19 DELETE_OTA_PACKAGES = '/otaPackage',
20 - GET_DEVICE_PROFILE_INFO_DEFAULT = '/deviceProfileInfo/default', 20 + GET_DEVICE_PROFILE_INFO_DEFAULT = '/deviceProfileInfo',
21 GET_OTA_PACKAGE_INFO = '/otaPackage/info', 21 GET_OTA_PACKAGE_INFO = '/otaPackage/info',
22 DOWNLOAD_PACKAGE = '/otaPackage', 22 DOWNLOAD_PACKAGE = '/otaPackage',
23 23
@@ -75,13 +75,13 @@ export const uploadOtaPackages = (params: UploadOtaPackagesParams) => { @@ -75,13 +75,13 @@ export const uploadOtaPackages = (params: UploadOtaPackagesParams) => {
75 }; 75 };
76 76
77 /** 77 /**
78 - * @description 获取设备默认信息 78 + * @description 获取设备信息
79 * @returns 79 * @returns
80 */ 80 */
81 -export const getDefaultDeviceProfile = () => { 81 +export const getDeviceProfileInfo = (id: string) => {
82 return defHttp.get<DefaultDeviceProfileInfo>( 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 joinPrefix: false, 87 joinPrefix: false,
@@ -88,6 +88,7 @@ export interface CreateOtaPackageParams { @@ -88,6 +88,7 @@ export interface CreateOtaPackageParams {
88 type: string; 88 type: string;
89 deviceProfileId: IdRecord; 89 deviceProfileId: IdRecord;
90 isURL: boolean; 90 isURL: boolean;
  91 + url: Nullable<string>;
91 additionalInfo: AdditionalInfo; 92 additionalInfo: AdditionalInfo;
92 } 93 }
93 94
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,7 +10,6 @@
10 import { Select } from 'ant-design-vue'; 10 import { Select } from 'ant-design-vue';
11 import { isFunction } from '/@/utils/is'; 11 import { isFunction } from '/@/utils/is';
12 import { useRuleFormItem } from '/@/hooks/component/useFormItem'; 12 import { useRuleFormItem } from '/@/hooks/component/useFormItem';
13 - import { useAttrs } from '/@/hooks/core/useAttrs';  
14 import { get, omit } from 'lodash-es'; 13 import { get, omit } from 'lodash-es';
15 import { LoadingOutlined } from '@ant-design/icons-vue'; 14 import { LoadingOutlined } from '@ant-design/icons-vue';
16 import { useI18n } from '/@/hooks/web/useI18n'; 15 import { useI18n } from '/@/hooks/web/useI18n';
@@ -21,17 +20,14 @@ @@ -21,17 +20,14 @@
21 defineProps<{ 20 defineProps<{
22 value?: Recordable | number | string; 21 value?: Recordable | number | string;
23 numberToString?: boolean; 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 resultField?: string; 26 resultField?: string;
28 labelField?: string; 27 labelField?: string;
29 valueField?: string; 28 valueField?: string;
30 immediate?: boolean; 29 immediate?: boolean;
31 - searchField?: string;  
32 queryEmptyDataAgin?: boolean; 30 queryEmptyDataAgin?: boolean;
33 - onChangeHook?: ({ options }: OnChangeHookParams) => void;  
34 - dropdownVisibleChangeHook?: ({ options }: OnChangeHookParams) => void;  
35 }>(), 31 }>(),
36 { 32 {
37 resultField: '', 33 resultField: '',
@@ -42,11 +38,12 @@ @@ -42,11 +38,12 @@
42 queryEmptyDataAgin: true, 38 queryEmptyDataAgin: true,
43 } 39 }
44 ); 40 );
  41 +
  42 + const selectOption = ref<OptionsItem>();
45 const options = ref<OptionsItem[]>([]); 43 const options = ref<OptionsItem[]>([]);
46 const loading = ref(false); 44 const loading = ref(false);
47 const isFirstLoad = ref(true); 45 const isFirstLoad = ref(true);
48 const emitData = ref<any[]>([]); 46 const emitData = ref<any[]>([]);
49 - const attrs = useAttrs();  
50 const { t } = useI18n(); 47 const { t } = useI18n();
51 48
52 // Embedded in the form, just use the hook binding to perform form verification 49 // Embedded in the form, just use the hook binding to perform form verification
@@ -54,7 +51,15 @@ @@ -54,7 +51,15 @@
54 51
55 const getOptions = computed(() => { 52 const getOptions = computed(() => {
56 const { labelField, valueField = 'value', numberToString } = props; 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 if (next) { 63 if (next) {
59 const value = get(next, valueField); 64 const value = get(next, valueField);
60 const label = get(next, labelField); 65 const label = get(next, labelField);
@@ -68,15 +73,6 @@ @@ -68,15 +73,6 @@
68 }, [] as OptionsItem[]); 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 watchEffect(() => { 76 watchEffect(() => {
81 props.immediate && fetch(); 77 props.immediate && fetch();
82 }); 78 });
@@ -89,18 +85,45 @@ @@ -89,18 +85,45 @@
89 { deep: true } 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 const api = props.api; 111 const api = props.api;
94 if (!api || !isFunction(api)) return; 112 if (!api || !isFunction(api)) return;
95 options.value = []; 113 options.value = [];
96 try { 114 try {
97 loading.value = true; 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 if (Array.isArray(res)) { 121 if (Array.isArray(res)) {
100 options.value = res; 122 options.value = res;
101 emitChange(); 123 emitChange();
102 return; 124 return;
103 } 125 }
  126 +
104 if (props.resultField) { 127 if (props.resultField) {
105 options.value = get(res, props.resultField) || []; 128 options.value = get(res, props.resultField) || [];
106 } 129 }
@@ -113,14 +136,11 @@ @@ -113,14 +136,11 @@
113 } 136 }
114 137
115 async function handleFetch() { 138 async function handleFetch() {
116 - const { immediate, dropdownVisibleChangeHook } = props; 139 + const { immediate } = props;
117 if (!immediate && unref(isFirstLoad)) { 140 if (!immediate && unref(isFirstLoad)) {
118 await fetch(); 141 await fetch();
119 isFirstLoad.value = false; 142 isFirstLoad.value = false;
120 } 143 }
121 - if (dropdownVisibleChangeHook && isFunction(dropdownVisibleChangeHook)) {  
122 - dropdownVisibleChangeHook({ options });  
123 - }  
124 } 144 }
125 145
126 function emitChange() { 146 function emitChange() {
@@ -129,47 +149,20 @@ @@ -129,47 +149,20 @@
129 149
130 function handleChange(value: string, ...args) { 150 function handleChange(value: string, ...args) {
131 emitData.value = args; 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 </script> 156 </script>
166 157
167 <template> 158 <template>
168 <Select 159 <Select
  160 + v-bind="$attrs"
  161 + show-search
169 @dropdownVisibleChange="handleFetch" 162 @dropdownVisibleChange="handleFetch"
170 - v-bind="getBindProps"  
171 @change="handleChange" 163 @change="handleChange"
172 :options="getOptions" 164 :options="getOptions"
  165 + :filter-option="false"
173 @search="debounceSearchFunction" 166 @search="debounceSearchFunction"
174 v-model:value="state" 167 v-model:value="state"
175 > 168 >
@@ -13,7 +13,6 @@ import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; @@ -13,7 +13,6 @@ import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
13 import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; 13 import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
14 import { HexInput, InputTypeEnum } from '../../profiles/components/ObjectModelForm/HexInput'; 14 import { HexInput, InputTypeEnum } from '../../profiles/components/ObjectModelForm/HexInput';
15 import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; 15 import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
16 -import ApiQuerySelectVue from '/@/components/Form/src/components/ApiQuerySelect.vue';  
17 import { getDeviceProfileOtaPackages, getOtaPackageInfo } from '/@/api/ota'; 16 import { getDeviceProfileOtaPackages, getOtaPackageInfo } from '/@/api/ota';
18 import { QueryDeviceProfileOtaPackagesType } from '/@/api/ota/model'; 17 import { QueryDeviceProfileOtaPackagesType } from '/@/api/ota/model';
19 import { OTAPackageType } from '/@/enums/otaEnum'; 18 import { OTAPackageType } from '/@/enums/otaEnum';
@@ -22,7 +21,6 @@ useComponentRegister('JSONEditor', JSONEditor); @@ -22,7 +21,6 @@ useComponentRegister('JSONEditor', JSONEditor);
22 useComponentRegister('LockControlGroup', LockControlGroup); 21 useComponentRegister('LockControlGroup', LockControlGroup);
23 useComponentRegister('OrgTreeSelect', OrgTreeSelect); 22 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
24 useComponentRegister('HexInput', HexInput); 23 useComponentRegister('HexInput', HexInput);
25 -useComponentRegister('ApiQuerySelect', ApiQuerySelectVue);  
26 24
27 export enum TypeEnum { 25 export enum TypeEnum {
28 IS_GATEWAY = 'GATEWAY', 26 IS_GATEWAY = 'GATEWAY',
@@ -382,7 +380,7 @@ export const step1Schemas: FormSchema[] = [ @@ -382,7 +380,7 @@ export const step1Schemas: FormSchema[] = [
382 { 380 {
383 field: 'firmwareId', 381 field: 'firmwareId',
384 label: '分配的固件', 382 label: '分配的固件',
385 - component: 'ApiQuerySelect', 383 + component: 'ApiSearchSelect',
386 ifShow: ({ model }) => model?.isUpdate, 384 ifShow: ({ model }) => model?.isUpdate,
387 componentProps: ({ formModel }) => { 385 componentProps: ({ formModel }) => {
388 return { 386 return {
@@ -390,7 +388,10 @@ export const step1Schemas: FormSchema[] = [ @@ -390,7 +388,10 @@ export const step1Schemas: FormSchema[] = [
390 api: async (params: QueryDeviceProfileOtaPackagesType) => { 388 api: async (params: QueryDeviceProfileOtaPackagesType) => {
391 if (!params.deviceProfileId) return []; 389 if (!params.deviceProfileId) return [];
392 const result = await getDeviceProfileOtaPackages(params); 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 params: (textSearch: string) => { 396 params: (textSearch: string) => {
396 return { 397 return {
@@ -403,7 +404,7 @@ export const step1Schemas: FormSchema[] = [ @@ -403,7 +404,7 @@ export const step1Schemas: FormSchema[] = [
403 }, 404 },
404 queryApi: async (id: string) => { 405 queryApi: async (id: string) => {
405 const result = await getOtaPackageInfo(id); 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,7 +412,7 @@ export const step1Schemas: FormSchema[] = [
411 { 412 {
412 field: 'softwareId', 413 field: 'softwareId',
413 label: '分配的软件', 414 label: '分配的软件',
414 - component: 'ApiQuerySelect', 415 + component: 'ApiSearchSelect',
415 ifShow: ({ model }) => model?.isUpdate, 416 ifShow: ({ model }) => model?.isUpdate,
416 componentProps: ({ formModel }) => { 417 componentProps: ({ formModel }) => {
417 return { 418 return {
@@ -419,7 +420,10 @@ export const step1Schemas: FormSchema[] = [ @@ -419,7 +420,10 @@ export const step1Schemas: FormSchema[] = [
419 api: async (params: QueryDeviceProfileOtaPackagesType) => { 420 api: async (params: QueryDeviceProfileOtaPackagesType) => {
420 if (!params.deviceProfileId) return []; 421 if (!params.deviceProfileId) return [];
421 const result = await getDeviceProfileOtaPackages(params); 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 params: (textSearch: string) => { 428 params: (textSearch: string) => {
425 return { 429 return {
@@ -432,7 +436,7 @@ export const step1Schemas: FormSchema[] = [ @@ -432,7 +436,7 @@ export const step1Schemas: FormSchema[] = [
432 }, 436 },
433 queryApi: async (id: string) => { 437 queryApi: async (id: string) => {
434 const result = await getOtaPackageInfo(id); 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,6 +16,9 @@ import { useMessage } from '/@/hooks/web/useMessage';
16 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 16 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
17 import { createImgPreview } from '/@/components/Preview'; 17 import { createImgPreview } from '/@/components/Preview';
18 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; 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 export enum Mode { 23 export enum Mode {
21 CARD = 'card', 24 CARD = 'card',
@@ -305,7 +308,84 @@ export const step1Schemas: FormSchema[] = [ @@ -305,7 +308,84 @@ export const step1Schemas: FormSchema[] = [
305 resultField: 'items', 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 label: '描述', 390 label: '描述',
311 field: 'description', 391 field: 'description',
@@ -90,7 +90,12 @@ @@ -90,7 +90,12 @@
90 // 不能把image字段回显进去,页面会显示大量警告 90 // 不能把image字段回显进去,页面会显示大量警告
91 const { image, ...params } = v; 91 const { image, ...params } = v;
92 imageUrl.value = image; 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 async function getFormData() { 101 async function getFormData() {
@@ -55,9 +55,12 @@ @@ -55,9 +55,12 @@
55 try { 55 try {
56 setLoading(true); 56 setLoading(true);
57 const { id } = await createOtaPackage(value); 57 const { id } = await createOtaPackage(value);
58 - const { isURL } = value;  
59 - if (!isURL) { 58 + const { url } = value;
  59 + if (!url) {
60 await handleUploadFile(value, id.id); 60 await handleUploadFile(value, id.id);
  61 + } else {
  62 + closeModal();
  63 + emit('update:list');
61 } 64 }
62 } catch (error) { 65 } catch (error) {
63 } finally { 66 } finally {
@@ -69,7 +72,10 @@ @@ -69,7 +72,10 @@
69 try { 72 try {
70 await validate(); 73 await validate();
71 const value = getFieldsValue(); 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 value[PackageField.ADDITIONAL_INFO] = { 79 value[PackageField.ADDITIONAL_INFO] = {
74 [PackageField.DESCRIPTION]: value[PackageField.DESCRIPTION], 80 [PackageField.DESCRIPTION]: value[PackageField.DESCRIPTION],
75 }; 81 };
@@ -71,9 +71,7 @@ export const columns: BasicColumn[] = [ @@ -71,9 +71,7 @@ export const columns: BasicColumn[] = [
71 { 71 {
72 title: '校验和', 72 title: '校验和',
73 dataIndex: PackageField.CHECK_SUM, 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 width: 120, 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 import { FormSchema } from '/@/components/Form'; 3 import { FormSchema } from '/@/components/Form';
4 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 4 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
5 5
@@ -109,29 +109,36 @@ export const formSchema: FormSchema[] = [ @@ -109,29 +109,36 @@ export const formSchema: FormSchema[] = [
109 label: '所属产品', 109 label: '所属产品',
110 component: 'ApiSearchSelect', 110 component: 'ApiSearchSelect',
111 helpMessage: ['上传的包仅适用于具有所选配置文件的设备'], 111 helpMessage: ['上传的包仅适用于具有所选配置文件的设备'],
112 - defaultValue: 'default',  
113 rules: [{ required: true, message: '所属产品为必填项' }], 112 rules: [{ required: true, message: '所属产品为必填项' }],
114 - componentProps: ({ formActionType }) => {  
115 - const { setFieldsValue } = formActionType; 113 + defaultValue: 'default',
  114 + componentProps: ({ formModel, formActionType }) => {
116 return { 115 return {
117 placeholder: '请选择所属产品', 116 placeholder: '请选择所属产品',
118 showSearch: true, 117 showSearch: true,
119 - resultField: 'data',  
120 labelField: 'name', 118 labelField: 'name',
121 valueField: 'id', 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 ...item, 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,9 +11,11 @@
11 import { useDrawer } from '/@/components/Drawer'; 11 import { useDrawer } from '/@/components/Drawer';
12 import { useMessage } from '/@/hooks/web/useMessage'; 12 import { useMessage } from '/@/hooks/web/useMessage';
13 import { useDownload } from './hook/useDownload'; 13 import { useDownload } from './hook/useDownload';
14 - import { computed } from 'vue'; 14 + import { computed, unref } from 'vue';
15 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm'; 15 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
16 import { Authority } from '/@/components/Authority'; 16 import { Authority } from '/@/components/Authority';
  17 + import Icon from '/@/components/Icon';
  18 + import { useClipboard } from '@vueuse/core';
17 19
18 const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({ 20 const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({
19 columns, 21 columns,
@@ -100,6 +102,13 @@ @@ -100,6 +102,13 @@
100 reload(); 102 reload();
101 } catch (error) {} 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 </script> 112 </script>
104 113
105 <template> 114 <template>
@@ -115,6 +124,15 @@ @@ -115,6 +124,15 @@
115 </Button> 124 </Button>
116 </Authority> 125 </Authority>
117 </template> 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 <template #action="{ record }"> 136 <template #action="{ record }">
119 <TableAction 137 <TableAction
120 @click.stop 138 @click.stop
@@ -17,21 +17,17 @@ const { t } = useI18n(); @@ -17,21 +17,17 @@ const { t } = useI18n();
17 17
18 import { 18 import {
19 getEntityDevice, 19 getEntityDevice,
20 - getEntityAssets,  
21 - getEntityViews,  
22 getEntityTenant, 20 getEntityTenant,
23 getEntityCustomer, 21 getEntityCustomer,
24 getEntityUser, 22 getEntityUser,
25 - getEntityDashboard,  
26 - getEntityEdge,  
27 } from '/@/api/ruleChainDesigner'; 23 } from '/@/api/ruleChainDesigner';
28 import { useUserStore } from '/@/store/modules/user'; 24 import { useUserStore } from '/@/store/modules/user';
29 25
30 export const getEntityIdSelect = (type: EntityTypeEnum) => { 26 export const getEntityIdSelect = (type: EntityTypeEnum) => {
31 const method = { 27 const method = {
32 [EntityTypeEnum.DEVICE]: getEntityDevice, 28 [EntityTypeEnum.DEVICE]: getEntityDevice,
33 - [EntityTypeEnum.ASSET]: getEntityAssets,  
34 - [EntityTypeEnum.ENTITY_VIEW]: getEntityViews, 29 + // [EntityTypeEnum.ASSET]: getEntityAssets,
  30 + // [EntityTypeEnum.ENTITY_VIEW]: getEntityViews,
35 [EntityTypeEnum.TENANT]: async () => { 31 [EntityTypeEnum.TENANT]: async () => {
36 const userInfo = useUserStore(); 32 const userInfo = useUserStore();
37 const params = { tenantId: userInfo.getUserInfo.tenantId! }; 33 const params = { tenantId: userInfo.getUserInfo.tenantId! };
@@ -40,8 +36,8 @@ export const getEntityIdSelect = (type: EntityTypeEnum) => { @@ -40,8 +36,8 @@ export const getEntityIdSelect = (type: EntityTypeEnum) => {
40 }, 36 },
41 [EntityTypeEnum.CUSTOMER]: getEntityCustomer, 37 [EntityTypeEnum.CUSTOMER]: getEntityCustomer,
42 [EntityTypeEnum.USER]: getEntityUser, 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 const params: Recordable = { 43 const params: Recordable = {
1 import { RouteLocationNormalizedLoaded } from 'vue-router'; 1 import { RouteLocationNormalizedLoaded } from 'vue-router';
2 import { RuleChainFieldsEnum, RuleChainFieldsNameEnum } from '../../../enum/formField/flow'; 2 import { RuleChainFieldsEnum, RuleChainFieldsNameEnum } from '../../../enum/formField/flow';
3 -import { getRuleChains } from '/@/api/ruleDesigner'; 3 +import { getRuleChainDetail, getRuleChains } from '/@/api/ruleDesigner';
4 import { FormSchema } from '/@/components/Form'; 4 import { FormSchema } from '/@/components/Form';
5 import { useI18n } from '/@/hooks/web/useI18n'; 5 import { useI18n } from '/@/hooks/web/useI18n';
6 6
@@ -31,13 +31,20 @@ export const getFormSchemas = (route: RouteLocationNormalizedLoaded): FormSchema @@ -31,13 +31,20 @@ export const getFormSchemas = (route: RouteLocationNormalizedLoaded): FormSchema
31 return { 31 return {
32 placeholder: '请选择规则链', 32 placeholder: '请选择规则链',
33 showSearch: true, 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 api: (params: Recordable) => fetch(params, ruleChainId), 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 getPopupContainer: () => document.body, 48 getPopupContainer: () => document.body,
42 }; 49 };
43 }, 50 },