Commit c31c36688f9f34f932ee773a3bc4d25bffc557e2
Merge branch 'ww' into 'main'
feat: 设备列表新增设备导入模版下载 See merge request yunteng/thingskit-front!509
Showing
6 changed files
with
118 additions
and
64 deletions
1 | -<script lang="ts" setup> | |
2 | - import { Icon } from '/@/components/Icon'; | |
3 | - import { computed, ExtractPropTypes, unref } from 'vue'; | |
4 | - import { usePermission } from '/@/hooks/web/usePermission'; | |
5 | - | |
6 | - interface AuthIconProps extends ExtractPropTypes<typeof Icon> { | |
7 | - auth?: string; | |
8 | - } | |
9 | - | |
10 | - const props = defineProps<AuthIconProps>(); | |
11 | - | |
12 | - const emit = defineEmits(['click']); | |
13 | - | |
14 | - const { hasPermission } = usePermission(); | |
15 | - | |
16 | - const getHasPermission = computed(() => { | |
17 | - const { auth } = props; | |
18 | - console.log(auth); | |
19 | - return auth ? hasPermission(auth) : true; | |
20 | - }); | |
21 | - | |
22 | - const getBindProps = computed(() => { | |
23 | - return { | |
24 | - ...props, | |
25 | - ...(unref(getHasPermission) ? { onClick: (event: Event) => emit('click', event) } : {}), | |
26 | - }; | |
27 | - }); | |
28 | -</script> | |
29 | - | |
30 | -<template> | |
31 | - <Icon | |
32 | - v-bind="getBindProps" | |
33 | - class="justify-center items-center" | |
34 | - :class="getHasPermission ? '' : '!cursor-not-allowed !text-gray-200'" | |
35 | - /> | |
36 | -</template> | |
1 | +<script lang="ts" setup> | |
2 | + import { Icon } from '/@/components/Icon'; | |
3 | + import { computed, ExtractPropTypes, unref } from 'vue'; | |
4 | + import { usePermission } from '/@/hooks/web/usePermission'; | |
5 | + | |
6 | + interface AuthIconProps extends ExtractPropTypes<typeof Icon> { | |
7 | + auth?: string; | |
8 | + } | |
9 | + | |
10 | + const props = defineProps<AuthIconProps>(); | |
11 | + | |
12 | + const emit = defineEmits(['click']); | |
13 | + | |
14 | + const { hasPermission } = usePermission(); | |
15 | + | |
16 | + const getHasPermission = computed(() => { | |
17 | + const { auth } = props; | |
18 | + return auth ? hasPermission(auth) : true; | |
19 | + }); | |
20 | + | |
21 | + const getBindProps = computed(() => { | |
22 | + return { | |
23 | + ...props, | |
24 | + ...(unref(getHasPermission) ? { onClick: (event: Event) => emit('click', event) } : {}), | |
25 | + }; | |
26 | + }); | |
27 | +</script> | |
28 | + | |
29 | +<template> | |
30 | + <Icon | |
31 | + v-bind="getBindProps" | |
32 | + class="justify-center items-center" | |
33 | + :class="getHasPermission ? '' : '!cursor-not-allowed !text-gray-200'" | |
34 | + /> | |
35 | +</template> | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | 2 | import { Button } from 'ant-design-vue'; |
3 | - import { nextTick, onMounted, ref } from 'vue'; | |
3 | + import { computed, nextTick, onMounted } from 'vue'; | |
4 | 4 | import { basicInfoForm, FieldsEnum } from './config'; |
5 | 5 | import { basicProps } from './props'; |
6 | 6 | import StepContainer from './StepContainer.vue'; |
... | ... | @@ -15,7 +15,14 @@ |
15 | 15 | |
16 | 16 | const emit = defineEmits(['update:value']); |
17 | 17 | |
18 | - const canGoNext = ref(false); | |
18 | + const canGoNext = computed(() => { | |
19 | + const values = props.value as Recordable; | |
20 | + return ( | |
21 | + Reflect.has(values, FieldsEnum.ORGANIZATION_ID) && | |
22 | + Reflect.has(values, FieldsEnum.TK_DEVICE_PROFILE_ID) && | |
23 | + Reflect.has(values, FieldsEnum.DEVICE_TYPE) | |
24 | + ); | |
25 | + }); | |
19 | 26 | const [register, { getFieldsValue, setFieldsValue }] = useForm({ |
20 | 27 | schemas: basicInfoForm, |
21 | 28 | layout: 'vertical', |
... | ... | @@ -23,9 +30,7 @@ |
23 | 30 | submitFunc: async () => { |
24 | 31 | await nextTick(); |
25 | 32 | const values = getFieldsValue() || {}; |
26 | - canGoNext.value = | |
27 | - Reflect.has(values, FieldsEnum.ORGANIZATION_ID) && | |
28 | - Reflect.has(values, FieldsEnum.TK_DEVICE_PROFILE_ID); | |
33 | + emit('update:value', values); | |
29 | 34 | }, |
30 | 35 | }); |
31 | 36 | ... | ... |
1 | 1 | import { Options } from './type'; |
2 | -import { getDeviceProfile } from '/@/api/alarm/position'; | |
2 | +import { queryDeviceProfileBy } from '/@/api/device/deviceManager'; | |
3 | +import { findDictItemByCode } from '/@/api/system/dict'; | |
3 | 4 | import { getOrganizationList } from '/@/api/system/system'; |
4 | 5 | import { FormSchema } from '/@/components/Form'; |
5 | 6 | import { BasicColumn } from '/@/components/Table'; |
... | ... | @@ -7,8 +8,8 @@ import { copyTransFun } from '/@/utils/fnUtils'; |
7 | 8 | |
8 | 9 | export enum FieldsEnum { |
9 | 10 | ORGANIZATION_ID = 'organizationId', |
11 | + DEVICE_TYPE = 'deviceTypeEnum', | |
10 | 12 | TK_DEVICE_PROFILE_ID = 'tkDeviceProfileId', |
11 | - DEVICE_TYPE_ENUM = 'deviceTypeEnum', | |
12 | 13 | DEVICE_TYPE_NAME = 'deviceTypeName', |
13 | 14 | DELIMITER = 'delimiter', |
14 | 15 | HEADER = 'header', |
... | ... | @@ -118,36 +119,54 @@ export const basicInfoForm: FormSchema[] = [ |
118 | 119 | }, |
119 | 120 | }, |
120 | 121 | { |
122 | + field: FieldsEnum.DEVICE_TYPE, | |
123 | + label: '设备类型', | |
124 | + component: 'ApiSelect', | |
125 | + rules: [{ required: true, message: '设备类型为必填项' }], | |
126 | + componentProps: ({ formActionType }) => { | |
127 | + const { setFieldsValue, submit, clearValidate } = formActionType; | |
128 | + return { | |
129 | + api: findDictItemByCode, | |
130 | + params: { | |
131 | + dictCode: 'device_type', | |
132 | + }, | |
133 | + labelField: 'itemText', | |
134 | + valueField: 'itemValue', | |
135 | + onChange: (value: string, options: Record<'deviceType' | 'label', string>) => { | |
136 | + setFieldsValue({ | |
137 | + [FieldsEnum.DEVICE_TYPE_NAME]: value ? options.label : null, | |
138 | + [FieldsEnum.TK_DEVICE_PROFILE_ID]: null, | |
139 | + }); | |
140 | + submit(); | |
141 | + clearValidate(); | |
142 | + }, | |
143 | + }; | |
144 | + }, | |
145 | + }, | |
146 | + { | |
121 | 147 | field: FieldsEnum.TK_DEVICE_PROFILE_ID, |
122 | 148 | component: 'ApiSelect', |
123 | 149 | label: '产品', |
124 | 150 | rules: [{ required: true, message: '产品为必填项' }], |
125 | - componentProps: ({ formActionType }) => { | |
126 | - const { submit, setFieldsValue } = formActionType; | |
151 | + componentProps: ({ formActionType, formModel }) => { | |
152 | + const { submit } = formActionType; | |
153 | + const deviceType = Reflect.get(formModel, FieldsEnum.DEVICE_TYPE); | |
127 | 154 | return { |
128 | - api: getDeviceProfile, | |
155 | + api: queryDeviceProfileBy, | |
129 | 156 | labelField: 'name', |
130 | 157 | valueField: 'id', |
131 | 158 | placeholder: '请选择产品', |
159 | + params: { deviceType }, | |
132 | 160 | getPopupContainer: () => document.body, |
133 | - onChange: (value: string, options: Record<'deviceType' | 'label', string>) => { | |
134 | - value && | |
135 | - setFieldsValue({ | |
136 | - [FieldsEnum.DEVICE_TYPE_ENUM]: options.deviceType, | |
137 | - [FieldsEnum.DEVICE_TYPE_NAME]: options.label, | |
138 | - }); | |
139 | - submit(); | |
161 | + onChange: () => submit(), | |
162 | + showSearch: true, | |
163 | + filterOption: (inputValue: string, options: Record<'label', string>) => { | |
164 | + return options.label.includes(inputValue); | |
140 | 165 | }, |
141 | 166 | }; |
142 | 167 | }, |
143 | 168 | }, |
144 | 169 | { |
145 | - field: FieldsEnum.DEVICE_TYPE_ENUM, | |
146 | - component: 'Input', | |
147 | - label: '设备类型', | |
148 | - show: false, | |
149 | - }, | |
150 | - { | |
151 | 170 | field: FieldsEnum.DEVICE_TYPE_NAME, |
152 | 171 | component: 'Input', |
153 | 172 | label: '设备名称', |
... | ... | @@ -226,4 +245,20 @@ export const columnTypeSchema: BasicColumn[] = [ |
226 | 245 | }, |
227 | 246 | ]; |
228 | 247 | |
229 | -export const assemblyData = () => {}; | |
248 | +export const csvTemplate = `name,username,password | |
249 | +Device 11,admin,password | |
250 | +Device 22,admin1,password1 | |
251 | +`; | |
252 | + | |
253 | +export const downloadFile = (data: string, fileName: string, type: string, ext: string) => { | |
254 | + const blob = new Blob([data], { type: type }); | |
255 | + const objectURL = URL.createObjectURL(blob); | |
256 | + const element = document.createElement('a'); | |
257 | + element.href = objectURL; | |
258 | + element.download = `${fileName}.${ext}`; | |
259 | + element.style.display = 'none'; | |
260 | + document.body.appendChild(element); | |
261 | + element.click(); | |
262 | + element.remove(); | |
263 | + URL.revokeObjectURL(objectURL); | |
264 | +}; | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | 2 | import { computed, ref, unref } from 'vue'; |
3 | 3 | import { BasicModal, useModal } from '/@/components/Modal'; |
4 | - import { Steps } from 'ant-design-vue'; | |
4 | + import { Steps, Button, Tooltip } from 'ant-design-vue'; | |
5 | 5 | import BasicInfoForm from './BasicInfoForm.vue'; |
6 | 6 | import ImportCsv from './ImportCsv.vue'; |
7 | 7 | import { CreateEntityValue, UploadFileParseValue } from './type'; |
... | ... | @@ -9,6 +9,7 @@ |
9 | 9 | import CreateEntity from './CreateEntity.vue'; |
10 | 10 | import CompleteResult from './CompleteResult.vue'; |
11 | 11 | import { ImportDeviceResponse } from '/@/api/device/model/batchImportModel'; |
12 | + import { csvTemplate, downloadFile } from './config'; | |
12 | 13 | |
13 | 14 | const emit = defineEmits(['importFinally']); |
14 | 15 | |
... | ... | @@ -73,6 +74,10 @@ |
73 | 74 | reset(); |
74 | 75 | return true; |
75 | 76 | }; |
77 | + | |
78 | + const handleTemplateDownload = () => { | |
79 | + downloadFile(csvTemplate, 'template', 'text/csv', 'csv'); | |
80 | + }; | |
76 | 81 | </script> |
77 | 82 | |
78 | 83 | <template> |
... | ... | @@ -86,7 +91,7 @@ |
86 | 91 | :close-func="handleCancel" |
87 | 92 | :destroy-on-close="true" |
88 | 93 | > |
89 | - <section class="overflow-auto"> | |
94 | + <section class="overflow-auto relative"> | |
90 | 95 | <Steps direction="vertical" :current="currentStep"> |
91 | 96 | <Steps.Step |
92 | 97 | v-for="item in Object.keys(StepsEnum).filter((key) => isNaN(key as unknown as number))" |
... | ... | @@ -132,6 +137,17 @@ |
132 | 137 | </template> |
133 | 138 | </Steps.Step> |
134 | 139 | </Steps> |
140 | + <Tooltip | |
141 | + title="模板表头的第一列为设备名称,且不能重复,如需预置访问凭证(ACCESS_TOKEN、MQTT client ID、MQTT user name、MQTT password)添加新的列即可。例如:username、password" | |
142 | + > | |
143 | + <Button | |
144 | + type="text" | |
145 | + class="!absolute top-0 right-0 !text-blue-400" | |
146 | + @click="handleTemplateDownload" | |
147 | + > | |
148 | + 模版下载 | |
149 | + </Button> | |
150 | + </Tooltip> | |
135 | 151 | </section> |
136 | 152 | </BasicModal> |
137 | 153 | </template> | ... | ... |