Commit aee2e0b87e73089617f3f967125e89cd7c5507d1

Authored by xp.Huang
2 parents bc6a803e f96b5c82

Merge branch 'main_dev' into 'main'

fix:设备批量导入产品类型错误

See merge request yunteng/thingskit-front!1117
Showing 89 changed files with 1116 additions and 881 deletions
... ... @@ -25,7 +25,7 @@ module.exports = defineConfig({
25 25 'plugin:jest/recommended',
26 26 ],
27 27 rules: {
28   - 'no-console': 'error',
  28 + 'no-console': ['error', { allow: ['warn', 'error'] }],
29 29 'vue/script-setup-uses-vars': 'error',
30 30 '@typescript-eslint/ban-ts-ignore': 'off',
31 31 '@typescript-eslint/explicit-function-return-type': 'off',
... ...
... ... @@ -51,6 +51,11 @@ enum DeviceManagerApi {
51 51 * @description 通过设备列表获取设备信息
52 52 */
53 53 QUERY_DEVICES = '/device/get/devices',
  54 +
  55 + /**
  56 + * @description 批量变更产品
  57 + */
  58 + BATCH_UPDATE_PRODUCT = '/device/batch/update',
54 59 }
55 60
56 61 export const devicePage = (params: DeviceQueryParam) => {
... ... @@ -378,3 +383,13 @@ export const doBatchClearAlarm = (ids: string[]) => {
378 383 { joinPrefix: false }
379 384 );
380 385 };
  386 +
  387 +export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfileId: string }) => {
  388 + return defHttp.post(
  389 + {
  390 + url: DeviceManagerApi.BATCH_UPDATE_PRODUCT,
  391 + data: params,
  392 + },
  393 + { joinPrefix: false }
  394 + );
  395 +};
... ...
... ... @@ -65,3 +65,19 @@ export interface ImportModelOfMatterType {
65 65 tkDeviceProfileId?: string;
66 66 categoryId?: string;
67 67 }
  68 +
  69 +export interface ModelOfMatterItemRecordType {
  70 + id: string;
  71 + creator: string;
  72 + createTime: string;
  73 + enabled: boolean;
  74 + tenantId: string;
  75 + functionType: string;
  76 + functionName: string;
  77 + identifier: string;
  78 + extensionDesc: any;
  79 + accessMode: string;
  80 + functionJson: FunctionJson;
  81 + status: number;
  82 + deviceProfileId: string;
  83 +}
... ...
... ... @@ -2,6 +2,7 @@ import { BasicPageParams } from '../model/baseModel';
2 2 import {
3 3 GetModelTslParams,
4 4 ImportModelOfMatterType,
  5 + ModelOfMatterItemRecordType,
5 6 ModelOfMatterParams,
6 7 } from './model/modelOfMatterModel';
7 8 import { defHttp } from '/@/utils/http/axios';
... ... @@ -36,7 +37,7 @@ export const getModelList = (
36 37 id?: string;
37 38 }
38 39 ) => {
39   - return defHttp.get({
  40 + return defHttp.get<ModelOfMatterItemRecordType[]>({
40 41 url: `${ModelOfMatter.LIST}`,
41 42 params,
42 43 });
... ...
... ... @@ -14,7 +14,6 @@ export function saveMenuApi(
14 14 update = false,
15 15 mode: ErrorMessageMode = 'modal'
16 16 ) {
17   - console.log(params);
18 17 if (!update) {
19 18 return defHttp.post<MenuOperationApiResult>(
20 19 {
... ...
... ... @@ -48,13 +48,21 @@
48 48 <template #content>
49 49 <section class="flex flex-wrap w-36">
50 50 <table class="border-collapse" @click="handleSelectConfirm">
51   - <tr v-for="row in MAX_ROW" :key="row" class="border border-gray-300 border-solid">
  51 + <tr
  52 + v-for="rowNumber in MAX_ROW"
  53 + :key="rowNumber"
  54 + class="border border-gray-300 border-solid"
  55 + >
52 56 <td
53   - v-for="col in MAX_COL"
54   - :class="selectedLayout.col >= col && selectedLayout.row >= row ? 'bg-blue-500' : ''"
55   - :key="col"
  57 + v-for="colNumber in MAX_COL"
  58 + :class="
  59 + selectedLayout.col >= colNumber && selectedLayout.row >= rowNumber
  60 + ? 'bg-blue-500'
  61 + : ''
  62 + "
  63 + :key="colNumber"
56 64 class="w-4 h-4 border border-gray-300 border-solid cursor-pointer"
57   - @mouseover="handleOver(row, col)"
  65 + @mouseover="handleOver(rowNumber, colNumber)"
58 66 >
59 67 </td>
60 68 </tr>
... ...
... ... @@ -77,7 +77,6 @@
77 77 try {
78 78 const data = e.target && e.target.result;
79 79 const workbook = XLSX.read(data, { type: 'array' });
80   - // console.log(workbook);
81 80 /* DO SOMETHING WITH workbook HERE */
82 81 const excelData = getExcelData(workbook);
83 82 emit('success', excelData);
... ...
... ... @@ -90,7 +90,7 @@
90 90 () => {
91 91 !unref(isFirstLoad) && fetch();
92 92 },
93   - { deep: true },
  93 + { deep: true }
94 94 );
95 95
96 96 async function fetch() {
... ... @@ -110,6 +110,7 @@
110 110 }
111 111 emitChange();
112 112 } catch (error) {
  113 + // eslint-disable-next-line no-console
113 114 console.warn(error);
114 115 } finally {
115 116 loading.value = false;
... ...
... ... @@ -156,6 +156,7 @@
156 156 }
157 157 emitChange();
158 158 } catch (error) {
  159 + // eslint-disable-next-line no-console
159 160 console.warn(error);
160 161 } finally {
161 162 loading.value = false;
... ...
... ... @@ -54,7 +54,7 @@
54 54 },
55 55 // api params
56 56 params: {
57   - type: Object as PropType<Recordable>,
  57 + type: [Object, String, Number] as any,
58 58 default: () => ({}),
59 59 },
60 60 // support xxx.xxx.xx
... ...
  1 +<script setup lang="ts">
  2 + import { InputGroup, Button, Popconfirm } from 'ant-design-vue';
  3 + import { computed, ref, unref, watch } from 'vue';
  4 + import { componentMap } from '../componentMap';
  5 + import { ComponentType } from '../types';
  6 + import Icon from '/@/components/Icon';
  7 + import { PopConfirm } from '/@/components/Table';
  8 + import { upperFirst } from '/@/components/Transition/src/ExpandTransition';
  9 +
  10 + const props = withDefaults(
  11 + defineProps<{
  12 + value?: number | string | Recordable | Recordable[];
  13 + component?: ComponentType;
  14 + componentProps?: Recordable;
  15 + disabled?: boolean;
  16 + changeEvent?: string;
  17 + valueField?: string;
  18 + defaultLockStatus?: boolean;
  19 + popconfirm?: Omit<PopConfirm, 'confirm' | 'cancel'>;
  20 + }>(),
  21 + {
  22 + changeEvent: 'change',
  23 + valueField: 'value',
  24 + disabled: false,
  25 + component: 'Input',
  26 + defaultLockStatus: true,
  27 + componentProps: () => ({}),
  28 + }
  29 + );
  30 +
  31 + const slots = defineSlots<{
  32 + popconfirmTitle?: () => any;
  33 + }>();
  34 +
  35 + const emits = defineEmits(['change']);
  36 +
  37 + const ControlComponent = computed(() => componentMap.get(props.component));
  38 +
  39 + const getControlComponentProps = computed(() => {
  40 + const { componentProps, changeEvent, component, valueField, value } = props;
  41 + const isCheck = component && ['Switch', 'Checkbox'].includes(component);
  42 + const eventKey = `on${upperFirst(changeEvent)}`;
  43 + return {
  44 + ...componentProps,
  45 + [eventKey]: (...args: Nullable<Recordable>[]) => {
  46 + const [e] = args;
  47 + if (componentProps?.[eventKey]) {
  48 + componentProps?.[eventKey](...args);
  49 + }
  50 + const target = e ? e.target : null;
  51 + const value = target ? (isCheck ? target.checked : target.value) : e;
  52 + emits('change', value);
  53 + },
  54 + [valueField]: value,
  55 + };
  56 + });
  57 +
  58 + const popconfirmVisible = ref(false);
  59 +
  60 + const confirm = () => {
  61 + controlDisableStatus.value = false;
  62 + popconfirmVisible.value = false;
  63 + };
  64 +
  65 + const cancel = () => {
  66 + popconfirmVisible.value = false;
  67 + };
  68 +
  69 + const getPopConfirmProps = computed(() => {
  70 + const { popconfirm } = props;
  71 +
  72 + return {
  73 + ...popconfirm,
  74 + onConfirm: confirm,
  75 + onCancel: cancel,
  76 + onVisibleChange: () => {
  77 + if (!unref(controlDisableStatus)) {
  78 + popconfirmVisible.value = false;
  79 + controlDisableStatus.value = true;
  80 + return;
  81 + }
  82 + },
  83 + title: slots.popconfirmTitle ? slots.popconfirmTitle : popconfirm?.title,
  84 + };
  85 + });
  86 +
  87 + const controlDisableStatus = ref(props.defaultLockStatus);
  88 +
  89 + watch(
  90 + () => props.defaultLockStatus,
  91 + (target) => {
  92 + controlDisableStatus.value = !!target;
  93 + },
  94 + { immediate: true }
  95 + );
  96 +</script>
  97 +
  98 +<template>
  99 + <InputGroup compact class="w-full !flex">
  100 + <ControlComponent
  101 + v-bind="getControlComponentProps"
  102 + class="flex-auto"
  103 + :disabled="controlDisableStatus"
  104 + />
  105 + <Popconfirm v-model:visible="popconfirmVisible" v-bind="getPopConfirmProps">
  106 + <Button type="primary" :disabled="disabled">
  107 + <Icon
  108 + :icon="controlDisableStatus ? 'ant-design:lock-outlined' : 'ant-design:unlock-outlined'"
  109 + />
  110 + </Button>
  111 + </Popconfirm>
  112 + </InputGroup>
  113 +</template>
... ...
... ... @@ -113,7 +113,7 @@
113 113 const { TabPane } = Tabs;
114 114 const { prefixCls } = useDesign('easy-cron-inner');
115 115 provide('prefixCls', prefixCls);
116   - const emit = defineEmits([...cronEmits]);
  116 + const emit = defineEmits(cronEmits);
117 117 const props = defineProps({ ...cronProps });
118 118 const activeKey = ref(props.hideSecond ? 'minute' : 'second');
119 119 const second = ref('*');
... ... @@ -236,7 +236,7 @@
236 236 if (/^[0-7]$/.test(week)) {
237 237 return convert(week);
238 238 } else if (patten1.test(week)) {
239   - return week.replace(patten1, ($0, before, separator, after) => {
  239 + return week.replace(patten1, (_$0, before, separator, after) => {
240 240 if (separator === '/') {
241 241 return convert(before) + separator + after;
242 242 } else {
... ...
... ... @@ -23,8 +23,8 @@
23 23
24 24 export default defineComponent({
25 25 name: 'EasyCronModal',
26   - inheritAttrs: false,
27 26 components: { BasicModal, EasyCron, Button },
  27 + inheritAttrs: false,
28 28 setup() {
29 29 const attrs = useAttrs();
30 30 const [registerModal, { closeModal }] = useModalInner();
... ...
... ... @@ -45,7 +45,7 @@ export function useTabSetup(props, context, options) {
45 45
46 46 // 根据不同的类型计算出的value
47 47 const computeValue = computed(() => {
48   - let valueArray: any[] = [];
  48 + const valueArray: any[] = [];
49 49 switch (type.value) {
50 50 case TypeEnum.unset:
51 51 valueArray.push('?');
... ... @@ -79,7 +79,7 @@ export function useTabSetup(props, context, options) {
79 79 });
80 80 // 指定值范围区间,介于最小值和最大值之间
81 81 const specifyRange = computed(() => {
82   - let range: number[] = [];
  82 + const range: number[] = [];
83 83 if (maxValue.value != null) {
84 84 for (let i = minValue.value; i <= maxValue.value; i++) {
85 85 range.push(i);
... ...
... ... @@ -143,4 +143,5 @@ export type ComponentType =
143 143 | 'ConditionFilter'
144 144 | 'TimeRangePicker'
145 145 | 'TriggerDurationInput'
146   - | 'AlarmProfileSelect';
  146 + | 'AlarmProfileSelect'
  147 + | 'LockControlGroup';
... ...
... ... @@ -44,7 +44,7 @@
44 44 </ModalWrapper>
45 45
46 46 <template #[item]="data" v-for="item in Object.keys(omit($slots, 'default'))">
47   - <slot :name="item" v-bind="data"></slot>
  47 + <slot :name="item" v-bind="data || {}"></slot>
48 48 </template>
49 49 </Modal>
50 50 </template>
... ...
... ... @@ -159,7 +159,7 @@
159 159 }
160 160 emit('height-change', unref(realHeightRef));
161 161 } catch (error) {
162   - console.log(error);
  162 + console.error(error);
163 163 }
164 164 }
165 165
... ...
... ... @@ -207,7 +207,6 @@
207 207 });
208 208
209 209 function handleSearch(searchValue: string) {
210   - console.log('searchValue', searchValue);
211 210 if (searchValue !== searchText.value) searchText.value = searchValue;
212 211 emit('update:searchValue', searchValue);
213 212 if (!searchValue) {
... ...
... ... @@ -202,7 +202,6 @@
202 202 error: null,
203 203 };
204 204 } catch (e) {
205   - console.log(e);
206 205 item.status = UploadResultStatus.ERROR;
207 206 return {
208 207 success: false,
... ...
... ... @@ -16,8 +16,6 @@
16 16 import { computed, defineComponent, unref, ref } from 'vue';
17 17 import { Layout } from 'ant-design-vue';
18 18
19   - import { GithubFilled } from '@ant-design/icons-vue';
20   -
21 19 import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting';
22 20 import { openWindow } from '/@/utils';
23 21
... ... @@ -29,7 +27,7 @@
29 27
30 28 export default defineComponent({
31 29 name: 'LayoutFooter',
32   - components: { Footer: Layout.Footer, GithubFilled },
  30 + components: { Footer: Layout.Footer },
33 31 setup() {
34 32 const { t } = useI18n();
35 33 const { getShowFooter } = useRootSetting();
... ...
... ... @@ -148,7 +148,6 @@
148 148
149 149 function renderMenu() {
150 150 const { menus, ...menuProps } = unref(getCommonProps);
151   - // console.log(menus);
152 151 if (!menus || !menus.length) return null;
153 152 return !props.isHorizontal ? (
154 153 <SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menus} />
... ...
... ... @@ -100,7 +100,7 @@ export default defineComponent({
100 100 <>
101 101 <TypePicker
102 102 menuTypeList={menuTypeList}
103   - handler={(item: typeof menuTypeList[0]) => {
  103 + handler={(item: (typeof menuTypeList)[0]) => {
104 104 baseHandler(HandlerEnum.CHANGE_LAYOUT, {
105 105 mode: item.mode,
106 106 type: item.type,
... ...
... ... @@ -47,7 +47,7 @@ export function initAppConfigStore() {
47 47 grayMode && updateGrayMode(grayMode);
48 48 colorWeak && updateColorWeak(colorWeak);
49 49 } catch (error) {
50   - console.log(error);
  50 + console.error(error);
51 51 }
52 52 appStore.setProjectConfig(projCfg);
53 53
... ...
... ... @@ -4,7 +4,6 @@ import { defineStore } from 'pinia';
4 4
5 5 import { LOCK_INFO_KEY } from '/@/enums/cacheEnum';
6 6 import { Persistent } from '/@/utils/cache/persistent';
7   -import { useUserStore } from './user';
8 7
9 8 interface LockState {
10 9 lockInfo: Nullable<LockInfo>;
... ...
... ... @@ -111,7 +111,6 @@ export const usePermissionStore = defineStore({
111 111 * 否则不是超级管理员-获取对应角色的权限列表
112 112 */
113 113 const codeList = await getPermCode();
114   - // console.log('codeList', codeList);
115 114 this.setPermCodeList(codeList);
116 115 /**
117 116 * 如果是超级管理员则获取对应权限列表
... ...
... ... @@ -234,7 +234,6 @@ export const useUserStore = defineStore({
234 234 // try {
235 235 // await doLogout();
236 236 // } catch {
237   - // console.log('注销Token失败');
238 237 // }
239 238 // this.resetState();
240 239 // setAuthCache(JWT_TOKEN_KEY, undefined);
... ...
... ... @@ -85,9 +85,7 @@
85 85 /^[1](([3][0-9])|([4][0,1,4-9])|([5][0-3,5-9])|([6][2,5,6,7])|([7][0-8])|([8][0-9])|([9][0-3,5-9]))[0-9]{8}$/;
86 86 if (reg.test(formData.mobile)) {
87 87 const sendRes = await passwordResetCode(formData.mobile);
88   - console.log(sendRes);
89 88 if (sendRes === '') {
90   - console.log('发送成功了');
91 89 return true;
92 90 }
93 91 return false;
... ...
... ... @@ -99,6 +99,5 @@
99 99 async function handleRegister() {
100 100 const data = await validForm();
101 101 if (!data) return;
102   - console.log(data);
103 102 }
104 103 </script>
... ...
... ... @@ -27,7 +27,6 @@
27 27 onMounted(() => {
28 28 // 记录当前的UserId
29 29 userId.value = userStore.getUserInfo?.userId;
30   - console.log('Mounted', userStore.getUserInfo);
31 30 });
32 31
33 32 onBeforeUnmount(() => {
... ...
... ... @@ -11,8 +11,7 @@
11 11 */
12 12 export function simpleDebounce(fn, delay = 100) {
13 13 let timer: any | null = null;
14   - return function () {
15   - let args = arguments;
  14 + return function (...args) {
16 15 if (timer) {
17 16 clearTimeout(timer);
18 17 }
... ... @@ -61,4 +60,3 @@ export function dateFormat(date, block) {
61 60 });
62 61 return format;
63 62 }
64   -
... ...
... ... @@ -36,7 +36,6 @@ export function checkStatus(
36 36 }
37 37 break;
38 38 case 403:
39   - // console.log('403', errMessage);
40 39 // errMessage = t('sys.api.errMsg403');
41 40 errMessage = errMessage;
42 41 break;
... ...
... ... @@ -8,7 +8,6 @@ export function listToTree(lists: getMenuListResultModel): getMenuListResultMode
8 8 lists.forEach((goods) => {
9 9 goods['menuName'] = t(goods.meta.title); // 为goods添加属性menuName
10 10
11   - // console.log(goods.children?.length);
12 11 if (goods.children?.length) {
13 12 listToTree(goods.children);
14 13 // goods.children.forEach((goodChildren) => {
... ...
... ... @@ -102,8 +102,7 @@
102 102 avatar: [{ uid: buildUUID(), name: 'name', url: data.record.avatar } as FileItem],
103 103 });
104 104 }
105   - const { avatar, ...params } = data.record;
106   - console.log(avatar);
  105 + const { ...params } = data.record;
107 106 await setFieldsValue({ ...params });
108 107 } else {
109 108 editId.value = '';
... ...
... ... @@ -82,11 +82,11 @@
82 82 </script>
83 83
84 84 <template>
85   - <section class="flex">
86   - <ApiTreeSelect v-bind="getBindProps" />
87   - <Button v-if="getShowCreate" type="link" @click="handleOpenCreate" :disabled="disabled"
88   - >新增组织</Button
89   - >
  85 + <section class="!flex">
  86 + <ApiTreeSelect v-bind="getBindProps" class="flex-auto" />
  87 + <Button v-if="getShowCreate" type="link" @click="handleOpenCreate" :disabled="disabled">
  88 + 新增组织
  89 + </Button>
90 90 <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" />
91 91 </section>
92 92 </template>
... ...
... ... @@ -337,7 +337,6 @@
337 337 }
338 338 },
339 339 onDisconnected() {
340   - console.log('断开连接了');
341 340 close();
342 341 },
343 342 });
... ...
... ... @@ -354,7 +354,6 @@
354 354 }
355 355 },
356 356 onDisconnected() {
357   - console.log('断开连接了');
358 357 close();
359 358 },
360 359 });
... ...
... ... @@ -75,9 +75,7 @@ export const formSchema: FormSchema[] = [
75 75 }
76 76 },
77 77 // showUploadList: true,
78   - onDownload(file) {
79   - console.log(file);
80   - },
  78 + onDownload() {},
81 79 onPreview: (fileList: FileItem) => {
82 80 createImgPreview({ imageList: [fileList.url!] });
83 81 },
... ...
... ... @@ -153,7 +153,7 @@
153 153 <template #renderItem="{ item }: BasicCardListRenderItem<BigScreenCenterItemsModel>">
154 154 <Card
155 155 :style="{
156   - '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',
  156 + '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#faad14' : '#1890ff',
157 157 }"
158 158 class="card-container"
159 159 >
... ...
... ... @@ -192,7 +192,6 @@
192 192 getRestData.value = resp;
193 193 commonRest(resp, jsonEditorRef.value?.setJsonValue);
194 194 } catch (e) {
195   - console.log(e);
196 195 if (Object.prototype.toString.call(e) === '[object Object]') {
197 196 jsonEditorRef.value?.setJsonValue(e);
198 197 } else {
... ...
... ... @@ -61,7 +61,7 @@ export const useUtils = () => {
61 61 }
62 62 }
63 63 } catch (e) {
64   - console.log(`Post没有传递params${e}`);
  64 + console.error(`Post没有传递params${e}`);
65 65 }
66 66 return _result.join('&');
67 67 };
... ...
1 1 import { FormProps, FormSchema, useComponentRegister } from '/@/components/Form';
2 2 import { findDictItemByCode } from '/@/api/system/dict';
3   -import { deviceProfile, getGatewayDevice } from '/@/api/device/deviceManager';
  3 +import { getGatewayDevice, queryDeviceProfileBy } from '/@/api/device/deviceManager';
4 4 import { TransportTypeEnum } from '../../profiles/components/TransportDescript/const';
5 5 import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor';
6 6 import { JSONEditor } from '/@/components/CodeEditor';
7 7 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
8 8 import { getModelServices } from '/@/api/device/modelOfMatter';
9 9 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
10   -import { nextTick, toRaw, unref } from 'vue';
  10 +import { h, nextTick, toRaw, unref } from 'vue';
11 11 import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';
12 12 import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum';
13 13 import { TaskTypeEnum } from '/@/views/task/center/config';
... ... @@ -18,9 +18,13 @@ import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter
18 18
19 19 import { getOrganizationList } from '/@/api/system/system';
20 20 import { copyTransFun } from '/@/utils/fnUtils';
  21 +import LockControlGroup from '/@/components/Form/src/components/LockControlGroup.vue';
  22 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
21 23
22 24 useComponentRegister('JSONEditor', JSONEditor);
23 25 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
  26 +useComponentRegister('LockControlGroup', LockControlGroup);
  27 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
24 28
25 29 export enum TypeEnum {
26 30 IS_GATEWAY = 'GATEWAY',
... ... @@ -30,6 +34,17 @@ export enum TypeEnum {
30 34 export const isGateWay = (type: string) => {
31 35 return type === TypeEnum.IS_GATEWAY;
32 36 };
  37 +
  38 +const updateProductHelpMessage = [
  39 + '注意:',
  40 + '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
  41 +];
  42 +
  43 +const updateOrgHelpMessage = [
  44 + '注意:',
  45 + '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
  46 + '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。',
  47 +];
33 48 // 第一步的表单
34 49 export const step1Schemas: FormSchema[] = [
35 50 {
... ... @@ -96,43 +111,60 @@ export const step1Schemas: FormSchema[] = [
96 111 show: false,
97 112 },
98 113 {
  114 + field: 'isUpdate',
  115 + label: '编辑模式',
  116 + component: 'Switch',
  117 + show: false,
  118 + },
  119 + {
99 120 field: 'profileId',
100 121 label: '所属产品',
101 122 required: true,
102   - component: 'ApiSelect',
  123 + component: 'LockControlGroup',
  124 + helpMessage: updateProductHelpMessage,
  125 + renderComponentContent: () => ({
  126 + popconfirmTitle: () =>
  127 + updateProductHelpMessage.map((text) => h('div', { style: { maxWidth: '200px' } }, text)),
  128 + }),
103 129 componentProps: ({ formActionType, formModel }) => {
104 130 const { setFieldsValue } = formActionType;
105 131 return {
106   - api: async () => {
107   - const options = await deviceProfile();
108   - const { profileId } = formModel;
109   - if (profileId) {
110   - const selectRecord = options.find((item) => item.tbProfileId === profileId);
111   - selectRecord && setFieldsValue({ transportType: selectRecord!.transportType });
112   - }
113   - return options;
114   - },
115   - labelField: 'name',
116   - valueField: 'tbProfileId',
117   - onChange(
118   - _value: string,
119   - option: { deviceType: string; transportType: string; id: string }
120   - ) {
121   - const { deviceType, transportType, id } = option;
122   - setFieldsValue({
123   - deviceType: deviceType,
124   - transportType,
125   - deviceProfileId: id,
126   - gatewayId: null,
127   - codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null,
128   - code: null,
129   - addressCode: null,
130   - });
  132 + component: 'ApiSelect',
  133 + defaultLockStatus: !!formModel?.isUpdate,
  134 + componentProps: {
  135 + api: async () => {
  136 + const options = await queryDeviceProfileBy({
  137 + deviceType: formModel?.isUpdate ? formModel?.deviceType : null,
  138 + });
  139 + const { profileId } = formModel;
  140 + if (profileId) {
  141 + const selectRecord = options.find((item) => item.tbProfileId === profileId);
  142 + selectRecord && setFieldsValue({ transportType: selectRecord!.transportType });
  143 + }
  144 + return options;
  145 + },
  146 + labelField: 'name',
  147 + valueField: 'tbProfileId',
  148 + onChange(
  149 + _value: string,
  150 + option: { deviceType: string; transportType: string; id: string }
  151 + ) {
  152 + const { deviceType, transportType, id } = option;
  153 + setFieldsValue({
  154 + deviceType: deviceType,
  155 + transportType,
  156 + deviceProfileId: id,
  157 + gatewayId: null,
  158 + codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null,
  159 + code: null,
  160 + addressCode: null,
  161 + });
  162 + },
  163 + showSearch: true,
  164 + placeholder: '请选择产品',
  165 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) =>
  166 + option.label.includes(inputValue),
131 167 },
132   - showSearch: true,
133   - placeholder: '请选择产品',
134   - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) =>
135   - option.label.includes(inputValue),
136 168 };
137 169 },
138 170 },
... ... @@ -311,9 +343,19 @@ export const step1Schemas: FormSchema[] = [
311 343 {
312 344 field: 'organizationId',
313 345 label: '所属组织',
314   - component: 'Select',
  346 + component: 'LockControlGroup',
315 347 required: true,
316   - slot: 'addOrg',
  348 + helpMessage: updateOrgHelpMessage,
  349 + renderComponentContent: () => ({
  350 + popconfirmTitle: () =>
  351 + updateOrgHelpMessage.map((text) => h('div', { style: { maxWidth: '240px' } }, text)),
  352 + }),
  353 + componentProps: ({ formModel }) => {
  354 + return {
  355 + component: 'OrgTreeSelect',
  356 + defaultLockStatus: !!formModel?.isUpdate,
  357 + };
  358 + },
317 359 },
318 360 {
319 361 field: 'label',
... ...
... ... @@ -7,6 +7,63 @@ import { h } from 'vue';
7 7 import { Tag, Tooltip } from 'ant-design-vue';
8 8 import { handeleCopy } from '../../profiles/step/topic';
9 9
  10 +export enum DeviceListAuthEnum {
  11 + /**
  12 + * @description 新增
  13 + */
  14 + CREATE = 'api:yt:device:post',
  15 +
  16 + /**
  17 + * @description 删除
  18 + */
  19 + DELETE = 'api:yt:device:delete',
  20 +
  21 + /**
  22 + * @description 编辑
  23 + */
  24 + UPDATE = 'api:yt:device:update',
  25 +
  26 + /**
  27 + * @description 详情
  28 + */
  29 + DETAIL = 'api:yt:device:get',
  30 +
  31 + /**
  32 + * @description 导入
  33 + */
  34 + IMPORT = 'api:yt:device:import',
  35 +
  36 + /**
  37 + * @description 公开
  38 + */
  39 + PUBLIC = 'api:yt:device:public',
  40 +
  41 + /**
  42 + * @description 上下线
  43 + */
  44 + ONLINE = 'api:yt:device:online:record',
  45 +
  46 + /**
  47 + * @description 管理设备凭证
  48 + */
  49 + EQUIPMENT = 'api:yt:device:equipment',
  50 +
  51 + /**
  52 + * @description 分配客户
  53 + */
  54 + ASSIGN = 'api:yt:device:assign',
  55 +
  56 + /**
  57 + * @description 命令下发
  58 + */
  59 + RPC = 'api:yt:device:rpc',
  60 +
  61 + /**
  62 + * @description 更新产品
  63 + */
  64 + UPDATE_PRODUCT = 'api:yt:device:update:product',
  65 +}
  66 +
10 67 // 表格列数据
11 68 export const columns: BasicColumn[] = [
12 69 {
... ...
... ... @@ -10,6 +10,7 @@
10 10 ...basicProps,
11 11 value: {
12 12 required: true,
  13 + type: Object,
13 14 },
14 15 });
15 16
... ...
  1 +import { getDeviceProfile } from '/@/api/alarm/position';
  2 +import { FormSchema } from '/@/components/Form';
  3 +
  4 +enum FormFieldsEnum {
  5 + DEVICE_TYPE = 'deviceType',
  6 + SOURCE_DEVICE_PROFILE_ID = 'sourceDeviceProfileName',
  7 + TARGET_DEVICE_PROFILE_ID = 'deviceProfileId',
  8 +}
  9 +
  10 +export type FormGetFiledValueResultType = Record<FormFieldsEnum, string>;
  11 +
  12 +export const formSchemas: FormSchema[] = [
  13 + {
  14 + field: FormFieldsEnum.SOURCE_DEVICE_PROFILE_ID,
  15 + component: 'Input',
  16 + label: '源产品',
  17 + dynamicDisabled: true,
  18 + required: true,
  19 + },
  20 + {
  21 + field: FormFieldsEnum.DEVICE_TYPE,
  22 + label: '设备类型',
  23 + component: 'Input',
  24 + show: false,
  25 + },
  26 + {
  27 + field: FormFieldsEnum.TARGET_DEVICE_PROFILE_ID,
  28 + component: 'ApiSelect',
  29 + label: '目标产品',
  30 + required: true,
  31 + componentProps: ({ formModel }) => {
  32 + return {
  33 + api: getDeviceProfile,
  34 + params: formModel[FormFieldsEnum.DEVICE_TYPE],
  35 + labelField: 'name',
  36 + valueField: 'tbProfileId',
  37 + placeholder: '请选择目标产品',
  38 + getPopupContainer: () => document.body,
  39 + };
  40 + },
  41 + },
  42 +];
... ...
  1 +export { default as BatchUpdateProductModal } from './index.vue';
  2 +
  3 +export type BatchUpdateProductModalParamsType = ModalParamsType<
  4 + Record<'sourceDeviceProfileName' | 'deviceType', string> & Record<'deviceIds', string[]>
  5 +>;
... ...
  1 +<script setup lang="ts">
  2 + import { ref, unref } from 'vue';
  3 + import { BatchUpdateProductModalParamsType } from '.';
  4 + import { FormGetFiledValueResultType, formSchemas } from './config';
  5 + import { doBatchUpdateProduct } from '/@/api/device/deviceManager';
  6 + import { BasicForm, useForm } from '/@/components/Form';
  7 + import Icon from '/@/components/Icon';
  8 + import { BasicModal, useModalInner } from '/@/components/Modal';
  9 + import { useMessage } from '/@/hooks/web/useMessage';
  10 +
  11 + const emits = defineEmits(['register', 'success']);
  12 +
  13 + const deviceIds = ref<string[]>([]);
  14 + const [registerForm, { validate, getFieldsValue, setFieldsValue, resetFields }] = useForm({
  15 + layout: 'vertical',
  16 + showActionButtonGroup: false,
  17 + schemas: formSchemas,
  18 + });
  19 +
  20 + const [registerModal, { setModalProps, closeModal }] = useModalInner(
  21 + async ({ record }: BatchUpdateProductModalParamsType) => {
  22 + resetFields();
  23 + deviceIds.value = record.deviceIds;
  24 + setFieldsValue(record);
  25 + }
  26 + );
  27 +
  28 + const { createMessage } = useMessage();
  29 + const handleOk = async () => {
  30 + try {
  31 + await validate();
  32 + setModalProps({ loading: true, confirmLoading: true });
  33 +
  34 + const result = getFieldsValue() as FormGetFiledValueResultType;
  35 +
  36 + await doBatchUpdateProduct({
  37 + deviceIds: unref(deviceIds),
  38 + deviceProfileId: result.deviceProfileId,
  39 + });
  40 +
  41 + createMessage.success('操作成功');
  42 +
  43 + closeModal();
  44 + emits('success');
  45 + } finally {
  46 + setModalProps({ loading: false, confirmLoading: false });
  47 + }
  48 + };
  49 +</script>
  50 +
  51 +<template>
  52 + <BasicModal @register="registerModal" @ok="handleOk">
  53 + <template #title>
  54 + <div>批量更新产品</div>
  55 + <div class="mt-4 font-normal text-sm">
  56 + <Icon icon="ant-design:info-circle-outlined" />
  57 + <span>
  58 + 注意:修改设备产品时只能修改为同类型产品,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。
  59 + </span>
  60 + </div>
  61 + </template>
  62 + <!-- <template #title>
  63 + <span></span>
  64 + </template> -->
  65 + <BasicForm @register="registerForm" />
  66 + </BasicModal>
  67 +</template>
... ...
... ... @@ -20,7 +20,7 @@
20 20 @next="handleStep1Next"
21 21 ref="DeviceStep1Ref"
22 22 v-show="current === 0"
23   - :isUpdate="!isUpdate"
  23 + :isUpdate="isUpdate"
24 24 />
25 25 <DeviceStep2
26 26 ref="DeviceStep2Ref"
... ... @@ -33,7 +33,7 @@
33 33 </BasicModal>
34 34 </template>
35 35 <script lang="ts">
36   - import { defineComponent, ref, computed, unref } from 'vue';
  36 + import { defineComponent, ref, computed, unref, nextTick } from 'vue';
37 37 import { BasicModal, useModalInner } from '/@/components/Modal';
38 38 import { createOrEditDevice } from '/@/api/device/deviceManager';
39 39 import DeviceStep1 from '../step/DeviceStep1.vue';
... ... @@ -60,16 +60,17 @@
60 60 const DeviceStep2Ref = ref<InstanceType<typeof DeviceStep2>>();
61 61 const { createMessage } = useMessage();
62 62 const current = ref(0);
63   - const isUpdate = ref<Boolean>(false);
  63 + const isUpdate = ref(false);
64 64 const deviceInfo = ref({});
65 65 let currentDeviceData = {} as Recordable;
66 66 const getTitle = computed(() => (!unref(isUpdate) ? '新增设备' : '编辑设备'));
67 67 // 所有参数
68 68 let stepState = ref();
69 69 // 编辑回显
70   - const [register, { closeModal, setModalProps }] = useModalInner((data) => {
  70 + const [register, { closeModal, setModalProps }] = useModalInner(async (data) => {
71 71 setModalProps({ confirmLoading: false, loading: true });
72 72 isUpdate.value = data?.isUpdate;
  73 + await nextTick();
73 74 if (unref(isUpdate)) {
74 75 const { record } = data;
75 76 currentDeviceData = record;
... ...
... ... @@ -2,26 +2,6 @@
2 2 <div class="step1">
3 3 <div class="step1-form">
4 4 <BasicForm @register="register">
5   - <template #addOrg="{ model, field }">
6   - <div style="display: flex; align-items: center">
7   - <div style="width: 245px">
8   - <a-tree-select
9   - @change="handleTreeOrg"
10   - v-model:value="model[field]"
11   - show-search
12   - style="width: 100%"
13   - :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
14   - placeholder="请选择组织"
15   - allow-clear
16   - tree-default-expand-all
17   - :tree-data="model?.['organizationList'] || treeData"
18   - />
19   - </div>
20   - <div>
21   - <a-button type="link" @click="handleOpenOrgDrawer">新增组织</a-button>
22   - </div>
23   - </div>
24   - </template>
25 5 <template #snCode="{ model, field }">
26 6 <div class="flex">
27 7 <Input :maxlength="36" v-model:value="model[field]" placeholder="请输入设备名称" />
... ... @@ -36,7 +16,7 @@
36 16 </Input>
37 17 </template>
38 18 </BasicForm>
39   - <div class="flex justify-center" v-if="isUpdate">
  19 + <div class="flex justify-center" v-if="!isUpdate">
40 20 <a-button type="primary" @click="nextStep">下一步</a-button>
41 21 </div>
42 22 </div>
... ... @@ -91,11 +71,10 @@
91 71 </Spin>
92 72 </div>
93 73 </Modal>
94   - <DeptDrawer @register="registerModal" @success="handleSuccess" />
95 74 </div>
96 75 </template>
97 76 <script lang="ts">
98   - import { defineComponent, ref, nextTick, unref, reactive, toRefs, onMounted } from 'vue';
  77 + import { defineComponent, ref, nextTick, unref, reactive } from 'vue';
99 78 import { BasicForm, useForm } from '/@/components/Form';
100 79 import { step1Schemas } from '../../config/data';
101 80 import { useScript } from '/@/hooks/web/useScript';
... ... @@ -109,8 +88,6 @@
109 88 import { validatorLongitude, validatorLatitude } from '/@/utils/rules';
110 89 import { getOrganizationList } from '/@/api/system/system';
111 90 import { copyTransFun } from '/@/utils/fnUtils';
112   - import { useDrawer } from '/@/components/Drawer';
113   - import DeptDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
114 91 import { TaskTypeEnum } from '/@/views/task/center/config';
115 92 import { toRaw } from 'vue';
116 93 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
... ... @@ -129,7 +106,6 @@
129 106 FormItem: Form.Item,
130 107 Row,
131 108 Col,
132   - DeptDrawer,
133 109 Spin,
134 110 },
135 111 props: {
... ... @@ -152,16 +128,7 @@
152 128 copyTransFun(data as any as any[]);
153 129 orgData.treeData = data;
154 130 };
155   - onMounted(async () => {
156   - await getOrganizationListFunc();
157   - });
158   - const { treeData } = toRefs(orgData);
159   - const [registerModal, { openDrawer }] = useDrawer();
160   - const handleOpenOrgDrawer = () => {
161   - openDrawer(true, {
162   - isUpdate: false,
163   - });
164   - };
  131 +
165 132 const handleSuccess = async () => {
166 133 await getOrganizationListFunc();
167 134 };
... ... @@ -178,27 +145,18 @@
178 145 const devicePic = ref('');
179 146 const loading = ref(false);
180 147
181   - const [
182   - register,
183   - {
184   - validate,
185   - resetFields,
186   - setFieldsValue,
187   - getFieldsValue,
188   - updateSchema,
189   - clearValidate,
190   - validateFields,
191   - },
192   - ] = useForm({
193   - labelWidth: 140,
194   - schemas: step1Schemas,
195   - actionColOptions: {
196   - span: 14,
197   - },
198   - labelAlign: 'right',
199   - showResetButton: false,
200   - showSubmitButton: false,
201   - });
  148 + const [register, { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema }] =
  149 + useForm({
  150 + labelWidth: 140,
  151 + schemas: step1Schemas,
  152 + actionColOptions: {
  153 + span: 14,
  154 + },
  155 + labelAlign: 'right',
  156 + showResetButton: false,
  157 + showSubmitButton: false,
  158 + });
  159 +
202 160 async function nextStep() {
203 161 try {
204 162 let values = await validate();
... ... @@ -245,7 +203,7 @@
245 203 const selectPosition = async () => {
246 204 visible.value = true;
247 205 if (
248   - !unref(isUpdate1) &&
  206 + unref(isUpdate1) &&
249 207 unref(devicePositionState).longitude &&
250 208 unref(devicePositionState).latitude
251 209 ) {
... ... @@ -440,8 +398,10 @@
440 398 positionState.address = deviceInfo.address;
441 399 devicePositionState.value = { ...toRaw(positionState) };
442 400 devicePic.value = deviceInfo.avatar;
  401 +
443 402 if (deviceInfo.avatar) {
444 403 setFieldsValue({
  404 + isUpdate: unref(isUpdate1),
445 405 icon: [{ uid: buildUUID(), name: 'name', url: deviceInfo.avatar } as FileItem],
446 406 });
447 407 }
... ... @@ -449,6 +409,7 @@
449 409 ...data,
450 410 code: data?.code,
451 411 addressCode: parseInt(data?.code || '', 16),
  412 + isUpdate: unref(isUpdate1),
452 413 });
453 414 }
454 415 // 父组件调用获取字段值的方法
... ... @@ -499,13 +460,6 @@
499 460 });
500 461 }
501 462
502   - const handleTreeOrg = (option: string) => {
503   - if (option) clearValidate('organizationId');
504   - else {
505   - validateFields(['organizationId']);
506   - }
507   - };
508   -
509 463 return {
510 464 resetFields,
511 465 positionState,
... ... @@ -534,11 +488,7 @@
534 488 loading,
535 489 rules,
536 490 redirectPosition,
537   - treeData,
538   - registerModal,
539   - handleOpenOrgDrawer,
540 491 handleSuccess,
541   - handleTreeOrg,
542 492 devicePositionState,
543 493 spinning,
544 494 };
... ...
... ... @@ -100,11 +100,9 @@
100 100 const { send, close } = useWebSocket(state.server, {
101 101 onConnected() {
102 102 send(state.sendValue);
103   - console.log('建立连接了');
104 103 },
105 104 onMessage(_, e) {
106 105 const { data } = JSON.parse(e.data);
107   - console.log('来新消息了', '---data---', data);
108 106 const newArray: socketDataType[] = [];
109 107 for (const key in data) {
110 108 const [time, value] = data[key].flat(1);
... ... @@ -134,7 +132,6 @@
134 132 });
135 133 },
136 134 onDisconnected() {
137   - console.log('断开连接了');
138 135 close();
139 136 },
140 137 onError() {
... ...
... ... @@ -32,13 +32,12 @@
32 32 import { BasicTable, useTable, TableAction } from '/@/components/Table';
33 33 import { Switch } from 'ant-design-vue';
34 34 import { DeviceRecord } from '/@/api/device/model/deviceModel';
35   - import { watch } from 'vue';
36 35 import VideoModal from './videoModal.vue';
37 36 import { useModal } from '/@/components/Modal';
38 37 import { onMounted } from 'vue';
39 38 import { useMessage } from '/@/hooks/web/useMessage';
40 39
41   - const props = defineProps({
  40 + defineProps({
42 41 fromId: {
43 42 type: String,
44 43 default: '',
... ... @@ -49,13 +48,6 @@
49 48 },
50 49 });
51 50
52   - watch(
53   - () => props,
54   - () => {
55   - console.log(props, 'props');
56   - }
57   - );
58   -
59 51 const [registerModal, { openModal }] = useModal();
60 52
61 53 const [registerTable, { setTableData, setProps, setSelectedRowKeys, reload }] = useTable({
... ... @@ -68,14 +60,10 @@
68 60 labelWidth: 120,
69 61 schemas: searchFormSchema,
70 62 },
71   - beforeFetch: (params) => {
72   - console.log(params);
73   - },
74 63 useSearchForm: true,
75 64 });
76 65
77   - const handleTurnVideo = async (checked: Boolean, record: Recordable) => {
78   - console.log(checked, record, 'record');
  66 + const handleTurnVideo = async (checked: Boolean, _record: Recordable) => {
79 67 setProps({
80 68 loading: true,
81 69 });
... ... @@ -281,7 +269,6 @@
281 269 });
282 270
283 271 const handlePlay = (record: Recordable) => {
284   - console.log(record);
285 272 openModal(true, {
286 273 record,
287 274 });
... ...
... ... @@ -4,41 +4,57 @@
4 4 <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
5 5 <BasicTable style="flex: auto" @register="registerTable" class="w-5/6 xl:w-4/5">
6 6 <template #toolbar>
7   - <Authority value="api:yt:device:post">
  7 + <Authority :value="DeviceListAuthEnum.CREATE">
8 8 <a-button type="primary" @click="handleCreate" v-if="authBtn(role)">
9 9 新增设备
10 10 </a-button>
11 11 </Authority>
12   - <Authority value="api:yt:device:delete">
13   - <Popconfirm
14   - title="您确定要批量删除数据"
15   - ok-text="确定"
16   - cancel-text="取消"
17   - @confirm="handleDelete()"
18   - >
19   - <a-button color="error" v-if="authBtn(role)" :disabled="!isExistOption">
20   - 批量删除
21   - </a-button>
22   - </Popconfirm>
23   - </Authority>
24   - <Authority value="api:yt:device:import">
  12 +
  13 + <Authority :value="DeviceListAuthEnum.IMPORT">
25 14 <Button type="primary" @click="handleBatchImport">导入</Button>
26 15 </Authority>
27   - <Authority value="api:yt:device:assign">
28   - <a-button
  16 +
  17 + <Authority
  18 + :value="[
  19 + DeviceListAuthEnum.DELETE,
  20 + DeviceListAuthEnum.ASSIGN,
  21 + DeviceListAuthEnum.UPDATE_PRODUCT,
  22 + ]"
  23 + >
  24 + <AuthDropDown
29 25 v-if="authBtn(role)"
30   - type="primary"
31   - @click="handleBatchAssign"
32 26 :disabled="!isExistOption"
  27 + :dropMenuList="[
  28 + {
  29 + text: '删除设备',
  30 + auth: DeviceListAuthEnum.DELETE,
  31 + icon: 'ant-design:delete-outlined',
  32 + event: '',
  33 + popconfirm: {
  34 + title: '您确定要批量删除数据',
  35 + onConfirm: () => handleDelete(),
  36 + },
  37 + },
  38 + {
  39 + text: '分配客户',
  40 + auth: DeviceListAuthEnum.ASSIGN,
  41 + icon: 'mdi:account-arrow-left',
  42 + event: '',
  43 + onClick: handleBatchAssign.bind(null),
  44 + },
  45 + {
  46 + text: '更新产品',
  47 + auth: DeviceListAuthEnum.UPDATE_PRODUCT,
  48 + icon: 'clarity:note-edit-line',
  49 + event: '',
  50 + disabled: batchUpdateProductFlag,
  51 + onClick: handelOpenBatchUpdateProductModal,
  52 + },
  53 + ]"
33 54 >
34   - 批量分配
35   - </a-button>
  55 + <Button type="primary" :disabled="!isExistOption">批量操作</Button>
  56 + </AuthDropDown>
36 57 </Authority>
37   - <!-- <Authority>
38   - <a-button type="primary" @click="handelCollect()" :disabled="!isExistOption">
39   - 批量收藏
40   - </a-button>
41   - </Authority> -->
42 58 </template>
43 59 <template #img="{ record }">
44 60 <TableImg
... ... @@ -121,12 +137,12 @@
121 137 {
122 138 label: '详情',
123 139 icon: 'ant-design:eye-outlined',
124   - auth: 'api:yt:device:get',
  140 + auth: DeviceListAuthEnum.DETAIL,
125 141 onClick: handleDetail.bind(null, record),
126 142 },
127 143 {
128 144 label: '编辑',
129   - auth: 'api:yt:device:update',
  145 + auth: DeviceListAuthEnum.UPDATE,
130 146 icon: 'clarity:note-edit-line',
131 147 ifShow: authBtn(role),
132 148 onClick: handleEdit.bind(null, record),
... ... @@ -138,7 +154,7 @@
138 154 label: '取消分配',
139 155 icon: 'mdi:account-arrow-left',
140 156 ifShow: authBtn(role) && !record?.customerAdditionalInfo?.isPublic,
141   - auth: 'api:yt:device:assign',
  157 + auth: DeviceListAuthEnum.ASSIGN,
142 158 popConfirm: {
143 159 title: '是否取消分配客户',
144 160 confirm: handleCancelDispatchCustomer.bind(null, record),
... ... @@ -148,12 +164,12 @@
148 164 label: '分配客户',
149 165 icon: 'mdi:account-arrow-right',
150 166 ifShow: authBtn(role),
151   - auth: 'api:yt:device:assign',
  167 + auth: DeviceListAuthEnum.ASSIGN,
152 168 onClick: handleDispatchCustomer.bind(null, record),
153 169 },
154 170 {
155 171 label: record?.customerAdditionalInfo?.isPublic ? '私有' : '公开',
156   - auth: 'api:yt:device:public',
  172 + auth: DeviceListAuthEnum.PUBLIC,
157 173 icon: record?.customerAdditionalInfo?.isPublic
158 174 ? 'ant-design:lock-outlined'
159 175 : 'ant-design:unlock-outlined',
... ... @@ -161,7 +177,7 @@
161 177 },
162 178 {
163 179 label: '上下线记录',
164   - auth: 'api:yt:device:online:record',
  180 + auth: DeviceListAuthEnum.ONLINE,
165 181 icon: 'ant-design:rise-outlined',
166 182 onClick: handleUpAndDownRecord.bind(null, record),
167 183 },
... ... @@ -173,7 +189,6 @@
173 189 }
174 190 : {
175 191 label: '取消收藏',
176   - auth: 'api:yt:device:online:record',
177 192 icon: 'ant-design:heart-outlined',
178 193 popConfirm: {
179 194 title: '是否取消收藏',
... ... @@ -182,7 +197,7 @@
182 197 },
183 198 {
184 199 label: '删除',
185   - auth: 'api:yt:device:delete',
  200 + auth: DeviceListAuthEnum.DELETE,
186 201 icon: 'ant-design:delete-outlined',
187 202 ifShow: authBtn(role) && record.customerId === undefined,
188 203 color: 'error',
... ... @@ -208,11 +223,16 @@
208 223 <CustomerModal @register="registerCustomerModal" @reload="handleReload" />
209 224
210 225 <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" />
  226 +
  227 + <BatchUpdateProductModal
  228 + @register="registerBatchUpdateProductModal"
  229 + @success="handleBatchUpdateProductSuccess"
  230 + />
211 231 </PageWrapper>
212 232 </div>
213 233 </template>
214   -<script lang="ts">
215   - import { defineComponent, reactive, unref, onMounted } from 'vue';
  234 +<script lang="ts" setup>
  235 + import { reactive, onMounted, ref } from 'vue';
216 236 import {
217 237 DeviceModel,
218 238 DeviceRecord,
... ... @@ -220,8 +240,8 @@
220 240 DeviceTypeEnum,
221 241 } from '/@/api/device/model/deviceModel';
222 242 import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table';
223   - import { columns, searchFormSchema } from './config/device.data';
224   - import { Tag, Popover, Popconfirm, Button, Tooltip } from 'ant-design-vue';
  243 + import { columns, DeviceListAuthEnum, searchFormSchema } from './config/device.data';
  244 + import { Tag, Popover, Button, Tooltip } from 'ant-design-vue';
225 245 import { HeartTwoTone } from '@ant-design/icons-vue';
226 246 import {
227 247 deleteDevice,
... ... @@ -246,326 +266,295 @@
246 266 import { USER_INFO_KEY } from '/@/enums/cacheEnum';
247 267 import { getAuthCache } from '/@/utils/auth';
248 268 import { authBtn } from '/@/enums/roleEnum';
249   - import { useClipboard } from '@vueuse/core';
250 269 import { QuestionCircleOutlined } from '@ant-design/icons-vue';
251 270 import { Authority } from '/@/components/Authority';
252 271 import { useRoute, useRouter } from 'vue-router';
253 272 import { useBatchOperation } from '/@/utils/useBatchOperation';
254 273 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
255 274 import { useAuthDeviceDetail } from './hook/useAuthDeviceDetail';
  275 + import {
  276 + BatchUpdateProductModal,
  277 + BatchUpdateProductModalParamsType,
  278 + } from './cpns/modal/BatchUpdateProductModal';
  279 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  280 + import { AuthDropDown } from '/@/components/Widget';
256 281
257   - export default defineComponent({
258   - name: 'DeviceManagement',
259   - components: {
260   - BasicTable,
261   - PageWrapper,
262   - TableAction,
263   - OrganizationIdTree,
264   - Tag,
265   - DeviceModal,
266   - DeviceDetailDrawer,
267   - CustomerModal,
268   - TableImg,
269   - QuestionCircleOutlined,
270   - Popover,
271   - Authority,
272   - Popconfirm,
273   - BatchImportModal,
274   - Button,
275   - // HeartOutlined,
276   - HeartTwoTone,
277   - Tooltip,
278   - },
279   - setup(_) {
280   - const { isCustomer } = useAuthDeviceDetail();
281   - const { createMessage } = useMessage();
282   - const go = useGo();
283   - const ROUTER = useRouter();
284   - const ROUTE = useRoute();
285   - const searchInfo = reactive<Recordable>({});
286   - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
287   - const [registerModal, { openModal }] = useModal();
288   - const [registerCustomerModal, { openModal: openCustomerModal }] = useModal();
289   - const [registerDetailDrawer, { openDrawer }] = useDrawer();
290   - const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer();
291   - const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer();
292   - const [registerImportModal, { openModal: openImportModal }] = useModal();
  282 + const { isCustomer } = useAuthDeviceDetail();
  283 + const { createMessage } = useMessage();
  284 + const go = useGo();
  285 + const ROUTER = useRouter();
  286 + const ROUTE = useRoute();
  287 + const searchInfo = reactive<Recordable>({});
  288 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  289 + const [registerModal, { openModal }] = useModal();
  290 + const [registerCustomerModal, { openModal: openCustomerModal }] = useModal();
  291 + const [registerDetailDrawer, { openDrawer }] = useDrawer();
  292 + const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer();
  293 + const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer();
  294 + const [registerImportModal, { openModal: openImportModal }] = useModal();
  295 + const [registerBatchUpdateProductModal, { openModal: openBatchUpdateProductModal }] = useModal();
293 296
294   - const [
295   - registerTable,
296   - {
297   - reload,
298   - setLoading,
299   - setSelectedRowKeys,
300   - getForm,
301   - getSelectRowKeys,
302   - getSelectRows,
303   - getRowSelection,
304   - },
305   - ] = useTable({
306   - title: '设备列表',
307   - api: devicePage,
308   - columns,
309   - beforeFetch: (params) => {
310   - const { deviceProfileId } = params;
311   - if (!deviceProfileId) return;
312   - const obj = {
313   - ...params,
314   - ...{
315   - deviceProfileIds: deviceProfileId ? [deviceProfileId] : null,
316   - },
317   - };
318   - delete obj.deviceProfileId;
319   - return obj;
320   - },
321   - formConfig: {
322   - labelWidth: 100,
323   - schemas: searchFormSchema,
324   - resetFunc: resetFn,
325   - },
326   - useSearchForm: true,
327   - showTableSetting: true,
328   - bordered: true,
329   - showIndexColumn: false,
330   - rowKey: 'id',
331   - searchInfo: searchInfo,
332   - clickToRowSelect: false,
333   - actionColumn: {
334   - width: 200,
335   - title: '操作',
336   - slots: { customRender: 'action' },
337   - fixed: 'right',
338   - },
339   - rowSelection: {
340   - type: 'checkbox',
341   - getCheckboxProps: (record: DeviceModel) => {
342   - return { disabled: !!record.customerId };
343   - },
  297 + const batchUpdateProductFlag = ref(true);
  298 +
  299 + const [
  300 + registerTable,
  301 + {
  302 + reload,
  303 + setLoading,
  304 + setSelectedRowKeys,
  305 + getForm,
  306 + getSelectRowKeys,
  307 + getSelectRows,
  308 + getRowSelection,
  309 + clearSelectedRowKeys,
  310 + },
  311 + ] = useTable({
  312 + title: '设备列表',
  313 + api: devicePage,
  314 + columns,
  315 + beforeFetch: (params) => {
  316 + const { deviceProfileId } = params;
  317 + if (!deviceProfileId) return;
  318 + const obj = {
  319 + ...params,
  320 + ...{
  321 + deviceProfileIds: deviceProfileId ? [deviceProfileId] : null,
344 322 },
345   - });
  323 + };
  324 + delete obj.deviceProfileId;
  325 + return obj;
  326 + },
  327 + formConfig: {
  328 + labelWidth: 100,
  329 + schemas: searchFormSchema,
  330 + resetFunc: resetFn,
  331 + },
  332 + useSearchForm: true,
  333 + showTableSetting: true,
  334 + bordered: true,
  335 + showIndexColumn: false,
  336 + rowKey: 'id',
  337 + searchInfo: searchInfo,
  338 + clickToRowSelect: false,
  339 + actionColumn: {
  340 + width: 200,
  341 + title: '操作',
  342 + slots: { customRender: 'action' },
  343 + fixed: 'right',
  344 + },
  345 + rowSelection: {
  346 + type: 'checkbox',
  347 + getCheckboxProps: (record: DeviceModel) => {
  348 + return { disabled: !!record.customerId };
  349 + },
  350 + onSelect(_record, _selected, selectedRows) {
  351 + const [firstItem] = selectedRows as DeviceRecord[];
  352 + const { deviceType } = firstItem || {};
  353 + batchUpdateProductFlag.value =
  354 + !selectedRows.length ||
  355 + !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  356 + },
  357 + onSelectAll(_selected, selectedRows) {
  358 + const [firstItem] = selectedRows as DeviceRecord[];
  359 + const { deviceType } = firstItem || {};
  360 + batchUpdateProductFlag.value =
  361 + !selectedRows.length ||
  362 + !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  363 + },
  364 + },
  365 + });
346 366
347   - const { isExistOption } = useBatchOperation(getRowSelection, setSelectedRowKeys);
  367 + const { isExistOption } = useBatchOperation(getRowSelection, setSelectedRowKeys);
348 368
349   - const userInfo: any = getAuthCache(USER_INFO_KEY);
350   - const role: string = userInfo.roles[0];
  369 + const userInfo: any = getAuthCache(USER_INFO_KEY);
  370 + const role: string = userInfo.roles[0];
351 371
352   - function handleCreate() {
353   - openModal(true, {
354   - isUpdate: false,
355   - });
356   - }
357   - // 分配客户
358   - function handleDispatchCustomer(record: Recordable) {
359   - openCustomerModal(true, record);
360   - }
361   - function handleReload() {
362   - setSelectedRowKeys([]);
363   - handleSuccess();
364   - }
365   - // 取消分配客户
366   - async function handleCancelDispatchCustomer(record: Recordable) {
367   - try {
368   - // 该设备是否正在被场景联动使用中?
369   - await cancelDispatchCustomer(record);
370   - handleReload();
371   - } catch {}
372   - }
  372 + function handleCreate() {
  373 + openModal(true, {
  374 + isUpdate: false,
  375 + });
  376 + }
  377 + // 分配客户
  378 + function handleDispatchCustomer(record: Recordable) {
  379 + openCustomerModal(true, record);
  380 + }
  381 + function handleReload() {
  382 + setSelectedRowKeys([]);
  383 + handleSuccess();
  384 + }
  385 + // 取消分配客户
  386 + async function handleCancelDispatchCustomer(record: Recordable) {
  387 + try {
  388 + // 该设备是否正在被场景联动使用中?
  389 + await cancelDispatchCustomer(record);
  390 + handleReload();
  391 + } catch {}
  392 + }
373 393
374   - function handleDetail(record: Recordable) {
375   - const { id, tbDeviceId, deviceProfile, deviceType } = record;
376   - const { transportType } = deviceProfile || {};
377   - openDrawer(true, {
378   - id,
379   - tbDeviceId,
380   - transportType,
381   - deviceType,
382   - });
383   - }
  394 + function handleDetail(record: Recordable) {
  395 + const { id, tbDeviceId, deviceProfile, deviceType } = record;
  396 + const { transportType } = deviceProfile || {};
  397 + openDrawer(true, {
  398 + id,
  399 + tbDeviceId,
  400 + transportType,
  401 + deviceType,
  402 + });
  403 + }
384 404
385   - async function handleEdit(record: Recordable) {
386   - if (record.deviceType === 'SENSOR') {
387   - const res = await getGATEWAY(record.tbDeviceId);
388   - Reflect.set(record, 'gatewayId', res.tbDeviceId);
389   - }
390   - openModal(true, {
391   - isUpdate: true,
392   - record,
393   - });
394   - }
395   - function handleSuccess() {
396   - reload();
397   - }
398   - function handleSelect(organization) {
399   - searchInfo.organizationId = organization;
400   - handleSuccess();
401   - }
402   - function goDeviceProfile(e) {
403   - go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(e)));
404   - }
405   - const { copied, copy } = useClipboard({ legacy: true });
406   - const copySN = async (snCode: string) => {
407   - await copy(snCode);
408   - if (unref(copied)) {
409   - createMessage.success('复制成功~');
410   - }
411   - };
  405 + async function handleEdit(record: Recordable) {
  406 + if (record.deviceType === 'SENSOR') {
  407 + const res = await getGATEWAY(record.tbDeviceId);
  408 + Reflect.set(record, 'gatewayId', res.tbDeviceId);
  409 + }
  410 + openModal(true, {
  411 + isUpdate: true,
  412 + record,
  413 + });
  414 + }
  415 + function handleSuccess() {
  416 + reload();
  417 + }
  418 + function handleSelect(organization) {
  419 + searchInfo.organizationId = organization;
  420 + handleSuccess();
  421 + }
  422 + function goDeviceProfile(e) {
  423 + go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(e)));
  424 + }
412 425
413   - const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => {
414   - openTbDeviceDrawer(true, data);
415   - };
  426 + const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => {
  427 + openTbDeviceDrawer(true, data);
  428 + };
416 429
417   - const handleOpenGatewayDetail = (data: { id: string; tbDeviceId: string }) => {
418   - openGatewayDetailDrawer(true, data);
419   - };
  430 + const handleOpenGatewayDetail = (data: { id: string; tbDeviceId: string }) => {
  431 + openGatewayDetailDrawer(true, data);
  432 + };
420 433
421   - const handleUpAndDownRecord = (record: Record<'name' | 'alias', string>) => {
422   - ROUTER.push({
423   - path: '/operation/onlinerecord',
424   - query: { deviceName: record.alias || record.name },
425   - });
426   - };
  434 + const handleUpAndDownRecord = (record: Record<'name' | 'alias', string>) => {
  435 + ROUTER.push({
  436 + path: '/operation/onlinerecord',
  437 + query: { deviceName: record.alias || record.name },
  438 + });
  439 + };
427 440
428   - const handleCheckHasDiffenterOrg = (options: DeviceModel[]) => {
429   - let orgId: string | undefined;
430   - let flag = false;
431   - for (const item of options) {
432   - const _orgId = item.organizationId;
433   - if (!orgId) orgId = _orgId;
434   - if (orgId !== _orgId) {
435   - flag = true;
436   - break;
437   - }
438   - }
439   - return flag;
440   - };
  441 + const handleCheckHasDiffenterOrg = (options: DeviceModel[]) => {
  442 + let orgId: string | undefined;
  443 + let flag = false;
  444 + for (const item of options) {
  445 + const _orgId = item.organizationId;
  446 + if (!orgId) orgId = _orgId;
  447 + if (orgId !== _orgId) {
  448 + flag = true;
  449 + break;
  450 + }
  451 + }
  452 + return flag;
  453 + };
441 454
442   - const handleBatchAssign = () => {
443   - const options = getSelectRows();
444   - if (handleCheckHasDiffenterOrg(options as DeviceModel[])) {
445   - createMessage.error('当前选中项中存在不同所属组织的设备!');
446   - return;
447   - }
448   - openCustomerModal(true, options);
449   - };
  455 + const handleBatchAssign = () => {
  456 + const options = getSelectRows();
  457 + if (handleCheckHasDiffenterOrg(options as DeviceModel[])) {
  458 + createMessage.error('当前选中项中存在不同所属组织的设备!');
  459 + return;
  460 + }
  461 + openCustomerModal(true, options);
  462 + };
450 463
451   - const handleDelete = async (record?: DeviceRecord) => {
452   - let ids: string[] = [];
453   - if (record) {
454   - ids.push(record.id);
455   - } else {
456   - ids = getSelectRowKeys();
457   - }
458   - try {
459   - setLoading(true);
460   - await deleteDevice(ids);
461   - createMessage.success('删除成功');
462   - handleReload();
463   - } catch (error) {
464   - throw error;
465   - } finally {
466   - setLoading(false);
467   - }
468   - };
  464 + const handleDelete = async (record?: DeviceRecord) => {
  465 + let ids: string[] = [];
  466 + if (record) {
  467 + ids.push(record.id);
  468 + } else {
  469 + ids = getSelectRowKeys();
  470 + }
  471 + try {
  472 + setLoading(true);
  473 + await deleteDevice(ids);
  474 + createMessage.success('删除成功');
  475 + handleReload();
  476 + } catch (error) {
  477 + throw error;
  478 + } finally {
  479 + setLoading(false);
  480 + }
  481 + };
469 482
470   - const handleBatchImport = () => {
471   - openImportModal(true);
472   - };
  483 + const handleBatchImport = () => {
  484 + openImportModal(true);
  485 + };
473 486
474   - const handleImportFinally = () => {
475   - reload();
476   - };
  487 + const handleImportFinally = () => {
  488 + reload();
  489 + };
477 490
478   - const { createSyncConfirm } = useSyncConfirm();
479   - const handlePublicDevice = async (record: DeviceRecord) => {
480   - try {
481   - const publicFlag = record?.customerAdditionalInfo?.isPublic;
482   - const type = publicFlag ? '私有' : '公开';
483   - const flag = await createSyncConfirm({
484   - iconType: 'warning',
485   - title: `您确定要将设备 '${record.name}' 设为${type}吗?`,
486   - content: `确认后,设备及其所有数据将被设为${type}并${
487   - publicFlag ? '不' : ''
488   - }可被其他人访问。`,
489   - });
490   - if (!flag) return;
491   - if (publicFlag) {
492   - await privateDevice(record.tbDeviceId);
493   - } else {
494   - await publicDevice(record.tbDeviceId);
495   - }
496   - reload();
497   - } catch (error) {}
498   - };
  491 + const { createSyncConfirm } = useSyncConfirm();
  492 + const handlePublicDevice = async (record: DeviceRecord) => {
  493 + try {
  494 + const publicFlag = record?.customerAdditionalInfo?.isPublic;
  495 + const type = publicFlag ? '私有' : '公开';
  496 + const flag = await createSyncConfirm({
  497 + iconType: 'warning',
  498 + title: `您确定要将设备 '${record.name}' 设为${type}吗?`,
  499 + content: `确认后,设备及其所有数据将被设为${type}并${
  500 + publicFlag ? '不' : ''
  501 + }可被其他人访问。`,
  502 + });
  503 + if (!flag) return;
  504 + if (publicFlag) {
  505 + await privateDevice(record.tbDeviceId);
  506 + } else {
  507 + await publicDevice(record.tbDeviceId);
  508 + }
  509 + reload();
  510 + } catch (error) {}
  511 + };
499 512
500   - // 收藏 && 批量收藏
501   - const handelCollect = async (record?: Recordable) => {
502   - let ids: string[] = [];
503   - if (record) {
504   - ids.push(record.id);
505   - } else {
506   - ids = await getSelectRowKeys();
507   - }
508   - try {
509   - setLoading(true);
510   - await deviceCollect(ids);
511   - createMessage.success('操作成功');
512   - handleReload();
513   - } catch (error) {
514   - throw error;
515   - } finally {
516   - setLoading(false);
517   - }
518   - };
  513 + // 收藏 && 批量收藏
  514 + const handelCollect = async (record?: Recordable) => {
  515 + let ids: string[] = [];
  516 + if (record) {
  517 + ids.push(record.id);
  518 + } else {
  519 + ids = await getSelectRowKeys();
  520 + }
  521 + try {
  522 + setLoading(true);
  523 + await deviceCollect(ids);
  524 + createMessage.success('操作成功');
  525 + handleReload();
  526 + } catch (error) {
  527 + throw error;
  528 + } finally {
  529 + setLoading(false);
  530 + }
  531 + };
519 532
520   - onMounted(() => {
521   - const queryParams = ROUTE.query as Record<'deviceProfileId', undefined | string>;
522   - const { setFieldsValue } = getForm();
523   - setFieldsValue({ deviceProfileId: queryParams.deviceProfileId });
524   - });
525   - return {
526   - registerTable,
527   - handleCreate,
528   - handleDetail,
529   - handleEdit,
530   - handleSuccess,
531   - goDeviceProfile,
532   - handleSelect,
533   - registerModal,
534   - registerDetailDrawer,
535   - DeviceTypeEnum,
536   - DeviceState,
537   - searchInfo,
538   - organizationIdTreeRef,
539   - handleDispatchCustomer,
540   - handleCancelDispatchCustomer,
541   - registerCustomerModal,
542   - authBtn,
543   - role,
544   - copySN,
545   - isExistOption,
546   - handleDelete,
547   - handelCollect,
548   - // hasBatchDelete,
549   - // handleDeleteOrBatchDelete,
550   - handleReload,
551   - registerTbDetailDrawer,
552   - handleOpenTbDeviceDetail,
553   - handleOpenGatewayDetail,
554   - registerGatewayDetailDrawer,
555   - handleUpAndDownRecord,
556   - handleBatchAssign,
557   - registerImportModal,
558   - handleBatchImport,
559   - handleImportFinally,
560   - handlePublicDevice,
561   - isCustomer,
562   - };
563   - },
  533 + onMounted(() => {
  534 + const queryParams = ROUTE.query as Record<'deviceProfileId', undefined | string>;
  535 + const { setFieldsValue } = getForm();
  536 + setFieldsValue({ deviceProfileId: queryParams.deviceProfileId });
564 537 });
  538 +
  539 + const handelOpenBatchUpdateProductModal = () => {
  540 + const rows: DeviceRecord[] = getSelectRows();
  541 + const [firstItem] = rows;
  542 +
  543 + openBatchUpdateProductModal(true, {
  544 + mode: DataActionModeEnum.UPDATE,
  545 + record: {
  546 + sourceDeviceProfileName: firstItem.deviceProfile.name,
  547 + deviceType: firstItem.deviceType,
  548 + deviceIds: rows.map((item) => item.tbDeviceId),
  549 + },
  550 + } as BatchUpdateProductModalParamsType);
  551 + };
  552 +
  553 + const handleBatchUpdateProductSuccess = () => {
  554 + reload();
  555 + clearSelectedRowKeys();
  556 + batchUpdateProductFlag.value = true;
  557 + };
565 558 </script>
566 559
567   -<style scoped lang="css">
568   - /* /deep/.ant-message-notice-content {
569   - max-width: 600px !important;
570   - } */
571   -</style>
  560 +<style scoped lang="css"></style>
... ...
... ... @@ -346,7 +346,6 @@ export const schemas: FormSchema[] = [
346 346 return {
347 347 onChange(value) {
348 348 const { updateSchema } = formActionType;
349   - console.log(value);
350 349 formModel.interval = '';
351 350 updateSchema({
352 351 field: 'interval',
... ...
... ... @@ -235,7 +235,6 @@
235 235
236 236 function loadDataSuccess(excelDataList: ExcelData[]) {
237 237 tableListRef.value = [];
238   - console.log(excelDataList);
239 238 for (const excelData of excelDataList) {
240 239 const {
241 240 header,
... ... @@ -294,9 +293,7 @@
294 293 });
295 294 };
296 295 //导入
297   - function handleImport() {
298   - console.log('record');
299   - }
  296 + function handleImport() {}
300 297 function handleSuccess() {
301 298 reload();
302 299 }
... ...
... ... @@ -204,8 +204,7 @@
204 204 });
205 205 flag && createMessage.info(flag?.message);
206 206 } catch (msg) {
207   - // eslint-disable-next-line no-console
208   - console.log(msg, 'msg');
  207 + console.error(msg, 'msg');
209 208 } finally {
210 209 closeLoading();
211 210 closeModal();
... ...
... ... @@ -96,7 +96,6 @@
96 96 render: (_, data: Bootstrap) => (data.notifIfDisabled ? '启用' : '禁用'),
97 97 },
98 98 ];
99   - console.log(unref(getOtherSetting));
100 99 const [registerOtherSettings] = useDescription({
101 100 layout: 'vertical',
102 101 column: 2,
... ...
... ... @@ -68,21 +68,18 @@
68 68 <Button v-if="isShowBtn" class="!bg-gray-200" type="text" @click="handleReturn">
69 69 返回
70 70 </Button>
71   - <Authority :value="[ModelOfMatterPermission.DELETE, ModelCategoryPermission.DELETE]">
  71 + <Authority
  72 + v-if="isShowBtn"
  73 + :value="[ModelOfMatterPermission.DELETE, ModelCategoryPermission.DELETE]"
  74 + >
72 75 <Popconfirm
73 76 title="您确定要批量删除数据"
74 77 ok-text="确定"
75 78 cancel-text="取消"
76   - @confirm="handleDeleteOrBatchDelete(null)"
  79 + @confirm="handleDeleteOrBatchDelete()"
  80 + :disabled="getHasBatchDelete"
77 81 >
78   - <Button
79   - style="display: none"
80   - type="primary"
81   - color="error"
82   - :disabled="hasBatchDelete"
83   - >
84   - 批量删除
85   - </Button>
  82 + <Button type="primary" danger :disabled="getHasBatchDelete"> 批量删除 </Button>
86 83 </Popconfirm>
87 84 </Authority>
88 85 </div>
... ... @@ -120,11 +117,7 @@
120 117 />
121 118 </template>
122 119 </BasicTable>
123   - <PhysicalModelModal
124   - :record="$props.record"
125   - @register="registerModal"
126   - @success="handleSuccess"
127   - />
  120 + <PhysicalModelModal :record="$props.record" @register="registerModal" @success="reload" />
128 121 <PhysicalModelTsl :record="$props.record" @register="registerModalTsl" />
129 122 <SelectImport
130 123 :record="$props.record"
... ... @@ -142,7 +135,6 @@
142 135 ModelCategoryPermission,
143 136 physicalColumn,
144 137 } from '../device.profile.data';
145   - import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
146 138 import { Authority } from '/@/components/Authority';
147 139 import PhysicalModelModal from './cpns/physical/PhysicalModelModal.vue';
148 140 import PhysicalModelTsl from './cpns/physical/PhysicalModelTsl.vue';
... ... @@ -156,8 +148,11 @@
156 148 releaseModel,
157 149 } from '/@/api/device/modelOfMatter';
158 150 import { OpenModelOfMatterModelParams, OpenModelMode } from './cpns/physical/types';
159   - import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
160   - import { ref, unref } from 'vue';
  151 + import {
  152 + ModelOfMatterItemRecordType,
  153 + ModelOfMatterParams,
  154 + } from '/@/api/device/model/modelOfMatterModel';
  155 + import { computed, ref, unref } from 'vue';
161 156 import { isObject } from '/@/utils/is';
162 157 import { useRole } from '/@/hooks/business/useRole';
163 158 import { ExportModelCategory } from '/@/api/device/modelOfMatter';
... ... @@ -177,50 +172,45 @@
177 172 const [registerModalTsl, { openModal: openModalTsl }] = useModal();
178 173 const [registerModalSelect, { openModal: openModalSelect }] = useModal();
179 174
180   - const [registerTable, { reload, setProps }] = useTable({
181   - api: async (params: Record<'page' | 'pageSize', number>) => {
182   - return await getModelList({
183   - ...params,
184   - id: props.record.id,
185   - selectType: props.record.ifShowClass ? 'category' : undefined,
186   - });
187   - },
188   - columns: props.record.ifShowClass
189   - ? physicalColumn.filter((item) => item.dataIndex !== 'status')
190   - : physicalColumn,
191   - showIndexColumn: false,
192   - clickToRowSelect: false,
193   - showTableSetting: true,
194   - bordered: true,
195   - useSearchForm: true,
196   - formConfig: {
197   - schemas: modelOfMatterForm,
198   - labelWidth: 120,
199   - },
200   - actionColumn: {
201   - width: 200,
202   - title: '操作',
203   - dataIndex: 'action',
204   - slots: { customRender: 'action' },
205   - fixed: 'right',
206   - },
207   - });
208   -
209   - // 刷新
210   - const handleSuccess = () => {
211   - reload();
212   - };
213   - const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
214   - props.record.ifShowClass && (unref(isPlatformAdmin) || unref(isSysadmin))
215   - ? deleteModelCategory
216   - : deleteModel,
217   - handleSuccess,
218   - setProps
219   - );
  175 + const [registerTable, { reload, getSelectRowKeys, getRowSelection, clearSelectedRowKeys }] =
  176 + useTable({
  177 + api: async (params: Record<'page' | 'pageSize', number>) => {
  178 + return await getModelList({
  179 + ...params,
  180 + id: props.record.id,
  181 + selectType: props.record.ifShowClass ? 'category' : undefined,
  182 + });
  183 + },
  184 + columns: props.record.ifShowClass
  185 + ? physicalColumn.filter((item) => item.dataIndex !== 'status')
  186 + : physicalColumn,
  187 + showIndexColumn: false,
  188 + clickToRowSelect: false,
  189 + showTableSetting: true,
  190 + bordered: true,
  191 + useSearchForm: true,
  192 + rowKey: 'id',
  193 + formConfig: {
  194 + schemas: modelOfMatterForm,
  195 + labelWidth: 120,
  196 + },
  197 + actionColumn: {
  198 + width: 200,
  199 + title: '操作',
  200 + dataIndex: 'action',
  201 + slots: { customRender: 'action' },
  202 + fixed: 'right',
  203 + },
  204 + rowSelection: {
  205 + getCheckboxProps: (record: ModelOfMatterItemRecordType) => {
  206 + return {
  207 + disabled: record.status === 1,
  208 + };
  209 + },
  210 + },
  211 + });
220 212
221   - selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
222   - return { disabled: record.status === 1 };
223   - };
  213 + const getHasBatchDelete = computed(() => !getRowSelection().selectedRowKeys?.length);
224 214
225 215 const handleViewDetail = (record: ModelOfMatterParams) => {
226 216 if (record) {
... ... @@ -245,6 +235,19 @@
245 235 }
246 236 };
247 237
  238 + const handleDeleteOrBatchDelete = async (record?: ModelOfMatterItemRecordType) => {
  239 + const deleteFn =
  240 + props.record.ifShowClass && (unref(isPlatformAdmin) || unref(isSysadmin))
  241 + ? deleteModelCategory
  242 + : deleteModel;
  243 +
  244 + const ids = record ? [record.id] : getSelectRowKeys();
  245 + await deleteFn(ids);
  246 + createMessage.success('删除成功');
  247 + clearSelectedRowKeys();
  248 + reload();
  249 + };
  250 +
248 251 const handleOpenTsl = () => {
249 252 openModalTsl(true, {
250 253 isUpdate: true,
... ...
... ... @@ -30,9 +30,7 @@
30 30 import { SelectTypes } from 'ant-design-vue/es/select';
31 31
32 32 const emit = defineEmits(['register', 'emitSelect']);
33   - const [register] = useModalInner((data) => {
34   - console.log(data);
35   - });
  33 + const [register] = useModalInner(() => {});
36 34 const heightNum = ref(80);
37 35 const visible = ref(false);
38 36 const selectValue = ref('LwM2M');
... ...
... ... @@ -130,14 +130,13 @@
130 130 telemetry: [],
131 131 });
132 132
133   - const [registerModel, { resetFields: resetObjectListValue, getFieldsValue: getObjectListValue }] =
134   - useForm({
135   - labelWidth: 100,
136   - schemas: modelSchemas,
137   - actionColOptions: {
138   - span: 14,
139   - },
140   - });
  133 + const [registerModel, { resetFields: resetObjectListValue }] = useForm({
  134 + labelWidth: 100,
  135 + schemas: modelSchemas,
  136 + actionColOptions: {
  137 + span: 14,
  138 + },
  139 + });
141 140 const [
142 141 registerSettings,
143 142 {
... ... @@ -152,14 +151,13 @@
152 151 span: 14,
153 152 },
154 153 });
155   - const [registerDevice, { resetFields: resetDeviceValue, getFieldsValue: getDeviceValue }] =
156   - useForm({
157   - labelWidth: 100,
158   - schemas: deviceSchemas,
159   - actionColOptions: {
160   - span: 14,
161   - },
162   - });
  154 + const [registerDevice, { resetFields: resetDeviceValue }] = useForm({
  155 + labelWidth: 100,
  156 + schemas: deviceSchemas,
  157 + actionColOptions: {
  158 + span: 14,
  159 + },
  160 + });
163 161 const [registerModal, { openModal }] = useModal();
164 162
165 163 const handleAdd = () => {
... ... @@ -205,10 +203,6 @@
205 203 };
206 204
207 205 const getFormData = async () => {
208   - const objectListVal = getObjectListValue();
209   - const deviceVal = getDeviceValue();
210   - console.log('第一个tab', objectListVal);
211   - console.log('第四个tab', deviceVal);
212 206 getBootStrapFormValue();
213 207 const settingsVal = await settingsValidate();
214 208 if (!settingsVal) return;
... ...
... ... @@ -69,7 +69,7 @@
69 69 if (unref(copied)) createMessage.success('复制成功!');
70 70 else createMessage.error('复制失败!');
71 71 } catch (e) {
72   - console.log(e);
  72 + console.error(e);
73 73 }
74 74 };
75 75
... ...
... ... @@ -172,8 +172,7 @@ export const formSchema: FormSchema[] = [
172 172 const record = await findMessageConfig(params);
173 173 return record.filter((item) => item.status === 1);
174 174 } catch (error) {
175   - // eslint-disable-next-line no-console
176   - console.log(error);
  175 + console.error(error);
177 176 return [];
178 177 }
179 178 },
... ...
... ... @@ -41,7 +41,7 @@
41 41 checksumAlgorithm: value.checksumAlgorithm,
42 42 });
43 43 } catch (error) {
44   - console.log(error);
  44 + console.error(error);
45 45 if ((error as { status: number }).status) {
46 46 await deleteOtaPackage(id);
47 47 }
... ...
... ... @@ -6,7 +6,7 @@
6 6 border-color: #6b7280;
7 7 }
8 8
9   -.vue-flow__handle:hover{
  9 +.vue-flow__handle:hover {
10 10 background-color: #000;
11 11 }
12 12
... ... @@ -15,7 +15,7 @@
15 15 }
16 16
17 17 .vue-flow__handle-left {
18   - left: -12px;
  18 + left: -12px;
19 19 }
20 20
21 21 .vue-flow__background.vue-flow__container {
... ... @@ -32,7 +32,7 @@
32 32 stroke-width: 4;
33 33 stroke: gray;
34 34 transition: stroke-width 0.3s;
35   -}
  35 +}
36 36
37 37 .connection-focus {
38 38 stroke-width: 6;
... ... @@ -44,7 +44,7 @@
44 44 }
45 45
46 46 /* 连接线移入放大效果 */
47   -.vue-flow__edge:hover > .vue-flow__edge-path {
  47 +.vue-flow__edge:hover > .vue-flow__edge-path {
48 48 stroke-width: 6 !important;
49 49 }
50 50
... ... @@ -54,18 +54,19 @@
54 54 }
55 55
56 56 .vue-flow__edge:hover .vue-flow__edge-textbg {
57   - transform: scale(1.1)
  57 + transform: scale(1.1);
58 58 }
59 59
60   -.vue-flow__edge:hover .vue-flow__edge-text{
61   - transform: scale(1.1)
  60 +.vue-flow__edge:hover .vue-flow__edge-text {
  61 + transform: scale(1.1);
62 62 }
63 63
64 64 /* selection 选择框 */
65   -.vue-flow__nodesselection-rect, .vue-flow__selection{
  65 +.vue-flow__nodesselection-rect,
  66 +.vue-flow__selection {
66 67 border-width: 3px;
67 68 }
68 69
69   -.vue-flow__nodesselection-rect {
  70 +.vue-flow__nodesselection-rect {
70 71 pointer-events: none;
71 72 }
... ...
... ... @@ -236,7 +236,7 @@
236 236 // 设置输出值
237 237 outputEditor.value.set(result);
238 238 } catch (e) {
239   - console.log(e);
  239 + console.error(e);
240 240 const { createMessage } = useMessage();
241 241 createMessage.error(e.toString());
242 242 }
... ...
... ... @@ -85,9 +85,7 @@
85 85 /^[1](([3][0-9])|([4][0,1,4-9])|([5][0-3,5-9])|([6][2,5,6,7])|([7][0-8])|([8][0-9])|([9][0-3,5-9]))[0-9]{8}$/;
86 86 if (reg.test(formData.mobile)) {
87 87 const sendRes = await passwordResetCode(formData.mobile);
88   - console.log(sendRes);
89 88 if (sendRes === '') {
90   - console.log('发送成功了');
91 89 return true;
92 90 }
93 91 return false;
... ...
... ... @@ -99,6 +99,5 @@
99 99 async function handleRegister() {
100 100 const data = await validForm();
101 101 if (!data) return;
102   - console.log(data);
103 102 }
104 103 </script>
... ...
... ... @@ -27,7 +27,6 @@
27 27 onMounted(() => {
28 28 // 记录当前的UserId
29 29 userId.value = userStore.getUserInfo?.userId;
30   - console.log('Mounted', userStore.getUserInfo);
31 30 });
32 31
33 32 onBeforeUnmount(() => {
... ...
... ... @@ -69,8 +69,8 @@ export function useFormRules(formData?: Recordable) {
69 69 const mobileFormRule = unref(getMobileFormRule);
70 70
71 71 const mobileRule = {
72   - sms: smsFormRule,
73   - mobile: mobileFormRule,
  72 + code: smsFormRule,
  73 + phoneNumber: mobileFormRule,
74 74 };
75 75 switch (unref(currentState)) {
76 76 // register form rules
... ...
... ... @@ -14,7 +14,6 @@
14 14 Reflect.deleteProperty(params, 'path');
15 15
16 16 const _path = Array.isArray(path) ? path.join('/') : path;
17   - console.log(unref(currentRoute));
18 17 if (_redirect_type === 'name') {
19 18 replace({
20 19 name: _path,
... ...
... ... @@ -138,7 +138,6 @@
138 138 }
139 139 if (newFieldValue.codeTown == undefined) {
140 140 validateArray.push('prov');
141   - console.log(validateArray);
142 141 } else {
143 142 const findExistIndex1 = validateArray.findIndex((o) => o == 'prov');
144 143 if (findExistIndex1 !== -1) {
... ...
... ... @@ -5,7 +5,8 @@
5 5 <Tabs.TabPane
6 6 v-if="
7 7 isWhereAdmin == 'TENANT_ADMIN' ||
8   - (isWhereAdmin == 'SYS_ADMIN' || isWhereAdmin == 'CUSTOMER_USER')
  8 + isWhereAdmin == 'SYS_ADMIN' ||
  9 + isWhereAdmin == 'CUSTOMER_USER'
9 10 "
10 11 key="企业信息"
11 12 tab="企业信息"
... ...
... ... @@ -264,7 +264,6 @@
264 264 setProps({
265 265 loading: true,
266 266 });
267   - console.log(sendTime.value);
268 267 let startTime = null;
269 268 let endTime = null;
270 269 if (sendTime.value.length > 0) {
... ...
... ... @@ -49,6 +49,7 @@
49 49 font-size: 16px;
50 50 font-weight: 500;
51 51 }
  52 +
52 53 :deep(.vben-collapse-container__header) {
53 54 border-bottom: none;
54 55 }
... ...
... ... @@ -134,7 +134,7 @@
134 134 copyTransFun(data as any);
135 135 return data;
136 136 } catch (error) {
137   - console.log(error);
  137 + console.error(error);
138 138 return [];
139 139 }
140 140 },
... ...
... ... @@ -77,7 +77,7 @@
77 77 copyTransFun(data as any);
78 78 return data;
79 79 } catch (error) {
80   - console.log(error);
  80 + console.error(error);
81 81 return [];
82 82 }
83 83 },
... ...
... ... @@ -10,7 +10,8 @@
10 10
11 11 const alert = {
12 12 [PackagesCategoryEnum.MAP]: [
13   - '地图组件,需绑定两个数据源,且数据源为同一设备。第一数据源为经度,第二数据源为纬度,否则地图组件不能正常显示。',
  13 + '1、绑定数据源为结构体时,可以自行选择结构体里的属性作为经纬度',
  14 + '2、绑定数据源为非结构体时,第一数据源为经度,第二数据源为纬度,且数据源为同一设备,并同时上报。否则地图组件不能正常显示。',
14 15 ],
15 16 };
16 17
... ...
... ... @@ -34,14 +34,6 @@
34 34 const { unit, fontColor, showDeviceName } = item.componentInfo || {};
35 35 const { deviceName, deviceRename, attribute, attributeRename, deviceId, attributeName } =
36 36 item;
37   - // return {
38   - // unit: unit ?? persetUnit,
39   - // fontColor: fontColor ?? persetFontColor,
40   - // showDeviceName: showDeviceName ?? persetShowDeviceName,
41   - // attribute,
42   - // deviceName,
43   - // deviceRename,
44   - // };
45 37 return {
46 38 unit: unit ?? presetUnit,
47 39 fontColor: fontColor ?? presetFontColor,
... ... @@ -58,7 +50,6 @@
58 50 });
59 51
60 52 const getOffset = (length: number, index: number) => {
61   - // try {
62 53 const offsetList = ref<any>([]);
63 54 switch (length) {
64 55 case 1:
... ... @@ -147,7 +138,6 @@
147 138 break;
148 139 }
149 140 return unref(offsetList);
150   - // } catch {}
151 141 };
152 142
153 143 const series = ref(
... ... @@ -263,7 +253,6 @@
263 253 series.value.forEach((item) => {
264 254 if (item.id === deviceId && item.attribute === attribute && value) {
265 255 item.value = getNumberValue(value);
266   - // time.value = timespan;
267 256 }
268 257 });
269 258 });
... ...
... ... @@ -62,7 +62,6 @@
62 62 const options = (): EChartsOption => {
63 63 const { unit, fontColor, pointerColor, maxNumber, valueSize } = unref(getDesign);
64 64
65   - // getStageColor(gradientInfo);
66 65 return {
67 66 series: [
68 67 {
... ... @@ -103,14 +102,6 @@
103 102 color: pointerColor,
104 103 },
105 104 },
106   - // anchor: {
107   - // show: true,
108   - // showAbove: true,
109   - // size: 10,
110   - // itemStyle: {
111   - // borderWidth: 4,
112   - // },
113   - // },
114 105 title: {
115 106 show: false,
116 107 },
... ...
... ... @@ -3,7 +3,7 @@
3 3 import { BasicModal, useModalInner } from '/@/components/Modal';
4 4 import { formSchema, getHistorySearchParams, SchemaFiled } from './history.config';
5 5 import { HistoryModalOkEmitParams } from './type';
6   - import { ref } from 'vue';
  6 + import { ref, unref } from 'vue';
7 7 import { getAllDeviceByOrg } from '/@/api/dataBoard';
8 8 import { getDeviceHistoryInfo } from '/@/api/alarm/position';
9 9 import { DataSource } from '/@/views/visual/palette/types';
... ... @@ -19,58 +19,154 @@
19 19 ],
20 20 });
21 21
22   - const [registerModal, { closeModal }] = useModalInner(async (dataSource: DataSource[]) => {
23   - try {
24   - dataSource = cloneDeep(dataSource);
25   - if (dataSource.length < 2) return;
26   - dataSource = dataSource.splice(0, 2);
27   - const deviceRecord = dataSource?.at(0) || ({} as DataSource);
28   - if (!deviceRecord.organizationId) return;
29   - const deviceList = await getAllDeviceByOrg(
30   - deviceRecord.organizationId,
31   - deviceRecord.deviceProfileId
32   - );
33   - const options = deviceList
34   - .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
35   - .map((item) => ({ ...item, label: item.alias || item.name, value: item.tbDeviceId }));
36   -
37   - const attKey = dataSource.map((item) => ({
38   - ...item,
39   - label: item.attribute,
40   - value: item.attribute,
41   - }));
42   - updateSchema([
43   - {
44   - field: SchemaFiled.DEVICE_ID,
45   - componentProps: {
46   - options,
47   - },
  22 + const loading = ref(false);
  23 + const getDesign = ref();
  24 +
  25 + const getTwoMap = async (dataSource) => {
  26 + if (dataSource.length < 2) return;
  27 + dataSource = dataSource.splice(0, 2);
  28 + const deviceRecord = dataSource?.at(0) || ({} as DataSource);
  29 +
  30 + if (!deviceRecord.organizationId) return;
  31 + const deviceList = await getAllDeviceByOrg(
  32 + deviceRecord.organizationId,
  33 + deviceRecord.deviceProfileId
  34 + );
  35 + const options = deviceList
  36 + .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
  37 + .map((item) => ({ ...item, label: item.alias || item.name, value: item.tbDeviceId }));
  38 +
  39 + const attKey = dataSource.map((item) => ({
  40 + ...item,
  41 + label: item.attribute,
  42 + value: item.attribute,
  43 + }));
  44 +
  45 + updateSchemaMap(options, attKey, deviceRecord);
  46 + };
  47 +
  48 + const getOneMap = async (dataSource) => {
  49 + const deviceRecord = dataSource?.[0];
  50 + if (!deviceRecord.organizationId) return;
  51 + const deviceList = await getAllDeviceByOrg(
  52 + deviceRecord.organizationId,
  53 + deviceRecord.deviceProfileId
  54 + );
  55 + const options = deviceList
  56 + .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
  57 + .map((item) => ({ ...item, label: item.alias || item.name, value: item.tbDeviceId }));
  58 +
  59 + const attKey = dataSource?.map((item) => ({
  60 + ...item,
  61 + label: item.attributeName,
  62 + value: item.attribute,
  63 + }));
  64 + updateSchemaMap(options, attKey, deviceRecord);
  65 + };
  66 +
  67 + const updateSchemaMap = (options, attKey, deviceRecord) => {
  68 + updateSchema([
  69 + {
  70 + field: SchemaFiled.DEVICE_ID,
  71 + componentProps: {
  72 + options,
48 73 },
49   - {
50   - field: SchemaFiled.KEYS,
51   - component: 'Select',
52   - defaultValue: attKey.map((item) => item.value),
53   - componentProps: {
54   - options: attKey,
55   - mode: 'multiple',
56   - disabled: true,
57   - },
  74 + },
  75 + {
  76 + field: SchemaFiled.KEYS,
  77 + component: 'Select',
  78 + defaultValue: attKey.map((item) => item.value),
  79 + componentProps: {
  80 + options: attKey,
  81 + mode: 'multiple',
  82 + disabled: true,
58 83 },
59   - ]);
  84 + },
  85 + ]);
60 86
61   - setFieldsValue({
62   - [SchemaFiled.DEVICE_ID]: deviceRecord.deviceId,
63   - [SchemaFiled.KEYS]: attKey.map((item) => item.value),
64   - });
  87 + setFieldsValue({
  88 + [SchemaFiled.DEVICE_ID]: deviceRecord.deviceId,
  89 + [SchemaFiled.KEYS]: attKey.map((item) => item.value),
  90 + });
  91 + };
  92 +
  93 + const [registerModal, { closeModal }] = useModalInner(async (dataSource: DataSource[]) => {
  94 + try {
  95 + getDesign.value = dataSource;
  96 + dataSource = cloneDeep(dataSource);
  97 + //判断选择是不是结构体
  98 + if (dataSource.length == 1 && dataSource?.[0]?.lal) {
  99 + getOneMap(dataSource);
  100 + return;
  101 + }
  102 + getTwoMap(dataSource);
65 103 } catch (error) {
66 104 throw error;
67 105 }
68 106 });
69 107
  108 + const getMultipleDataSource = async (res) => {
  109 + let timespanList = Object.keys(res).reduce((prev, next) => {
  110 + const ts = res[next].map((item) => item.ts);
  111 + return [...prev, ...ts];
  112 + }, [] as number[]);
  113 + timespanList = [...new Set(timespanList)];
  114 +
  115 + const track: Record<'lng' | 'lat', number>[] = [];
  116 + const keys = Object.keys(res);
  117 +
  118 + for (const ts of timespanList) {
  119 + const list: { ts: number; value: number }[] = [];
  120 + for (const key of keys) {
  121 + const record = res[key].find((item) => ts === item.ts);
  122 + if (!validEffective(record?.value)) {
  123 + continue;
  124 + }
  125 + list.push(record as any);
  126 + }
  127 +
  128 + if (list.length === 2 && list.every(Boolean)) {
  129 + const lng = list.at(0)?.value;
  130 + const lat = list.at(1)?.value;
  131 + if (lng && lat) track.push({ lng, lat });
  132 + }
  133 + }
  134 + return track;
  135 + };
  136 +
  137 + // 单数据源选择结构体
  138 + const getSingleDataSource = async (res) => {
  139 + const [keys] = Object.keys(res);
  140 + const track: Record<'lng' | 'lat', number>[] = [];
  141 + const dataSource = res[keys]?.map((item) => {
  142 + return {
  143 + value: item.value ? JSON.parse(item.value) : {},
  144 + };
  145 + });
  146 + const list: { value: number }[] = [];
  147 + dataSource?.forEach((item) => {
  148 + if (
  149 + //判断结构体得值是不是数字
  150 + Object.values(item.value).every((item1) => {
  151 + return validEffective(item1 as any);
  152 + })
  153 + ) {
  154 + list.push(item.value);
  155 + }
  156 + });
  157 + const { latitude, longitude } = unref(getDesign)?.[0]; //获取选择的经纬度选项
  158 +
  159 + list.forEach((item) => {
  160 + const lng = item[longitude];
  161 + const lat = item[latitude];
  162 + if (lng && lat) track.push({ lng, lat });
  163 + });
  164 + return track;
  165 + };
  166 +
70 167 const validEffective = (value = '') => {
71 168 return !!(value && !isNaN(value as unknown as number));
72 169 };
73   - const loading = ref(false);
74 170 const handleOk = async () => {
75 171 try {
76 172 await validate();
... ... @@ -84,33 +180,9 @@
84 180 ...value,
85 181 [SchemaFiled.KEYS]: value[SchemaFiled.KEYS].join(','),
86 182 });
  183 + const ifSingle = unref(getDesign)?.length === 1 && unref(getDesign)?.[0]?.lal;
87 184
88   - let timespanList = Object.keys(res).reduce((prev, next) => {
89   - const ts = res[next].map((item) => item.ts);
90   - return [...prev, ...ts];
91   - }, [] as number[]);
92   - timespanList = [...new Set(timespanList)];
93   -
94   - const track: Record<'lng' | 'lat', number>[] = [];
95   - const keys = Object.keys(res);
96   -
97   - for (const ts of timespanList) {
98   - const list: { ts: number; value: number }[] = [];
99   - for (const key of keys) {
100   - const record = res[key].find((item) => ts === item.ts);
101   - if (!validEffective(record?.value)) {
102   - continue;
103   - }
104   - list.push(record as any);
105   - }
106   -
107   - if (list.length === 2 && list.every(Boolean)) {
108   - const lng = list.at(0)?.value;
109   - const lat = list.at(1)?.value;
110   - if (lng && lat) track.push({ lng, lat });
111   - }
112   - }
113   -
  185 + const track = ifSingle ? await getSingleDataSource(res) : await getMultipleDataSource(res);
114 186 emit('ok', { track, value } as HistoryModalOkEmitParams);
115 187 closeModal();
116 188 } catch (error) {
... ...
... ... @@ -24,6 +24,14 @@
24 24 return props.config.option.dataSource?.at(0)?.deviceId;
25 25 });
26 26
  27 + const getDesign = computed(() => {
  28 + const { option } = props.config;
  29 + const { dataSource } = option || {};
  30 + return {
  31 + dataSource: dataSource,
  32 + };
  33 + });
  34 +
27 35 /**
28 36 * @description 经度key
29 37 */
... ... @@ -42,7 +50,7 @@
42 50 return !!(value && !isNaN(value as unknown as number));
43 51 };
44 52
45   - const updateFn: MultipleDataFetchUpdateFn = (message, deviceId) => {
  53 + const getTwoMap = (message, deviceId) => {
46 54 if (unref(getDeviceId) !== deviceId) return;
47 55
48 56 const { data = {} } = message;
... ... @@ -51,7 +59,7 @@
51 59
52 60 const lngData = bindMessage[unref(getLngKey)] || [];
53 61 const [lngLatest] = lngData;
54   - const [, lng] = lngLatest;
  62 + const [, lng] = lngLatest || [];
55 63
56 64 const latData = bindMessage[unref(getLatKey)] || [];
57 65 const [latLatest] = latData;
... ... @@ -62,6 +70,34 @@
62 70 }
63 71 };
64 72
  73 + const getOneMap = (message, deviceId) => {
  74 + if (unref(getDeviceId) !== deviceId) return;
  75 +
  76 + const { longitude, latitude, attribute } = unref(getDesign)?.dataSource?.[0] || {};
  77 + const { data } = message || {};
  78 + const bindMessage = data[deviceId];
  79 + const [, values] = attribute && bindMessage?.[attribute][0];
  80 +
  81 + const mapValues = values ? JSON.parse(values) : {};
  82 + const lng = longitude && mapValues[longitude];
  83 + const lat = latitude && mapValues[latitude];
  84 +
  85 + if (validEffective(lng) && validEffective(lat)) {
  86 + drawLine({ lng: Number(lng), lat: Number(lat) });
  87 + }
  88 + };
  89 +
  90 + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId) => {
  91 + const { lal } = unref(getDesign)?.dataSource?.[0] || {};
  92 + // 属性选择结构体类型时
  93 + if (lal && unref(getDesign)?.dataSource?.length == 1) {
  94 + getOneMap(message, deviceId);
  95 + return;
  96 + }
  97 + // 选择两个属性时
  98 + getTwoMap(message, deviceId);
  99 + };
  100 +
65 101 useMultipleDataFetch(props, updateFn);
66 102
67 103 const { drawLine } = useMapTrackPlayBack(mapInstance);
... ... @@ -100,11 +136,3 @@
100 136 <div v-show="!loading" ref="wrapRef" :id="wrapId" class="w-full h-full no-drag"> </div>
101 137 </main>
102 138 </template>
103   -
104   -<style lang="less" scoped>
105   - // .map-spin-wrapper {
106   - // :deep(.ant-spin-container) {
107   - // @apply justify-center items-center p-2 w-full h-full;
108   - // }
109   - // }
110   -</style>
... ...
... ... @@ -247,7 +247,6 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
247 247 });
248 248 },
249 249 placeholder: '请选择设备',
250   - getPopupContainer: () => document.body,
251 250 ...createPickerSearch(),
252 251 };
253 252 },
... ... @@ -382,11 +381,18 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
382 381 return [];
383 382 },
384 383 placeholder: '请选择属性',
385   - getPopupContainer: () => document.body,
386 384 onChange(value: string, option: Record<'label' | 'value' | any, string>) {
  385 + const { detail }: any = option || {};
387 386 setFieldsValue({
388 387 [DataSourceField.ATTRIBUTE_NAME]: value ? option.label : null,
389 388 [DataSourceField.EXTENSION_DESC]: value ? JSON.stringify(option.extensionDesc) : '',
  389 + // 地图组件结构体
  390 + lal:
  391 + category === 'MAP' && value && detail?.dataType.type === 'STRUCT'
  392 + ? JSON.stringify(detail?.dataType.specs)
  393 + : null,
  394 + latitude: null,
  395 + longitude: null,
390 396 });
391 397 },
392 398 ...createPickerSearch(),
... ... @@ -394,6 +400,46 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
394 400 },
395 401 },
396 402 {
  403 + field: 'lal',
  404 + label: '经纬度存储的值',
  405 + component: 'Input',
  406 + show: false,
  407 + },
  408 + {
  409 + field: 'longitude',
  410 + label: '经度',
  411 + colProps: { span: 8 },
  412 + component: 'Select',
  413 + required: true,
  414 + ifShow: ({ model }) => category === 'MAP' && model.lal,
  415 + componentProps({ formModel }) {
  416 + const { lal } = formModel || {};
  417 + return {
  418 + placeholder: '请选择经度',
  419 + options: lal
  420 + ? JSON.parse(lal)?.map((item) => ({ label: item.functionName, value: item.identifier }))
  421 + : [],
  422 + };
  423 + },
  424 + },
  425 + {
  426 + field: 'latitude',
  427 + label: '纬度',
  428 + colProps: { span: 8 },
  429 + required: true,
  430 + component: 'Select',
  431 + ifShow: ({ model }) => category === 'MAP' && model.lal,
  432 + componentProps({ formModel }) {
  433 + const { lal } = formModel || {};
  434 + return {
  435 + placeholder: '请选择纬度',
  436 + options: lal
  437 + ? JSON.parse(lal)?.map((item) => ({ label: item.functionName, value: item.identifier }))
  438 + : [],
  439 + };
  440 + },
  441 + },
  442 + {
397 443 field: DataSourceField.EXTENSION_DESC,
398 444 component: 'Input',
399 445 show: false,
... ...
1   -import { ReceiveTsSubCmdsGroupMessageType } from '../index.type';
  1 +import { ReceiveTsSubCmdsGroupMessageType } from './socket/useSocket.type';
2 2
3 3 export const useReceiveMessage = () => {
4 4 const forEachGroupMessage = (
... ...
1   -import cloneDeep from 'lodash-es/cloneDeep';
2   -import { ComponentConfig } from '.';
3   -import {
4   - ConfigType,
5   - CreateComponentType,
6   - PublicComponentOptions,
7   - PublicPresetOptions,
8   -} from '/@/views/visual/packages/index.type';
9   -import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig';
10   -import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
11   -
12   -export const option: PublicPresetOptions = {
13   - [ComponentConfigFieldEnum.FONT_COLOR]: '#',
14   - [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
15   -};
16   -
17   -export default class Config extends PublicConfigClass implements CreateComponentType {
18   - public key: string = ComponentConfig.key;
19   -
20   - public attr = { ...componentInitConfig };
21   -
22   - public componentConfig: ConfigType = cloneDeep(ComponentConfig);
23   -
24   - public persetOption = cloneDeep(option);
25   -
26   - public option: PublicComponentOptions;
27   -
28   - constructor(option: PublicComponentOptions) {
29   - super();
30   - this.option = { ...option };
31   - }
32   -}
1   -<script lang="ts" setup>
2   - import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
3   - import { useForm, BasicForm } from '/@/components/Form';
4   - import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5   -
6   - const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
7   - schemas: [
8   - {
9   - field: ComponentConfigFieldEnum.FONT_COLOR,
10   - label: '数值字体颜色',
11   - component: 'ColorPicker',
12   - changeEvent: 'update:value',
13   - componentProps: {
14   - defaultValue: '#FD7347',
15   - },
16   - },
17   - {
18   - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
19   - label: '显示设备名称',
20   - component: 'Checkbox',
21   - },
22   - ],
23   - showActionButtonGroup: false,
24   - labelWidth: 120,
25   - baseColProps: {
26   - span: 12,
27   - },
28   - });
29   -
30   - const getFormValues = () => {
31   - return getFieldsValue();
32   - };
33   -
34   - const setFormValues = (data: Recordable) => {
35   - return setFieldsValue(data);
36   - };
37   -
38   - defineExpose({
39   - getFormValues,
40   - setFormValues,
41   - resetFormValues: resetFields,
42   - } as PublicFormInstaceType);
43   -</script>
44   -
45   -<template>
46   - <BasicForm @register="register" />
47   -</template>
1   -<script lang="ts" setup>
2   - import { CreateComponentType } from '/@/views/visual/packages/index.type';
3   - import { BasicForm, useForm } from '/@/components/Form';
4   - import {
5   - PublicComponentValueType,
6   - PublicFormInstaceType,
7   - } from '/@/views/visual/dataSourceBindPanel/index.type';
8   - import { commonDataSourceSchemas } from '../config/common.config';
9   -
10   - defineProps<{
11   - values: PublicComponentValueType;
12   - componentConfig: CreateComponentType;
13   - }>();
14   -
15   - const [register, { getFieldsValue, setFieldsValue, validate, resetFields }] = useForm({
16   - labelWidth: 0,
17   - showActionButtonGroup: false,
18   - layout: 'horizontal',
19   - labelCol: { span: 0 },
20   - schemas: commonDataSourceSchemas(),
21   - });
22   -
23   - const getFormValues = () => {
24   - return getFieldsValue();
25   - };
26   -
27   - const setFormValues = (record: Recordable) => {
28   - return setFieldsValue(record);
29   - };
30   -
31   - defineExpose({
32   - getFormValues,
33   - setFormValues,
34   - validate,
35   - resetFormValues: resetFields,
36   - } as PublicFormInstaceType);
37   -</script>
38   -
39   -<template>
40   - <BasicForm @register="register" />
41   -</template>
1   -import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
2   -import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
3   -
4   -const componentKeys = useComponentKeys('componentKeys');
5   -
6   -export const ComponentConfig: ConfigType = {
7   - ...componentKeys,
8   - title: '组件名',
9   - package: PackagesCategoryEnum.TEXT,
10   -};
1   -<script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3   - import { option } from './config';
4   - import { useDataFetch } from '../hook/socket/useSocket';
5   - import { DataFetchUpdateFn } from '../hook/socket/useSocket.type';
6   -
7   - const props = defineProps<{
8   - config: ComponentPropsConfigType<typeof option>;
9   - }>();
10   -
11   - const updateFn: DataFetchUpdateFn = (_message) => {};
12   -
13   - useDataFetch(props, updateFn);
14   -</script>
15   -
16   -<template>
17   - <main class="w-full h-full flex flex-col justify-center items-center"> </main>
18   -</template>
... ... @@ -150,7 +150,6 @@
150 150 getIsSharePage,
151 151 (value) => {
152 152 if (value) {
153   - console.log(unref(getDarkMode));
154 153 const root = document.querySelector('#app');
155 154 (root as HTMLDivElement).style.backgroundColor =
156 155 unref(getDarkMode) === ThemeEnum.LIGHT ? '#F5F5F5' : '#1b1b1b';
... ...
... ... @@ -29,6 +29,9 @@ export interface DataSource {
29 29 customCommand: CustomCommand;
30 30 videoConfig?: VideoConfigType;
31 31 [key: string]: any;
  32 + lal?: string;
  33 + latitude?: string | number;
  34 + longitude?: string | number;
32 35 }
33 36
34 37 export interface ExtraDataSource extends DataSource, PublicComponentOptions {
... ...
... ... @@ -32,7 +32,6 @@ app.ws.use(
32 32 });
33 33 ctx.websocket.send(data);
34 34 }
35   - console.log(message);
36 35 });
37 36 })
38 37 );
... ... @@ -59,5 +58,6 @@ app.use(router.allowedMethods());
59 58 app.use(koaStatic(path.join(__dirname)));
60 59
61 60 app.listen(PORT, () => {
  61 + // eslint-disable-next-line no-console
62 62 console.log(`Application started successfully: http://localhost:${PORT}`);
63 63 });
... ...