Commit dd5ff901c81d71f44f065a11a9c968222304811c

Authored by xp.Huang
2 parents c31c3668 69a1cdb1

Merge branch 'local_dev_ft' into 'main'

fix:修复组态设计和大屏设计器列表分页点击刷新请求两次问题,pref:优化首页饼图布局

See merge request yunteng/thingskit-front!508
1 -<script setup lang="ts">  
2 - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';  
3 - import { ReloadOutlined } from '@ant-design/icons-vue';  
4 - import { computed, onMounted, reactive, ref, unref } from 'vue';  
5 - import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree';  
6 - import {  
7 - deleteConfigurationCenter,  
8 - getPage,  
9 - } from '/@/api/configuration/center/configurationCenter';  
10 - import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';  
11 - import { PageWrapper } from '/@/components/Page';  
12 - import { BasicForm, useForm } from '/@/components/Form';  
13 - import { ConfigurationPermission, searchFormSchema } from './center.data';  
14 - import { useMessage } from '/@/hooks/web/useMessage';  
15 - import { Authority } from '/@/components/Authority';  
16 - import { isDevMode } from '/@/utils/env';  
17 - import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';  
18 - import { useDrawer } from '/@/components/Drawer';  
19 - import { getBoundingClientRect } from '/@/utils/domUtils';  
20 - import configurationSrc from '/@/assets/icons/configuration.svg';  
21 - import { cloneDeep } from 'lodash';  
22 - import { usePermission } from '/@/hooks/web/usePermission';  
23 - import { useGlobSetting } from '/@/hooks/setting';  
24 - import { AuthIcon, CardLayoutButton } from '/@/components/Widget';  
25 - import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';  
26 -  
27 - const listColumn = ref(5);  
28 -  
29 - const { createMessage } = useMessage();  
30 -  
31 - const organizationId = ref<Nullable<number>>(null);  
32 -  
33 - const pagination = reactive<PaginationProps>({  
34 - size: 'small',  
35 - showTotal: (total: number) => `共 ${total} 条数据`,  
36 - current: 1,  
37 - pageSize: unref(listColumn) * 2,  
38 - onChange: (page: number) => {  
39 - pagination.current = page;  
40 - getListData();  
41 - },  
42 - });  
43 -  
44 - const loading = ref(false);  
45 -  
46 - const dataSource = ref<ConfigurationCenterItemsModal[]>([]);  
47 -  
48 - const [registerForm, { getFieldsValue }] = useForm({  
49 - schemas: searchFormSchema,  
50 - showAdvancedButton: true,  
51 - labelWidth: 100,  
52 - compact: true,  
53 - resetFunc: () => {  
54 - resetFn();  
55 - organizationId.value = null;  
56 - return getListData();  
57 - },  
58 - submitFunc: async () => {  
59 - const value = getFieldsValue();  
60 - getListData(value);  
61 - },  
62 - });  
63 -  
64 - async function getListData(value: Recordable = {}) {  
65 - try {  
66 - loading.value = true;  
67 - const pageSize = unref(listColumn) * 2;  
68 - const { items, total } = await getPage({  
69 - organizationId: unref(organizationId),  
70 - ...value,  
71 - page: pagination.current!,  
72 - pageSize,  
73 - });  
74 -  
75 - dataSource.value = items;  
76 - Object.assign(pagination, { total, pageSize });  
77 - } catch (error) {  
78 - } finally {  
79 - loading.value = false;  
80 - }  
81 - }  
82 -  
83 - onMounted(() => {  
84 - getListData();  
85 - });  
86 -  
87 - const searchInfo = reactive<Recordable>({});  
88 - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);  
89 - const handleSelect = (orgId: number) => {  
90 - organizationId.value = orgId;  
91 - getListData();  
92 - };  
93 -  
94 - const [registerDrawer, { openDrawer }] = useDrawer();  
95 -  
96 - const { hasPermission } = usePermission();  
97 -  
98 - const getPreviewFlag = computed(() => {  
99 - return hasPermission(ConfigurationPermission.PREVIEW);  
100 - });  
101 -  
102 - const getDesignFlag = computed(() => {  
103 - return hasPermission(ConfigurationPermission.DESIGN);  
104 - });  
105 -  
106 - const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {  
107 - if (record) {  
108 - openDrawer(true, {  
109 - isUpdate: true,  
110 - record: cloneDeep(record),  
111 - });  
112 - } else {  
113 - openDrawer(true, {  
114 - isUpdate: false,  
115 - });  
116 - }  
117 - };  
118 -  
119 - const { configurationPrefix } = useGlobSetting();  
120 - const isDev = isDevMode();  
121 -  
122 - const handlePreview = (record: ConfigurationCenterItemsModal) => {  
123 - if (!unref(getPreviewFlag)) return;  
124 - window.open(  
125 - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}&lightbox=1`  
126 - );  
127 - };  
128 -  
129 - const handleDesign = (record: ConfigurationCenterItemsModal) => {  
130 - if (!unref(getDesignFlag)) return;  
131 - window.open(`${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`);  
132 - };  
133 -  
134 - const handleDelete = async (record: ConfigurationCenterItemsModal) => {  
135 - try {  
136 - await deleteConfigurationCenter([record.id]);  
137 - createMessage.success('删除成功');  
138 - await getListData();  
139 - } catch (error) {}  
140 - };  
141 -  
142 - const handleCardLayoutChange = () => {  
143 - pagination.current = 1;  
144 - getListData();  
145 - };  
146 -  
147 - const listEl = ref<Nullable<ComponentElRef>>(null);  
148 -  
149 - onMounted(() => {  
150 - const clientHeight = document.documentElement.clientHeight;  
151 - const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect;  
152 - // margin-top 24 height 24  
153 - const paginationHeight = 24 + 24 + 8;  
154 - // list pading top 8 maring-top 8 extra slot 56  
155 - const listContainerMarginBottom = 8 + 8 + 56;  
156 - const listContainerHeight =  
157 - clientHeight - rect.top - paginationHeight - listContainerMarginBottom;  
158 - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(  
159 - '.ant-spin-container'  
160 - ) as HTMLElement;  
161 - listContainerEl &&  
162 - (listContainerEl.style.height = listContainerHeight + 'px') &&  
163 - (listContainerEl.style.overflowY = 'auto') &&  
164 - (listContainerEl.style.overflowX = 'hidden');  
165 - });  
166 -</script>  
167 -  
168 -<template>  
169 - <PageWrapper dense contentFullHeight contentClass="flex">  
170 - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />  
171 - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">  
172 - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">  
173 - <BasicForm @register="registerForm" />  
174 - </div>  
175 - <List  
176 - ref="listEl"  
177 - :loading="loading"  
178 - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"  
179 - position="bottom"  
180 - :pagination="pagination"  
181 - :data-source="dataSource"  
182 - :grid="{ gutter: 4, column: listColumn }"  
183 - >  
184 - <template #header>  
185 - <div class="flex gap-3 justify-end">  
186 - <Authority :value="ConfigurationPermission.CREATE">  
187 - <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>  
188 - </Authority>  
189 - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />  
190 - <Tooltip title="刷新">  
191 - <Button type="primary" @click="getListData">  
192 - <ReloadOutlined @click="getListData" />  
193 - </Button>  
194 - </Tooltip>  
195 - </div>  
196 - </template>  
197 - <template #renderItem="{ item }">  
198 - <List.Item>  
199 - <Card hoverable>  
200 - <template #cover>  
201 - <div class="h-full w-full !flex justify-center items-center text-center p-1">  
202 - <img  
203 - class="w-full h-36"  
204 - alt="example"  
205 - :src="item.thumbnail || configurationSrc"  
206 - @click="handlePreview(item)"  
207 - />  
208 - </div>  
209 - </template>  
210 - <template class="ant-card-actions" #actions>  
211 - <Tooltip title="预览">  
212 - <AuthIcon  
213 - :auth="ConfigurationPermission.PREVIEW"  
214 - class="!text-lg"  
215 - icon="ant-design:eye-outlined"  
216 - @click="handlePreview(item)"  
217 - />  
218 - </Tooltip>  
219 - <Tooltip title="设计">  
220 - <AuthIcon  
221 - :auth="ConfigurationPermission.DESIGN"  
222 - class="!text-lg"  
223 - icon="ant-design:edit-outlined"  
224 - @click="handleDesign(item)"  
225 - />  
226 - </Tooltip>  
227 - <AuthDropDown  
228 - :dropMenuList="[  
229 - {  
230 - text: '编辑',  
231 - auth: ConfigurationPermission.UPDATE,  
232 - icon: 'clarity:note-edit-line',  
233 - event: '',  
234 - onClick: handleCreateOrUpdate.bind(null, item),  
235 - },  
236 - {  
237 - text: '删除',  
238 - auth: ConfigurationPermission.DELETE,  
239 - icon: 'ant-design:delete-outlined',  
240 - event: '',  
241 - popconfirm: {  
242 - title: '是否确认删除操作?',  
243 - onConfirm: handleDelete.bind(null, item),  
244 - },  
245 - },  
246 - ]"  
247 - :trigger="['hover']"  
248 - />  
249 - </template>  
250 - <Card.Meta>  
251 - <template #title>  
252 - <span class="truncate">{{ item.name }}</span>  
253 - </template>  
254 - <template #description>  
255 - <div class="truncate h-11">  
256 - <div class="truncate">{{ item.organizationDTO.name }}</div>  
257 - <div class="truncate">{{ item.remark || '' }} </div>  
258 - </div>  
259 - </template>  
260 - </Card.Meta>  
261 - </Card>  
262 - </List.Item>  
263 - </template>  
264 - </List>  
265 - </section>  
266 - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />  
267 - </PageWrapper>  
268 -</template>  
269 -  
270 -<style lang="less" scoped>  
271 - .configuration-list:deep(.ant-list-header) {  
272 - border-bottom: none !important;  
273 - }  
274 -  
275 - .configuration-list:deep(.ant-list-pagination) {  
276 - height: 24px;  
277 - }  
278 -  
279 - .configuration-list:deep(.ant-card-body) {  
280 - padding: 16px !important;  
281 - }  
282 -  
283 - .configuration-list:deep(.ant-list-empty-text) {  
284 - @apply w-full h-full flex justify-center items-center;  
285 - }  
286 -</style> 1 +<script setup lang="ts">
  2 + import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';
  3 + import { ReloadOutlined } from '@ant-design/icons-vue';
  4 + import { computed, onMounted, reactive, ref, unref } from 'vue';
  5 + import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree';
  6 + import {
  7 + deleteConfigurationCenter,
  8 + getPage,
  9 + } from '/@/api/configuration/center/configurationCenter';
  10 + import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
  11 + import { PageWrapper } from '/@/components/Page';
  12 + import { BasicForm, useForm } from '/@/components/Form';
  13 + import { ConfigurationPermission, searchFormSchema } from './center.data';
  14 + import { useMessage } from '/@/hooks/web/useMessage';
  15 + import { Authority } from '/@/components/Authority';
  16 + import { isDevMode } from '/@/utils/env';
  17 + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';
  18 + import { useDrawer } from '/@/components/Drawer';
  19 + import { getBoundingClientRect } from '/@/utils/domUtils';
  20 + import configurationSrc from '/@/assets/icons/configuration.svg';
  21 + import { cloneDeep } from 'lodash';
  22 + import { usePermission } from '/@/hooks/web/usePermission';
  23 + import { useGlobSetting } from '/@/hooks/setting';
  24 + import { AuthIcon, CardLayoutButton } from '/@/components/Widget';
  25 + import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
  26 +
  27 + const listColumn = ref(5);
  28 +
  29 + const { createMessage } = useMessage();
  30 +
  31 + const organizationId = ref<Nullable<number>>(null);
  32 +
  33 + const pagination = reactive<PaginationProps>({
  34 + size: 'small',
  35 + showTotal: (total: number) => `共 ${total} 条数据`,
  36 + current: 1,
  37 + pageSize: unref(listColumn) * 2,
  38 + onChange: (page: number) => {
  39 + pagination.current = page;
  40 + getListData();
  41 + },
  42 + });
  43 +
  44 + const loading = ref(false);
  45 +
  46 + const dataSource = ref<ConfigurationCenterItemsModal[]>([]);
  47 +
  48 + const [registerForm, { getFieldsValue }] = useForm({
  49 + schemas: searchFormSchema,
  50 + showAdvancedButton: true,
  51 + labelWidth: 100,
  52 + compact: true,
  53 + resetFunc: () => {
  54 + resetFn();
  55 + organizationId.value = null;
  56 + return getListData();
  57 + },
  58 + submitFunc: async () => {
  59 + const value = getFieldsValue();
  60 + getListData(value);
  61 + },
  62 + });
  63 +
  64 + async function getListData(value: Recordable = {}) {
  65 + try {
  66 + loading.value = true;
  67 + const pageSize = unref(listColumn) * 2;
  68 + const { items, total } = await getPage({
  69 + organizationId: unref(organizationId),
  70 + ...value,
  71 + page: pagination.current!,
  72 + pageSize,
  73 + });
  74 +
  75 + dataSource.value = items;
  76 + Object.assign(pagination, { total, pageSize });
  77 + } catch (error) {
  78 + } finally {
  79 + loading.value = false;
  80 + }
  81 + }
  82 +
  83 + onMounted(() => {
  84 + getListData();
  85 + });
  86 +
  87 + const searchInfo = reactive<Recordable>({});
  88 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  89 + const handleSelect = (orgId: number) => {
  90 + organizationId.value = orgId;
  91 + getListData();
  92 + };
  93 +
  94 + const [registerDrawer, { openDrawer }] = useDrawer();
  95 +
  96 + const { hasPermission } = usePermission();
  97 +
  98 + const getPreviewFlag = computed(() => {
  99 + return hasPermission(ConfigurationPermission.PREVIEW);
  100 + });
  101 +
  102 + const getDesignFlag = computed(() => {
  103 + return hasPermission(ConfigurationPermission.DESIGN);
  104 + });
  105 +
  106 + const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {
  107 + if (record) {
  108 + openDrawer(true, {
  109 + isUpdate: true,
  110 + record: cloneDeep(record),
  111 + });
  112 + } else {
  113 + openDrawer(true, {
  114 + isUpdate: false,
  115 + });
  116 + }
  117 + };
  118 +
  119 + const { configurationPrefix } = useGlobSetting();
  120 + const isDev = isDevMode();
  121 +
  122 + const handlePreview = (record: ConfigurationCenterItemsModal) => {
  123 + if (!unref(getPreviewFlag)) return;
  124 + window.open(
  125 + `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}&lightbox=1`
  126 + );
  127 + };
  128 +
  129 + const handleDesign = (record: ConfigurationCenterItemsModal) => {
  130 + if (!unref(getDesignFlag)) return;
  131 + window.open(`${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`);
  132 + };
  133 +
  134 + const handleDelete = async (record: ConfigurationCenterItemsModal) => {
  135 + try {
  136 + await deleteConfigurationCenter([record.id]);
  137 + createMessage.success('删除成功');
  138 + await getListData();
  139 + } catch (error) {}
  140 + };
  141 +
  142 + const handleCardLayoutChange = () => {
  143 + pagination.current = 1;
  144 + getListData();
  145 + };
  146 +
  147 + const listEl = ref<Nullable<ComponentElRef>>(null);
  148 +
  149 + onMounted(() => {
  150 + const clientHeight = document.documentElement.clientHeight;
  151 + const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect;
  152 + // margin-top 24 height 24
  153 + const paginationHeight = 24 + 24 + 8;
  154 + // list pading top 8 maring-top 8 extra slot 56
  155 + const listContainerMarginBottom = 8 + 8 + 56;
  156 + const listContainerHeight =
  157 + clientHeight - rect.top - paginationHeight - listContainerMarginBottom;
  158 + const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(
  159 + '.ant-spin-container'
  160 + ) as HTMLElement;
  161 + listContainerEl &&
  162 + (listContainerEl.style.height = listContainerHeight + 'px') &&
  163 + (listContainerEl.style.overflowY = 'auto') &&
  164 + (listContainerEl.style.overflowX = 'hidden');
  165 + });
  166 +</script>
  167 +
  168 +<template>
  169 + <PageWrapper dense contentFullHeight contentClass="flex">
  170 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  171 + <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">
  172 + <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
  173 + <BasicForm @register="registerForm" />
  174 + </div>
  175 + <List
  176 + ref="listEl"
  177 + :loading="loading"
  178 + class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
  179 + position="bottom"
  180 + :pagination="pagination"
  181 + :data-source="dataSource"
  182 + :grid="{ gutter: 4, column: listColumn }"
  183 + >
  184 + <template #header>
  185 + <div class="flex gap-3 justify-end">
  186 + <Authority :value="ConfigurationPermission.CREATE">
  187 + <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
  188 + </Authority>
  189 + <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
  190 + <Tooltip title="刷新">
  191 + <Button type="primary" @click="getListData">
  192 + <ReloadOutlined />
  193 + </Button>
  194 + </Tooltip>
  195 + </div>
  196 + </template>
  197 + <template #renderItem="{ item }">
  198 + <List.Item>
  199 + <Card hoverable>
  200 + <template #cover>
  201 + <div class="h-full w-full !flex justify-center items-center text-center p-1">
  202 + <img
  203 + class="w-full h-36"
  204 + alt="example"
  205 + :src="item.thumbnail || configurationSrc"
  206 + @click="handlePreview(item)"
  207 + />
  208 + </div>
  209 + </template>
  210 + <template class="ant-card-actions" #actions>
  211 + <Tooltip title="预览">
  212 + <AuthIcon
  213 + :auth="ConfigurationPermission.PREVIEW"
  214 + class="!text-lg"
  215 + icon="ant-design:eye-outlined"
  216 + @click="handlePreview(item)"
  217 + />
  218 + </Tooltip>
  219 + <Tooltip title="设计">
  220 + <AuthIcon
  221 + :auth="ConfigurationPermission.DESIGN"
  222 + class="!text-lg"
  223 + icon="ant-design:edit-outlined"
  224 + @click="handleDesign(item)"
  225 + />
  226 + </Tooltip>
  227 + <AuthDropDown
  228 + :dropMenuList="[
  229 + {
  230 + text: '编辑',
  231 + auth: ConfigurationPermission.UPDATE,
  232 + icon: 'clarity:note-edit-line',
  233 + event: '',
  234 + onClick: handleCreateOrUpdate.bind(null, item),
  235 + },
  236 + {
  237 + text: '删除',
  238 + auth: ConfigurationPermission.DELETE,
  239 + icon: 'ant-design:delete-outlined',
  240 + event: '',
  241 + popconfirm: {
  242 + title: '是否确认删除操作?',
  243 + onConfirm: handleDelete.bind(null, item),
  244 + },
  245 + },
  246 + ]"
  247 + :trigger="['hover']"
  248 + />
  249 + </template>
  250 + <Card.Meta>
  251 + <template #title>
  252 + <span class="truncate">{{ item.name }}</span>
  253 + </template>
  254 + <template #description>
  255 + <div class="truncate h-11">
  256 + <div class="truncate">{{ item.organizationDTO.name }}</div>
  257 + <div class="truncate">{{ item.remark || '' }} </div>
  258 + </div>
  259 + </template>
  260 + </Card.Meta>
  261 + </Card>
  262 + </List.Item>
  263 + </template>
  264 + </List>
  265 + </section>
  266 + <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />
  267 + </PageWrapper>
  268 +</template>
  269 +
  270 +<style lang="less" scoped>
  271 + .configuration-list:deep(.ant-list-header) {
  272 + border-bottom: none !important;
  273 + }
  274 +
  275 + .configuration-list:deep(.ant-list-pagination) {
  276 + height: 24px;
  277 + }
  278 +
  279 + .configuration-list:deep(.ant-card-body) {
  280 + padding: 16px !important;
  281 + }
  282 +
  283 + .configuration-list:deep(.ant-list-empty-text) {
  284 + @apply w-full h-full flex justify-center items-center;
  285 + }
  286 +</style>
1 -<template>  
2 - <div>  
3 - <!-- 首页基础信息 -->  
4 - <div class="md:flex">  
5 - <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" style="color: #666">  
6 - <div class="flex" style="height: 100px">  
7 - <div class="mr-4"  
8 - ><img  
9 - src="/src/assets/images/device-count.png"  
10 - style="width: 5.625rem; height: 5.625rem"  
11 - /></div>  
12 - <div class="flex-auto">  
13 - <div class="flex justify-between" style="align-items: center">  
14 - <div style="font-size: 1.625rem; color: #333; font-weight: bold">  
15 - <CountTo  
16 - v-if="growCardList?.deviceInfo?.sumCount"  
17 - :endVal="growCardList.deviceInfo.sumCount"  
18 - />  
19 - <CountTo v-else :endVal="0" />  
20 - </div>  
21 - <Tooltip>  
22 - <template #title>  
23 - 设备数 : {{ growCardList?.deviceInfo.sumCount }} 今日新增  
24 - {{ toThousands(growCardList?.deviceInfo?.todayAdd) }}  
25 - </template>  
26 - <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />  
27 - </Tooltip>  
28 - </div>  
29 - <div> 设备数 </div>  
30 - </div>  
31 - </div>  
32 - <div class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
33 - 今日新增 {{ toThousands(growCardList?.deviceInfo?.todayAdd) }}  
34 - </div>  
35 - </Card>  
36 - <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" style="color: #666">  
37 - <div class="flex" style="height: 100px">  
38 - <div class="mr-4">  
39 - <img  
40 - v-if="!isAdmin(role)"  
41 - src="/src/assets/images/alarm-count.png"  
42 - style="width: 5.625rem; height: 5.625rem"  
43 - />  
44 - <img v-else src="/src/assets/images/zh.png" style="width: 5.625rem; height: 5.625rem" />  
45 - </div>  
46 - <div class="flex-auto">  
47 - <div class="flex justify-between" style="align-items: center">  
48 - <div  
49 - v-if="!isAdmin(role)"  
50 - style="font-size: 1.625rem; color: #333; font-weight: bold"  
51 - >  
52 - <CountTo  
53 - v-if="growCardList?.alarmInfo?.sumCount"  
54 - :end-val="growCardList.alarmInfo.sumCount"  
55 - />  
56 - <CountTo v-else :end-val="0" />  
57 - </div>  
58 - <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>  
59 - <CountTo  
60 - v-if="growCardList?.tenantInfo?.sumCount"  
61 - :end-val="growCardList.tenantInfo.sumCount"  
62 - />  
63 - <CountTo v-else :end-val="0" />  
64 - </div>  
65 - <Tooltip>  
66 - <template #title>  
67 - {{  
68 - !isAdmin(role)  
69 - ? `告警数:${growCardList?.alarmInfo?.sumCount} 今日新增 ${toThousands(  
70 - growCardList?.alarmInfo?.todayAdd  
71 - )}`  
72 - : `租户总量:${growCardList?.tenantInfo?.sumCount} 今日新增 ${toThousands(  
73 - growCardList?.alarmInfo?.todayAdd  
74 - )}`  
75 - }}  
76 - </template>  
77 - <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />  
78 - </Tooltip>  
79 - </div>  
80 - <div> {{ !isAdmin(role) ? `告警数` : '租户总量' }}</div>  
81 - </div>  
82 - </div>  
83 - <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
84 - 今日新增 {{ toThousands(growCardList?.alarmInfo?.todayAdd) }}</div  
85 - >  
86 - <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
87 - 今日新增 {{ toThousands(growCardList?.tenantInfo?.todayAdd) }}</div  
88 - >  
89 - </Card>  
90 - <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4" style="color: #666">  
91 - <div class="flex" style="height: 100px">  
92 - <div class="mr-4">  
93 - <img  
94 - v-if="!isAdmin(role)"  
95 - src="/src/assets/images/msg-count.png"  
96 - style="width: 5.625rem; height: 5.625rem"  
97 - />  
98 - <img v-else src="/src/assets/images/kf.png" style="width: 5.625rem; height: 5.625rem" />  
99 - </div>  
100 - <div class="flex-auto">  
101 - <div class="flex justify-between" style="align-items: center">  
102 - <div  
103 - v-if="!isAdmin(role)"  
104 - style="font-size: 1.625rem; color: #333; font-weight: bold"  
105 - >  
106 - <CountTo  
107 - v-if="growCardList?.messageInfo?.messageCount"  
108 - :end-val="growCardList.messageInfo.messageCount"  
109 - />  
110 - <CountTo v-else :end-val="0" />  
111 - </div>  
112 - <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>  
113 - <CountTo  
114 - v-if="growCardList?.customerInfo?.sumCount"  
115 - :end-val="growCardList.customerInfo.sumCount"  
116 - />  
117 - <CountTo v-else :end-val="0" />  
118 - </div>  
119 - <Tooltip>  
120 - <template #title>  
121 - {{  
122 - !isAdmin(role)  
123 - ? `消息数:${growCardList?.messageInfo?.messageCount} 今日新增 ${toThousands(  
124 - growCardList?.messageInfo?.todayMessageAdd  
125 - )}`  
126 - : `客户总量:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(  
127 - growCardList?.messageInfo?.todayMessageAdd  
128 - )}`  
129 - }}  
130 - </template>  
131 - <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />  
132 - </Tooltip>  
133 - </div>  
134 - <div> {{ !isAdmin(role) ? `消息数` : '客户总量' }}</div>  
135 - </div>  
136 - </div>  
137 - <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
138 - 今日新增 {{ toThousands(growCardList?.messageInfo?.todayMessageAdd) }}</div  
139 - >  
140 - <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
141 - 今日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div  
142 - >  
143 - </Card>  
144 - <Card  
145 - v-if="!isAdmin(role)"  
146 - size="small"  
147 - class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-4"  
148 - style="color: #666"  
149 - >  
150 - <div class="flex" style="height: 100px">  
151 - <div class="mr-4">  
152 - <img  
153 - v-if="!isAdmin(role)"  
154 - src="/src/assets/images/product.png"  
155 - style="width: 5.625rem; height: 5.625rem"  
156 - />  
157 - <img  
158 - v-else  
159 - src="/src/assets/images/product.png"  
160 - style="width: 5.625rem; height: 5.625rem"  
161 - />  
162 - </div>  
163 - <div class="flex-auto">  
164 - <div class="flex justify-between" style="align-items: center">  
165 - <div  
166 - v-if="!isAdmin(role)"  
167 - style="font-size: 1.625rem; color: #333; font-weight: bold"  
168 - >  
169 - <CountTo  
170 - v-if="growCardList?.productInfo?.sumCount"  
171 - :end-val="growCardList.productInfo.sumCount"  
172 - />  
173 - <CountTo v-else :end-val="0" />  
174 - </div>  
175 - <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>  
176 - <CountTo  
177 - v-if="growCardList?.productInfo?.sumCount"  
178 - :end-val="growCardList.productInfo?.sumCount"  
179 - />  
180 - <CountTo v-else :end-val="0" />  
181 - </div>  
182 - <Tooltip>  
183 - <template #title>  
184 - {{  
185 - !isAdmin(role)  
186 - ? `产品数:${growCardList?.productInfo?.sumCount} 今日新增 ${toThousands(  
187 - growCardList?.productInfo?.todayAdd  
188 - )}`  
189 - : `产品数:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(  
190 - growCardList?.productInfo?.todayAdd  
191 - )}`  
192 - }}  
193 - </template>  
194 - <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />  
195 - </Tooltip>  
196 - </div>  
197 - <div> {{ !isAdmin(role) ? `产品数` : '产品数' }}</div>  
198 - </div>  
199 - </div>  
200 - <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
201 - 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div  
202 - >  
203 - <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">  
204 - 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div  
205 - >  
206 - </Card>  
207 - </div>  
208 - <!-- 首页饼图 -->  
209 - <div class="md:flex mt-4" v-if="!isAdmin(role)">  
210 - <Card  
211 - size="small"  
212 - class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4"  
213 - style="color: #666; width: 50%"  
214 - >  
215 - <div class="flex container ml-4">  
216 - <div class="mr-4 flex chart-top">  
217 - <PieChartDeviceSub  
218 - v-if="seriesData.length > 0"  
219 - :legendData="legendData"  
220 - :seriesData="seriesData"  
221 - :isCircle="true"  
222 - />  
223 - </div>  
224 - <div class="ml-20 flex justify-around right-text">  
225 - <div class="text">  
226 - 直连设备:{{ growCardList?.deviceInfo?.directConnection ?? 0 }}个  
227 - </div>  
228 - <div class="text"> 网关设备:{{ growCardList?.deviceInfo?.gateWay ?? 0 }}个 </div>  
229 - <div class="text"> 网关子设备:{{ growCardList?.deviceInfo?.sensor ?? 0 }}个 </div>  
230 - </div>  
231 - </div>  
232 - </Card>  
233 - <Card  
234 - size="small"  
235 - class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-1"  
236 - style="color: #666; width: 50%"  
237 - >  
238 - <div class="flex container ml-4">  
239 - <div class="mr-4 flex chart-top">  
240 - <PieChartDeviceSub  
241 - v-if="seriesStatusData.length > 0"  
242 - :legendData="legendStatusData"  
243 - :seriesData="seriesStatusData"  
244 - :isCircle="false"  
245 - />  
246 - </div>  
247 - <div class="ml-20 flex justify-around right-text">  
248 - <div class="text"> 待激活设备:{{ growCardList?.deviceInfo?.inActive ?? 0 }}个 </div>  
249 - <div class="text"> 在线设备:{{ growCardList?.deviceInfo?.onLine ?? 0 }}个 </div>  
250 - <div class="text"> 离线设备:{{ growCardList?.deviceInfo?.offLine ?? 0 }}个 </div>  
251 - </div>  
252 - </div>  
253 - </Card>  
254 - </div>  
255 - </div>  
256 -</template>  
257 -<script lang="ts" setup>  
258 - import { ref, onMounted, defineComponent, Ref } from 'vue';  
259 - import { Card } from 'ant-design-vue';  
260 - import { getHomeData } from '/@/api/dashboard';  
261 - import { isAdmin } from '/@/enums/roleEnum';  
262 - import { toThousands } from '/@/utils/fnUtils';  
263 - import { CountTo } from '/@/components/CountTo/index';  
264 - import { Tooltip } from 'ant-design-vue';  
265 - import { CardList, seriesDataT } from './props';  
266 - import PieChartDeviceSub from './PieChartDeviceSub.vue';  
267 -  
268 - defineProps<{  
269 - role: string;  
270 - }>();  
271 -  
272 - defineExpose({  
273 - isAdmin,  
274 - toThousands,  
275 - });  
276 -  
277 - defineComponent({  
278 - Card,  
279 - });  
280 -  
281 - const legendData = ref(['网关设备', '直连设备', '网关子设备']);  
282 -  
283 - const seriesData: Ref<seriesDataT[]> = ref([]);  
284 -  
285 - const legendStatusData = ref(['待激活', '在线', '离线']);  
286 -  
287 - const seriesStatusData: Ref<seriesDataT[]> = ref([]);  
288 -  
289 - const growCardList = ref<CardList>();  
290 -  
291 - const devicePieColor = [  
292 - { key: 'directConnection', itemStyle: { color: '#5C7BD9' }, value: '直连设备' },  
293 - { key: 'gateWay', itemStyle: { color: '#91CC75' }, value: '网关设备' },  
294 - { key: 'sensor', itemStyle: { color: '#FAC859' }, value: '网关子设备' },  
295 - { key: 'inActive', itemStyle: { color: '#5C7BD9' }, value: '待激活' },  
296 - { key: 'onLine', itemStyle: { color: '#91CC75 ' }, value: '在线' },  
297 - { key: 'offLine', itemStyle: { color: '#EC4040' }, value: '离线' },  
298 - ];  
299 -  
300 - onMounted(async () => {  
301 - const res = await getHomeData();  
302 - growCardList.value = res;  
303 - const { deviceInfo } = growCardList.value!;  
304 - let data = Object.entries(deviceInfo).map(([key, value]) => {  
305 - const name = devicePieColor?.find((f) => f.key === key)?.value;  
306 - const itemStyle = devicePieColor?.find((f) => f.key === key)?.itemStyle;  
307 - return { key, value, itemStyle, name };  
308 - });  
309 - seriesData.value = data.filter(  
310 - (f) => f.key === 'directConnection' || f.key === 'gateWay' || f.key === 'sensor'  
311 - );  
312 - seriesStatusData.value = data.filter(  
313 - (f) => f.key === 'inActive' || f.key === 'onLine' || f.key === 'offLine'  
314 - );  
315 - });  
316 -</script>  
317 -<style lang="css">  
318 - .right-text {  
319 - width: 40%;  
320 - flex-direction: column;  
321 - height: 240px;  
322 - margin: 10px 0 10px 50px;  
323 - }  
324 -  
325 - .text {  
326 - color: #333;  
327 - font-weight: bold;  
328 - display: flex;  
329 - flex-wrap: nowrap;  
330 - }  
331 -  
332 - .chart-top {  
333 - width: 60%;  
334 - height: 300px;  
335 - align-items: center;  
336 - margin-top: -30px;  
337 - }  
338 -  
339 - .container {  
340 - width: 100%;  
341 - }  
342 -</style> 1 +<template>
  2 + <div>
  3 + <!-- 首页基础信息 -->
  4 + <div class="md:flex">
  5 + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" style="color: #666">
  6 + <div class="flex" style="height: 100px">
  7 + <div class="mr-4"
  8 + ><img
  9 + src="/src/assets/images/device-count.png"
  10 + style="width: 5.625rem; height: 5.625rem"
  11 + /></div>
  12 + <div class="flex-auto">
  13 + <div class="flex justify-between" style="align-items: center">
  14 + <div style="font-size: 1.625rem; color: #333; font-weight: bold">
  15 + <CountTo
  16 + v-if="growCardList?.deviceInfo?.sumCount"
  17 + :endVal="growCardList.deviceInfo.sumCount"
  18 + />
  19 + <CountTo v-else :endVal="0" />
  20 + </div>
  21 + <Tooltip>
  22 + <template #title>
  23 + 设备数 : {{ growCardList?.deviceInfo.sumCount }} 今日新增
  24 + {{ toThousands(growCardList?.deviceInfo?.todayAdd) }}
  25 + </template>
  26 + <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
  27 + </Tooltip>
  28 + </div>
  29 + <div> 设备数 </div>
  30 + </div>
  31 + </div>
  32 + <div class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  33 + 今日新增 {{ toThousands(growCardList?.deviceInfo?.todayAdd) }}
  34 + </div>
  35 + </Card>
  36 + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" style="color: #666">
  37 + <div class="flex" style="height: 100px">
  38 + <div class="mr-4">
  39 + <img
  40 + v-if="!isAdmin(role)"
  41 + src="/src/assets/images/alarm-count.png"
  42 + style="width: 5.625rem; height: 5.625rem"
  43 + />
  44 + <img v-else src="/src/assets/images/zh.png" style="width: 5.625rem; height: 5.625rem" />
  45 + </div>
  46 + <div class="flex-auto">
  47 + <div class="flex justify-between" style="align-items: center">
  48 + <div
  49 + v-if="!isAdmin(role)"
  50 + style="font-size: 1.625rem; color: #333; font-weight: bold"
  51 + >
  52 + <CountTo
  53 + v-if="growCardList?.alarmInfo?.sumCount"
  54 + :end-val="growCardList.alarmInfo.sumCount"
  55 + />
  56 + <CountTo v-else :end-val="0" />
  57 + </div>
  58 + <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>
  59 + <CountTo
  60 + v-if="growCardList?.tenantInfo?.sumCount"
  61 + :end-val="growCardList.tenantInfo.sumCount"
  62 + />
  63 + <CountTo v-else :end-val="0" />
  64 + </div>
  65 + <Tooltip>
  66 + <template #title>
  67 + {{
  68 + !isAdmin(role)
  69 + ? `告警数:${growCardList?.alarmInfo?.sumCount} 今日新增 ${toThousands(
  70 + growCardList?.alarmInfo?.todayAdd
  71 + )}`
  72 + : `租户总量:${growCardList?.tenantInfo?.sumCount} 今日新增 ${toThousands(
  73 + growCardList?.alarmInfo?.todayAdd
  74 + )}`
  75 + }}
  76 + </template>
  77 + <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
  78 + </Tooltip>
  79 + </div>
  80 + <div> {{ !isAdmin(role) ? `告警数` : '租户总量' }}</div>
  81 + </div>
  82 + </div>
  83 + <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  84 + 今日新增 {{ toThousands(growCardList?.alarmInfo?.todayAdd) }}</div
  85 + >
  86 + <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  87 + 今日新增 {{ toThousands(growCardList?.tenantInfo?.todayAdd) }}</div
  88 + >
  89 + </Card>
  90 + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4" style="color: #666">
  91 + <div class="flex" style="height: 100px">
  92 + <div class="mr-4">
  93 + <img
  94 + v-if="!isAdmin(role)"
  95 + src="/src/assets/images/msg-count.png"
  96 + style="width: 5.625rem; height: 5.625rem"
  97 + />
  98 + <img v-else src="/src/assets/images/kf.png" style="width: 5.625rem; height: 5.625rem" />
  99 + </div>
  100 + <div class="flex-auto">
  101 + <div class="flex justify-between" style="align-items: center">
  102 + <div
  103 + v-if="!isAdmin(role)"
  104 + style="font-size: 1.625rem; color: #333; font-weight: bold"
  105 + >
  106 + <CountTo
  107 + v-if="growCardList?.messageInfo?.messageCount"
  108 + :end-val="growCardList.messageInfo.messageCount"
  109 + />
  110 + <CountTo v-else :end-val="0" />
  111 + </div>
  112 + <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>
  113 + <CountTo
  114 + v-if="growCardList?.customerInfo?.sumCount"
  115 + :end-val="growCardList.customerInfo.sumCount"
  116 + />
  117 + <CountTo v-else :end-val="0" />
  118 + </div>
  119 + <Tooltip>
  120 + <template #title>
  121 + {{
  122 + !isAdmin(role)
  123 + ? `消息数:${growCardList?.messageInfo?.messageCount} 今日新增 ${toThousands(
  124 + growCardList?.messageInfo?.todayMessageAdd
  125 + )}`
  126 + : `客户总量:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(
  127 + growCardList?.messageInfo?.todayMessageAdd
  128 + )}`
  129 + }}
  130 + </template>
  131 + <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
  132 + </Tooltip>
  133 + </div>
  134 + <div> {{ !isAdmin(role) ? `消息数` : '客户总量' }}</div>
  135 + </div>
  136 + </div>
  137 + <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  138 + 今日新增 {{ toThousands(growCardList?.messageInfo?.todayMessageAdd) }}</div
  139 + >
  140 + <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  141 + 今日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div
  142 + >
  143 + </Card>
  144 + <Card
  145 + v-if="!isAdmin(role)"
  146 + size="small"
  147 + class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-4"
  148 + style="color: #666"
  149 + >
  150 + <div class="flex" style="height: 100px">
  151 + <div class="mr-4">
  152 + <img
  153 + v-if="!isAdmin(role)"
  154 + src="/src/assets/images/product.png"
  155 + style="width: 5.625rem; height: 5.625rem"
  156 + />
  157 + <img
  158 + v-else
  159 + src="/src/assets/images/product.png"
  160 + style="width: 5.625rem; height: 5.625rem"
  161 + />
  162 + </div>
  163 + <div class="flex-auto">
  164 + <div class="flex justify-between" style="align-items: center">
  165 + <div
  166 + v-if="!isAdmin(role)"
  167 + style="font-size: 1.625rem; color: #333; font-weight: bold"
  168 + >
  169 + <CountTo
  170 + v-if="growCardList?.productInfo?.sumCount"
  171 + :end-val="growCardList.productInfo.sumCount"
  172 + />
  173 + <CountTo v-else :end-val="0" />
  174 + </div>
  175 + <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>
  176 + <CountTo
  177 + v-if="growCardList?.productInfo?.sumCount"
  178 + :end-val="growCardList.productInfo?.sumCount"
  179 + />
  180 + <CountTo v-else :end-val="0" />
  181 + </div>
  182 + <Tooltip>
  183 + <template #title>
  184 + {{
  185 + !isAdmin(role)
  186 + ? `产品数:${growCardList?.productInfo?.sumCount} 今日新增 ${toThousands(
  187 + growCardList?.productInfo?.todayAdd
  188 + )}`
  189 + : `产品数:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(
  190 + growCardList?.productInfo?.todayAdd
  191 + )}`
  192 + }}
  193 + </template>
  194 + <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
  195 + </Tooltip>
  196 + </div>
  197 + <div> {{ !isAdmin(role) ? `产品数` : '产品数' }}</div>
  198 + </div>
  199 + </div>
  200 + <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  201 + 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div
  202 + >
  203 + <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  204 + 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div
  205 + >
  206 + </Card>
  207 + </div>
  208 + <!-- 首页饼图 -->
  209 + <div class="md:flex mt-4" v-if="!isAdmin(role)">
  210 + <Card
  211 + size="small"
  212 + class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4"
  213 + style="color: #666; width: 50%"
  214 + title="设备数量统计"
  215 + >
  216 + <a-row type="flex" justify="space-between" align="middle">
  217 + <a-col :span="12">
  218 + <PieChartDeviceSub
  219 + v-if="seriesData.length > 0"
  220 + :legendData="legendData"
  221 + :seriesData="seriesData"
  222 + :isCircle="false"
  223 + /></a-col>
  224 + <a-col :span="12">
  225 + <a-row type="flex" justify="space-between" align="middle" style="row-gap: 30px">
  226 + <a-col :offset="8" class="flex items-center">
  227 + <span class="left-icon-d-color"></span>
  228 + 直连设备:
  229 + <span class="bold-text">{{ growCardList?.deviceInfo?.directConnection ?? 0 }}</span
  230 + >个</a-col
  231 + >
  232 + <a-col :offset="8" class="flex items-center">
  233 + <span class="left-icon-g-color"></span>
  234 + 网关设备:
  235 + <span class="bold-text">{{ growCardList?.deviceInfo?.gateWay ?? 0 }}</span
  236 + >个</a-col
  237 + >
  238 + <a-col :offset="8" class="flex items-center">
  239 + <span class="left-icon-s-color"></span>
  240 + 网关子设备:<span class="bold-text">{{
  241 + growCardList?.deviceInfo?.sensor ?? 0
  242 + }}</span
  243 + >个</a-col
  244 + >
  245 + </a-row>
  246 + </a-col>
  247 + </a-row>
  248 + </Card>
  249 + <Card
  250 + size="small"
  251 + class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-1"
  252 + style="color: #666; width: 50%"
  253 + title="设备状态统计"
  254 + >
  255 + <a-row type="flex" justify="space-between" align="middle">
  256 + <a-col :span="12">
  257 + <PieChartDeviceStatus
  258 + v-if="seriesStatusData.length > 0"
  259 + :seriesStatusData="seriesStatusData"
  260 + />
  261 + <div class="empty-box" v-else><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /></div>
  262 + </a-col>
  263 + <a-col :span="12">
  264 + <a-row type="flex" justify="space-between" align="middle" style="row-gap: 30px">
  265 + <a-col :offset="8" class="flex items-center">
  266 + <span class="right-icon-d-color"></span>
  267 + 待激活设备:
  268 + <span class="bold-text">{{ growCardList?.deviceInfo?.inActive ?? 0 }}</span
  269 + >个</a-col
  270 + >
  271 + <a-col :offset="8" class="flex items-center">
  272 + <span class="right-icon-g-color"></span>
  273 + 在线设备:<span class="bold-text">{{ growCardList?.deviceInfo?.onLine ?? 0 }}</span
  274 + >个</a-col
  275 + >
  276 + <a-col :offset="8" class="flex items-center">
  277 + <span class="right-icon-s-color"></span>
  278 + 离线设备:<span class="bold-text">{{ growCardList?.deviceInfo?.offLine ?? 0 }}</span
  279 + >个</a-col
  280 + >
  281 + </a-row>
  282 + </a-col>
  283 + </a-row>
  284 + </Card>
  285 + </div>
  286 + </div>
  287 +</template>
  288 +<script lang="ts" setup>
  289 + import { ref, onMounted, defineComponent, Ref } from 'vue';
  290 + import { Card } from 'ant-design-vue';
  291 + import { getHomeData } from '/@/api/dashboard';
  292 + import { isAdmin } from '/@/enums/roleEnum';
  293 + import { toThousands } from '/@/utils/fnUtils';
  294 + import { CountTo } from '/@/components/CountTo/index';
  295 + import { Tooltip } from 'ant-design-vue';
  296 + import { CardList, seriesDataT } from './props';
  297 + import PieChartDeviceSub from './PieChartDeviceSub.vue';
  298 + import PieChartDeviceStatus from './PieChartDeviceStatus.vue';
  299 + import { Empty } from 'ant-design-vue';
  300 +
  301 + defineProps<{
  302 + role: string;
  303 + }>();
  304 +
  305 + defineExpose({
  306 + isAdmin,
  307 + toThousands,
  308 + });
  309 +
  310 + defineComponent({
  311 + Card,
  312 + });
  313 +
  314 + const legendData = ref(['网关设备', '直连设备', '网关子设备']);
  315 +
  316 + const seriesData: Ref<seriesDataT[]> = ref([]);
  317 +
  318 + const seriesStatusData: Ref<seriesDataT[]> = ref([]);
  319 +
  320 + const growCardList = ref<CardList>();
  321 +
  322 + const devicePieColor = [
  323 + { key: 'directConnection', itemStyle: { color: '#5C7BD9' }, value: '直连设备' },
  324 + { key: 'gateWay', itemStyle: { color: '#91CC75' }, value: '网关设备' },
  325 + { key: 'sensor', itemStyle: { color: '#FAC859' }, value: '网关子设备' },
  326 + { key: 'inActive', itemStyle: { color: '#5C7BD9' }, value: '待激活' },
  327 + { key: 'onLine', itemStyle: { color: '#91CC75 ' }, value: '在线' },
  328 + { key: 'offLine', itemStyle: { color: '#EC4040' }, value: '离线' },
  329 + ];
  330 +
  331 + onMounted(async () => {
  332 + const res = await getHomeData();
  333 + growCardList.value = res;
  334 + const { deviceInfo } = growCardList.value!;
  335 + let data = Object.entries(deviceInfo).map(([key, value]) => {
  336 + const name = devicePieColor?.find((f) => f.key === key)?.value;
  337 + const itemStyle = devicePieColor?.find((f) => f.key === key)?.itemStyle;
  338 + return { key, value, itemStyle, name };
  339 + });
  340 + seriesData.value = data.filter(
  341 + (f) => f.key === 'directConnection' || f.key === 'gateWay' || f.key === 'sensor'
  342 + );
  343 + seriesStatusData.value = data.filter(
  344 + (f) => f.key === 'inActive' || f.key === 'onLine' || f.key === 'offLine'
  345 + );
  346 + });
  347 +</script>
  348 +<style lang="less">
  349 + .text {
  350 + color: #333;
  351 + display: flex;
  352 + flex-wrap: nowrap;
  353 + }
  354 +
  355 + .bold-text {
  356 + font-weight: bold;
  357 + }
  358 +
  359 + .base-left-icon-color {
  360 + border-radius: 50%;
  361 + width: 0.75rem;
  362 + height: 0.75rem;
  363 + display: block;
  364 + position: relative;
  365 + right: 0.5rem;
  366 + }
  367 +
  368 + .left-icon-d-color :extend(.base-left-icon-color) {
  369 + background-color: #5c7bd9 !important;
  370 + }
  371 + .left-icon-g-color :extend(.base-left-icon-color) {
  372 + background-color: #91cc75 !important;
  373 + }
  374 + .left-icon-s-color :extend(.base-left-icon-color) {
  375 + background-color: #fac859 !important;
  376 + }
  377 + .right-icon-d-color :extend(.base-left-icon-color) {
  378 + background-color: #5c7bd9;
  379 + }
  380 + .right-icon-g-color :extend(.base-left-icon-color) {
  381 + background-color: #91cc75;
  382 + }
  383 + .right-icon-s-color :extend(.base-left-icon-color) {
  384 + background-color: #ec4040;
  385 + }
  386 +</style>
  1 +<template>
  2 + <a-row type="flex" justify="space-between" align="middle">
  3 + <a-col
  4 + class="flex items-center"
  5 + v-for="(item, index) in seriesStatusData"
  6 + :key="index"
  7 + :span="8"
  8 + >
  9 + <a-col
  10 + :id="`chartPie${item.key}`"
  11 + style="width: 10rem; height: 12rem; position: relative; top: -0.7rem"
  12 + />
  13 + </a-col>
  14 + </a-row>
  15 +</template>
  16 +<script lang="ts">
  17 + import {
  18 + defineComponent,
  19 + PropType,
  20 + onMounted,
  21 + toRefs,
  22 + shallowReactive,
  23 + onUnmounted,
  24 + nextTick,
  25 + } from 'vue';
  26 + import * as echarts from 'echarts';
  27 + import { seriesDataT } from './props';
  28 +
  29 + export default defineComponent({
  30 + props: {
  31 + seriesStatusData: {
  32 + type: Array as PropType<seriesDataT[]>,
  33 + default: () => [],
  34 + },
  35 + },
  36 +
  37 + setup(props) {
  38 + const chartsInstance = shallowReactive<{ [key: string]: echarts.ECharts }>({});
  39 +
  40 + const { seriesStatusData } = toRefs(props);
  41 +
  42 + const total = seriesStatusData.value
  43 + .map((m) => m.value)
  44 + .reduce((prev, cur) => prev! + cur!, 0);
  45 +
  46 + nextTick(() => {
  47 + for (const item of seriesStatusData.value) {
  48 + const { key, value } = item;
  49 + //牵引线条颜色
  50 + const labelLine = {
  51 + normal: {
  52 + show: true,
  53 + length: 20,
  54 + length2: 20,
  55 + lineStyle: {
  56 + color: '#808080',
  57 + },
  58 + },
  59 + } as any;
  60 + const legendKey = key == 'onLine' ? '在线' : key == 'offLine' ? '离线' : '待激活';
  61 + chartsInstance[key!] = echarts.init(
  62 + document.getElementById(`chartPie${key}`) as HTMLElement
  63 + );
  64 + const option = {
  65 + backgroundColor: '#ffffff',
  66 + tooltip: {
  67 + trigger: 'item',
  68 + formatter: `${legendKey}设备${((value! / total!) * 100).toFixed(2)}%`,
  69 + },
  70 + graphic: [
  71 + {
  72 + type: 'text',
  73 + left: '42%',
  74 + top: '48%',
  75 + z: 10,
  76 + style: {
  77 + fill: '#1a1a1a',
  78 + text: value + '个',
  79 + font: '1.25vh Microsoft YaHei',
  80 + },
  81 + },
  82 + ],
  83 + series: [
  84 + {
  85 + data: [item],
  86 + avoidLabelOverlap: false,
  87 + type: 'pie',
  88 + radius: ['40%', '60%'],
  89 + label: {
  90 + normal: {
  91 + show: true,
  92 + position: 'outer',
  93 + formatter: `${legendKey}${((value! / total!) * 100).toFixed(2)}%`,
  94 + borderWidth: 5,
  95 + borderRadius: 0,
  96 + padding: [90, -60],
  97 + },
  98 + },
  99 + labelLine,
  100 + },
  101 + ],
  102 + };
  103 + chartsInstance[key!].setOption(option);
  104 + }
  105 + });
  106 +
  107 + const resize = (flag) => {
  108 + Object.keys(chartsInstance).forEach((key) => {
  109 + const doFuncKey = chartsInstance[key];
  110 + flag === 'r' ? doFuncKey.resize() : doFuncKey.dispose();
  111 + });
  112 + };
  113 +
  114 + onMounted(() => {
  115 + window.addEventListener('resize', () => resize('r'));
  116 + });
  117 +
  118 + onUnmounted(() => {
  119 + window.removeEventListener('resize', () => resize('d'));
  120 + });
  121 + },
  122 + });
  123 +</script>
1 -<template>  
2 - <div v-show="dataSeries.length" ref="chartRef" :style="{ height, width }"></div>  
3 - <div class="empty-box" v-show="!dataSeries.length"  
4 - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"  
5 - /></div>  
6 -</template>  
7 -<script lang="ts">  
8 - import { defineComponent, PropType, ref, Ref, onMounted, toRefs } from 'vue';  
9 - import { useECharts } from '/@/hooks/web/useECharts';  
10 - import { Empty } from 'ant-design-vue';  
11 - import { seriesDataT } from './props';  
12 -  
13 - export default defineComponent({  
14 - components: { Empty },  
15 - props: {  
16 - width: {  
17 - type: String as PropType<string>,  
18 - default: '100%',  
19 - },  
20 - height: {  
21 - type: String as PropType<string>,  
22 - default: '200px',  
23 - },  
24 - legendData: {  
25 - type: Array,  
26 - default: () => [],  
27 - },  
28 - seriesData: {  
29 - type: Array,  
30 - default: () => [],  
31 - },  
32 - isCircle: {  
33 - type: Boolean,  
34 - default: true,  
35 - },  
36 - },  
37 - setup(props) {  
38 - const { legendData, seriesData } = toRefs(props);  
39 - const dataSeries: Ref<seriesDataT[]> = ref([]);  
40 - const legendDatas: Ref<seriesDataT[]> = ref([]);  
41 - dataSeries.value = seriesData.value as unknown as seriesDataT[];  
42 - legendDatas.value = legendData.value as unknown as seriesDataT[];  
43 -  
44 - const chartRef = ref<HTMLDivElement | null>(null);  
45 - const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>);  
46 - const labelLine = {  
47 - normal: {  
48 - show: true,  
49 - length2: 1,  
50 - },  
51 - } as any;  
52 - onMounted(() => {  
53 - setOptions({  
54 - backgroundColor: '#ffffff',  
55 - tooltip: {  
56 - trigger: 'item',  
57 - formatter: '{b} {d}%',  
58 - },  
59 - legend: {  
60 - bottom: 0,  
61 - width: 500,  
62 - height: 200,  
63 - left: 'center',  
64 - data: legendDatas.value,  
65 - },  
66 - series: [  
67 - {  
68 - avoidLabelOverlap: false,  
69 - type: 'pie',  
70 - radius: props.isCircle ? '60%' : ['40%', '60%'],  
71 - data: dataSeries.value,  
72 - emphasis: {  
73 - itemStyle: {  
74 - shadowBlur: 10,  
75 - shadowOffsetX: 0,  
76 - shadowColor: 'rgba(0, 0, 0, 0.5)',  
77 - },  
78 - },  
79 - labelLine,  
80 - },  
81 - ],  
82 - });  
83 - //自适应  
84 - window.addEventListener('resize', () => resize());  
85 - });  
86 - return { chartRef, Empty, dataSeries };  
87 - },  
88 - });  
89 -</script>  
90 -  
91 -<style>  
92 - .empty-box {  
93 - display: flex;  
94 - align-items: center;  
95 - margin: 0 120px;  
96 - }  
97 -</style> 1 +<template>
  2 + <div v-show="dataSeries.length" ref="chartRef" :style="{ height, width }"></div>
  3 + <div class="empty-box" v-show="!dataSeries.length"
  4 + ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"
  5 + /></div>
  6 +</template>
  7 +<script lang="ts">
  8 + import { defineComponent, PropType, ref, Ref, onMounted, toRefs } from 'vue';
  9 + import { useECharts } from '/@/hooks/web/useECharts';
  10 + import { Empty } from 'ant-design-vue';
  11 + import { seriesDataT } from './props';
  12 +
  13 + export default defineComponent({
  14 + components: { Empty },
  15 + props: {
  16 + width: {
  17 + type: String as PropType<string>,
  18 + default: '100%',
  19 + },
  20 + height: {
  21 + type: String as PropType<string>,
  22 + default: '200px',
  23 + },
  24 + legendData: {
  25 + type: Array,
  26 + default: () => [],
  27 + },
  28 + seriesData: {
  29 + type: Array,
  30 + default: () => [],
  31 + },
  32 + isCircle: {
  33 + type: Boolean,
  34 + default: true,
  35 + },
  36 + },
  37 + setup(props) {
  38 + const { legendData, seriesData } = toRefs(props);
  39 + const dataSeries: Ref<seriesDataT[]> = ref([]);
  40 + const legendDatas: Ref<seriesDataT[]> = ref([]);
  41 + dataSeries.value = seriesData.value as unknown as seriesDataT[];
  42 + legendDatas.value = legendData.value as unknown as seriesDataT[];
  43 +
  44 + const chartRef = ref<HTMLDivElement | null>(null);
  45 + const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>);
  46 + const labelLine = {
  47 + normal: {
  48 + show: true,
  49 + length2: 1,
  50 + },
  51 + } as any;
  52 + onMounted(() => {
  53 + setOptions({
  54 + backgroundColor: '#ffffff',
  55 + tooltip: {
  56 + trigger: 'item',
  57 + formatter: '{b} {d}%',
  58 + },
  59 + series: [
  60 + {
  61 + avoidLabelOverlap: false,
  62 + type: 'pie',
  63 + radius: props.isCircle ? '60%' : ['40%', '60%'],
  64 + data: dataSeries.value,
  65 + emphasis: {
  66 + itemStyle: {
  67 + shadowBlur: 10,
  68 + shadowOffsetX: 0,
  69 + shadowColor: 'rgba(0, 0, 0, 0.5)',
  70 + },
  71 + },
  72 + labelLine,
  73 + },
  74 + ],
  75 + });
  76 + //自适应
  77 + window.addEventListener('resize', () => resize());
  78 + });
  79 + return { chartRef, Empty, dataSeries };
  80 + },
  81 + });
  82 +</script>
  83 +
  84 +<style>
  85 + .empty-box {
  86 + display: flex;
  87 + align-items: center;
  88 + margin: 0 120px;
  89 + }
  90 +</style>
1 -import { PropType } from 'vue';  
2 -export interface BasicProps {  
3 - width: string;  
4 - height: string;  
5 -}  
6 -export const basicProps = {  
7 - width: {  
8 - type: String as PropType<string>,  
9 - default: '100%',  
10 - },  
11 - height: {  
12 - type: String as PropType<string>,  
13 - default: '280px',  
14 - },  
15 -};  
16 -  
17 -export interface CardList {  
18 - deviceInfo: {  
19 - sumCount: number;  
20 - onLine: number;  
21 - offLine: number;  
22 - inActive: number;  
23 - todayAdd: number;  
24 - directConnection: number;  
25 - gateWay: number;  
26 - sensor: number;  
27 - };  
28 - tenantInfo?: { sumCount: number; todayAdd: number };  
29 - customerInfo?: { sumCount: number; todayAdd: number };  
30 - alarmInfo?: {  
31 - sumCount: number;  
32 - todayAdd: number;  
33 - };  
34 - messageInfo?: {  
35 - dataPointsCount: number;  
36 - messageCount: number;  
37 - todayDataPointsAdd: number;  
38 - todayMessageAdd: number;  
39 - };  
40 - productInfo?: {  
41 - sumCount: number;  
42 - todayAdd: number;  
43 - };  
44 -}  
45 -export type seriesDataT = {  
46 - value: number | undefined;  
47 - name: string | undefined;  
48 - itemStyle: object | undefined;  
49 - key?: string | undefined;  
50 -}; 1 +import { PropType } from 'vue';
  2 +export interface BasicProps {
  3 + width: string;
  4 + height: string;
  5 +}
  6 +export const basicProps = {
  7 + width: {
  8 + type: String as PropType<string>,
  9 + default: '100%',
  10 + },
  11 + height: {
  12 + type: String as PropType<string>,
  13 + default: '280px',
  14 + },
  15 +};
  16 +
  17 +export interface CardList {
  18 + deviceInfo: {
  19 + sumCount: number;
  20 + onLine: number;
  21 + offLine: number;
  22 + inActive: number;
  23 + todayAdd: number;
  24 + directConnection: number;
  25 + gateWay: number;
  26 + sensor: number;
  27 + };
  28 + tenantInfo?: { sumCount: number; todayAdd: number };
  29 + customerInfo?: { sumCount: number; todayAdd: number };
  30 + alarmInfo?: {
  31 + sumCount: number;
  32 + todayAdd: number;
  33 + };
  34 + messageInfo?: {
  35 + dataPointsCount: number;
  36 + messageCount: number;
  37 + todayDataPointsAdd: number;
  38 + todayMessageAdd: number;
  39 + };
  40 + productInfo?: {
  41 + sumCount: number;
  42 + todayAdd: number;
  43 + };
  44 +}
  45 +export type seriesDataT = {
  46 + value: number | undefined;
  47 + name: string | undefined;
  48 + itemStyle: object | undefined;
  49 + key?: string | undefined;
  50 +};
@@ -86,20 +86,20 @@ export const formSchema: FormSchema[] = [ @@ -86,20 +86,20 @@ export const formSchema: FormSchema[] = [
86 }, 86 },
87 }, 87 },
88 }, 88 },
89 - {  
90 - field: 'state',  
91 - label: '发布状态',  
92 - required: true,  
93 - component: 'RadioGroup',  
94 - defaultValue: Platform.PC,  
95 - componentProps: {  
96 - defaultValue: Platform.PC,  
97 - options: [  
98 - { label: '未发布', value: Platform.PC },  
99 - { label: '已发布', value: Platform.PHONE },  
100 - ],  
101 - },  
102 - }, 89 + // {
  90 + // field: 'state',
  91 + // label: '发布状态',
  92 + // required: true,
  93 + // component: 'RadioGroup',
  94 + // defaultValue: Platform.PC,
  95 + // componentProps: {
  96 + // defaultValue: Platform.PC,
  97 + // options: [
  98 + // { label: '未发布', value: Platform.PC },
  99 + // { label: '已发布', value: Platform.PHONE },
  100 + // ],
  101 + // },
  102 + // },
103 { 103 {
104 field: 'remark', 104 field: 'remark',
105 label: '备注', 105 label: '备注',
@@ -169,22 +169,35 @@ @@ -169,22 +169,35 @@
169 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" /> 169 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
170 <Tooltip title="刷新"> 170 <Tooltip title="刷新">
171 <Button type="primary" @click="getListData"> 171 <Button type="primary" @click="getListData">
172 - <ReloadOutlined @click="getListData" /> 172 + <ReloadOutlined />
173 </Button> 173 </Button>
174 </Tooltip> 174 </Tooltip>
175 </div> 175 </div>
176 </template> 176 </template>
177 <template #renderItem="{ item }"> 177 <template #renderItem="{ item }">
178 <List.Item> 178 <List.Item>
179 - <Card hoverable> 179 + <Card>
180 <template #cover> 180 <template #cover>
181 - <div class="h-full w-full !flex justify-center items-center text-center p-1"> 181 + <div class="h-full w-full hover-show-modal-content">
182 <img 182 <img
183 - class="w-full h-36" 183 + style="position: relative"
  184 + class="w-full h-45 hover-show-modal"
184 alt="example" 185 alt="example"
185 :src="item.thumbnail || configurationSrc" 186 :src="item.thumbnail || configurationSrc"
186 @click="handlePreview(item)" 187 @click="handlePreview(item)"
187 /> 188 />
  189 + <div class="masker-content">
  190 + <div class="masker-text">
  191 + <div
  192 + ><span>{{ item.name }}</span></div
  193 + >
  194 + <div>
  195 + <span class="masker-text-org"
  196 + >所属组织:{{ item.organizationDTO.name }}</span
  197 + >
  198 + </div>
  199 + </div>
  200 + </div>
188 </div> 201 </div>
189 </template> 202 </template>
190 <template class="ant-card-actions" #actions> 203 <template class="ant-card-actions" #actions>
@@ -225,17 +238,6 @@ @@ -225,17 +238,6 @@
225 :trigger="['hover']" 238 :trigger="['hover']"
226 /> 239 />
227 </template> 240 </template>
228 - <Card.Meta>  
229 - <template #title>  
230 - <span class="truncate">{{ item.name }}</span>  
231 - </template>  
232 - <template #description>  
233 - <div class="truncate h-11">  
234 - <div class="truncate">{{ item.organizationDTO.name }}</div>  
235 - <div class="truncate">{{ item.remark || '' }} </div>  
236 - </div>  
237 - </template>  
238 - </Card.Meta>  
239 </Card> 241 </Card>
240 </List.Item> 242 </List.Item>
241 </template> 243 </template>
@@ -261,4 +263,45 @@ @@ -261,4 +263,45 @@
261 .configuration-list:deep(.ant-list-empty-text) { 263 .configuration-list:deep(.ant-list-empty-text) {
262 @apply w-full h-full flex justify-center items-center; 264 @apply w-full h-full flex justify-center items-center;
263 } 265 }
  266 +
  267 + :deep(.ant-card-body) {
  268 + display: none;
  269 + }
  270 +
  271 + .masker-content {
  272 + opacity: 0;
  273 + z-index: 1000;
  274 + width: 100%;
  275 + height: 11.25rem;
  276 + background-color: rgba(0, 0, 0, 0.5);
  277 + position: absolute;
  278 + transition: opacity 0.5;
  279 + pointer-events: none;
  280 + top: 0;
  281 + left: 0;
  282 + right: 0;
  283 + bottom: 0;
  284 +
  285 + .masker-text {
  286 + color: #fff;
  287 + font-size: 16px;
  288 + display: flex;
  289 + flex-direction: column;
  290 + align-items: center;
  291 + justify-content: center;
  292 + line-height: 2.5rem;
  293 + margin: 4rem 0;
  294 +
  295 + .masker-text-org {
  296 + font-size: 12px;
  297 + position: absolute;
  298 + bottom: 0;
  299 + left: 0.8rem;
  300 + }
  301 + }
  302 + }
  303 +
  304 + .hover-show-modal-content:hover > .masker-content {
  305 + opacity: 1;
  306 + }
264 </style> 307 </style>