Commit 106ae3768443387c14b8ad6c38625c684347f852

Authored by loveumiko
2 parents 3df887c7 77fe68ab

Merge branch 'main_dev' of http://git.yunteng.com/yunteng/thingskit-front into fix/DEFECT-1362

... ... @@ -18,4 +18,5 @@ export interface Platform {
18 18 copyright: string;
19 19 presentedOurselves: string;
20 20 domain: string;
  21 + icon: string;
21 22 }
... ...
... ... @@ -49,13 +49,6 @@
49 49 return userStore.platInfo?.logo;
50 50 });
51 51 const getTitle = computed(() => {
52   - // 设置icon
53   - let link = (document.querySelector("link[rel*='icon']") ||
54   - document.createElement('link')) as HTMLLinkElement;
55   - link.type = 'image/x-icon';
56   - link.rel = 'shortcut icon';
57   - link.href = userStore.platInfo?.icon ?? '/favicon.ico';
58   - document.getElementsByTagName('head')[0].appendChild(link);
59 52 return userStore.platInfo?.name ?? title;
60 53 });
61 54 </script>
... ... @@ -67,6 +60,7 @@
67 60 padding-left: 7px;
68 61 cursor: pointer;
69 62 transition: all 0.2s ease;
  63 +
70 64 &.light {
71 65 border-bottom: 1px solid @border-color-base;
72 66 }
... ...
... ... @@ -113,7 +113,7 @@
113 113 componentProps: {
114 114 options: [
115 115 { label: `${boolClose}-0`, value: 0 },
116   - { label: `${boolOpen}-0`, value: 1 },
  116 + { label: `${boolOpen}-1`, value: 1 },
117 117 ],
118 118 onChange: (value: string) => {
119 119 syncValue(identifier, value);
... ...
... ... @@ -397,22 +397,6 @@
397 397 );
398 398 });
399 399 }
400   - /**
401   - * 只针对表格分页和组织列表分页的Tree(不是通过弹窗显示,默认是关闭的并且图标显示)
402   - * 如果是其他弹窗出来的Tree(会造成默认是关闭的并且图标显示),则在对应页面重写css样式即可
403   - * <style scoped lang="less">
404   - :deep(.vben-basic-tree) {
405   - width: 100% !important;
406   - }
407   - :deep(.is-unflod) {
408   - display: none !important;
409   - }
410   - :deep(.is-flod) {
411   - display: none !important;
412   - }
413   - </style>
414   - TODO下次优化通过传配置值来动态显示那些页面需要默认展开或收起
415   - */
416 400 return () => {
417 401 const { title, helpMessage, toolbar, search, checkable } = props;
418 402 const showTitle = title || toolbar || search || slots.headerTitle;
... ... @@ -454,51 +438,6 @@
454 438 });
455 439 </script>
456 440 <style lang="less">
457   - // 使用媒体查询兼容 1920 1280 1024 800
458   - @media screen and (max-width: 1980px) {
459   - .fold-left {
460   - z-index: 1;
461   - cursor: pointer;
462   - position: absolute;
463   - top: 0.85rem;
464   - left: 1.1vw;
465   - }
466   -
467   - .fold-right {
468   - z-index: 1;
469   - cursor: pointer;
470   - position: absolute;
471   - top: 0.85rem;
472   - left: 18.2vw;
473   - }
474   - }
475   - @media screen and (max-width: 1280px) {
476   - .fold-right {
477   - z-index: 1;
478   - cursor: pointer;
479   - position: absolute;
480   - top: 0.85rem;
481   - left: 17.5vw;
482   - }
483   - }
484   - @media screen and (max-width: 1024px) {
485   - .fold-right {
486   - z-index: 1;
487   - cursor: pointer;
488   - position: absolute;
489   - top: 0.85rem;
490   - left: 14.2vw;
491   - }
492   - }
493   - @media screen and (max-width: 800px) {
494   - .fold-right {
495   - z-index: 1;
496   - cursor: pointer;
497   - position: absolute;
498   - top: 0.85rem;
499   - left: 18vw;
500   - }
501   - }
502 441 @prefix-cls: ~'@{namespace}-basic-tree';
503 442
504 443 .@{prefix-cls} {
... ...
... ... @@ -35,6 +35,8 @@ export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';
35 35
36 36 export const PLATFORM = 'PLATFORM';
37 37
  38 +export const PLATFORM_INFO_CACHE_KEY = 'PLATFORM_INFO';
  39 +
38 40 export const MENU_LIST = 'MENU_LIST';
39 41 export enum CacheTypeEnum {
40 42 SESSION,
... ...
... ... @@ -71,7 +71,6 @@
71 71
72 72 import { useUserStore } from '/@/store/modules/user';
73 73 import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin';
74   - import { useDesign } from '/@/hooks/web/useDesign';
75 74 import { getPlatForm } from '/@/api/oem';
76 75 import { createLocalStorage } from '/@/utils/cache';
77 76
... ... @@ -80,8 +79,7 @@
80 79 const FormItem = Form.Item;
81 80 const InputPassword = Input.Password;
82 81 const { t } = useI18n();
83   - const { notification, createMessage } = useMessage();
84   - const { prefixCls } = useDesign('login');
  82 + const { notification } = useMessage();
85 83 const userStore = useUserStore();
86 84
87 85 const { setLoginState, getLoginState } = useLoginState();
... ... @@ -115,7 +113,7 @@
115 113 username: data.account,
116 114 mode: 'modal', //不要默认的错误提示
117 115 })
118   - .catch((data) => {
  116 + .catch(() => {
119 117 //登录失败返回的html,所以提示框什么都没有
120 118 //去掉提示框
121 119 // createMessage.error(data.message);
... ... @@ -129,13 +127,6 @@
129 127 const res = await getPlatForm();
130 128 storage.set('platformInfo', res);
131 129 userStore.setPlatInfo(res);
132   - // 设置icon
133   - let link = (document.querySelector("link[rel*='icon']") ||
134   - document.createElement('link')) as HTMLLinkElement;
135   - link.type = 'image/x-icon';
136   - link.rel = 'shortcut icon';
137   - link.href = res.icon ?? '/favicon.ico';
138   - document.getElementsByTagName('head')[0].appendChild(link);
139 130
140 131 var _hmt = _hmt || [];
141 132 (function () {
... ...
1 1 <template>
2   - <div
3   - style="background-size: 100% 100%"
4   - :style="{ backgroundImage: 'url(' + (logoUrl !== undefined ? logoUrl : '') + ')' }"
5   - :class="prefixCls"
6   - class="relative w-full h-full px-4"
7   - >
  2 + <div :class="prefixCls" class="relative w-full h-full px-4">
8 3 <AppLocalePicker
9 4 class="absolute text-white top-4 right-4 enter-x xl:text-gray-600"
10 5 :showText="false"
... ... @@ -36,12 +31,8 @@
36 31 {{ defaultTitle }}
37 32 </div>
38 33 </div>
39   - <div v-if="ifCustom" class="my-auto">
40   - <img
41   - :alt="title"
42   - src="../../../assets/svg/thingskit-login-background.svg"
43   - class="w-1/2 -mt-16 -enter-x"
44   - />
  34 + <div v-if="show" class="my-auto">
  35 + <img :alt="title" :src="defaultBackgroundImage" class="w-1/2 -mt-16 -enter-x" />
45 36 <div class="mt-10 font-medium text-white -enter-x">
46 37 <span class="inline-block mt-4 text-3xl"> {{ t('sys.login.signInTitle') }}</span>
47 38 </div>
... ... @@ -67,7 +58,6 @@
67 58 </template>
68 59 <script lang="ts" setup>
69 60 import { ref, onMounted } from 'vue';
70   - // import { AppLogo } from '/@/components/Application';
71 61 import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
72 62 import LoginForm from './LoginForm.vue';
73 63 import ForgetPasswordForm from './ForgetPasswordForm.vue';
... ... @@ -77,13 +67,9 @@
77 67 import { useI18n } from '/@/hooks/web/useI18n';
78 68 import { useDesign } from '/@/hooks/web/useDesign';
79 69 import { useLocaleStore } from '/@/store/modules/locale';
80   - // import { useUserStore } from '/@/store/modules/user';
81   - import { getPlatForm } from '/@/api/oem/index';
82   - import defaultShowLogoImg from '/@/assets/svg/login-bg.svg';
83   - import defaultShowLogoDarkImg from '/@/assets/svg/login-bg-dark.svg';
84 70 import { useTitle } from '@vueuse/core';
85   - import { createLocalStorage } from '/@/utils/cache';
86   - // import {usePlatform} from '/@/views/system/customize/hook/usePlatformInfo'
  71 + import defaultBackgroundImage from '/@/assets/svg/thingskit-login-background.svg';
  72 + import { getPlatFormInfo } from '../../system/customize/hook/usePlatformInfo';
87 73
88 74 defineProps({
89 75 sessionTimeout: {
... ... @@ -93,88 +79,22 @@
93 79 const { title } = useGlobSetting();
94 80 const defaultTitle = ref('');
95 81 const defaultLogo = ref('');
96   - const logoUrl = ref('');
97   - // const userStore = useUserStore();
98   - const storage = createLocalStorage();
99   -
100   - onMounted(async () => {
101   - let res = storage.get('platformInfo');
102   - if (res === '' || res === null) {
103   - res = await getPlatForm();
104   - }
105   - logoUrl.value = res?.background;
106   - defaultTitle.value = res?.name || title;
107   - defaultLogo.value = res?.logo;
108   - let link = (document.querySelector("link[rel*='icon']") ||
109   - document.createElement('link')) as HTMLLinkElement;
110   - link.type = 'image/x-icon';
111   - link.rel = 'shortcut icon';
112   - link.href = res?.icon ?? '/favicon.ico';
113   - document.getElementsByTagName('head')[0].appendChild(link);
114   - let defaultLogoBg = document.createElement('style');
115   - let defaultLogoDarkBg = document.createElement('style');
116   - if (logoUrl.value !== undefined) {
117   - //企业自定义
118   - ifCustom.value = false;
119   - //默认图片
120   - defaultLogoBg.innerHTML = `.vben-login::before{
121   - background-image:url("");
122   - position:absolute;
123   - }`;
124   - //切换黑暗模式图片
125   - defaultLogoDarkBg.innerHTML = `html[data-theme='dark'] .vben-login::before{
126   - background-image:url("");
127   - position:absolute;
128   - }`;
129   - } else {
130   - logoUrl.value = 'url(' + defaultShowLogoImg + ')';
131   - //默认图片
132   - defaultLogoBg.innerHTML = `.vben-login::before{
133   - background-image:url(${defaultShowLogoImg});
134   - position:absolute;
135   - }`;
136   - //切换黑暗模式图片
137   - defaultLogoDarkBg.innerHTML = `html[data-theme='dark'] .vben-login::before{
138   - background-image:url(${defaultShowLogoDarkImg});
139   - position:absolute;
140   - }`;
141   - }
142   - document.head.appendChild(defaultLogoBg);
143   - document.head.appendChild(defaultLogoDarkBg);
144   - });
145 82
146   - // const userStore = useUserStore();
147   -
148   - const ifCustom = ref(true);
149   - // const getLogo = computed(() => {
150   - // return userStore.platInfo?.logo;
151   - // });
152   - // const getTitle = computed(() => {
153   - // // 设置icon
154   - // let link = (document.querySelector("link[rel*='icon']") ||
155   - // document.createElement('link')) as HTMLLinkElement;
156   - // link.type = 'image/x-icon';
157   - // link.rel = 'shortcut icon';
158   - // link.href = userStore.platInfo?.icon ?? '/favicon.ico';
159   - // document.getElementsByTagName('head')[0].appendChild(link);
160   - // // logoUrl.value = userStore.platInfo?.background;
161   - // // if (logoUrl.value !== undefined) {
162   - // // ifCustom.value = false;
163   - // // } else {
164   - // // logoUrl.value = 'url(' + defaultShowLogoImg + ')';
165   - // // }
166   - // return userStore.platInfo?.name ?? title;
167   - // });
168   - // const globSetting = useGlobSetting();
169 83 const { prefixCls } = useDesign('login');
170 84 const { t } = useI18n();
171 85 const localeStore = useLocaleStore();
172 86 const showLocale = localeStore.getShowPicker;
173   - // const title = computed(() => globSetting?.title ?? '');
174 87
175 88 onMounted(() => {
176 89 useTitle('ThingsKit 物联网平台');
177 90 });
  91 +
  92 + const show = ref(false);
  93 +
  94 + onMounted(() => {
  95 + const platform = getPlatFormInfo();
  96 + show.value = !platform.background;
  97 + });
178 98 </script>
179 99 <style lang="less">
180 100 @prefix-cls: ~'@{namespace}-login';
... ...
... ... @@ -71,7 +71,6 @@
71 71
72 72 import { useUserStore } from '/@/store/modules/user';
73 73 import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin';
74   - import { useDesign } from '/@/hooks/web/useDesign';
75 74 import { getPlatForm } from '/@/api/oem';
76 75 import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
77 76
... ... @@ -81,7 +80,6 @@
81 80 const InputPassword = Input.Password;
82 81 const { t } = useI18n();
83 82 const { notification, createMessage } = useMessage();
84   - const { prefixCls } = useDesign('login');
85 83 const userStore = useUserStore();
86 84
87 85 const { setLoginState, getLoginState } = useLoginState();
... ... @@ -137,13 +135,6 @@
137 135 const res = await getPlatForm();
138 136 storage.set('platformInfo', res);
139 137 userStore.setPlatInfo(res);
140   - // 设置icon
141   - let link = (document.querySelector("link[rel*='icon']") ||
142   - document.createElement('link')) as HTMLLinkElement;
143   - link.type = 'image/x-icon';
144   - link.rel = 'shortcut icon';
145   - link.href = res.icon ?? '/favicon.ico';
146   - document.getElementsByTagName('head')[0].appendChild(link);
147 138
148 139 var _hmt = _hmt || [];
149 140 (function () {
... ...
... ... @@ -38,7 +38,7 @@
38 38 >
39 39 </template>
40 40 <template #uploadText>
41   - <div class="box-outline"> 支持.ICON格式,建议尺寸为16*16px </div>
  41 + <div class="box-outline"> 支持.ICO格式,建议尺寸为16*16px </div>
42 42 </template>
43 43 </ContentUploadText>
44 44 </template>
... ... @@ -149,9 +149,10 @@
149 149 }
150 150 }
151 151 const beforeUploadIconPic = (file: FileItem) => {
152   - const isJpgOrPng = file.type === 'image/x-icon';
  152 + console.log(file);
  153 + const isJpgOrPng = file.type === 'image/vnd.microsoft.icon';
153 154 if (!isJpgOrPng) {
154   - createMessage.error('只能上传.icon图片文件!');
  155 + createMessage.error('只能上传.ico图片文件!');
155 156 }
156 157 const isLt2M = (file.size as number) / 1024 < 500;
157 158 if (!isLt2M) {
... ...
1 1 import { getPlatForm } from '/@/api/oem';
2   -import { createLocalStorage } from '/@/utils/cache';
  2 +import lightThemeBgImage from '/@/assets/svg/login-bg.svg';
  3 +import darkThemeBgImage from '/@/assets/svg/login-bg-dark.svg';
  4 +import { createStorage } from '/@/utils/cache';
  5 +import { Platform } from '/@/api/oem/model';
  6 +import { PLATFORM_INFO_CACHE_KEY } from '/@/enums/cacheEnum';
3 7
4 8 enum DefaultPlatform {
5 9 LOGO = '/resource/img/logo.png',
... ... @@ -7,19 +11,15 @@ enum DefaultPlatform {
7 11 ICO = '/favicon.ico',
8 12 }
9 13
  14 +const storage = createStorage();
  15 +
  16 +export const getPlatFormInfo = () => storage.get(PLATFORM_INFO_CACHE_KEY) as Platform;
  17 +export const setPlatFormInfo = (info: Recordable) => storage.set(PLATFORM_INFO_CACHE_KEY, info);
  18 +
10 19 export const usePlatform = async () => {
11   - const platformInfo = await getPlatForm();
12   - const storage = createLocalStorage();
13   - if (
14   - platformInfo === '' ||
15   - platformInfo === null ||
16   - Object.getOwnPropertyNames(platformInfo).length === 0
17   - ) {
18   - storage.set('platformInfo', 1234);
19   - } else {
20   - storage.set('platformInfo', platformInfo);
21   - }
  20 + const platformInfo = (await getPlatForm()) || {};
22 21
  22 + setPlatFormInfo(platformInfo);
23 23 const createLoadingEffect = () => {
24 24 const wrap = document.createElement('div');
25 25 wrap.setAttribute('class', 'app-loading-wrap');
... ... @@ -50,7 +50,7 @@ export const usePlatform = async () => {
50 50 const replaceSiteIco = () => {
51 51 const linkEl = document.querySelectorAll('link[rel*="icon"]');
52 52 linkEl.forEach((item) => {
53   - item.setAttribute('href', platformInfo.logo || DefaultPlatform.ICO);
  53 + item.setAttribute('href', platformInfo.icon || DefaultPlatform.ICO);
54 54 });
55 55 };
56 56
... ... @@ -60,9 +60,31 @@ export const usePlatform = async () => {
60 60 loadingWrapper?.appendChild(createLoadingEffect());
61 61 };
62 62
  63 + const setBackgroundImage = () => {
  64 + const { background } = platformInfo;
  65 + const styleEl = document.createElement('style');
  66 +
  67 + styleEl.innerHTML = `
  68 + .vben-login::before{
  69 + background-image:url("${background || lightThemeBgImage}");
  70 + position:absolute;
  71 + margin-left: ${background ? 0 : '-48%'} !important;
  72 + }
  73 + html[data-theme='dark'] .vben-login::before{
  74 + background-image:url("${background || darkThemeBgImage}");
  75 + position:absolute;
  76 + margin-left: ${background ? 0 : '-48%'} !important;
  77 + }
  78 + `;
  79 +
  80 + console.log(styleEl);
  81 + document.head.appendChild(styleEl);
  82 + };
  83 +
63 84 const bootstrap = () => {
64 85 replaceSiteIco();
65 86 replaceLoadingEffect();
  87 + setBackgroundImage();
66 88 };
67 89
68 90 bootstrap();
... ...
... ... @@ -16,6 +16,7 @@
16 16 toolbar
17 17 ref="treeRef"
18 18 :treeData="treeData"
  19 + @check="handleCheckClick"
19 20 :replace-fields="{ title: 'name', key: 'id' }"
20 21 :checkedKeys="roleMenus"
21 22 title="菜单分配"
... ... @@ -58,6 +59,7 @@
58 59 const treeRef = ref<Nullable<TreeActionType>>();
59 60 const checked = ref<string[]>([]); //需要选中的节点
60 61 const spinning = ref(false);
  62 + const checkedKeysWithHalfChecked = ref<string[]>([]);
61 63
62 64 const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
63 65 labelWidth: 100,
... ... @@ -126,7 +128,9 @@
126 128 roleId.value = data.record.id;
127 129
128 130 //通过角色id去获取角色对应的菜单的ids
129   - roleMenus.value = await getMenusIdsByRoleId(data.record.id);
  131 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  132 + data.record.id
  133 + );
130 134 excludeHalfCheckedKeys(unref(treeData));
131 135 await nextTick();
132 136 unref(treeRef)?.setCheckedKeys(roleMenus.value);
... ... @@ -147,14 +151,13 @@
147 151 setDrawerProps({ confirmLoading: true });
148 152 const { createMessage } = useMessage();
149 153 try {
150   - const menu = (unref(treeRef)?.getCheckedKeys() as string[]) || [];
151 154 const values = await validate();
152 155 const req = {
153 156 id: roleId.value,
154 157 name: values.name,
155 158 remark: values.remark,
156 159 status: values.status,
157   - menu,
  160 + menu: unref(checkedKeysWithHalfChecked),
158 161 };
159 162 if (req.menu == undefined) return createMessage.error('请勾选权限菜单');
160 163 saveOrUpdateRoleInfoWithMenu(req).then(() => {
... ... @@ -239,6 +242,13 @@
239 242 return needExcludeKeys;
240 243 };
241 244
  245 + const handleCheckClick = (selectedKeys: string[], event: CheckEvent) => {
  246 + checkedKeysWithHalfChecked.value = [
  247 + ...selectedKeys,
  248 + ...(event.halfCheckedKeys as string[]),
  249 + ];
  250 + };
  251 +
242 252 return {
243 253 spinning,
244 254 registerDrawer,
... ... @@ -248,6 +258,7 @@
248 258 treeData,
249 259 roleMenus,
250 260 treeRef,
  261 + handleCheckClick,
251 262 };
252 263 },
253 264 });
... ...
... ... @@ -15,6 +15,7 @@
15 15 :treeData="treeData"
16 16 :replaceFields="{ title: 'name', key: 'id' }"
17 17 :checkedKeys="roleMenus"
  18 + @check="handleCheckClick"
18 19 checkable
19 20 toolbar
20 21 ref="treeRef"
... ... @@ -30,7 +31,7 @@
30 31 import { BasicForm, useForm } from '/@/components/Form/index';
31 32 import { formSchema } from './role.data';
32 33 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
33   - import { BasicTree, TreeActionType, TreeItem } from '/@/components/Tree';
  34 + import { BasicTree, CheckEvent, TreeActionType, TreeItem } from '/@/components/Tree';
34 35 const { t } = useI18n(); //加载国际化
35 36 // 加载菜单数据
36 37 import { getAdminMenuList, getMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu';
... ... @@ -58,6 +59,7 @@
58 59 const treeRef = ref<Nullable<TreeActionType>>(null);
59 60 const checked = ref<string[]>([]); //需要选中的节点
60 61 const spinning = ref(false);
  62 + const checkedKeysWithHalfChecked = ref<string[]>([]);
61 63
62 64 const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
63 65 labelWidth: 90,
... ... @@ -85,7 +87,6 @@
85 87 const roleType = RoleEnum.TENANT_ADMIN;
86 88 try {
87 89 spinning.value = true;
88   - // 需要在setFieldsValue之前先填充treeData,否则Tree组件可能会报key not exist警告
89 90
90 91 if (!unref(treeData).length) {
91 92 // 获取全部的菜单
... ... @@ -108,7 +109,10 @@
108 109 roleId.value = data.record.id;
109 110
110 111 //通过角色id去获取角色对应的菜单的ids
111   - roleMenus.value = await getMenusIdsByRoleId(data.record.id);
  112 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  113 + data.record.id
  114 + );
  115 +
112 116 excludeHalfCheckedKeys(unref(treeData));
113 117
114 118 await nextTick();
... ... @@ -125,10 +129,8 @@
125 129 const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色'));
126 130
127 131 async function handleSubmit() {
128   - setDrawerProps({ loading: true });
129   - setDrawerProps({ confirmLoading: true });
  132 + setDrawerProps({ loading: true, confirmLoading: true });
130 133 const { createMessage } = useMessage();
131   - const menu = (unref(treeRef)?.getCheckedKeys() as string[]) || [];
132 134 try {
133 135 const values = await validate();
134 136 const req = {
... ... @@ -137,7 +139,7 @@
137 139 remark: values.remark,
138 140 status: values.status,
139 141 roleType: RoleEnum.TENANT_ADMIN,
140   - menu,
  142 + menu: unref(checkedKeysWithHalfChecked),
141 143 };
142 144 if (req.menu == undefined) return createMessage.error('请勾选权限菜单');
143 145 const res = await saveOrUpdateRoleInfoWithMenu(req);
... ... @@ -226,6 +228,13 @@
226 228 return needExcludeKeys;
227 229 };
228 230
  231 + const handleCheckClick = (selectedKeys: string[], event: CheckEvent) => {
  232 + checkedKeysWithHalfChecked.value = [
  233 + ...selectedKeys,
  234 + ...(event.halfCheckedKeys as string[]),
  235 + ];
  236 + };
  237 +
229 238 return {
230 239 spinning,
231 240 registerDrawer,
... ... @@ -235,6 +244,7 @@
235 244 treeData,
236 245 roleMenus,
237 246 treeRef,
  247 + handleCheckClick,
238 248 };
239 249 },
240 250 });
... ...
... ... @@ -75,7 +75,7 @@
75 75
76 76 createSelectWidgetModeContext(currentMode);
77 77
78   - const currentRecord = ref<WidgetDataType>();
  78 + const currentRecord = ref<Nullable<Recordable>>({});
79 79
80 80 const [registerModal, { closeModal }] = useModalInner(
81 81 (params: ModalParamsType<WidgetDataType>) => {
... ... @@ -105,6 +105,7 @@
105 105 if (activeKey === TabKeyEnum.VISUAL) {
106 106 dataSource.value = (dataSourceFormEl.value?.getFormValues() as DataSourceType[]) || [];
107 107 }
  108 + currentRecord.value = basicInfoFromEl.value?.getFormValues() || {};
108 109 };
109 110
110 111 const handleNewRecord = () => {
... ...
... ... @@ -22,6 +22,10 @@
22 22
23 23 const wrapId = `bai-map-${buildUUID()}`;
24 24
  25 + const getDeviceId = computed(() => {
  26 + return props.config.option.dataSource?.at(0)?.deviceId;
  27 + });
  28 +
25 29 /**
26 30 * @description 经度key
27 31 */
... ... @@ -40,14 +44,18 @@
40 44 return !!(value && !isNaN(value as unknown as number));
41 45 };
42 46
43   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
  47 + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId) => {
  48 + if (unref(getDeviceId) !== deviceId) return;
  49 +
44 50 const { data = {} } = message;
45 51
46   - const lngData = data[unref(getLngKey)] || [];
  52 + const bindMessage = data[deviceId];
  53 +
  54 + const lngData = bindMessage[unref(getLngKey)] || [];
47 55 const [lngLatest] = lngData;
48 56 const [, lng] = lngLatest;
49 57
50   - const latData = data[unref(getLatKey)] || [];
  58 + const latData = bindMessage[unref(getLatKey)] || [];
51 59 const [latLatest] = latData;
52 60 const [, lat] = latLatest;
53 61
... ...
... ... @@ -9,40 +9,48 @@
9 9 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10 10 import { unref } from 'vue';
11 11 import { onMounted } from 'vue';
  12 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  13 + import { buildUUID } from '/@/utils/uuid';
  14 + import { useReceiveValue } from '../../../hook/useReceiveValue';
12 15
13 16 const props = defineProps<{
14 17 config: ComponentPropsConfigType<typeof option>;
15 18 }>();
16 19
17   - const percentList = ref<any[]>([
  20 + interface PercentType {
  21 + value: number;
  22 + fontColor: string;
  23 + backgroundColor: string;
  24 + unit: string;
  25 + id: string;
  26 + attribute?: string;
  27 + attributeRename?: string;
  28 + deviceId?: string;
  29 + }
  30 +
  31 + const defaultValue: PercentType[] = [
18 32 {
19 33 value: 20,
20 34 fontColor: '#19eff',
21 35 backgroundColor: '#19eff',
22   - color: '#000',
23   - fontSize: '16px',
24 36 unit: '℃',
25   - id: 1,
  37 + id: buildUUID(),
26 38 },
27 39 {
28 40 value: 66,
29 41 fontColor: '#1E8667',
30 42 backgroundColor: '#1E8667',
31   - color: '#000',
32   - fontSize: '16px',
33 43 unit: '℃',
34   - id: 2,
  44 + id: buildUUID(),
35 45 },
36 46 {
37 47 value: 49,
38 48 fontColor: '#2196F3',
39 49 backgroundColor: '#2196F3',
40   - color: '#000',
41   - fontSize: '16px',
42 50 unit: '℃',
43   - id: 3,
  51 + id: buildUUID(),
44 52 },
45   - ]);
  53 + ];
46 54
47 55 const getDesign = computed(() => {
48 56 const { persetOption = {}, option } = props.config;
... ... @@ -52,65 +60,52 @@
52 60 fontColor: presetFontColor,
53 61 backgroundColor: persetBackgroundColor,
54 62 } = persetOption || {};
  63 +
55 64 return {
56   - dataSource: dataSource?.map((item) => {
57   - const { unit, fontColor, fontSize, backgroundColor } = item.componentInfo || {};
58   - const { attribute, attributeRename } = item;
  65 + dataSource: dataSource.map((item) => {
  66 + const { unit, fontColor, backgroundColor } = item.componentInfo || {};
  67 + const { attribute, attributeRename, deviceId } = item;
59 68 return {
60 69 unit: unit ?? presetUnit,
61 70 fontColor: fontColor ?? presetFontColor,
62   - fontSize,
63 71 backgroundColor: backgroundColor ?? persetBackgroundColor,
64 72 attribute,
65 73 attributeRename,
66   - };
  74 + id: deviceId,
  75 + } as PercentType;
67 76 }),
68 77 };
69 78 });
  79 + const percentList = ref(
  80 + props.config.option.dataSource ? unref(getDesign).dataSource : defaultValue
  81 + );
  82 + const { forEachGroupMessage } = useReceiveMessage();
70 83
71   - const newPercentList = ref<any[]>([]);
72   -
73   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
74   - const { data = {} } = message;
75   - const { dataSource } = unref(getDesign);
76   - newPercentList.value = dataSource.map((item) => {
77   - const { attribute, fontSize, attributeRename, fontColor, unit, backgroundColor } = item;
78   -
79   - const [latest] = data[attribute] || [];
80   - const [_timespan, value] = latest || [];
81   - return {
82   - value: Number(value),
83   - name: attributeRename ?? attribute,
84   - fontSize,
85   - unit,
86   - backgroundColor: backgroundColor,
87   - fontColor: fontColor,
88   - };
  84 + const { getNumberValue } = useReceiveValue();
  85 + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => {
  86 + forEachGroupMessage(message, deviceId, attribute, (attribute, value) => {
  87 + percentList.value.forEach((item) => {
  88 + if (item.id === deviceId && item.attribute === attribute) {
  89 + item.value = getNumberValue(value);
  90 + }
  91 + });
89 92 });
90   - // console.log(numberList, 'numberList');
91 93 };
92 94
93   - onMounted(() => {
94   - newPercentList.value = percentList.value;
95   - !props.config.option.uuid;
96   - });
  95 + onMounted(() => {});
97 96
98 97 useMultipleDataFetch(props, updateFn);
99   -
100   - // const { getScale } = useComponentScale(props);
101 98 </script>
102 99
103 100 <template>
104 101 <main class="w-full h-full flex flex-col justify-center">
105 102 <DeviceName :config="config" />
106 103
107   - <div
108   - v-for="item in newPercentList"
109   - :key="item.id"
110   - class="flex flex-col ml-3 mr-3 items-stretch"
111   - >
  104 + <div v-for="item in percentList" :key="item.id" class="flex flex-col ml-3 mr-3 items-stretch">
112 105 <div class="flex justify-between">
113   - <div class="text-gray-500 text-lg truncate">{{ item.name || '温度' }}</div>
  106 + <div class="text-gray-500 text-lg truncate">
  107 + {{ item.attributeRename || item.attribute || '温度' }}
  108 + </div>
114 109 <span class="text-lg" :style="{ color: item.fontColor }"
115 110 >{{ item.value }} {{ item.unit }}</span
116 111 >
... ...
... ... @@ -302,7 +302,9 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
302 302 placeholder: '请选择服务',
303 303 getPopupContainer: () => document.body,
304 304 onChange(value: string, options: ModelOfMatterParams) {
305   - const command = value ? (options.functionJson.inputData || [])[0].serviceCommand : null;
  305 + const command = value
  306 + ? (options.functionJson.inputData || [])[0]?.serviceCommand
  307 + : null;
306 308 setFieldsValue({ [DataSourceField.COMMAND]: command });
307 309 },
308 310 };
... ...
  1 +import { ReceiveGroupMessageType } from '../index.type';
  2 +
  3 +export const useReceiveMessage = () => {
  4 + const forEachGroupMessage = (
  5 + message: ReceiveGroupMessageType,
  6 + deviceId: string,
  7 + attributes: string[],
  8 + Fn: (attribute: string, value: any, timespan: number) => void
  9 + ) => {
  10 + const { data = {} } = message;
  11 + const bindMessage = data[deviceId];
  12 +
  13 + for (const attribute of attributes) {
  14 + const [latest] = bindMessage[attribute] || [];
  15 + const [timespan, value] = latest || [];
  16 + Fn?.(attribute, value, timespan);
  17 + }
  18 + };
  19 +
  20 + return { forEachGroupMessage };
  21 +};
... ...
  1 +export const useReceiveValue = () => {
  2 + const getNumberValue = (value: any, defaultValue = 0) => {
  3 + const newValue = isNaN(value) ? defaultValue : Number(value);
  4 + return newValue;
  5 + };
  6 +
  7 + return { getNumberValue };
  8 +};
... ...
... ... @@ -31,11 +31,13 @@ export function useSendCommand() {
31 31 params = customCommand.command!;
32 32 }
33 33
  34 + console.log(params);
  35 +
34 36 // 控制按钮下发命令为0 或 1
35 37 await sendCommandOneway({
36 38 deviceId,
37 39 value: {
38   - params: params,
  40 + params: params || null,
39 41 persistent: true,
40 42 additionalInfo: {
41 43 cmdType: 'API',
... ...
... ... @@ -10,6 +10,7 @@ import {
10 10 DataFetchUpdateFn,
11 11 EntityTypeEnum,
12 12 MultipleDataFetchUpdateFn,
  13 + ReceiveGroupMessageType,
13 14 ReceiveMessageType,
14 15 ScopeTypeEnum,
15 16 SubscribeMessageItemType,
... ... @@ -115,6 +116,20 @@ class Subscriber {
115 116 } as ReceiveMessageType;
116 117 }
117 118
  119 + getGroupScopeMessage(message: ReceiveMessageType, attribute: string[], deviceId: string) {
  120 + const result = this.getScopeMessage(message, attribute);
  121 +
  122 + return {
  123 + ...result,
  124 + data: {
  125 + [deviceId]: result.data,
  126 + },
  127 + latestValues: {
  128 + [deviceId]: result.latestValues,
  129 + },
  130 + } as ReceiveGroupMessageType;
  131 + }
  132 +
118 133 trackUpdate(uuid: string, fn: Fn) {
119 134 if (!uuid || !fn) return;
120 135 this.componentUpdateFnMap.set(uuid, fn);
... ... @@ -144,7 +159,7 @@ class Subscriber {
144 159 const { attributes, fn } = item;
145 160 try {
146 161 if (!fn) return;
147   - fn?.(this.getScopeMessage(message, attributes), attributes);
  162 + fn?.(this.getGroupScopeMessage(message, attributes, deviceId), deviceId, attributes);
148 163 } catch (error) {
149 164 console.error(`deviceId: ${deviceId}`);
150 165 throw error;
... ... @@ -253,23 +268,47 @@ export const useMultipleDataFetch = (
253 268 props: { config: ComponentPropsConfigType },
254 269 updateFn: MultipleDataFetchUpdateFn
255 270 ) => {
256   - const getBindAttributes = computed(() => {
257   - const attributes = props.config.option.dataSource?.map((item) => item.attribute);
258   - return [...new Set(attributes)];
259   - });
  271 + const getDataSourceGroup = computed(() => {
  272 + const { config } = props;
  273 + const { option } = config;
  274 + const { dataSource } = option || {};
260 275
261   - if (!unref(getBindAttributes).length) return;
  276 + const group: Record<string, DataSource[]> = {};
262 277
263   - const getDeviceId = computed(() => {
264   - return props.config.option.dataSource?.at(0)?.deviceId;
  278 + dataSource?.forEach((item) => {
  279 + const { deviceId } = item;
  280 + if (group[deviceId]) {
  281 + group[deviceId].push(item);
  282 + } else {
  283 + group[deviceId] = [item];
  284 + }
  285 + });
  286 +
  287 + return group;
265 288 });
266 289
  290 + if (!Object.keys(unref(getDataSourceGroup)).length) return;
  291 + // const getBindAttributes = computed(() => {
  292 + // const attributes = props.config.option.dataSource?.map((item) => item.attribute);
  293 + // return [...new Set(attributes)];
  294 + // });
  295 +
  296 + // if (!unref(getBindAttributes).length) return;
  297 +
  298 + // const getDeviceId = computed(() => {
  299 + // return props.config.option.dataSource?.at(0)?.deviceId;
  300 + // });
  301 +
267 302 watch(
268   - () => getDeviceId,
  303 + () => getDataSourceGroup,
269 304 () => {
270   - subscriber.trackUpdateGroup(unref(getDeviceId)!, {
271   - attributes: unref(getBindAttributes),
272   - fn: updateFn,
  305 + Object.keys(unref(getDataSourceGroup)).forEach((key) => {
  306 + const item = unref(getDataSourceGroup)[key];
  307 + const attributes = [...new Set(item.map((item) => item.attribute))];
  308 + subscriber.trackUpdateGroup(key, {
  309 + attributes,
  310 + fn: updateFn,
  311 + });
273 312 });
274 313 },
275 314 {
... ... @@ -277,5 +316,5 @@ export const useMultipleDataFetch = (
277 316 }
278 317 );
279 318
280   - return { getDeviceId, getBindAttributes };
  319 + return {};
281 320 };
... ...
... ... @@ -162,6 +162,18 @@ export interface ReceiveMessageType {
162 162 latestValues: Record<string, number>;
163 163 }
164 164
  165 +export interface ReceiveGroupMessageType {
  166 + subscriptionId: number;
  167 + errorCode: number;
  168 + errorMsg: Nullable<string>;
  169 + data: {
  170 + string: Record<string, [number, string][]>;
  171 + };
  172 + latestValues: {
  173 + string: Record<string, number>;
  174 + };
  175 +}
  176 +
165 177 export enum EntityTypeEnum {
166 178 DEVICE = 'DEVICE',
167 179 }
... ... @@ -172,7 +184,11 @@ export enum ScopeTypeEnum {
172 184
173 185 export type DataFetchUpdateFn = (message: ReceiveMessageType, attr: string) => void;
174 186
175   -export type MultipleDataFetchUpdateFn = (message: ReceiveMessageType, attr: string[]) => void;
  187 +export type MultipleDataFetchUpdateFn = (
  188 + message: ReceiveGroupMessageType,
  189 + deviceId: string,
  190 + attr: string[]
  191 +) => void;
176 192
177 193 // 旧 组件key
178 194 // TEXT_COMPONENT_1 = 'text-component-1',
... ...
... ... @@ -27,6 +27,8 @@
27 27 import { HistoryTrendModal } from './components/HistoryTrendModal';
28 28 import { useSocket } from '../packages/hook/useSocket';
29 29 import { watch } from 'vue';
  30 + import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  31 + import { ThemeEnum } from '/@/enums/appEnum';
30 32
31 33 const props = defineProps<{
32 34 value?: Recordable;
... ... @@ -72,12 +74,15 @@
72 74
73 75 useSocket(dataSource);
74 76
  77 + const { getDarkMode } = useRootSetting();
75 78 watch(
76 79 getIsSharePage,
77 80 (value) => {
78 81 if (value) {
  82 + console.log(unref(getDarkMode));
79 83 const root = document.querySelector('#app');
80   - (root as HTMLDivElement).style.backgroundColor = '#F5F5F5';
  84 + (root as HTMLDivElement).style.backgroundColor =
  85 + unref(getDarkMode) === ThemeEnum.LIGHT ? '#F5F5F5' : '#1b1b1b';
81 86 }
82 87 },
83 88 { immediate: true }
... ...