Commit 267f30b0f307c2cece5f6ebc61502f8a6ebb9676

Authored by ww
1 parent a9490791

chore: 删除数据看板重构后无用的文件

Showing 44 changed files with 4 additions and 4806 deletions

Too many changes to show.

To preserve performance only 44 of 59 files are displayed.

@@ -6,8 +6,6 @@ import { @@ -6,8 +6,6 @@ import {
6 EXCEPTION_COMPONENT, 6 EXCEPTION_COMPONENT,
7 PAGE_NOT_FOUND_NAME, 7 PAGE_NOT_FOUND_NAME,
8 } from '/@/router/constant'; 8 } from '/@/router/constant';
9 -import { DATA_BOARD_SHARE_URL } from '../../views/visual/board/config/config';  
10 -  
11 // 404 on a page 9 // 404 on a page
12 export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { 10 export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
13 path: '/:path(.*)*', 11 path: '/:path(.*)*',
@@ -77,13 +75,3 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { @@ -77,13 +75,3 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = {
77 }, 75 },
78 ], 76 ],
79 }; 77 };
80 -  
81 -export const DATA_BOARD_SHARE: AppRouteRecordRaw = {  
82 - path: DATA_BOARD_SHARE_URL(),  
83 - name: 'dataBoardSharePage',  
84 - component: () => import('/@/views/visual/board/detail/index.vue'),  
85 - meta: {  
86 - ignoreAuth: true,  
87 - title: '分享看板',  
88 - },  
89 -};  
1 import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; 1 import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';
2 -import { DATA_BOARD_SHARE, PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; 2 +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic';
3 import { mainOutRoutes } from './mainOut'; 3 import { mainOutRoutes } from './mainOut';
4 import { PageEnum } from '/@/enums/pageEnum'; 4 import { PageEnum } from '/@/enums/pageEnum';
5 import { t } from '/@/hooks/web/useI18n'; 5 import { t } from '/@/hooks/web/useI18n';
@@ -86,6 +86,5 @@ export const basicRoutes = [ @@ -86,6 +86,5 @@ export const basicRoutes = [
86 ...mainOutRoutes, 86 ...mainOutRoutes,
87 REDIRECT_ROUTE, 87 REDIRECT_ROUTE,
88 PAGE_NOT_FOUND_ROUTE, 88 PAGE_NOT_FOUND_ROUTE,
89 - DATA_BOARD_SHARE,  
90 PUBLIC_PAGE_ROUTER, 89 PUBLIC_PAGE_ROUTER,
91 ]; 90 ];
@@ -161,7 +161,7 @@ const transform: AxiosTransform = { @@ -161,7 +161,7 @@ const transform: AxiosTransform = {
161 throw new Error(error); 161 throw new Error(error);
162 } 162 }
163 checkStatus(error?.response?.status, msg, errorMessageMode); 163 checkStatus(error?.response?.status, msg, errorMessageMode);
164 - return Promise.reject(response.data); 164 + return Promise.reject(response?.data);
165 }, 165 },
166 }; 166 };
167 167
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 TABLE_CHART_MODE_LIST, 18 TABLE_CHART_MODE_LIST,
19 EnumTableChartMode, 19 EnumTableChartMode,
20 } from '/@/components/Widget'; 20 } from '/@/components/Widget';
21 - import { SchemaFiled } from '/@/views/visual/board/detail/config/historyTrend.config'; 21 + import { SchemaFiled } from '/@/views/visual/palette/components/HistoryTrendModal/config';
22 22
23 interface DeviceDetail { 23 interface DeviceDetail {
24 tbDeviceId: string; 24 tbDeviceId: string;
@@ -7,8 +7,8 @@ import { getDeviceAttributes } from '/@/api/dataBoard'; @@ -7,8 +7,8 @@ import { getDeviceAttributes } from '/@/api/dataBoard';
7 import { DeviceAttributeRecord } from '/@/api/dataBoard/model'; 7 import { DeviceAttributeRecord } from '/@/api/dataBoard/model';
8 import { dateUtil } from '/@/utils/dateUtil'; 8 import { dateUtil } from '/@/utils/dateUtil';
9 import { isArray } from '/@/utils/is'; 9 import { isArray } from '/@/utils/is';
10 -import { QueryWay, SchemaFiled } from '/@/views/visual/board/detail/config/historyTrend.config';  
11 import { DEFAULT_DATE_FORMAT } from '/@/views/visual/board/detail/config/util'; 10 import { DEFAULT_DATE_FORMAT } from '/@/views/visual/board/detail/config/util';
  11 +import { QueryWay, SchemaFiled } from '/@/views/visual/palette/components/HistoryTrendModal/config';
12 12
13 interface DeviceOption { 13 interface DeviceOption {
14 deviceProfileId: string; 14 deviceProfileId: string;
1 -<script lang="ts">  
2 - export default {  
3 - components: { Spin },  
4 - inheritAttrs: false,  
5 - };  
6 -</script>  
7 -<script lang="ts" setup>  
8 - import { Spin } from 'ant-design-vue';  
9 - import { RadioRecord } from '../../detail/config/util';  
10 - import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';  
11 - import { useSendCommand } from './useSendCommand';  
12 - import { ref } from 'vue';  
13 -  
14 - interface VisualComponentProps<Layout = Recordable, Value = ControlComponentValue> {  
15 - value?: Value;  
16 - layout?: Layout;  
17 - radio?: RadioRecord;  
18 - random?: boolean;  
19 - add?: (key: string, method: Fn) => void;  
20 - update?: () => void;  
21 - remove?: (key: string) => void;  
22 - }  
23 -  
24 - const props = defineProps<VisualComponentProps>();  
25 -  
26 - const emit = defineEmits(['update:value', 'change']);  
27 -  
28 - const { sendCommand } = useSendCommand();  
29 -  
30 - const loading = ref(false);  
31 - const handleChange = async (event: Event) => {  
32 - const _value = (event.target as HTMLInputElement).checked;  
33 - if (props.value) {  
34 - loading.value = true;  
35 - const flag = await sendCommand(props.value, _value);  
36 - loading.value = false;  
37 - if (!flag) {  
38 - (event.target as HTMLInputElement).checked = !_value;  
39 - return;  
40 - }  
41 - }  
42 - emit('update:value', _value);  
43 - emit('change', _value);  
44 - };  
45 -</script>  
46 -  
47 -<template>  
48 - <div class="flex flex-col justify-center">  
49 - <Spin :spinning="loading">  
50 - <label class="sliding-switch">  
51 - <input  
52 - :value="!!Number(props.value?.value)"  
53 - type="checkbox"  
54 - :checked="!!Number(props.value?.value)"  
55 - @change="handleChange"  
56 - />  
57 - <span class="slider"></span>  
58 - <span class="on">ON</span>  
59 - <span class="off">OFF</span>  
60 - </label>  
61 - <div  
62 - class="text-center mt-2 text-gray-700"  
63 - :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"  
64 - >  
65 - {{ props.value?.attributeRename || props.value?.attribute }}</div  
66 - >  
67 - </Spin>  
68 - </div>  
69 -</template>  
70 -  
71 -<style scoped lang="less">  
72 - .sliding-switch {  
73 - position: relative;  
74 - display: block;  
75 - font-weight: 700;  
76 - line-height: 40px;  
77 - width: 80px;  
78 - height: 40px;  
79 - font-size: 14px;  
80 - cursor: pointer;  
81 - user-select: none;  
82 -  
83 - input[type='checkbox'] {  
84 - display: none;  
85 - }  
86 -  
87 - .slider {  
88 - width: 80px;  
89 - height: 40px;  
90 - display: flex;  
91 - align-items: center;  
92 - box-sizing: border-box;  
93 - border: 2px solid #ecf0f3;  
94 - border-radius: 20px;  
95 - box-shadow: -2px -2px 8px #fff, -2px -2px 12px hsl(0deg 0% 100% / 50%),  
96 - inset -2px -2px 8px #fff, inset -2px -2px 12px hsl(0deg 0% 100% / 50%),  
97 - inset 2px 2px 4px hsl(0deg 0% 100% / 10%), inset 2px 2px 8px rgb(0 0 0 / 30%),  
98 - 2px 2px 8px rgb(0 0 0 / 30%);  
99 - background-color: #ecf0f3;  
100 - z-index: -1;  
101 - }  
102 -  
103 - .slider::after {  
104 - cursor: pointer;  
105 - display: block;  
106 - content: '';  
107 - width: 24px;  
108 - height: 24px;  
109 - border-radius: 50%;  
110 - margin-left: 6px;  
111 - margin-right: 6px;  
112 - background-color: #ecf0f3;  
113 - box-shadow: -2px -2px 8px #fff, -2px -2px 12px hsl(0deg 0% 100% / 50%),  
114 - inset 2px 2px 4px hsl(0deg 0% 100% / 10%), 2px 2px 8px rgb(0 0 0 / 30%);  
115 - z-index: 999;  
116 - transition: 0.5s;  
117 - }  
118 -  
119 - input:checked ~ .off {  
120 - opacity: 0;  
121 - }  
122 -  
123 - input:checked ~ .slider::after {  
124 - transform: translateX(35px);  
125 - }  
126 -  
127 - input:not(:checked) ~ .on {  
128 - opacity: 0;  
129 - transform: translateX(0);  
130 - }  
131 -  
132 - .on,  
133 - .off {  
134 - position: absolute;  
135 - top: 0;  
136 - display: inline-block;  
137 - margin-left: 3px;  
138 - width: 34px;  
139 - text-align: center;  
140 - transition: 0.2s;  
141 - }  
142 -  
143 - .on {  
144 - color: #039be5;  
145 - }  
146 -  
147 - .off {  
148 - right: 6px;  
149 - color: #999;  
150 - }  
151 - }  
152 -</style>  
1 -<script lang="ts">  
2 - export default {  
3 - inheritAttrs: false,  
4 - };  
5 -</script>  
6 -<script lang="ts" setup>  
7 - import { Switch } from 'ant-design-vue';  
8 - import { computed, ref, watchEffect } from 'vue';  
9 - import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util';  
10 - import SvgIcon from '/@/components/Icon/src/SvgIcon.vue';  
11 - import {  
12 - ControlComponentDefaultConfig,  
13 - ControlComponentValue,  
14 - ControlComponentLayout,  
15 - } from './control.config';  
16 - import { useSendCommand } from './useSendCommand';  
17 - const props = withDefaults(  
18 - defineProps<{  
19 - layout?: ControlComponentLayout;  
20 - value?: ControlComponentValue;  
21 - radio?: RadioRecord;  
22 - }>(),  
23 - {  
24 - value: () => ControlComponentDefaultConfig,  
25 - }  
26 - );  
27 - const getRadio = computed(() => {  
28 - return props.radio || DEFAULT_RADIO_RECORD;  
29 - });  
30 -  
31 - const checked = ref(!!Number(props.value.value));  
32 -  
33 - const { sendCommand } = useSendCommand();  
34 - const loading = ref(false);  
35 - const handleChange = async (value: boolean) => {  
36 - loading.value = true;  
37 - const flag = await sendCommand(props.value, value);  
38 - loading.value = false;  
39 - if (!flag) {  
40 - checked.value = !value;  
41 - }  
42 - };  
43 -  
44 - watchEffect(() => {  
45 - checked.value = !!Number(props.value.value);  
46 - });  
47 -</script>  
48 -  
49 -<template>  
50 - <div class="flex items-center w-full h-full p-4">  
51 - <div class="flex-auto flex truncate">  
52 - <SvgIcon  
53 - :name="props.value?.icon! || ControlComponentDefaultConfig.icon!"  
54 - prefix="iconfont"  
55 - :style="{  
56 - color: props.value?.iconColor || ControlComponentDefaultConfig.iconColor,  
57 - width: fontSize({ radioRecord: getRadio, basic: 30, min: 16 }),  
58 - height: fontSize({ radioRecord: getRadio, basic: 30, min: 16 }),  
59 - }"  
60 - />  
61 - <span  
62 - class="flex-auto mx-4 flex items-center truncate inline-block text-gray-700"  
63 - :style="{ color: props.value.fontColor || ControlComponentDefaultConfig.fontColor }"  
64 - >  
65 - {{ props.value.attributeRename || props.value.attribute }}  
66 - </span>  
67 - </div>  
68 - <Switch v-model:checked="checked" :loading="loading" @change="handleChange" />  
69 - </div>  
70 -</template>  
1 -<script lang="ts">  
2 - export default {  
3 - components: { Spin },  
4 - inheritAttrs: false,  
5 - };  
6 -</script>  
7 -<script lang="ts" setup>  
8 - import { computed } from '@vue/reactivity';  
9 - import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util';  
10 - import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';  
11 - import { useSendCommand } from './useSendCommand';  
12 - import { ref } from 'vue';  
13 - import { Spin } from 'ant-design-vue';  
14 -  
15 - const props = defineProps<{  
16 - value?: ControlComponentValue;  
17 - layout?: Recordable;  
18 - radio?: RadioRecord;  
19 - }>();  
20 -  
21 - const emit = defineEmits(['update:value', 'change']);  
22 -  
23 - const { sendCommand } = useSendCommand();  
24 - const loading = ref(false);  
25 - const handleChange = async (event: Event) => {  
26 - const _value = (event.target as HTMLInputElement).checked;  
27 - if (props.value) {  
28 - loading.value = true;  
29 - const flag = await sendCommand(props.value, _value);  
30 - loading.value = false;  
31 - if (!flag) {  
32 - (event.target as HTMLInputElement).checked = !_value;  
33 - return;  
34 - }  
35 - }  
36 - emit('update:value', _value);  
37 - emit('change', _value);  
38 - };  
39 -  
40 - const getRadio = computed(() => {  
41 - return props.radio! || DEFAULT_RADIO_RECORD;  
42 - });  
43 -</script>  
44 -  
45 -<template>  
46 - <div class="flex flex-col">  
47 - <Spin :spinning="loading">  
48 - <div  
49 - class="toggle-switch"  
50 - :style="{  
51 - width: fontSize({ radioRecord: getRadio, basic: 75, max: 75, min: 60 }),  
52 - height: fontSize({ radioRecord: getRadio, basic: 97.5, max: 97.5, min: 80 }),  
53 - }"  
54 - >  
55 - <label class="switch">  
56 - <input  
57 - :value="!!Number(props.value?.value)"  
58 - type="checkbox"  
59 - :checked="!!Number(props.value?.value)"  
60 - @change="handleChange"  
61 - />  
62 - <div class="button">  
63 - <div class="light"></div>  
64 - <div class="dots"></div>  
65 - <div class="characters"></div>  
66 - <div class="shine"></div>  
67 - <div class="shadow"></div>  
68 - </div>  
69 - </label>  
70 - </div>  
71 - <div  
72 - class="text-center mt-2 text-gray-700"  
73 - :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"  
74 - >  
75 - {{ props.value?.attributeRename || props.value?.attribute }}</div  
76 - >  
77 - </Spin>  
78 - </div>  
79 -</template>  
80 -  
81 -<style scoped>  
82 - .toggle-switch {  
83 - /* flex: 1 1 auto; */  
84 - max-width: 75px;  
85 -  
86 - /* height: 97.5px; */  
87 - display: flex;  
88 - }  
89 -  
90 - .switch {  
91 - background-color: black;  
92 - box-sizing: border-box;  
93 - width: 100%;  
94 - height: 100%;  
95 - box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px black, inset 0 2px 2px -2px white,  
96 - inset 0 0 2px 15px #47434c, inset 0 0 2px 22px black;  
97 - border-radius: 5px;  
98 - padding: 10px;  
99 - perspective: 700px;  
100 - }  
101 -  
102 - .switch input {  
103 - display: none;  
104 - }  
105 -  
106 - .switch input:checked + .button {  
107 - transform: translateZ(20px) rotateX(25deg);  
108 - box-shadow: 0 -5px 10px #ff1818;  
109 - }  
110 -  
111 - .switch input:checked + .button .light {  
112 - animation: flicker 0.2s infinite 0.3s;  
113 - }  
114 -  
115 - .switch input:checked + .button .shine {  
116 - opacity: 1;  
117 - }  
118 -  
119 - .switch input:checked + .button .shadow {  
120 - opacity: 0;  
121 - }  
122 -  
123 - .switch .button {  
124 - display: flex;  
125 - justify-content: center;  
126 - align-items: center;  
127 - transition: all 0.3s cubic-bezier(1, 0, 1, 1);  
128 - transform-origin: center center -20px;  
129 - transform: translateZ(20px) rotateX(-25deg);  
130 - transform-style: preserve-3d;  
131 - width: 100%;  
132 - height: 100%;  
133 - position: relative;  
134 - cursor: pointer;  
135 - background: linear-gradient(#980000 0%, #6f0000 30%, #6f0000 70%, #980000 100%);  
136 - background-color: #9b0621;  
137 - background-repeat: no-repeat;  
138 - }  
139 -  
140 - .switch .button::before {  
141 - content: '';  
142 - background: linear-gradient(  
143 - rgba(255, 255, 255, 0.8) 10%,  
144 - rgba(255, 255, 255, 0.3) 30%,  
145 - #650000 75%,  
146 - #320000  
147 - )  
148 - 50% 50%/97% 97%,  
149 - #b10000;  
150 - background-repeat: no-repeat;  
151 - width: 100%;  
152 - height: 30px;  
153 - transform-origin: top;  
154 - transform: rotateX(-90deg);  
155 - position: absolute;  
156 - top: 0;  
157 - }  
158 -  
159 - .switch .button::after {  
160 - content: '';  
161 - background-image: linear-gradient(#650000, #320000);  
162 - width: 100%;  
163 - height: 30px;  
164 - transform-origin: top;  
165 - transform: translateY(30px) rotateX(-90deg);  
166 - position: absolute;  
167 - bottom: 0;  
168 - box-shadow: 0 30px 8px 0 black, 0 60px 20px 0 rgb(0 0 0 / 50%);  
169 - }  
170 -  
171 - .switch .light {  
172 - opacity: 0;  
173 - animation: light-off 1s;  
174 - position: absolute;  
175 - width: 80%;  
176 - height: 80%;  
177 - background-image: radial-gradient(#ffc97e, transparent 40%),  
178 - radial-gradient(circle, #ff1818 50%, transparent 80%);  
179 - }  
180 -  
181 - .switch .dots {  
182 - position: absolute;  
183 - width: 100%;  
184 - height: 100%;  
185 - background-image: radial-gradient(transparent 30%, rgba(101, 0, 0, 0.7) 70%);  
186 - background-size: 10px 10px;  
187 - }  
188 -  
189 - .switch .characters {  
190 - position: absolute;  
191 - width: 100%;  
192 - height: 100%;  
193 - background: linear-gradient(white, white) 50% 20%/5% 20%,  
194 - radial-gradient(circle, transparent 50%, white 52%, white 70%, transparent 72%) 50% 80%/33%  
195 - 25%;  
196 - background-repeat: no-repeat;  
197 - }  
198 -  
199 - .switch .shine {  
200 - transition: all 0.3s cubic-bezier(1, 0, 1, 1);  
201 - opacity: 0.3;  
202 - position: absolute;  
203 - width: 100%;  
204 - height: 100%;  
205 - background: linear-gradient(white, transparent 3%) 50% 50%/97% 97%,  
206 - linear-gradient(  
207 - rgba(255, 255, 255, 0.5),  
208 - transparent 50%,  
209 - transparent 80%,  
210 - rgba(255, 255, 255, 0.5)  
211 - )  
212 - 50% 50%/97% 97%;  
213 - background-repeat: no-repeat;  
214 - }  
215 -  
216 - .switch .shadow {  
217 - transition: all 0.3s cubic-bezier(1, 0, 1, 1);  
218 - opacity: 1;  
219 - position: absolute;  
220 - width: 100%;  
221 - height: 100%;  
222 - background: linear-gradient(transparent 70%, rgba(0, 0, 0, 0.8));  
223 - background-repeat: no-repeat;  
224 - }  
225 -  
226 - @keyframes flicker {  
227 - 0% {  
228 - opacity: 1;  
229 - }  
230 -  
231 - 80% {  
232 - opacity: 0.8;  
233 - }  
234 -  
235 - 100% {  
236 - opacity: 1;  
237 - }  
238 - }  
239 -  
240 - @keyframes light-off {  
241 - 0% {  
242 - opacity: 1;  
243 - }  
244 -  
245 - 80% {  
246 - opacity: 0;  
247 - }  
248 - }  
249 -</style>  
1 -import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';  
2 -import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';  
3 -  
4 -export interface ControlComponentLayout {  
5 - [key: string]: any;  
6 -}  
7 -  
8 -export interface ControlComponentValue {  
9 - value?: boolean;  
10 - attribute?: string;  
11 - attributeRename?: string;  
12 - icon?: string;  
13 - iconColor?: string;  
14 - deviceId?: string;  
15 - fontColor?: string;  
16 - slaveDeviceId?: string;  
17 - deviceProfileId?: string;  
18 - deviceType?: DeviceTypeEnum;  
19 - organizationId?: string;  
20 -  
21 - dataSource?: DataSource;  
22 - [key: string]: any;  
23 -}  
24 -  
25 -export const ControlComponentDefaultConfig: ControlComponentValue = {  
26 - icon: 'shuiwen',  
27 - iconColor: '#367BFF',  
28 - fontColor: '#000',  
29 -};  
30 -  
31 -export const transformControlConfig = (  
32 - _ComponentConfig: Recordable,  
33 - _record: DataComponentRecord,  
34 - dataSourceRecord: DataSource  
35 -) => {  
36 - return {  
37 - value: {  
38 - ...dataSourceRecord.componentInfo,  
39 - attribute: dataSourceRecord.attribute,  
40 - attributeRename: dataSourceRecord.attributeRename,  
41 - deviceProfileId: dataSourceRecord.deviceProfileId,  
42 - deviceId: dataSourceRecord.deviceId,  
43 - deviceType: dataSourceRecord.deviceType,  
44 - slaveDeviceId: dataSourceRecord.slaveDeviceId,  
45 - organizationId: dataSourceRecord.organizationId,  
46 - dataSource: dataSourceRecord,  
47 - } as ControlComponentValue,  
48 - };  
49 -};  
1 -import { ControlComponentValue } from './control.config';  
2 -import { sendCommandOneway } from '/@/api/dataBoard';  
3 -import { useMessage } from '/@/hooks/web/useMessage';  
4 -import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';  
5 -  
6 -const { createMessage } = useMessage();  
7 -export function useSendCommand() {  
8 - const error = () => {  
9 - createMessage.error('下发指令失败');  
10 - return false;  
11 - };  
12 - const sendCommand = async (record: ControlComponentValue, value: any) => {  
13 - if (!record) return error();  
14 - const { attribute } = record;  
15 - const { dataSource } = record;  
16 - const { customCommand } = dataSource || {};  
17 -  
18 - const { deviceId } = record;  
19 - if (!deviceId) return error();  
20 - try {  
21 - let params: string | Recordable = {  
22 - [attribute!]: Number(value),  
23 - };  
24 -  
25 - // 如果是TCP设备从物模型中获取下发命令(TCP网关子设备无物模型服务与事件)  
26 - if (customCommand?.transportType === TransportTypeEnum.TCP) {  
27 - params = customCommand.command!;  
28 - }  
29 -  
30 - // 控制按钮下发命令为0 或 1  
31 - await sendCommandOneway({  
32 - deviceId,  
33 - value: {  
34 - params: params,  
35 - persistent: true,  
36 - additionalInfo: {  
37 - cmdType: 'API',  
38 - },  
39 - method: 'methodThingskit',  
40 - },  
41 - });  
42 - createMessage.success('命令下发成功');  
43 - return true;  
44 - } catch (msg) {  
45 - return error();  
46 - }  
47 - };  
48 - return {  
49 - sendCommand,  
50 - };  
51 -}  
1 -<script lang="ts">  
2 - export default {  
3 - inheritAttrs: false,  
4 - };  
5 -</script>  
6 -<script lang="ts" setup>  
7 - import type { ECharts, EChartsOption } from 'echarts';  
8 - import { watch } from 'vue';  
9 - import { nextTick, onMounted, onUnmounted, ref, unref, computed } from 'vue';  
10 - import { init } from 'echarts';  
11 - import {  
12 - DashboardComponentLayout,  
13 - DashBoardValue,  
14 - instrumentComponent1,  
15 - update_instrument_1_font,  
16 - update_instrument_2_font,  
17 - update_instrument_1_value,  
18 - update_instrument_2_value,  
19 - } from './dashBoardComponent.config';  
20 - import {  
21 - DEFAULT_RADIO_RECORD,  
22 - RadioRecord,  
23 - fontSize,  
24 - getUpdateTime,  
25 - } from '../../detail/config/util';  
26 - import { Tooltip } from 'ant-design-vue';  
27 - import { useThrottleFn } from '@vueuse/shared';  
28 - import { buildUUID } from '/@/utils/uuid';  
29 - import { FrontComponent } from '../../const/const';  
30 -  
31 - const props = withDefaults(  
32 - defineProps<{  
33 - add?: Function;  
34 - layout?: DashboardComponentLayout;  
35 - value?: DashBoardValue;  
36 - radio?: RadioRecord;  
37 - random?: boolean;  
38 - }>(),  
39 - {  
40 - layout: () => ({} as unknown as DashboardComponentLayout),  
41 - value: () => ({ id: buildUUID() }),  
42 - radio: () => DEFAULT_RADIO_RECORD,  
43 - random: true,  
44 - }  
45 - );  
46 -  
47 - const getControlsWidgetId = () => `widget-chart-${props.value.id}`;  
48 -  
49 - const chartRef = ref<Nullable<ECharts>>(null);  
50 -  
51 - function initChart() {  
52 - const chartDom = document.getElementById(getControlsWidgetId())!;  
53 - chartRef.value = init(chartDom);  
54 - const option: EChartsOption = props.layout.chartOption || instrumentComponent1();  
55 -  
56 - nextTick(() => {  
57 - option && unref(chartRef)?.setOption(option);  
58 - });  
59 - }  
60 -  
61 - const getRadio = computed(() => {  
62 - return props.radio;  
63 - });  
64 -  
65 - const getChardRadio = computed(() => {  
66 - const realWidth = unref(chartRef)?.getWidth();  
67 - const realHeight = unref(chartRef)?.getHeight();  
68 - const radioRecord = props.radio;  
69 - return {  
70 - ...radioRecord,  
71 - height: realHeight || radioRecord.height,  
72 - width: realWidth || radioRecord.height,  
73 - };  
74 - });  
75 -  
76 - const beforeUpdateFn = (componentType: FrontComponent) => {  
77 - if (componentType === 'instrument-component-1') return update_instrument_1_font;  
78 - if (componentType === 'instrument-component-2') return update_instrument_2_font;  
79 - return (_radio: RadioRecord) => {};  
80 - };  
81 -  
82 - function update() {  
83 - const option = beforeUpdateFn(props.layout.componentType);  
84 - unref(chartRef)?.setOption((option(unref(getChardRadio)) as unknown as EChartsOption) || {});  
85 - unref(chartRef)?.resize();  
86 - }  
87 -  
88 - const getUpdateValueFn = (componentType: FrontComponent) => {  
89 - if (componentType === 'instrument-component-1') return update_instrument_1_value;  
90 - if (componentType === 'instrument-component-2') return update_instrument_2_value;  
91 - return (_radio: DashBoardValue) => {};  
92 - };  
93 -  
94 - const updateChartValue = useThrottleFn(() => {  
95 - const updateFn = getUpdateValueFn(props.layout.componentType);  
96 - unref(chartRef)?.setOption((updateFn(props.value) as unknown as EChartsOption) || {});  
97 - }, 500);  
98 -  
99 - watch(() => props.value, updateChartValue);  
100 -  
101 - const updateChartFont = useThrottleFn(() => {  
102 - const option = beforeUpdateFn(props.layout.componentType);  
103 - setTimeout(() => {  
104 - unref(chartRef)?.setOption((option(unref(getChardRadio)) as unknown as EChartsOption) || {});  
105 - });  
106 - }, 500);  
107 -  
108 - watch(() => props.radio, updateChartFont);  
109 -  
110 - const updateChartType = useThrottleFn(() => {  
111 - unref(chartRef)?.clear();  
112 - unref(chartRef)?.setOption(props?.layout?.chartOption || {});  
113 - }, 500);  
114 -  
115 - watch(() => props.layout.componentType, updateChartType);  
116 -  
117 - let timeout: Nullable<number> = null;  
118 -  
119 - function handleRandomValue() {  
120 - const newValue = Math.floor(Math.random() * 100);  
121 - const updateFn = getUpdateValueFn(props.layout.componentType);  
122 - unref(chartRef)?.setOption(  
123 - (updateFn({ ...props.value, value: newValue }) as unknown as EChartsOption) || {}  
124 - );  
125 - }  
126 -  
127 - onMounted(() => {  
128 - initChart();  
129 - props.add && props.add(props.value.id, update);  
130 - if (props.random) timeout = setInterval(handleRandomValue, 2000) as unknown as number;  
131 - });  
132 -  
133 - onUnmounted(() => {  
134 - unref(chartRef)?.clear();  
135 - clearInterval(timeout as number);  
136 - timeout = null;  
137 - });  
138 -  
139 - defineExpose({ update });  
140 -</script>  
141 -  
142 -<template>  
143 - <div class="flex flex-col w-full h-full min-w-3 min-h-3">  
144 - <div :id="getControlsWidgetId()" class="widget-charts w-full h-full flex-auto"></div>  
145 - <div>  
146 - <div  
147 - class="text-center"  
148 - :style="{  
149 - fontSize: fontSize({ radioRecord: getRadio, basic: 16, max: 18 }),  
150 - color: '#666',  
151 - }"  
152 - >  
153 - {{ props.value.name }}  
154 - </div>  
155 -  
156 - <div  
157 - class="text-xs text-center p-5"  
158 - :style="{  
159 - fontSize: fontSize({ radioRecord: getRadio, basic: 12, max: 12 }),  
160 - color: '#999',  
161 - }"  
162 - >  
163 - <Tooltip placement="top" :title="getUpdateTime(props.value?.updateTime)">  
164 - <div class="truncate">  
165 - <span class="mr-2">更新时间:</span>  
166 - <span>  
167 - {{ getUpdateTime(props.value?.updateTime) }}  
168 - </span>  
169 - </div>  
170 - </Tooltip>  
171 - </div>  
172 - </div>  
173 - </div>  
174 -</template>  
175 -  
176 -<style scoped>  
177 - .widget-charts > div {  
178 - width: 100%;  
179 - height: 100%;  
180 - }  
181 -</style>  
1 -<script lang="ts">  
2 - export default {  
3 - inheritAttrs: false,  
4 - };  
5 -</script>  
6 -<script lang="ts" setup>  
7 - import { computed, onMounted, onUnmounted, ref, unref } from 'vue';  
8 - import { Space, Tooltip } from 'ant-design-vue';  
9 - import {  
10 - DigitalComponentDefaultConfig,  
11 - DigitalDashBoardLayout,  
12 - DigitalDashBoardValue,  
13 - } from './digitalDashBoard.config';  
14 - import {  
15 - fontSize,  
16 - RadioRecord,  
17 - getUpdateTime,  
18 - DEFAULT_RADIO_RECORD,  
19 - DEFAULT_ANIMATION_INTERVAL,  
20 - } from '../../detail/config/util';  
21 - import { isNaN } from 'lodash';  
22 -  
23 - const props = withDefaults(  
24 - defineProps<{  
25 - layout?: DigitalDashBoardLayout;  
26 - value?: DigitalDashBoardValue;  
27 - radio?: RadioRecord;  
28 - random?: boolean;  
29 - }>(),  
30 - {  
31 - value: () => ({}),  
32 - layout: () => ({ max: 5, keepNumber: 2 }),  
33 - }  
34 - );  
35 -  
36 - const changeValue = ref(0);  
37 -  
38 - const getPropsValue = computed(() => {  
39 - return { value: unref(changeValue), ...DigitalComponentDefaultConfig, ...props.value };  
40 - });  
41 -  
42 - const integerPart = computed(() => {  
43 - let { value = 0 } = unref(getPropsValue);  
44 - const { max = 5 } = props.layout;  
45 - if (isNaN(value)) value = 0;  
46 - let _value = Number(value).toFixed(2).split('.')[0];  
47 - if (_value.length < max) _value = _value.padStart(5, '0');  
48 - if (_value.length > max) _value = ''.padStart(5, '9');  
49 -  
50 - return _value;  
51 - });  
52 -  
53 - const decimalPart = computed(() => {  
54 - let { value = 0 } = unref(getPropsValue);  
55 - const { keepNumber = 2 } = props.layout;  
56 - if (isNaN(value)) value = 0;  
57 - let _value = Number(value)?.toFixed(2).split('.')[1];  
58 - if (_value.length < keepNumber) _value = _value.padStart(5, '0');  
59 -  
60 - if (_value.length > keepNumber) _value = ''.padStart(5, '0');  
61 -  
62 - return _value;  
63 - });  
64 -  
65 - const getRadio = computed(() => {  
66 - return props.radio || DEFAULT_RADIO_RECORD;  
67 - });  
68 -  
69 - let timeout: Nullable<number> = null;  
70 -  
71 - const handleRandom = () => {  
72 - const newValue = Math.floor(Math.random() * 100);  
73 - changeValue.value = newValue;  
74 - };  
75 -  
76 - const getScale = computed(() => {  
77 - const { width } = props.radio || DEFAULT_RADIO_RECORD;  
78 - return width / 360 > 1 ? 1 : width / 360;  
79 - });  
80 -  
81 - onMounted(() => {  
82 - if (props.random)  
83 - timeout = setInterval(handleRandom, DEFAULT_ANIMATION_INTERVAL) as unknown as number;  
84 - });  
85 -  
86 - onUnmounted(() => {  
87 - clearInterval(timeout as number);  
88 - timeout = null;  
89 - });  
90 -</script>  
91 -  
92 -<template>  
93 - <section class="w-full h-full">  
94 - <div class="flex flex-col w-full h-full">  
95 - <div class="flex-1 flex justify-center items-center">  
96 - <div class="flex px-4 items-center" :style="{ transform: `scale(${getScale})` }">  
97 - <Space  
98 - justify="end"  
99 - class="justify-end"  
100 - :size="4"  
101 - :style="{  
102 - backgroundColor: '#585357',  
103 - padding: fontSize({ radioRecord: getRadio, basic: 10 }),  
104 - }"  
105 - >  
106 - <div  
107 - v-for="number in integerPart"  
108 - :key="number"  
109 - class="digital-wrapper__int"  
110 - :style="{  
111 - color: getPropsValue.fontColor,  
112 - fontSize: fontSize({ radioRecord: getRadio, basic: 20, max: 20 }),  
113 - padding: fontSize({ radioRecord: getRadio, basic: 5 }),  
114 - }"  
115 - >  
116 - <div class="digital-text__int p-1 text-light-50"> {{ number }}</div>  
117 - </div>  
118 - </Space>  
119 - <div  
120 - class="m-0.5 rounded-1/2"  
121 - style="background-color: #333; width: 6px; height: 6px; align-self: flex-end"  
122 - >  
123 - </div>  
124 - <Space  
125 - justify="end"  
126 - class="justify-end"  
127 - :size="4"  
128 - :style="{  
129 - backgroundColor: '#b74940',  
130 - padding: fontSize({ radioRecord: getRadio, basic: 10 }),  
131 - }"  
132 - >  
133 - <div  
134 - v-for="number in decimalPart"  
135 - :key="number"  
136 - class="digital-wrapper__float"  
137 - :style="{  
138 - color: getPropsValue.fontColor,  
139 - fontSize: fontSize({ radioRecord: getRadio, basic: 20, max: 20 }),  
140 - padding: fontSize({ radioRecord: getRadio, basic: 5 }),  
141 - }"  
142 - >  
143 - <div class="digital-text__float p-1 text-light-50">  
144 - {{ number }}  
145 - </div>  
146 - </div>  
147 - </Space>  
148 - <div  
149 - class="px-1 font-bold"  
150 - :style="{ fontSize: fontSize({ radioRecord: getRadio, basic: 18, max: 18 }) }"  
151 - >  
152 - {{ getPropsValue.unit }}  
153 - </div>  
154 - </div>  
155 - </div>  
156 -  
157 - <div  
158 - class="text-center truncate"  
159 - :style="{ fontSize: fontSize({ radioRecord: getRadio, basic: 18 }) }"  
160 - >  
161 - <span>{{ props.value.name || '电表' }}</span>  
162 - </div>  
163 -  
164 - <div  
165 - class="text-center text-xs p-5"  
166 - :style="{  
167 - fontSize: fontSize({ radioRecord: getRadio, basic: 12, max: 16 }),  
168 - color: '#999',  
169 - }"  
170 - >  
171 - <Tooltip placement="top" :title="getUpdateTime(props.value?.updateTime)">  
172 - <div class="truncate">  
173 - <span class="mr-1">更新时间:</span>  
174 - <span>  
175 - {{ getUpdateTime(props.value?.updateTime) }}  
176 - </span>  
177 - </div>  
178 - </Tooltip>  
179 - </div>  
180 - </div>  
181 - <div></div>  
182 - </section>  
183 -</template>  
184 -  
185 -<style scoped lang="less">  
186 - .digital-wrapper__int {  
187 - border-radius: 1px;  
188 - box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.7);  
189 - background: url('/@/assets/images/digital-wrapper-bg-int.png') 0 -1px no-repeat;  
190 - padding: 5px;  
191 - background-size: 100% 100%;  
192 - }  
193 -  
194 - .digital-text_int {  
195 - display: inline-block;  
196 - overflow-wrap: break-word;  
197 - color: rgba(255, 255, 255, 1);  
198 - white-space: nowrap;  
199 - text-align: center;  
200 - }  
201 -  
202 - .digital-wrapper__float {  
203 - border-radius: 1px;  
204 - box-shadow: inset 0 1px 3px 0 rgba(112, 22, 15, 1);  
205 - background: url('/@/assets/images/digital-wrapper-bg-float.png') 0 -1px no-repeat;  
206 - padding: 5px;  
207 - background-size: 100% 100%;  
208 - }  
209 -  
210 - .digital-text_float {  
211 - display: inline-block;  
212 - overflow-wrap: break-word;  
213 - color: rgba(255, 255, 255, 1);  
214 - white-space: nowrap;  
215 - text-align: center;  
216 - }  
217 -</style>  
1 -import { EChartsOption } from 'echarts';  
2 -import { FrontComponent, Gradient, GradientColor } from '../../const/const';  
3 -import { fontSize, RadioRecord } from '../../detail/config/util';  
4 -import {  
5 - ComponentInfo,  
6 - DataComponentRecord,  
7 - DataSource,  
8 - GradientInfo,  
9 -} from '/@/api/dataBoard/model';  
10 -import { isArray } from '/@/utils/is';  
11 -import { buildUUID } from '/@/utils/uuid';  
12 -  
13 -export interface GradientInfoRecord {  
14 - key: Gradient;  
15 - value: number;  
16 - color: string;  
17 -}  
18 -  
19 -export interface DashBoardValue {  
20 - id: string;  
21 - unit?: string;  
22 - name?: string;  
23 - updateTime?: string;  
24 - value?: number;  
25 - fontColor?: string;  
26 - gradientInfo?: GradientInfoRecord[];  
27 -}  
28 -  
29 -export interface DashboardComponentLayout {  
30 - chartOption: EChartsOption;  
31 - componentType: FrontComponent;  
32 -}  
33 -  
34 -export const instrumentComponent1 = (params?: Partial<ComponentInfo>): EChartsOption => {  
35 - const { value = 10, unit = '°C' } = params || {};  
36 - return {  
37 - series: [  
38 - {  
39 - type: 'gauge',  
40 - radius: '50%',  
41 - center: ['50%', '60%'],  
42 - startAngle: 200,  
43 - endAngle: -20,  
44 - min: 0,  
45 - max: 100,  
46 - splitNumber: 10,  
47 - itemStyle: {  
48 - color: '#FFAB91',  
49 - },  
50 - progress: {  
51 - show: true,  
52 - width: 30,  
53 - },  
54 - pointer: {  
55 - show: false,  
56 - },  
57 - axisLine: {  
58 - lineStyle: {  
59 - width: 30,  
60 - },  
61 - },  
62 - axisTick: {  
63 - distance: -35,  
64 - splitNumber: 5,  
65 - lineStyle: {  
66 - width: 2,  
67 - color: '#999',  
68 - },  
69 - },  
70 - splitLine: {  
71 - distance: -40,  
72 - length: 10,  
73 - lineStyle: {  
74 - width: 3,  
75 - color: '#999',  
76 - },  
77 - },  
78 - axisLabel: {  
79 - distance: 0,  
80 - color: '#999',  
81 - },  
82 - anchor: {  
83 - show: false,  
84 - },  
85 - title: {  
86 - show: false,  
87 - },  
88 - detail: {  
89 - valueAnimation: true,  
90 - width: '60%',  
91 - lineHeight: 10,  
92 - borderRadius: 8,  
93 - offsetCenter: [0, '30%'],  
94 - fontSize: 14,  
95 - fontWeight: 'bolder',  
96 - formatter: `{value} ${unit ?? ''}`,  
97 - color: params?.fontColor || 'inherit',  
98 - },  
99 - data: [  
100 - {  
101 - value: value as number,  
102 - },  
103 - ],  
104 - },  
105 - {  
106 - type: 'gauge',  
107 - radius: '50%',  
108 - center: ['50%', '60%'],  
109 - startAngle: 200,  
110 - endAngle: -20,  
111 - min: 0,  
112 - max: 100,  
113 - itemStyle: {  
114 - color: '#FD7347',  
115 - },  
116 - progress: {  
117 - show: true,  
118 - width: 8,  
119 - },  
120 - pointer: {  
121 - show: false,  
122 - },  
123 - axisLine: {  
124 - show: false,  
125 - },  
126 - axisTick: {  
127 - show: false,  
128 - },  
129 - splitLine: {  
130 - show: false,  
131 - },  
132 - axisLabel: {  
133 - show: false,  
134 - },  
135 - detail: {  
136 - show: false,  
137 - },  
138 - data: [  
139 - {  
140 - value: value as number,  
141 - },  
142 - ],  
143 - },  
144 - ],  
145 - };  
146 -};  
147 -  
148 -export const instrumentComponent2 = (params?: Partial<ComponentInfo>): EChartsOption => {  
149 - const { gradientInfo = [], value = 0, unit = 'km/h' } = params || {};  
150 - const firstRecord = getGradient(Gradient.FIRST, gradientInfo);  
151 - const secondRecord = getGradient(Gradient.SECOND, gradientInfo);  
152 - const thirdRecord = getGradient(Gradient.THIRD, gradientInfo);  
153 -  
154 - let max = thirdRecord?.value || secondRecord?.value || firstRecord?.value || 70;  
155 - max = Number(1 + Array(String(max).length).fill(0).join(''));  
156 -  
157 - const firstGradient = firstRecord?.value ? firstRecord.value / max : 0.3;  
158 - const secondGradient = secondRecord?.value ? secondRecord.value / max : 0.7;  
159 -  
160 - return {  
161 - series: [  
162 - {  
163 - type: 'gauge',  
164 - min: 0,  
165 - max,  
166 - axisLine: {  
167 - lineStyle: {  
168 - width: 20,  
169 - color: [  
170 - [firstGradient, firstRecord?.color || GradientColor.FIRST],  
171 - [secondGradient, secondRecord?.color || GradientColor.SECOND],  
172 - [1, thirdRecord?.color || GradientColor.THIRD],  
173 - ],  
174 - },  
175 - },  
176 - pointer: {  
177 - itemStyle: {  
178 - color: 'inherit',  
179 - },  
180 - },  
181 - axisTick: {  
182 - distance: -30,  
183 - length: 8,  
184 - splitNumber: max / 100,  
185 - lineStyle: {  
186 - color: '#fff',  
187 - width: 2,  
188 - },  
189 - },  
190 - splitLine: {  
191 - distance: -10,  
192 - length: 30,  
193 - lineStyle: {  
194 - color: '#fff',  
195 - width: 4,  
196 - },  
197 - },  
198 - axisLabel: {  
199 - color: 'inherit',  
200 - distance: 5,  
201 - fontSize: 6,  
202 - },  
203 - detail: {  
204 - valueAnimation: true,  
205 - formatter: `{value} ${unit ?? ''}`,  
206 - color: params?.fontColor || 'inherit',  
207 - offsetCenter: [0, '70%'],  
208 - fontSize: 14,  
209 - },  
210 - data: [  
211 - {  
212 - value: value as number,  
213 - },  
214 - ],  
215 - },  
216 - ],  
217 - };  
218 -};  
219 -  
220 -export const getGradient = (key: Gradient, record: GradientInfo[] = []) => {  
221 - if (!isArray(record)) return;  
222 - return record.find((item) => item.key === key);  
223 -};  
224 -  
225 -export const update_instrument_1_font = (radioRecord: RadioRecord) => {  
226 - const basicFontSize = fontSize({ radioRecord, basic: 16, max: 16, min: 12 });  
227 - return {  
228 - series: [  
229 - {  
230 - axisLabel: {  
231 - fontSize: basicFontSize,  
232 - },  
233 - detail: {  
234 - fontSize: basicFontSize,  
235 - },  
236 - },  
237 - ],  
238 - } as EChartsOption;  
239 -};  
240 -  
241 -export const update_instrument_2_font = (radioRecord: RadioRecord) => {  
242 - const axisLabelFontSize = fontSize({ radioRecord, basic: 10, max: 16 });  
243 - const detailFontSize = fontSize({ radioRecord, basic: 16, max: 16, min: 10 });  
244 - return {  
245 - series: [  
246 - {  
247 - axisLabel: {  
248 - fontSize: axisLabelFontSize,  
249 - },  
250 - detail: {  
251 - fontSize: detailFontSize,  
252 - },  
253 - },  
254 - ],  
255 - } as EChartsOption;  
256 -};  
257 -  
258 -const handleValue = (value: any) => {  
259 - return isNaN(value) ? 0 : Number(value).toFixed(2);  
260 -};  
261 -  
262 -export const update_instrument_1_value = (params: DashBoardValue) => {  
263 - const { value = 0, unit = '°C', fontColor } = params;  
264 - let max =  
265 - value > 1  
266 - ? Number(  
267 - 1 +  
268 - Array(String(Math.floor(value)).length)  
269 - .fill(0)  
270 - .join('')  
271 - ) / 2  
272 - : 100 / 2;  
273 - max = value > max ? max * 2 : max;  
274 -  
275 - return {  
276 - series: [  
277 - {  
278 - max: max < 100 ? 100 : max,  
279 - data: [{ value: handleValue(value) }],  
280 - detail: {  
281 - formatter: `{value} ${unit ?? ''}`,  
282 - color: fontColor || 'inherit',  
283 - },  
284 - },  
285 - {  
286 - max: max < 100 ? 100 : max,  
287 - data: [{ value: handleValue(value) }],  
288 - },  
289 - ],  
290 - } as EChartsOption;  
291 -};  
292 -  
293 -export const update_instrument_2_value = (params: DashBoardValue) => {  
294 - const { value = 0, unit = 'km/h', fontColor, gradientInfo } = params;  
295 - const firstRecord = getGradient(Gradient.FIRST, gradientInfo);  
296 - const secondRecord = getGradient(Gradient.SECOND, gradientInfo);  
297 - const thirdRecord = getGradient(Gradient.THIRD, gradientInfo);  
298 -  
299 - let max = thirdRecord?.value || secondRecord?.value || firstRecord?.value || 70;  
300 - max = Number(  
301 - 1 +  
302 - Array(String(Math.floor(max)).length)  
303 - .fill(0)  
304 - .join('')  
305 - );  
306 -  
307 - max =  
308 - value > 1  
309 - ? Number(  
310 - 1 +  
311 - Array(String(Math.floor(value)).length)  
312 - .fill(0)  
313 - .join('')  
314 - ) / 2  
315 - : 100 / 2;  
316 - max = value > max ? max * 2 : max;  
317 -  
318 - const firstGradient = firstRecord?.value ? firstRecord.value / max : 0.3;  
319 - const secondGradient = secondRecord?.value ? secondRecord.value / max : 0.7;  
320 - return {  
321 - series: [  
322 - {  
323 - max: max < 100 ? 100 : max,  
324 - data: [{ value: handleValue(value) }],  
325 - detail: {  
326 - formatter: `{value} ${unit ?? ''}`,  
327 - color: fontColor || 'inherit',  
328 - },  
329 - axisLine: {  
330 - lineStyle: {  
331 - width: 20,  
332 - color: [  
333 - [firstGradient, firstRecord?.color || GradientColor.FIRST],  
334 - [secondGradient, secondRecord?.color || GradientColor.SECOND],  
335 - [1, thirdRecord?.color || GradientColor.THIRD],  
336 - ],  
337 - },  
338 - },  
339 - },  
340 - ],  
341 - } as EChartsOption;  
342 -};  
343 -  
344 -function setGradientInfo(dataSource: DataSource) {  
345 - const componentInfo = dataSource.componentInfo;  
346 - return instrumentComponent2(componentInfo);  
347 -}  
348 -  
349 -export const Instrument1DefaultConfig: Partial<ComponentInfo> = {  
350 - fontColor: '#FD7347',  
351 -};  
352 -  
353 -export const Instrument2DefaultConfig: Partial<ComponentInfo> = {  
354 - fontColor: GradientColor.FIRST,  
355 - unit: 'km/h',  
356 - gradientInfo: [  
357 - { key: Gradient.FIRST, value: 30, color: GradientColor.FIRST },  
358 - { key: Gradient.SECOND, value: 70, color: GradientColor.SECOND },  
359 - { key: Gradient.THIRD, value: 80, color: GradientColor.THIRD },  
360 - ],  
361 -};  
362 -  
363 -export const transformDashboardComponentConfig = (  
364 - config: DashboardComponentLayout,  
365 - _record: DataComponentRecord,  
366 - dataSourceRecord: DataSource  
367 -) => {  
368 - let chartOption = config.chartOption;  
369 - if (config.componentType === 'instrument-component-2') {  
370 - chartOption = setGradientInfo(dataSourceRecord);  
371 - }  
372 - if (config.componentType === 'instrument-component-1') {  
373 - const componentInfo = dataSourceRecord.componentInfo;  
374 - chartOption = instrumentComponent1({  
375 - unit: componentInfo.unit,  
376 - value: 0,  
377 - fontColor: componentInfo.fontColor,  
378 - });  
379 - }  
380 - return {  
381 - layout: {  
382 - chartOption: chartOption,  
383 - componentType: config.componentType,  
384 - } as DashboardComponentLayout,  
385 - value: {  
386 - id: buildUUID(),  
387 - name: dataSourceRecord.attributeRename || dataSourceRecord.attribute,  
388 - value: dataSourceRecord.componentInfo.value,  
389 - unit: dataSourceRecord.componentInfo.unit,  
390 - updateTime: dataSourceRecord.componentInfo.updateTime,  
391 - fontColor: dataSourceRecord.componentInfo.fontColor,  
392 - gradientInfo: dataSourceRecord.componentInfo.gradientInfo,  
393 - },  
394 - };  
395 -};  
1 -import { ComponentInfo } from '/@/api/dataBoard/model';  
2 -  
3 -export interface DigitalDashBoardLayout {  
4 - max: number;  
5 - keepNumber: number;  
6 -}  
7 -  
8 -export interface DigitalDashBoardValue {  
9 - unit?: string;  
10 - name?: string;  
11 - updateTime?: string;  
12 - value?: number;  
13 - fontColor?: string;  
14 -}  
15 -  
16 -export const DigitalComponentDefaultConfig: Partial<ComponentInfo> = {  
17 - fontColor: '#000',  
18 - unit: 'kw/h',  
19 -};  
1 -import { EChartsOption } from 'echarts';  
2 -import { InstrumentComponentType } from './dashBoardComponent.config';  
3 -  
4 -export interface DashboardComponentLayout {  
5 - chartOption: EChartsOption;  
6 - componentType: InstrumentComponentType;  
7 -}  
1 -<script lang="ts" setup>  
2 - import { BasicForm, useForm } from '/@/components/Form';  
3 - import { BasicModal, useModalInner } from '/@/components/Modal';  
4 - import {  
5 - formSchema,  
6 - getHistorySearchParams,  
7 - SchemaFiled,  
8 - } from '../../../board/detail/config/historyTrend.config';  
9 - import { HistoryModalOkEmitParams, HistoryModalParams } from './type';  
10 - import { DataSource } from '/@/api/dataBoard/model';  
11 - import { ref } from 'vue';  
12 - import { getAllDeviceByOrg } from '/@/api/dataBoard';  
13 - import { getDeviceHistoryInfo } from '/@/api/alarm/position';  
14 -  
15 - const emit = defineEmits(['register', 'ok']);  
16 -  
17 - const [registerForm, { updateSchema, setFieldsValue, validate, getFieldsValue }] = useForm({  
18 - schemas: formSchema(),  
19 - showActionButtonGroup: false,  
20 - fieldMapToTime: [  
21 - [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:mm:ss'],  
22 - ],  
23 - });  
24 -  
25 - const [registerModal, { closeModal }] = useModalInner(async (params: HistoryModalParams) => {  
26 - try {  
27 - const { dataSource = [] } = params;  
28 - const deviceRecord = dataSource?.at(0) || ({} as DataSource);  
29 - if (!deviceRecord.organizationId) return;  
30 - const deviceList = await getAllDeviceByOrg(  
31 - deviceRecord.organizationId,  
32 - deviceRecord.deviceProfileId  
33 - );  
34 - const options = deviceList  
35 - .filter((item) => item.tbDeviceId === deviceRecord.deviceId)  
36 - .map((item) => ({ ...item, label: item.name, value: item.tbDeviceId }));  
37 - const attKey = dataSource.map((item) => ({  
38 - ...item,  
39 - label: item.attribute,  
40 - value: item.attribute,  
41 - }));  
42 - updateSchema([  
43 - {  
44 - field: SchemaFiled.DEVICE_ID,  
45 - componentProps: {  
46 - options,  
47 - },  
48 - },  
49 - {  
50 - field: SchemaFiled.KEYS,  
51 - component: 'Select',  
52 - defaultValue: attKey.map((item) => item.value),  
53 - componentProps: {  
54 - options: attKey,  
55 - mode: 'multiple',  
56 - disabled: true,  
57 - },  
58 - },  
59 - ]);  
60 -  
61 - setFieldsValue({  
62 - [SchemaFiled.DEVICE_ID]: deviceRecord.deviceId,  
63 - [SchemaFiled.KEYS]: attKey.map((item) => item.value),  
64 - });  
65 - } catch (error) {  
66 - throw error;  
67 - }  
68 - });  
69 -  
70 - const loading = ref(false);  
71 - const handleOk = async () => {  
72 - try {  
73 - await validate();  
74 - let value = getFieldsValue();  
75 -  
76 - value = getHistorySearchParams(value);  
77 -  
78 - loading.value = true;  
79 -  
80 - const res = await getDeviceHistoryInfo({  
81 - ...value,  
82 - [SchemaFiled.KEYS]: value[SchemaFiled.KEYS].join(','),  
83 - });  
84 -  
85 - let timespanList = Object.keys(res).reduce((prev, next) => {  
86 - const ts = res[next].map((item) => item.ts);  
87 - return [...prev, ...ts];  
88 - }, [] as number[]);  
89 - timespanList = [...new Set(timespanList)];  
90 -  
91 - const track: Record<'lng' | 'lat', number>[] = [];  
92 - const keys = Object.keys(res);  
93 -  
94 - for (const ts of timespanList) {  
95 - const list: { ts: number; value: number }[] = [];  
96 - for (const key of keys) {  
97 - const record = res[key].find((item) => ts === item.ts);  
98 - list.push(record as any);  
99 - }  
100 - if (list.every(Boolean)) {  
101 - const lng = list.at(0)?.value;  
102 - const lat = list.at(1)?.value;  
103 - if (lng && lat) track.push({ lng, lat });  
104 - }  
105 - }  
106 - emit('ok', { track, value } as HistoryModalOkEmitParams);  
107 - closeModal();  
108 - } catch (error) {  
109 - throw error;  
110 - } finally {  
111 - loading.value = false;  
112 - }  
113 - };  
114 -</script>  
115 -  
116 -<template>  
117 - <BasicModal  
118 - title="历史轨迹"  
119 - @register="registerModal"  
120 - @ok="handleOk"  
121 - :ok-button-props="{ loading }"  
122 - >  
123 - <BasicForm @register="registerForm" />  
124 - </BasicModal>  
125 -</template>  
1 -<script lang="ts">  
2 - export default {  
3 - components: { Spin },  
4 - inheritAttrs: false,  
5 - };  
6 -</script>  
7 -<script lang="ts" setup>  
8 - import { computed, onMounted, reactive, ref, unref, watchEffect } from 'vue';  
9 - import { RadioRecord } from '../../detail/config/util';  
10 - import { MapComponentLayout, MapComponentValue } from './map.config';  
11 - import {  
12 - ClockCircleOutlined,  
13 - PlayCircleOutlined,  
14 - PauseCircleOutlined,  
15 - } from '@ant-design/icons-vue';  
16 - import { Button, Spin, Tooltip } from 'ant-design-vue';  
17 - import { FrontComponent } from '../../const/const';  
18 - import { buildUUID } from '/@/utils/uuid';  
19 - import { useModal } from '/@/components/Modal';  
20 - import HistoryDataModel from './HistoryDataModel.vue';  
21 - import { HistoryModalOkEmitParams, HistoryModalParams } from './type';  
22 - import { formatToDateTime } from '/@/utils/dateUtil';  
23 - import { isEqual } from 'lodash-es';  
24 - import { useAsyncQueue } from '/@/views/device/localtion/useAsyncQueue';  
25 - import { useMessage } from '/@/hooks/web/useMessage';  
26 -  
27 - // useVisualBoardContext();  
28 - type TrackRecord = Record<'lng' | 'lat' | 'ts', number>;  
29 -  
30 - const startMethodName = `trackPlayMethod_${buildUUID()}`;  
31 -  
32 - const wrapId = `bai-map-${buildUUID()}`;  
33 -  
34 - enum TrackAnimationStatus {  
35 - PLAY = 1,  
36 - DONE = 2,  
37 - PAUSE = 3,  
38 - }  
39 -  
40 - const props = withDefaults(  
41 - defineProps<{  
42 - value?: MapComponentValue;  
43 - layout?: MapComponentLayout;  
44 - radio?: RadioRecord;  
45 - random?: boolean;  
46 - }>(),  
47 - {  
48 - random: true,  
49 - }  
50 - );  
51 -  
52 - const wrapRef = ref<HTMLDivElement | null>(null);  
53 - const trackAni = ref<Nullable<any>>(null);  
54 - let mapInstance: Nullable<Recordable> = null;  
55 -  
56 - const trackList = ref<TrackRecord[]>([]);  
57 -  
58 - watchEffect(() => {  
59 - if (  
60 - props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_REAL &&  
61 - props.value?.track?.length  
62 - ) {  
63 - const lng = props.value.track.at(0)!;  
64 - const lat = props.value.track.at(1)!;  
65 - const record = {  
66 - lng: lng.value,  
67 - lat: lat.value,  
68 - ts: lng.ts,  
69 - };  
70 - if (unref(trackList).length && isEqual(unref(trackList).at(-1), record)) return;  
71 - marketPoint({ lat: lat.value, lng: lng.value });  
72 - trackList.value.push(record);  
73 -  
74 - randomAnimation(unref(trackList));  
75 - // marketPoint(record);  
76 - }  
77 - });  
78 -  
79 - function marketPoint(params: Record<'lng' | 'lat', number>) {  
80 - const { lng, lat } = params;  
81 - const BMap = (window as any).BMapGL;  
82 - const marker = new BMap.Marker(new BMap.Point(lng, lat));  
83 - unref(mapInstance)?.centerAndZoom(new BMap.Point(lng, lat));  
84 - unref(mapInstance)?.addOverlay(marker);  
85 - }  
86 -  
87 - const prepare = ref(false);  
88 - async function initMap() {  
89 - const wrapEl = unref(wrapRef);  
90 - if (!wrapEl) return;  
91 - const BMapGL = (window as any).BMapGL;  
92 - mapInstance = new BMapGL.Map(wrapId);  
93 - const point = new BMapGL.Point(104.09457, 30.53189);  
94 - mapInstance!.centerAndZoom(point, 15);  
95 - mapInstance!.enableScrollWheelZoom(true);  
96 - props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY &&  
97 - props.random &&  
98 - randomAnimation();  
99 - }  
100 -  
101 - const randomAnimation = (path?: Record<'lng' | 'lat', number>[], clearOverlays = true) => {  
102 - path = path || [  
103 - {  
104 - lng: 116.297611,  
105 - lat: 40.047363,  
106 - },  
107 - {  
108 - lng: 116.302839,  
109 - lat: 40.048219,  
110 - },  
111 - {  
112 - lng: 116.308301,  
113 - lat: 40.050566,  
114 - },  
115 - {  
116 - lng: 116.305732,  
117 - lat: 40.054957,  
118 - },  
119 - {  
120 - lng: 116.304754,  
121 - lat: 40.057953,  
122 - },  
123 - {  
124 - lng: 116.306487,  
125 - lat: 40.058312,  
126 - },  
127 - {  
128 - lng: 116.307223,  
129 - lat: 40.056379,  
130 - },  
131 - ];  
132 -  
133 - const point: any[] = [];  
134 - const BMapGL = (window as any).BMapGL;  
135 -  
136 - clearOverlays && unref(mapInstance)?.clearOverlays();  
137 -  
138 - for (const { lng, lat } of path) {  
139 - point.push(new BMapGL.Point(lng, lat));  
140 - }  
141 -  
142 - const pl = new BMapGL.Polyline(point);  
143 - const BMapGLLib = (window as any).BMapGLLib;  
144 -  
145 - marketPoint({ lat: path.at(0)?.lat, lng: path.at(0)?.lng });  
146 - const dynamicPlayMethod = {  
147 - [startMethodName]() {  
148 - const duration = 5000;  
149 - const delay = 300;  
150 - trackAni.value = new BMapGLLib.TrackAnimation(unref(mapInstance), pl, {  
151 - overallView: true,  
152 - tilt: 30,  
153 - duration,  
154 - delay,  
155 - });  
156 - trackAni.value!.start();  
157 - setTimeout(() => {  
158 - marketPoint({ lat: path.at(-1)?.lat, lng: path.at(-1)?.lng });  
159 - }, duration + delay);  
160 - },  
161 - };  
162 -  
163 - (window as any)[startMethodName] = dynamicPlayMethod[startMethodName];  
164 -  
165 - setTimeout(`${startMethodName}()`);  
166 - };  
167 -  
168 - const { setTask, executeFlag } = useAsyncQueue();  
169 - onMounted(() => {  
170 - let interval: Nullable<NodeJS.Timer> = setInterval(() => {  
171 - if ((window as any).BMapGL) {  
172 - prepare.value = true;  
173 - executeFlag.value = true;  
174 - clearInterval(interval!);  
175 - interval = null;  
176 - }  
177 - }, 1000);  
178 - if (!(window as any).BMapGL) {  
179 - setTask(initMap);  
180 - return;  
181 - }  
182 - initMap();  
183 - });  
184 -  
185 - const timeRange = reactive<Record<'start' | 'end', Nullable<number>>>({  
186 - start: null,  
187 - end: null,  
188 - });  
189 - const getTimeRange = computed(() => {  
190 - const { start, end } = timeRange;  
191 - if (!start || !end) return `- 请选择`;  
192 - return ` - 从 ${formatToDateTime(start, 'YYYY-MM-DD HH:mm:ss')} 到 ${formatToDateTime(  
193 - end,  
194 - 'YYYY-MM-DD HH:mm:ss'  
195 - )}`;  
196 - });  
197 -  
198 - const [register, { openModal }] = useModal();  
199 -  
200 - const handleTrackSwitch = () => {  
201 - openModal(true, {  
202 - dataSource: props.value?.dataSource || [],  
203 - } as HistoryModalParams);  
204 - };  
205 -  
206 - const getTrackPlayStatus = computed(() => {  
207 - return (trackAni.value || {})._status;  
208 - });  
209 -  
210 - const { createMessage } = useMessage();  
211 - const handlePlay = () => {  
212 - const { start, end } = timeRange;  
213 -  
214 - if (!props.random && (!start || !end)) {  
215 - createMessage.warning('请先选择时间范围');  
216 - }  
217 - if (unref(getTrackPlayStatus) === TrackAnimationStatus.DONE) unref(trackAni).start();  
218 - else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PLAY) unref(trackAni).pause();  
219 - else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PAUSE) unref(trackAni).continue();  
220 - };  
221 -  
222 - const handleRenderHistroyData = (params: HistoryModalOkEmitParams) => {  
223 - const { track, value } = params;  
224 - track.length && randomAnimation(track as unknown as Record<'lng' | 'lat', number>[]);  
225 - timeRange.start = value.startTs as number;  
226 - timeRange.end = value.endTs as number;  
227 - };  
228 -</script>  
229 -  
230 -<template>  
231 - <div class="w-full h-full flex justify-center items-center flex-col p-2">  
232 - <div  
233 - class="w-full flex justify-end"  
234 - v-if="props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY"  
235 - >  
236 - <Button  
237 - v-if="!random"  
238 - type="text"  
239 - class="!px-2 flex-auto !text-left truncate"  
240 - @click="handleTrackSwitch"  
241 - >  
242 - <div class="w-full truncate text-gray-500 flex items-center">  
243 - <ClockCircleOutlined />  
244 - <span class="mx-1">历史</span>  
245 - <Tooltip :title="getTimeRange.replace('-', '')">  
246 - <span class="truncate">  
247 - {{ getTimeRange }}  
248 - </span>  
249 - </Tooltip>  
250 - </div>  
251 - </Button>  
252 - <Button type="text" class="!px-2 !text-gray-500" @click="handlePlay">  
253 - <PlayCircleOutlined v-show="getTrackPlayStatus !== TrackAnimationStatus.PLAY" />  
254 - <PauseCircleOutlined  
255 - class="!ml-0"  
256 - v-show="getTrackPlayStatus === TrackAnimationStatus.PLAY"  
257 - />  
258 - <span>  
259 - {{ getTrackPlayStatus !== TrackAnimationStatus.PLAY ? '播放轨迹' : '暂停播放' }}  
260 - </span>  
261 - </Button>  
262 - </div>  
263 - <Spin  
264 - class="w-full h-full !flex flex-col justify-center items-center pointer-events-none"  
265 - :spinning="!prepare"  
266 - tip="地图加载中..."  
267 - />  
268 - <div v-show="prepare" ref="wrapRef" :id="wrapId" class="w-full h-full no-drag"></div>  
269 - <HistoryDataModel @register="register" @ok="handleRenderHistroyData" />  
270 - </div>  
271 -</template>  
1 -import { FrontComponent } from '../../const/const';  
2 -import { ComponentConfig } from '../../types/type';  
3 -import { DataSource } from '/@/api/dataBoard/model';  
4 -  
5 -export interface MapComponentLayout {  
6 - componentType?: FrontComponent;  
7 -}  
8 -  
9 -export interface MapComponentValue {  
10 - icon?: string;  
11 - track?: Record<'ts' | 'value', number>[];  
12 - dataSource?: DataSource[];  
13 -}  
14 -  
15 -interface Config {  
16 - componentType?: FrontComponent;  
17 -}  
18 -  
19 -export const MaphistoryTrackConfig: Config = {  
20 - componentType: FrontComponent.MAP_COMPONENT_TRACK_HISTORY,  
21 -};  
22 -  
23 -export const MapRealTrackConfig: Config = {  
24 - componentType: FrontComponent.MAP_COMPONENT_TRACK_REAL,  
25 -};  
26 -  
27 -const getTrack = (dataSource: DataSource[], config: Config) => {  
28 - if (dataSource.length >= 2) {  
29 - const trackRecord = dataSource.slice(0, 2);  
30 - if (  
31 - !trackRecord.every((item) => item.componentInfo.value) &&  
32 - config.componentType === FrontComponent.MAP_COMPONENT_TRACK_REAL  
33 - )  
34 - return { track: [], dataSource: [] };  
35 -  
36 - const track = trackRecord.map((item) => {  
37 - return {  
38 - ts: item.componentInfo.updateTime,  
39 - value: item.componentInfo.value || 0,  
40 - };  
41 - });  
42 - return { track, dataSource };  
43 - }  
44 - return { track: [], dataSource: [] };  
45 -};  
46 -  
47 -export const transfromMapComponentConfig: ComponentConfig['transformConfig'] = (  
48 - componentConfig: Config,  
49 - _record,  
50 - dataSourceRecord  
51 -) => {  
52 - const { track, dataSource } = getTrack(dataSourceRecord as DataSource[], componentConfig);  
53 - return {  
54 - layout: {  
55 - ...componentConfig,  
56 - } as MapComponentLayout,  
57 - value: {  
58 - track,  
59 - dataSource,  
60 - } as MapComponentValue,  
61 - };  
62 -};  
1 -import { SchemaFiled } from '../../detail/config/historyTrend.config';  
2 -import { DataSource } from '/@/api/dataBoard/model';  
3 -  
4 -export interface HistoryModalParams {  
5 - dataSource?: DataSource[];  
6 -}  
7 -  
8 -export interface HistoryModalOkEmitParams {  
9 - track: {  
10 - lng: number | string;  
11 - lat: number | string;  
12 - }[];  
13 - value: Record<SchemaFiled, string | number>;  
14 -}  
1 -<script lang="ts" setup>  
2 - import icon from '/src/assets/icons/command.svg';  
3 -  
4 - const props = defineProps<{  
5 - value?: boolean;  
6 - }>();  
7 -  
8 - const emit = defineEmits(['update:value', 'change']);  
9 -  
10 - const handleChange = (event: Event) => {  
11 - const _value = (event.target as HTMLInputElement).checked;  
12 - emit('update:value', _value);  
13 - emit('change', _value);  
14 - };  
15 -</script>  
16 -  
17 -<template>  
18 - <div class="command-send-button">  
19 - 123  
20 - <a href="javascript:;">  
21 - <img :src="icon" alt="" />  
22 - </a>  
23 - </div>  
24 -</template>  
25 -  
26 -<style scoped lang="less">  
27 - .command-send-button {  
28 - width: 80px;  
29 - height: 60px;  
30 - position: relative;  
31 -  
32 - a {  
33 - position: absolute;  
34 - box-sizing: border-box;  
35 - cursor: pointer;  
36 - text-decoration: none;  
37 - color: #fff;  
38 - background: #f2385a;  
39 - top: 0;  
40 - padding: 0 16px;  
41 - height: 60px;  
42 - width: 80px;  
43 - overflow: hidden;  
44 - font-size: 20px;  
45 - line-height: 60px;  
46 - border-radius: 10px;  
47 - box-shadow: 0 15px 0 0 #f02046, 0 0 20px 0 #bbb;  
48 - transition: all 0.2s;  
49 - }  
50 - }  
51 -</style>  
1 -<script lang="ts" setup>  
2 - const props = defineProps<{  
3 - value?: boolean;  
4 - }>();  
5 -  
6 - const emit = defineEmits(['update:value', 'change']);  
7 -  
8 - const handleChange = (event: Event) => {  
9 - const _value = (event.target as HTMLInputElement).checked;  
10 - emit('update:value', _value);  
11 - emit('change', _value);  
12 - };  
13 -</script>  
14 -  
15 -<template>  
16 - <label class="indicator-light-switch">  
17 - <input :value="props.value" :checked="props.value" type="checkbox" @change="handleChange" />  
18 - <div class="panel"></div>  
19 - </label>  
20 -</template>  
21 -  
22 -<style scoped lang="less">  
23 - .indicator-light-switch {  
24 - input[type='checkbox'] {  
25 - display: none;  
26 - }  
27 - .panel {  
28 - position: relative;  
29 - width: 60px;  
30 - height: 60px;  
31 - cursor: pointer;  
32 - border-radius: 50%;  
33 - background: linear-gradient(#dedede, #fdfdfd);  
34 - box-shadow: 0 3px 5px rgb(0 0 0 / 25%), inset 0 1px 0 hsl(0deg 0% 100% / 30%),  
35 - inset 0 -5px 5px hsl(0deg 0% 39% / 10%), inset 0 5px 5px hsl(0deg 0% 100% / 30%);  
36 - }  
37 -  
38 - .panel::after,  
39 - .panel::before {  
40 - content: '';  
41 - position: absolute;  
42 - width: 20%;  
43 - height: 20%;  
44 - border-radius: 50%;  
45 - left: 40%;  
46 - top: 40%;  
47 - }  
48 -  
49 - input:checked ~ .panel::after {  
50 - display: none;  
51 - }  
52 -  
53 - input:not(:checked) ~ .panel::before {  
54 - display: none;  
55 - }  
56 -  
57 - input:not(:checked) ~ .panel {  
58 - background: linear-gradient(#eaeaea, #eaeaea);  
59 - }  
60 -  
61 - .panel::after {  
62 - background-color: #ddd;  
63 - background: radial-gradient(40% 35%, #ccc, #969696 60%);  
64 - box-shadow: inset 0 2px 1px rgb(0 0 0 / 15%), 0 2px 5px hsl(0deg 0% 78% / 10%);  
65 - }  
66 -  
67 - .panel::before {  
68 - background-color: #25d025;  
69 - box-shadow: inset 0 3px 5px 1px rgb(0 0 0 / 10%), 0 1px 0 hsl(0deg 0% 100% / 40%),  
70 - 0 0 10px 2px rgb(0 210 0 / 50%);  
71 - }  
72 - }  
73 -</style>  
1 -<script lang="ts" setup>  
2 - import { SvgIcon } from '/@/components/Icon';  
3 - import { Statistic } from 'ant-design-vue';  
4 -  
5 - const props = defineProps({  
6 - icon: {  
7 - type: String,  
8 - default: 'wind-speed',  
9 - },  
10 - color: {  
11 - type: String,  
12 - default: 'blue',  
13 - },  
14 - updateTime: {  
15 - type: String,  
16 - default: '2022-08-26 11:11:23',  
17 - },  
18 - });  
19 -</script>  
20 -  
21 -<template>  
22 - <section class="flex flex-col h-full justify-center w-full p-2">  
23 - <div class="flex items-center">  
24 - <div class="flex flex-col justify-center items-center">  
25 - <SvgIcon  
26 - :style="{ color: props.color, width: '40%', height: '40%' }"  
27 - :name="props.icon"  
28 - prefix="iconfont"  
29 - />  
30 - <div class="text-sm my-2">温度</div>  
31 - </div>  
32 - <div class="text-center flex-auto">  
33 - <Statistic  
34 - value="123"  
35 - :value-style="{ display: 'flex', fontSize: '20px', justifyContent: 'center' }"  
36 - :suffix="'%'"  
37 - />  
38 - </div>  
39 - </div>  
40 - <div class="text-xs text-gray-500 text-center truncate" :title="props.updateTime">  
41 - 更新时间: {{ props.updateTime }}  
42 - </div>  
43 - </section>  
44 -</template>  
1 -<script lang="ts" setup>  
2 - import on from '/@/assets/icons/light-bulb-on.svg';  
3 - import off from '/@/assets/icons/light-bulb-off.svg';  
4 -  
5 - const props = defineProps<{  
6 - value?: boolean;  
7 - }>();  
8 -  
9 - const emit = defineEmits(['update:value', 'change']);  
10 -  
11 - const handleChange = (event: Event) => {  
12 - const _value = (event.target as HTMLInputElement).checked;  
13 - emit('update:value', _value);  
14 - emit('change', _value);  
15 - };  
16 -</script>  
17 -  
18 -<template>  
19 - <label class="light-bulb-switch">  
20 - <input :value="props.value" :checked="props.value" type="checkbox" @change="handleChange" />  
21 - <div class="light-bulb__on"> <img :src="on" alt="开" /> </div>  
22 - <div class="light-bulb__off"> <img :src="off" alt="关" /> </div>  
23 - </label>  
24 -</template>  
25 -  
26 -<style scoped lang="less">  
27 - .light-bulb-switch {  
28 - input[type='checkbox'] {  
29 - display: none;  
30 - }  
31 -  
32 - input:checked ~ .light-bulb__off {  
33 - display: none;  
34 - }  
35 -  
36 - input:not(:checked) ~ .light-bulb__on {  
37 - display: none;  
38 - }  
39 -  
40 - .light-bulb__on > img,  
41 - .light-bulb__off > img {  
42 - width: 48px;  
43 - height: 48px;  
44 - cursor: pointer;  
45 - }  
46 - }  
47 -</style>  
1 -<script lang="ts" setup>  
2 - const props = defineProps<{  
3 - value?: boolean;  
4 - }>();  
5 -  
6 - const emit = defineEmits(['update:value', 'change']);  
7 -  
8 - const handleChange = (event: Event) => {  
9 - const _value = (event.target as HTMLInputElement).checked;  
10 - emit('update:value', _value);  
11 - emit('change', _value);  
12 - };  
13 -</script>  
14 -  
15 -<template>  
16 - <label class="rocker-switch">  
17 - <input :value="props.value" type="checkbox" :checked="props.value" @change="handleChange" />  
18 - <span class="switch-left">ON</span>  
19 - <span class="switch-right">OFF</span>  
20 - </label>  
21 -</template>  
22 -  
23 -<style scoped lang="less">  
24 - .rocker-switch {  
25 - display: flex;  
26 - width: 72px;  
27 - font-size: 14px;  
28 - color: #fff;  
29 - font-weight: 700;  
30 - letter-spacing: 1px;  
31 - border: 0.5em solid #eee;  
32 - background-color: #999;  
33 - user-select: none;  
34 -  
35 - input[type='checkbox'] {  
36 - display: none;  
37 - }  
38 -  
39 - input:checked + .switch-left + .switch-right {  
40 - transform: rotate(-15deg) skew(-15deg) translate(-1px, -5px);  
41 - }  
42 - input:checked + .switch-left + .switch-right::before {  
43 - background-color: #ccc;  
44 - content: '';  
45 - position: absolute;  
46 - width: 3px;  
47 - height: 30px;  
48 - transform: skewY(75deg) skewX(4deg) rotate(5deg) translateX(1px);  
49 - top: 1.5px;  
50 - right: -1.5px;  
51 - }  
52 -  
53 - input + .switch-left {  
54 - transform: rotate(15deg) skew(17deg) translate(1px, -5px);  
55 - }  
56 -  
57 - input:checked + .switch-left {  
58 - background-color: #0084d0;  
59 - transform: none;  
60 - }  
61 -  
62 - input:not(:checked) + .switch-left {  
63 - background-color: #ccc;  
64 - }  
65 -  
66 - input:not(:checked) + .switch-left + .switch-right {  
67 - background-color: #bd5757;  
68 - color: #fff;  
69 - }  
70 -  
71 - input + .switch-left::before {  
72 - background-color: #ccc;  
73 - content: '';  
74 - position: absolute;  
75 - width: 3px;  
76 - height: 30px;  
77 - background-color: #ccc;  
78 - transform: skewY(-75deg) skewX(-4deg) rotate(-5deg) translateX(-1.5px);  
79 - top: -1.5px;  
80 - left: -1.5px;  
81 - }  
82 -  
83 - .switch-left {  
84 - width: 36px;  
85 - height: 30px;  
86 - text-align: center;  
87 - line-height: 30px;  
88 - cursor: pointer;  
89 - }  
90 -  
91 - .switch-right {  
92 - color: #888;  
93 - width: 36px;  
94 - height: 30px;  
95 - text-align: center;  
96 - line-height: 30px;  
97 - background-color: #ddd;  
98 - cursor: pointer;  
99 - }  
100 - }  
101 -</style>  
1 -<script lang="ts">  
2 - export default {  
3 - inheritAttrs: false,  
4 - };  
5 -</script>  
6 -<script lang="ts" setup>  
7 - import { computed, ref, watch } from 'vue';  
8 - import { Tooltip, Image as AntImage } from 'ant-design-vue';  
9 - import {  
10 - getUpdateTime,  
11 - DEFAULT_RADIO_RECORD,  
12 - fontSize,  
13 - RadioRecord,  
14 - } from '../../detail/config/util';  
15 - import { PictureComponentValue } from './pictureComponent.config';  
16 -  
17 - const props = defineProps<{  
18 - layout?: Recordable;  
19 - value?: PictureComponentValue;  
20 - radio?: RadioRecord;  
21 - }>();  
22 -  
23 - const fallback =  
24 - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==';  
25 -  
26 - const getImagBase64 = ref(fallback);  
27 -  
28 - const getRadio = computed(() => {  
29 - return props.radio || DEFAULT_RADIO_RECORD;  
30 - });  
31 -  
32 - const getWidth = computed(() => {  
33 - const marign = 5;  
34 - const offsetHight = 62 + (props.radio ? 62 : 0);  
35 - const { width = 240, height = 200 } = props.radio || {};  
36 - return width > height - offsetHight ? height - offsetHight - marign : width - marign;  
37 - });  
38 -  
39 - watch(  
40 - () => props.value?.value,  
41 - () => {  
42 - if (props.value?.value) {  
43 - getBase64Image(props.value.value);  
44 - }  
45 - }  
46 - );  
47 -  
48 - const getBase64Image = (url: string) => {  
49 - let canvas: Nullable<HTMLCanvasElement> = document.createElement('canvas');  
50 - const ctx = canvas.getContext('2d');  
51 - let image: Nullable<HTMLImageElement> = new Image();  
52 -  
53 - image.onload = function () {  
54 - canvas!.height = image!.height;  
55 - canvas!.width = image!.width;  
56 - ctx?.drawImage(image!, 0, 0);  
57 - const dataUrl = canvas!.toDataURL('image/png');  
58 - getImagBase64.value = dataUrl;  
59 - image = null;  
60 - canvas = null;  
61 - };  
62 - image.setAttribute('crossOrigin', 'Anonymous');  
63 - image.src = url;  
64 - };  
65 -</script>  
66 -  
67 -<template>  
68 - <section  
69 - class="w-full h-full flex flex-col justify-center items-center justify-between widget-picture"  
70 - >  
71 - <AntImage :width="getWidth" :src="getImagBase64" :fallback="fallback" />  
72 - <div  
73 - class="w-full text-center truncate p-5"  
74 - :style="{ fontSize: fontSize({ radioRecord: getRadio, basic: 12, max: 12 }), color: '#999' }"  
75 - >  
76 - <Tooltip placement="top" :title="getUpdateTime(props.value?.updateTime)">  
77 - <span class="mr-1">更新时间:</span>  
78 - <span class="truncate">  
79 - {{ getUpdateTime(props.value?.updateTime) }}  
80 - </span>  
81 - </Tooltip>  
82 - </div>  
83 - </section>  
84 -</template>  
85 -  
86 -<style scoped lang="less">  
87 - .widget-picture:deep(.ant-image) {  
88 - flex: auto;  
89 - display: flex;  
90 - justify-content: center;  
91 - align-items: center;  
92 - }  
93 -</style>  
1 -import PictureComponent from './PictureComponent.vue';  
2 -  
3 -export { PictureComponent };  
1 -import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';  
2 -  
3 -export interface PictureComponentValue {  
4 - value?: string;  
5 - updateTime?: string;  
6 -}  
7 -  
8 -export const transformPictureConfig = (  
9 - _config: Recordable,  
10 - _record: DataComponentRecord,  
11 - dataSourceRecord: DataSource  
12 -) => {  
13 - const componentInfo = dataSourceRecord.componentInfo;  
14 - return {  
15 - value: {  
16 - value: componentInfo.value,  
17 - updateTime: componentInfo.updateTime,  
18 - } as PictureComponentValue,  
19 - };  
20 -};  
1 -<script lang="ts">  
2 - export default {  
3 - inheritAttrs: false,  
4 - };  
5 -</script>  
6 -<script lang="ts" setup>  
7 - import { computed } from 'vue';  
8 - import { Statistic, Tooltip } from 'ant-design-vue';  
9 - import {  
10 - getUpdateTime,  
11 - fontSize,  
12 - RadioRecord,  
13 - DEFAULT_RADIO_RECORD,  
14 - } from '../../detail/config/util';  
15 - import { TextComponentDefaultConfig, TextComponentLayout, TextComponentValue } from './config';  
16 - import { SvgIcon } from '/@/components/Icon';  
17 - const props = defineProps({  
18 - layout: {  
19 - type: Object as PropType<TextComponentLayout>,  
20 - default: () => ({ base: true } as TextComponentLayout),  
21 - },  
22 - value: {  
23 - type: Object as PropType<TextComponentValue>,  
24 - default: () =>  
25 - ({ ...TextComponentDefaultConfig, name: '温度', value: 123 } as TextComponentValue),  
26 - },  
27 - radio: {  
28 - type: Object as PropType<RadioRecord>,  
29 - default: () => DEFAULT_RADIO_RECORD as RadioRecord,  
30 - },  
31 - });  
32 -  
33 - const getIsColumnLayout = computed(() => {  
34 - const { base } = props.layout;  
35 - return base;  
36 - });  
37 -  
38 - const getShowIcon = computed(() => {  
39 - const { showIcon, base } = props.layout;  
40 - return base ? false : showIcon;  
41 - });  
42 -  
43 - const getShowUpdate = computed(() => {  
44 - const { showUpdate, base } = props.layout;  
45 - return base ? false : showUpdate;  
46 - });  
47 -  
48 - const getShowUnit = computed(() => {  
49 - const { showUnit, base } = props.layout;  
50 - return base ? false : showUnit;  
51 - });  
52 -  
53 - const getRadio = computed(() => {  
54 - return props.radio || DEFAULT_RADIO_RECORD;  
55 - });  
56 -</script>  
57 -  
58 -<template>  
59 - <div class="w-full h-full flex flex-col">  
60 - <div  
61 - class="flex justify-center items-center w-full text-center flex-auto"  
62 - :style="{ flexDirection: getIsColumnLayout ? 'column' : 'row' }"  
63 - >  
64 - <div class="w-1/2">  
65 - <div>  
66 - <div v-if="getShowIcon">  
67 - <SvgIcon  
68 - :name="props.value.icon || TextComponentDefaultConfig.icon!"  
69 - prefix="iconfont"  
70 - :style="{  
71 - color: props.value.iconColor,  
72 - width: fontSize({ radioRecord: getRadio, basic: 50, min: 16 }),  
73 - height: fontSize({ radioRecord: getRadio, basic: 50, min: 16 }),  
74 - }"  
75 - />  
76 - </div>  
77 - <div class="flex justify-center">  
78 - <Statistic  
79 - :value="props.value.value || 0"  
80 - class="truncate"  
81 - :suffix="getShowUnit ? props.value.unit : ''"  
82 - :value-style="{  
83 - fontSize: fontSize({ radioRecord: getRadio, basic: 24, min: 16 }),  
84 - color: props.value.fontColor,  
85 - }"  
86 - />  
87 - </div>  
88 - <div :style="{ color: '#666', fontSize: fontSize({ radioRecord: getRadio, basic: 16 }) }">  
89 - {{ props.value.name }}  
90 - </div>  
91 - </div>  
92 - </div>  
93 - </div>  
94 -  
95 - <div class="text-center text-xs truncate p-5" style="color: #999">  
96 - <Tooltip v-if="getShowUpdate" placement="top" :title="getUpdateTime(props.value?.updateTime)">  
97 - <div  
98 - :style="{ fontSize: fontSize({ radioRecord: getRadio, basic: 12, max: 12 }) }"  
99 - class="truncate"  
100 - >  
101 - <span class="mr-1">更新时间:</span>  
102 - <span class="truncate">  
103 - {{ getUpdateTime(props.value?.updateTime) }}  
104 - </span>  
105 - </div>  
106 - </Tooltip>  
107 - </div>  
108 - </div>  
109 -</template>  
1 -<script lang="ts" setup>  
2 - import { MoreOutlined } from '@ant-design/icons-vue';  
3 - import { DropMenu } from '/@/components/Dropdown';  
4 - import Dropdown from '/@/components/Dropdown/src/Dropdown.vue';  
5 - import { Tooltip } from 'ant-design-vue';  
6 - import {  
7 - isBataBoardSharePage,  
8 - MoreActionEvent,  
9 - VisualComponentPermission,  
10 - } from '../../config/config';  
11 - import { computed } from '@vue/reactivity';  
12 - import { usePermission } from '/@/hooks/web/usePermission';  
13 - import { DataSource } from '/@/api/dataBoard/model';  
14 - import { useRoute } from 'vue-router';  
15 - import { useRole } from '/@/hooks/business/useRole';  
16 -  
17 - const emit = defineEmits(['action']);  
18 - const props = defineProps<{  
19 - id: string;  
20 - record: DataSource[];  
21 - panelName?: string;  
22 - }>();  
23 - const { isCustomerUser } = useRole();  
24 - const { hasPermission } = usePermission();  
25 - const dropMenuList = computed<DropMenu[]>(() => {  
26 - const basicMenu: DropMenu[] = [];  
27 - const hasUpdatePermission = hasPermission(VisualComponentPermission.UPDATE);  
28 - const hasDeletePermission = hasPermission(VisualComponentPermission.DELETE);  
29 - const hasCopyPermission = hasPermission(VisualComponentPermission.COPY);  
30 - if (hasUpdatePermission)  
31 - basicMenu.push({  
32 - text: '编辑组件',  
33 - event: MoreActionEvent.EDIT,  
34 - icon: 'ant-design:edit-outlined',  
35 - });  
36 - if (hasDeletePermission)  
37 - basicMenu.push({  
38 - text: '删除组件',  
39 - event: MoreActionEvent.DELETE,  
40 - icon: 'ant-design:delete-outlined',  
41 - });  
42 - if (hasCopyPermission)  
43 - basicMenu.push({  
44 - text: '复制组件',  
45 - event: MoreActionEvent.COPY,  
46 - icon: 'ant-design:copy-outlined',  
47 - });  
48 - return basicMenu;  
49 - });  
50 -  
51 - const ROUTE = useRoute();  
52 -  
53 - const getIsSharePage = computed(() => {  
54 - return isBataBoardSharePage(ROUTE.path);  
55 - });  
56 -  
57 - const handleMenuEvent = (event: DropMenu) => {  
58 - emit('action', event, props.id);  
59 - };  
60 -</script>  
61 -  
62 -<template>  
63 - <div>  
64 - <div class="text-center pt-5 px-5 pb-3 font-bold text-lg truncate">  
65 - {{ props.panelName || '' }}  
66 - </div>  
67 - <div class="flex justify-between w-full px-5 pb-5">  
68 - <div class="flex" :style="{ width: `calc(100% - 60px)` }">  
69 - <div  
70 - v-for="(item, index) in props.record"  
71 - class="box-border truncate"  
72 - :style="{ width: `${100 / props.record.length}%` }"  
73 - :key="index"  
74 - >  
75 - <Tooltip :title="item.deviceName" placement="topLeft">  
76 - <div class="flex p-1">  
77 - <div v-if="item.componentInfo.showDeviceName" class="truncate font-bold">  
78 - {{ item.deviceRename || item.deviceName }}  
79 - </div>  
80 - </div>  
81 - </Tooltip>  
82 - </div>  
83 - </div>  
84 - <div class="flex items-center gap-5">  
85 - <slot name="moreAction"></slot>  
86 - <Dropdown  
87 - v-if="!isCustomerUser && dropMenuList.length"  
88 - :drop-menu-list="dropMenuList"  
89 - :trigger="['click']"  
90 - @menu-event="handleMenuEvent"  
91 - >  
92 - <Tooltip title="更多">  
93 - <MoreOutlined  
94 - v-if="!getIsSharePage"  
95 - class="transform rotate-90 cursor-pointer w-4.5 h-4.5 text-lg"  
96 - />  
97 - </Tooltip>  
98 - </Dropdown>  
99 - </div>  
100 - </div>  
101 - </div>  
102 -</template>  
1 -<script lang="ts" setup>  
2 - import { useUpdateCenter } from '../../hook/useUpdateCenter';  
3 - import { FrontDataSourceRecord } from '../../types/type';  
4 - import { createVisualBoardContext } from '../../hook/useVisualBoardContext';  
5 - import { DataComponentRecord } from '/@/api/dataBoard/model';  
6 - import { computed } from 'vue';  
7 - import { frontComponentMap } from '../help';  
8 - import { FrontComponent } from '../../const/const';  
9 -  
10 - const props = defineProps<{  
11 - record: DataComponentRecord;  
12 - dataSource: FrontDataSourceRecord[];  
13 - }>();  
14 -  
15 - const isMultipleDataSource = computed(() => {  
16 - const { record } = props;  
17 - const componentInfo = frontComponentMap.get(record.frontId as FrontComponent);  
18 - return componentInfo?.isMultipleDataSource;  
19 - });  
20 -  
21 - const { update, add, remove } = useUpdateCenter();  
22 -  
23 - createVisualBoardContext({ update, add, remove });  
24 -  
25 - defineExpose({ update });  
26 -</script>  
27 -  
28 -<template>  
29 - <section class="widget !dark:bg-dark-900">  
30 - <slot name="header"></slot>  
31 -  
32 - <div class="widget-content">  
33 - <template v-if="!isMultipleDataSource">  
34 - <div  
35 - v-for="item in props.dataSource"  
36 - :key="item.id"  
37 - :style="{ width: `${item.width || 100}%`, height: `${item.height || 100}%` }"  
38 - class="widget-item"  
39 - >  
40 - <div class="widget-box">  
41 - <div class="widget-controls-container">  
42 - <slot  
43 - name="controls"  
44 - :record="item"  
45 - :add="add"  
46 - :remove="remove"  
47 - :update="update"  
48 - ></slot>  
49 - </div>  
50 - </div>  
51 - </div>  
52 - </template>  
53 - <template v-if="isMultipleDataSource">  
54 - <div :style="{ width: `${100}%`, height: `${100}%` }" class="widget-item">  
55 - <div class="widget-box">  
56 - <div class="widget-controls-container">  
57 - <slot  
58 - name="controls"  
59 - :record="props.dataSource"  
60 - :add="add"  
61 - :remove="remove"  
62 - :update="update"  
63 - ></slot>  
64 - </div>  
65 - </div>  
66 - </div>  
67 - </template>  
68 - </div>  
69 - <slot name="footer"></slot>  
70 - </section>  
71 -</template>  
72 -  
73 -<style scoped>  
74 - .widget {  
75 - display: flex;  
76 - flex-direction: column;  
77 - width: 100%;  
78 - height: 100%;  
79 - background-color: #fff;  
80 - border-radius: 3px;  
81 - box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1);  
82 - }  
83 -  
84 - .widget-content {  
85 - display: flex;  
86 - flex-wrap: wrap;  
87 - justify-content: center;  
88 - align-items: center;  
89 - width: 100%;  
90 - height: 100%;  
91 - }  
92 -  
93 - .widget-box .widget-charts {  
94 - display: flex;  
95 - }  
96 -  
97 - .widget-item {  
98 - display: flex;  
99 - flex-direction: column;  
100 - overflow: hidden;  
101 - justify-content: center;  
102 - align-items: center;  
103 - }  
104 -  
105 - .widget-box {  
106 - position: relative;  
107 - width: 100%;  
108 - height: 100%;  
109 - }  
110 -  
111 - .widget-controls-container {  
112 - flex: 1 1 auto;  
113 - width: 100%;  
114 - height: 100%;  
115 - display: flex;  
116 - align-items: center;  
117 - justify-content: center;  
118 - }  
119 -  
120 - .widget-controls-container > div {  
121 - width: 100%;  
122 - height: 100%;  
123 - background-color: red;  
124 - }  
125 -  
126 - .widget-value {  
127 - font-size: 14px;  
128 - position: absolute;  
129 - width: 100%;  
130 - top: 0;  
131 - text-align: center;  
132 - overflow: hidden;  
133 - text-overflow: ellipsis;  
134 - }  
135 -  
136 - .widget-label {  
137 - font-size: 14px;  
138 - line-height: 1.2;  
139 - text-align: center;  
140 - overflow: hidden;  
141 - text-overflow: ellipsis;  
142 - }  
143 -</style>  
1 -import {  
2 - TextComponent1Config,  
3 - TextComponent3Config,  
4 - TextComponent4Config,  
5 - TextComponent5Config,  
6 - TextComponentDefaultConfig,  
7 - transformTextComponentConfig,  
8 -} from './TextComponent/config';  
9 -import { ComponentInfo } from '/@/api/dataBoard/model';  
10 -import { transformPictureConfig } from './PictureComponent/pictureComponent.config';  
11 -import {  
12 - DashboardComponentLayout,  
13 - Instrument1DefaultConfig,  
14 - Instrument2DefaultConfig,  
15 - instrumentComponent1,  
16 - instrumentComponent2,  
17 - transformDashboardComponentConfig,  
18 -} from './InstrumentComponent/dashBoardComponent.config';  
19 -import { DigitalComponentDefaultConfig } from './InstrumentComponent/digitalDashBoard.config';  
20 -import {  
21 - ControlComponentDefaultConfig,  
22 - transformControlConfig,  
23 -} from './ControlComponent/control.config';  
24 -  
25 -import TextComponent from './TextComponent/TextComponent.vue';  
26 -import DashBoardComponent from './InstrumentComponent/DashBoardComponent.vue';  
27 -import PictureComponent from './PictureComponent/PictureComponent.vue';  
28 -import DigitalDashBoard from './InstrumentComponent/DigitalDashBoard.vue';  
29 -import ToggleSwitch from './ControlComponent/ToggleSwitch.vue';  
30 -import SlidingSwitch from './ControlComponent/SlidingSwitch.vue';  
31 -import SwitchWithIcon from './ControlComponent/SwitchWithIcon.vue';  
32 -import MapComponent from './MapComponent/MapComponent.vue';  
33 -import {  
34 - MaphistoryTrackConfig,  
35 - MapRealTrackConfig,  
36 - transfromMapComponentConfig,  
37 -} from './MapComponent/map.config';  
38 -import { ComponentConfig } from '../types/type';  
39 -import { FrontComponent, FrontComponentCategory } from '../const/const';  
40 -  
41 -export const frontComponentDefaultConfigMap = new Map<FrontComponent, Partial<ComponentInfo>>();  
42 -  
43 -export const frontComponentMap = new Map<FrontComponent, ComponentConfig>();  
44 -  
45 -frontComponentMap.set(FrontComponent.TEXT_COMPONENT_1, {  
46 - Component: TextComponent,  
47 - ComponentName: '文本组件1',  
48 - ComponentKey: FrontComponent.TEXT_COMPONENT_1,  
49 - ComponentConfig: TextComponent1Config,  
50 - ComponentCategory: FrontComponentCategory.TEXT,  
51 - isMultipleDataSource: false,  
52 - hasHistoryTrend: true,  
53 - hasSetting: true,  
54 - transformConfig: transformTextComponentConfig,  
55 -});  
56 -  
57 -frontComponentMap.set(FrontComponent.TEXT_COMPONENT_3, {  
58 - Component: TextComponent,  
59 - ComponentName: '文本组件2',  
60 - ComponentKey: FrontComponent.TEXT_COMPONENT_3,  
61 - ComponentConfig: TextComponent3Config,  
62 - ComponentCategory: FrontComponentCategory.TEXT,  
63 - isMultipleDataSource: false,  
64 - hasHistoryTrend: true,  
65 - hasSetting: true,  
66 - transformConfig: transformTextComponentConfig,  
67 -});  
68 -  
69 -frontComponentMap.set(FrontComponent.TEXT_COMPONENT_4, {  
70 - Component: TextComponent,  
71 - ComponentName: '文本组件3',  
72 - ComponentKey: FrontComponent.TEXT_COMPONENT_4,  
73 - ComponentConfig: TextComponent4Config,  
74 - ComponentCategory: FrontComponentCategory.TEXT,  
75 - isMultipleDataSource: false,  
76 - hasHistoryTrend: true,  
77 - hasSetting: true,  
78 - transformConfig: transformTextComponentConfig,  
79 -});  
80 -  
81 -frontComponentMap.set(FrontComponent.TEXT_COMPONENT_5, {  
82 - Component: TextComponent,  
83 - ComponentName: '文本组件4',  
84 - ComponentKey: FrontComponent.TEXT_COMPONENT_5,  
85 - ComponentConfig: TextComponent5Config,  
86 - ComponentCategory: FrontComponentCategory.TEXT,  
87 - isMultipleDataSource: false,  
88 - hasHistoryTrend: true,  
89 - hasSetting: true,  
90 - transformConfig: transformTextComponentConfig,  
91 -});  
92 -  
93 -frontComponentMap.set(FrontComponent.INSTRUMENT_COMPONENT_1, {  
94 - Component: DashBoardComponent,  
95 - ComponentName: '仪表盘',  
96 - ComponentKey: FrontComponent.INSTRUMENT_COMPONENT_1,  
97 - ComponentConfig: {  
98 - chartOption: instrumentComponent1(Instrument1DefaultConfig),  
99 - componentType: FrontComponent.INSTRUMENT_COMPONENT_1,  
100 - } as DashboardComponentLayout,  
101 - ComponentCategory: FrontComponentCategory.INSTRUMENT,  
102 - isMultipleDataSource: false,  
103 - hasHistoryTrend: true,  
104 - hasSetting: true,  
105 - transformConfig: transformDashboardComponentConfig,  
106 -});  
107 -  
108 -frontComponentMap.set(FrontComponent.INSTRUMENT_COMPONENT_2, {  
109 - Component: DashBoardComponent,  
110 - ComponentName: '阶段仪表盘',  
111 - ComponentKey: FrontComponent.INSTRUMENT_COMPONENT_2,  
112 - ComponentConfig: {  
113 - chartOption: instrumentComponent2(Instrument2DefaultConfig),  
114 - componentType: FrontComponent.INSTRUMENT_COMPONENT_2,  
115 - } as DashboardComponentLayout,  
116 - ComponentCategory: FrontComponentCategory.INSTRUMENT,  
117 - isMultipleDataSource: false,  
118 - hasHistoryTrend: true,  
119 - hasSetting: true,  
120 - transformConfig: transformDashboardComponentConfig,  
121 -});  
122 -  
123 -frontComponentMap.set(FrontComponent.DIGITAL_DASHBOARD_COMPONENT, {  
124 - Component: DigitalDashBoard,  
125 - ComponentName: '数字仪表盘',  
126 - ComponentKey: FrontComponent.DIGITAL_DASHBOARD_COMPONENT,  
127 - ComponentCategory: FrontComponentCategory.INSTRUMENT,  
128 - isMultipleDataSource: false,  
129 - hasHistoryTrend: true,  
130 - hasSetting: true,  
131 - transformConfig: transformDashboardComponentConfig,  
132 -});  
133 -  
134 -frontComponentMap.set(FrontComponent.PICTURE_COMPONENT_1, {  
135 - Component: PictureComponent,  
136 - ComponentName: '图片组件',  
137 - ComponentKey: FrontComponent.PICTURE_COMPONENT_1,  
138 - ComponentCategory: FrontComponentCategory.PICTURE,  
139 - isMultipleDataSource: false,  
140 - hasHistoryTrend: true,  
141 - hasSetting: false,  
142 - transformConfig: transformPictureConfig,  
143 -});  
144 -  
145 -frontComponentMap.set(FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON, {  
146 - Component: SwitchWithIcon,  
147 - ComponentName: '控制按钮1',  
148 - ComponentKey: FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON,  
149 - ComponentCategory: FrontComponentCategory.CONTROL,  
150 - isMultipleDataSource: false,  
151 - hasHistoryTrend: true,  
152 - hasSetting: true,  
153 - transformConfig: transformControlConfig,  
154 -});  
155 -  
156 -frontComponentMap.set(FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH, {  
157 - Component: SlidingSwitch,  
158 - ComponentName: '控制按钮2',  
159 - ComponentKey: FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH,  
160 - ComponentCategory: FrontComponentCategory.CONTROL,  
161 - isMultipleDataSource: false,  
162 - hasHistoryTrend: true,  
163 - hasSetting: false,  
164 - transformConfig: transformControlConfig,  
165 -});  
166 -  
167 -frontComponentMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, {  
168 - Component: ToggleSwitch,  
169 - ComponentName: '控制按钮3',  
170 - ComponentKey: FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH,  
171 - ComponentCategory: FrontComponentCategory.CONTROL,  
172 - isMultipleDataSource: false,  
173 - hasHistoryTrend: true,  
174 - hasSetting: false,  
175 - transformConfig: transformControlConfig,  
176 -});  
177 -  
178 -frontComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK_REAL, {  
179 - Component: MapComponent,  
180 - ComponentName: '实时轨迹',  
181 - ComponentKey: FrontComponent.MAP_COMPONENT_TRACK_REAL,  
182 - ComponentConfig: MapRealTrackConfig,  
183 - ComponentCategory: FrontComponentCategory.MAP,  
184 - isMultipleDataSource: true,  
185 - hasHistoryTrend: false,  
186 - hasSetting: false,  
187 - transformConfig: transfromMapComponentConfig,  
188 -});  
189 -  
190 -frontComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK_HISTORY, {  
191 - Component: MapComponent,  
192 - ComponentName: '历史轨迹',  
193 - ComponentKey: FrontComponent.MAP_COMPONENT_TRACK_HISTORY,  
194 - ComponentConfig: MaphistoryTrackConfig,  
195 - ComponentCategory: FrontComponentCategory.MAP,  
196 - isMultipleDataSource: true,  
197 - hasHistoryTrend: false,  
198 - hasSetting: false,  
199 - transformConfig: transfromMapComponentConfig,  
200 -});  
201 -  
202 -frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_1, TextComponentDefaultConfig);  
203 -frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_3, TextComponentDefaultConfig);  
204 -frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_4, TextComponentDefaultConfig);  
205 -frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_5, TextComponentDefaultConfig);  
206 -  
207 -frontComponentDefaultConfigMap.set(FrontComponent.INSTRUMENT_COMPONENT_1, Instrument1DefaultConfig);  
208 -frontComponentDefaultConfigMap.set(FrontComponent.INSTRUMENT_COMPONENT_2, Instrument2DefaultConfig);  
209 -  
210 -frontComponentDefaultConfigMap.set(  
211 - FrontComponent.DIGITAL_DASHBOARD_COMPONENT,  
212 - DigitalComponentDefaultConfig  
213 -);  
214 -  
215 -frontComponentDefaultConfigMap.set(  
216 - FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON,  
217 - ControlComponentDefaultConfig  
218 -);  
219 -  
220 -export const getComponentDefaultConfig = (key: FrontComponent) => {  
221 - return frontComponentDefaultConfigMap.get(key) || {};  
222 -};  
1 -import { ViewTypeEnum } from '/@/views/sys/share/config/config';  
2 -  
3 export enum MoreActionEvent { 1 export enum MoreActionEvent {
4 EDIT = 'edit', 2 EDIT = 'edit',
5 COPY = 'copy', 3 COPY = 'copy',
@@ -27,9 +25,6 @@ export const DEFAULT_WIDGET_HEIGHT = 6; @@ -27,9 +25,6 @@ export const DEFAULT_WIDGET_HEIGHT = 6;
27 export const DEFAULT_MIN_HEIGHT = 5; 25 export const DEFAULT_MIN_HEIGHT = 5;
28 export const DEFAULT_MIN_WIDTH = 3; 26 export const DEFAULT_MIN_WIDTH = 3;
29 27
30 -export const DATA_BOARD_SHARE_URL = (id: string, publicId: string) =>  
31 - `/share/${ViewTypeEnum.DATA_BOARD}/${id}/${publicId}`;  
32 -  
33 export const isBataBoardSharePage = (url: string) => { 28 export const isBataBoardSharePage = (url: string) => {
34 const reg = /^\/data\/board\/share/g; 29 const reg = /^\/data\/board\/share/g;
35 return reg.test(url); 30 return reg.test(url);
1 -export enum FrontComponentCategory {  
2 - TEXT = 'text',  
3 - PICTURE = 'picture',  
4 - INSTRUMENT = 'instrument',  
5 - CONTROL = 'control',  
6 - MAP = 'map',  
7 -}  
8 -  
9 -export const FrontComponentCategoryName = {  
10 - [FrontComponentCategory.TEXT]: '文本组件',  
11 - [FrontComponentCategory.INSTRUMENT]: '仪表组件',  
12 - [FrontComponentCategory.CONTROL]: '控制组件',  
13 - [FrontComponentCategory.PICTURE]: '图片组件',  
14 - [FrontComponentCategory.MAP]: '地图组件',  
15 -};  
16 -  
17 -export enum FrontComponent {  
18 - TEXT_COMPONENT_1 = 'text-component-1',  
19 - TEXT_COMPONENT_2 = 'text-component-2',  
20 - TEXT_COMPONENT_3 = 'text-component-3',  
21 - TEXT_COMPONENT_4 = 'text-component-4',  
22 - TEXT_COMPONENT_5 = 'text-component-5',  
23 - INSTRUMENT_COMPONENT_1 = 'instrument-component-1',  
24 - INSTRUMENT_COMPONENT_2 = 'instrument-component-2',  
25 - DIGITAL_DASHBOARD_COMPONENT = 'digital-dashboard-component',  
26 - PICTURE_COMPONENT_1 = 'picture-component-1',  
27 - CONTROL_COMPONENT_TOGGLE_SWITCH = 'control-component-toggle-switch',  
28 - CONTROL_COMPONENT_SWITCH_WITH_ICON = 'control-component-switch-with-icon',  
29 - CONTROL_COMPONENT_SLIDING_SWITCH = 'control-component-sliding-switch',  
30 - MAP_COMPONENT_TRACK_REAL = 'map-component-track-real',  
31 - MAP_COMPONENT_TRACK_HISTORY = 'map-component-track-history',  
32 -}  
33 -  
34 -export enum Gradient {  
35 - FIRST = 'first',  
36 - SECOND = 'second',  
37 - THIRD = 'third',  
38 -}  
39 -export enum GradientColor {  
40 - FIRST = '#67e0e3',  
41 - SECOND = '#37a2da',  
42 - THIRD = '#fd666d',  
43 -}  
44 -  
45 -export enum visualOptionField {  
46 - FONT_COLOR = 'fontColor',  
47 - UNIT = 'unit',  
48 - ICON_COLOR = 'iconColor',  
49 - ICON = 'icon',  
50 - FIRST_PHASE_COLOR = 'firstPhaseColor',  
51 - SECOND_PHASE_COLOR = 'secondPhaseColor',  
52 - THIRD_PHASE_COLOR = 'thirdPhaseColor',  
53 - FIRST_PHASE_VALUE = 'firstPhaseValue',  
54 - SECOND_PHASE_VALUE = 'secondPhaseValue',  
55 - THIRD_PHASE_VALUE = 'thirdPhaseValue',  
56 -}  
1 -<script lang="ts" setup>  
2 - import {  
3 - CopyOutlined,  
4 - DeleteOutlined,  
5 - SettingOutlined,  
6 - SwapOutlined,  
7 - } from '@ant-design/icons-vue';  
8 - import { Tooltip, Button, Alert } from 'ant-design-vue';  
9 - import { FormActionType, useForm } from '/@/components/Form';  
10 - import { basicSchema, DataSourceField } from '../config/basicConfiguration';  
11 - import BasicForm from '/@/components/Form/src/BasicForm.vue';  
12 - import { ref, shallowReactive, unref, nextTick, watch, computed, onMounted } from 'vue';  
13 - import VisualOptionsModal from './VisualOptionsModal.vue';  
14 - import { useModal } from '/@/components/Modal';  
15 - import { buildUUID } from '/@/utils/uuid';  
16 - import type { ComponentInfo, DataSource } from '/@/api/dataBoard/model';  
17 - import { useMessage } from '/@/hooks/web/useMessage';  
18 - import { DataBoardLayoutInfo } from '../../types/type';  
19 - import { getDataSourceComponent } from './DataSourceForm/help';  
20 - import { FrontComponent, FrontComponentCategory } from '../../const/const';  
21 - import { isNullAndUnDef } from '/@/utils/is';  
22 - import { useSortable } from '/@/hooks/web/useSortable';  
23 - import { cloneDeep } from 'lodash-es';  
24 - import { frontComponentMap } from '../../components/help';  
25 - import { isControlComponent } from '../config/basicConfiguration';  
26 -  
27 - type DataSourceFormEL = { [key: string]: Nullable<FormActionType> };  
28 -  
29 - type DataSourceEl = DataSource & { id: string };  
30 -  
31 - const props = defineProps<{  
32 - record: DataBoardLayoutInfo;  
33 - isEdit: boolean;  
34 - frontId?: FrontComponent;  
35 - defaultConfig?: Partial<ComponentInfo>;  
36 - componentCategory?: FrontComponentCategory;  
37 - }>();  
38 -  
39 - const { createMessage } = useMessage();  
40 -  
41 - const dataSource = ref<DataSourceEl[]>([  
42 - { id: buildUUID(), componentInfo: props.defaultConfig || {} } as unknown as DataSourceEl,  
43 - ]);  
44 -  
45 - const [basicRegister, basicMethod] = useForm({  
46 - schemas: basicSchema,  
47 - showActionButtonGroup: false,  
48 - labelWidth: 96,  
49 - });  
50 -  
51 - const dataSourceEl = shallowReactive<DataSourceFormEL>({} as unknown as DataSourceFormEL);  
52 -  
53 - const setFormEl = (el: any, id: string) => {  
54 - if (id && el) {  
55 - const { formActionType } = el as unknown as { formActionType: FormActionType };  
56 - dataSourceEl[id] = formActionType;  
57 - }  
58 - };  
59 -  
60 - const resetFormFields = async () => {  
61 - const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);  
62 - for (const id of hasExistEl) {  
63 - const oldValues = dataSourceEl[id]?.getFieldsValue();  
64 - dataSourceEl[id]?.setFieldsValue({  
65 - ...oldValues,  
66 - [DataSourceField.ATTRIBUTE]: null,  
67 - [DataSourceField.COMMAND_TYPE]: null,  
68 - [DataSourceField.SERVICE]: null,  
69 - });  
70 - dataSourceEl[id]?.clearValidate();  
71 - }  
72 - };  
73 -  
74 - const validate = async () => {  
75 - await basicMethod.validate();  
76 - await validateDataSourceField();  
77 - };  
78 -  
79 - const getAllDataSourceFieldValue = () => {  
80 - const _dataSource = getDataSourceField();  
81 - const basicInfo = basicMethod.getFieldsValue();  
82 - return {  
83 - ...basicInfo,  
84 - dataSource: _dataSource,  
85 - };  
86 - };  
87 -  
88 - const validateDataSourceField = async () => {  
89 - const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);  
90 - const _dataSource: Record<DataSourceField, string>[] = [];  
91 - for (const id of hasExistEl) {  
92 - const flag = (await (dataSourceEl[id] as FormActionType).validate()) as Record<  
93 - DataSourceField,  
94 - string  
95 - >;  
96 - flag && _dataSource.push(flag);  
97 - }  
98 -  
99 - if (  
100 - [  
101 - FrontComponent.MAP_COMPONENT_TRACK_HISTORY,  
102 - FrontComponent.MAP_COMPONENT_TRACK_REAL,  
103 - ].includes(props.frontId!)  
104 - ) {  
105 - await validateMapComponent(_dataSource);  
106 - }  
107 - return _dataSource;  
108 - };  
109 -  
110 - const validateMapComponent = async (dataSource: Record<DataSourceField, string>[]) => {  
111 - if (dataSource.length) {  
112 - const firstRecord = dataSource.at(0)!;  
113 - const { deviceId } = firstRecord;  
114 - const flag = dataSource.every((item) => item.deviceId === deviceId);  
115 - if (!flag) {  
116 - createMessage.warning('地图组件绑定的数据源应该一致');  
117 - return Promise.reject(false);  
118 - }  
119 - }  
120 - };  
121 -  
122 - const getDataSourceField = () => {  
123 - const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);  
124 - const _dataSource: DataSource[] = [];  
125 -  
126 - for (const id of hasExistEl) {  
127 - const index = unref(dataSource).findIndex((item) => item.id === id);  
128 - const value = (dataSourceEl[id] as FormActionType).getFieldsValue() as DataSource;  
129 - if (!~index) continue;  
130 - const componentInfo = unref(dataSource)[index].componentInfo || {};  
131 - _dataSource[index] = {  
132 - ...value,  
133 - componentInfo: { ...(props.defaultConfig || {}), ...componentInfo },  
134 - customCommand: {  
135 - transportType: value.transportType,  
136 - service: value.service,  
137 - command: value.command,  
138 - commandType: value.commandType,  
139 - },  
140 - };  
141 - }  
142 -  
143 - return _dataSource;  
144 - };  
145 -  
146 - const handleCopy = async (data: DataSourceEl) => {  
147 - const value = (dataSourceEl[data.id] as FormActionType).getFieldsValue() as DataSource;  
148 - const index = unref(dataSource).findIndex((item) => item.id === data.id);  
149 -  
150 - const componentInfo = ~index  
151 - ? unref(dataSource)[index].componentInfo  
152 - : ({} as unknown as ComponentInfo);  
153 -  
154 - const copyRecordId = buildUUID();  
155 - unref(dataSource).push({  
156 - ...value,  
157 - id: copyRecordId,  
158 - componentInfo,  
159 - });  
160 - await nextTick();  
161 - (dataSourceEl[copyRecordId] as FormActionType).setFieldsValue(value);  
162 - (dataSourceEl[copyRecordId] as FormActionType).clearValidate();  
163 - };  
164 -  
165 - const [registerVisualOptionModal, { openModal }] = useModal();  
166 -  
167 - const handleSetting = (item: DataSourceEl) => {  
168 - if (!props.frontId) {  
169 - createMessage.warning('请先选择可视化组件');  
170 - return;  
171 - }  
172 -  
173 - const componentInfo: ComponentInfo = {  
174 - ...(props.defaultConfig || {}),  
175 - ...(item.componentInfo || {}),  
176 - };  
177 -  
178 - openModal(true, {  
179 - recordId: item.id,  
180 - componentInfo,  
181 - });  
182 - };  
183 -  
184 - const handleDelete = (data: DataSourceEl) => {  
185 - const index = unref(dataSource).findIndex((item) => item.id === data.id);  
186 - ~index && unref(dataSource).splice(index, 1);  
187 - dataSourceEl[data.id] = null;  
188 - };  
189 -  
190 - const isMapComponent = computed(() => {  
191 - return props.componentCategory === FrontComponentCategory.MAP;  
192 - });  
193 -  
194 - const handleAdd = () => {  
195 - if (unref(isMapComponent) && unref(dataSource).length === 2) {  
196 - createMessage.warning('地图组件只能绑定两条数据源');  
197 - return;  
198 - }  
199 - unref(dataSource).push({  
200 - id: buildUUID(),  
201 - componentInfo: props.defaultConfig || {},  
202 - } as unknown as DataSourceEl);  
203 - };  
204 -  
205 - const echoDataSource = () => {  
206 - basicMethod.setFieldsValue(props.record.record);  
207 - basicMethod.clearValidate();  
208 - dataSource.value = [];  
209 - dataSource.value = props.record.record.dataSource.map((item) => {  
210 - const id = buildUUID();  
211 -  
212 - const customCommand = item.customCommand || {};  
213 -  
214 - nextTick(() => {  
215 - (dataSourceEl[id] as FormActionType).setFieldsValue({  
216 - ...item,  
217 - transportType: customCommand.transportType,  
218 - command: customCommand?.command,  
219 - commandType: customCommand.commandType,  
220 - service: customCommand.service,  
221 - });  
222 - (dataSourceEl[id] as FormActionType).clearValidate();  
223 - });  
224 -  
225 - return {  
226 - id,  
227 - ...item,  
228 - transportType: customCommand.transportType,  
229 - command: customCommand?.command,  
230 - commandType: customCommand.commandType,  
231 - service: customCommand.service,  
232 - };  
233 - });  
234 - };  
235 -  
236 - const showSettingButton = computed(() => {  
237 - const flag = frontComponentMap.get(props.frontId!)?.hasSetting;  
238 - return flag;  
239 - });  
240 -  
241 - watch(  
242 - () => props.record,  
243 - () => {  
244 - if (Object.keys(props.record).length) echoDataSource();  
245 - }  
246 - );  
247 -  
248 - const handleRowComponentInfo = (recordId: string, value: ComponentInfo) => {  
249 - const index = unref(dataSource).findIndex((item) => item.id === recordId);  
250 - ~index && (unref(dataSource)[index].componentInfo = value);  
251 - };  
252 -  
253 - const dataSourceComponent = computed(() => {  
254 - return getDataSourceComponent(props.frontId as FrontComponent);  
255 - });  
256 -  
257 - let inited = false;  
258 - const formListEl = ref<HTMLElement>();  
259 - async function handleSort() {  
260 - if (inited) return;  
261 - await nextTick();  
262 - const formList = unref(formListEl);  
263 - if (!formList) return;  
264 -  
265 - const { initSortable } = useSortable(unref(formList), {  
266 - handle: '.sort-icon',  
267 - onEnd: (evt) => {  
268 - const { oldIndex, newIndex } = evt;  
269 - if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {  
270 - return;  
271 - }  
272 -  
273 - const _dataSource = cloneDeep(unref(dataSource));  
274 -  
275 - if (oldIndex > newIndex) {  
276 - _dataSource.splice(newIndex, 0, _dataSource[oldIndex]);  
277 - _dataSource.splice(oldIndex + 1, 1);  
278 - } else {  
279 - _dataSource.splice(newIndex + 1, 0, _dataSource[oldIndex]);  
280 - _dataSource.splice(oldIndex, 1);  
281 - }  
282 -  
283 - dataSource.value = _dataSource;  
284 - },  
285 - });  
286 - initSortable();  
287 - inited = true;  
288 - }  
289 -  
290 - const isControlCmp = computed(() => {  
291 - return isControlComponent(props.frontId as FrontComponent);  
292 - });  
293 -  
294 - watch(  
295 - () => props.frontId,  
296 - async (target, oldTarget) => {  
297 - if ([isControlComponent(oldTarget!), isControlComponent(target!)].some(Boolean)) {  
298 - await resetFormFields();  
299 - }  
300 - }  
301 - );  
302 -  
303 - onMounted(() => handleSort());  
304 -  
305 - defineExpose({  
306 - getAllDataSourceFieldValue,  
307 - validate,  
308 - });  
309 -</script>  
310 -  
311 -<template>  
312 - <section>  
313 - <h3 class="w-24 text-right pr-2 my-4">基础信息</h3>  
314 - <div class="w-3/4">  
315 - <BasicForm @register="basicRegister" class="w-full" />  
316 - </div>  
317 - <Alert type="info" show-icon v-if="isControlCmp">  
318 - <template #description>  
319 - <div>  
320 - 控制组件数据源为TCP产品,则其控制命令下发为TCP产品 物模型=>服务,且不具备状态显示功能.  
321 - </div>  
322 - <div>  
323 - 控制组件数据源为非TCP产品,则其控制命令下发为产品 物模型=>属性,且具备状态显示功能.  
324 - </div>  
325 - </template>  
326 - </Alert>  
327 - <Alert type="info" show-icon v-if="isMapComponent">  
328 - <template #description>  
329 - <div>  
330 - 地图组件,需绑定两个数据源,且数据源为同一设备。第一数据源为经度,第二数据源为纬度,否则地图组件不能正常显示。  
331 - </div>  
332 - </template>  
333 - </Alert>  
334 - <h3 class="w-24 flex-shrink-0 text-right pr-2 my-4">选择数据源</h3>  
335 -  
336 - <section ref="formListEl">  
337 - <div  
338 - v-for="item in dataSource"  
339 - :data-id="item.id"  
340 - :key="item.id"  
341 - class="flex bg-light-50 dark:text-gray-300 dark:bg-dark-700"  
342 - >  
343 - <div class="w-24 text-right flex justify-end" style="flex: 0 0 96px"> 选择设备 </div>  
344 - <div class="pl-2 flex-auto">  
345 - <component  
346 - :frontId="$props.frontId"  
347 - :isEdit="isEdit"  
348 - :is="dataSourceComponent"  
349 - :ref="(el) => setFormEl(el, item.id)"  
350 - />  
351 - </div>  
352 - <div class="flex justify-center gap-3 w-28">  
353 - <Tooltip title="复制">  
354 - <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-32px" />  
355 - </Tooltip>  
356 - <Tooltip title="设置">  
357 - <SettingOutlined  
358 - v-show="showSettingButton"  
359 - @click="handleSetting(item)"  
360 - class="cursor-pointer text-lg !leading-32px"  
361 - />  
362 - </Tooltip>  
363 - <Tooltip title="拖拽排序">  
364 - <SwapOutlined  
365 - class="cursor-pointer text-lg !leading-32px svg:transform svg:rotate-90 sort-icon"  
366 - />  
367 - </Tooltip>  
368 - <Tooltip title="删除">  
369 - <DeleteOutlined  
370 - @click="handleDelete(item)"  
371 - class="cursor-pointer text-lg !leading-32px"  
372 - />  
373 - </Tooltip>  
374 - </div>  
375 - </div>  
376 - </section>  
377 -  
378 - <div class="text-center">  
379 - <Button type="primary" @click="handleAdd">添加数据源</Button>  
380 - </div>  
381 - <VisualOptionsModal  
382 - :value="props.frontId"  
383 - @close="handleRowComponentInfo"  
384 - @register="registerVisualOptionModal"  
385 - />  
386 - </section>  
387 -</template>  
388 -  
389 -<style scoped>  
390 - .data-source-form:deep(.ant-row) {  
391 - width: 100%;  
392 - }  
393 -  
394 - .data-source-form:deep(.ant-form-item-control-input-content > div > div) {  
395 - width: 100%;  
396 - }  
397 -</style>  
1 -<script lang="ts" setup>  
2 - import { Tabs } from 'ant-design-vue';  
3 - import BasicModal from '/@/components/Modal/src/BasicModal.vue';  
4 - import BasicConfiguration from './BasicConfiguration.vue';  
5 - import VisualConfiguration from './VisualConfiguration.vue';  
6 - import { computed, ref, unref } from 'vue';  
7 - import { RouteParams, useRoute } from 'vue-router';  
8 - import { addDataComponent, updateDataComponent } from '/@/api/dataBoard';  
9 - import { useModalInner } from '/@/components/Modal';  
10 - import { DataBoardLayoutInfo } from '../../types/type';  
11 - import { useMessage } from '/@/hooks/web/useMessage';  
12 - import { decode } from '../../config/config';  
13 - import { ComponentInfo } from '/@/api/dataBoard/model';  
14 - import { useCalcGridLayout } from '../../hook/useCalcGridLayout';  
15 - import { FrontComponent } from '../../const/const';  
16 - import { frontComponentMap } from '../../components/help';  
17 - import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';  
18 -  
19 - interface DataComponentRouteParams extends RouteParams {  
20 - id: string;  
21 - }  
22 -  
23 - const props = defineProps<{  
24 - layout: DataBoardLayoutInfo[];  
25 - }>();  
26 -  
27 - const emit = defineEmits(['update', 'create', 'register']);  
28 - const ROUTE = useRoute();  
29 -  
30 - const loading = ref(false);  
31 - const { createMessage } = useMessage();  
32 -  
33 - const boardId = computed(() => {  
34 - return decode((ROUTE.params as DataComponentRouteParams).boardId as string);  
35 - });  
36 -  
37 - const frontId = ref();  
38 -  
39 - const isEdit = ref(false);  
40 -  
41 - const componentRecord = ref<DataBoardLayoutInfo>({} as unknown as DataBoardLayoutInfo);  
42 -  
43 - const componentDefaultConfig = ref<Partial<ComponentInfo>>({});  
44 -  
45 - const getComponentCategory = computed(() => {  
46 - return frontComponentMap.get(unref(frontId))?.ComponentCategory;  
47 - });  
48 -  
49 - const [register, { closeModal, changeOkLoading }] = useModalInner(  
50 - (data: { isEdit: boolean; record?: DataBoardLayoutInfo }) => {  
51 - componentRecord.value = data.record || ({} as unknown as DataBoardLayoutInfo);  
52 - if (!unref(isEdit)) frontId.value = FrontComponent.TEXT_COMPONENT_1;  
53 - frontId.value =  
54 - (data.record?.record?.frontId as FrontComponent) || FrontComponent.TEXT_COMPONENT_1;  
55 - isEdit.value = data.isEdit || false;  
56 - }  
57 - );  
58 -  
59 - const basicConfigurationEl = ref<{  
60 - getAllDataSourceFieldValue: Fn<any, Recordable>;  
61 - validate: Fn;  
62 - }>();  
63 -  
64 - const resetForm = () => {  
65 - isEdit.value = false;  
66 - frontId.value = undefined;  
67 - componentRecord.value = {} as unknown as DataBoardLayoutInfo;  
68 - componentDefaultConfig.value = {};  
69 - };  
70 -  
71 - const handleSubmit = async () => {  
72 - try {  
73 - const { getAllDataSourceFieldValue, validate } = unref(basicConfigurationEl)!;  
74 - await validate();  
75 - const value = getAllDataSourceFieldValue();  
76 - unref(isEdit) ? handleUpdateComponent(value) : handleAddComponent(value);  
77 - resetForm();  
78 - } catch (error: unknown) {  
79 - if (  
80 - ((error || {}) as ValidateErrorEntity).errorFields &&  
81 - ((error || {}) as ValidateErrorEntity).errorFields.length  
82 - ) {  
83 - const tooltip = ((error || {}) as ValidateErrorEntity).errorFields[0];  
84 - createMessage.warning(tooltip.errors[0]);  
85 - }  
86 - throw error;  
87 - }  
88 - };  
89 -  
90 - const { calcLayoutInfo } = useCalcGridLayout();  
91 - const handleAddComponent = async (value: Recordable) => {  
92 - try {  
93 - if (!unref(frontId)) {  
94 - createMessage.warning('请选择可视化组件');  
95 - return;  
96 - }  
97 - const layout = calcLayoutInfo(unref(props.layout));  
98 - changeOkLoading(true);  
99 - loading.value = true;  
100 - await addDataComponent({  
101 - boardId: unref(boardId),  
102 - record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value, layout },  
103 - });  
104 - createMessage.success('创建成功');  
105 - closeModal();  
106 - emit('create');  
107 - } catch (error) {  
108 - throw error;  
109 - // createMessage.error('创建失败');  
110 - } finally {  
111 - changeOkLoading(false);  
112 - loading.value = false;  
113 - }  
114 - };  
115 -  
116 - const handleUpdateComponent = async (value: Recordable) => {  
117 - try {  
118 - changeOkLoading(true);  
119 - loading.value = true;  
120 - const res = await updateDataComponent({  
121 - boardId: unref(boardId),  
122 - record: {  
123 - id: unref(componentRecord).i,  
124 - dataBoardId: unref(boardId),  
125 - frontId: unref(frontId),  
126 - ...value,  
127 - },  
128 - });  
129 - createMessage.success('修改成功');  
130 - closeModal();  
131 - // emit('submit');  
132 - emit('update', res.data.id);  
133 - } catch (error) {  
134 - // createMessage.error('修改失败');  
135 - } finally {  
136 - changeOkLoading(false);  
137 - loading.value = false;  
138 - }  
139 - };  
140 -  
141 - const handleComponentCheckedChange = (record: ComponentInfo) => {  
142 - componentDefaultConfig.value = record;  
143 - };  
144 -</script>  
145 -  
146 -<template>  
147 - <BasicModal  
148 - v-bind="$attrs"  
149 - @register="register"  
150 - title="自定义组件"  
151 - width="70%"  
152 - :destroy-on-close="true"  
153 - @ok="handleSubmit"  
154 - @cancel="resetForm"  
155 - :ok-button-props="{ loading }"  
156 - >  
157 - <section>  
158 - <Tabs type="card">  
159 - <Tabs.TabPane key="basicConfig" tab="基础配置">  
160 - <BasicConfiguration  
161 - ref="basicConfigurationEl"  
162 - :front-id="frontId"  
163 - :isEdit="isEdit"  
164 - :record="componentRecord"  
165 - :defaultConfig="componentDefaultConfig"  
166 - :componentCategory="getComponentCategory"  
167 - />  
168 - </Tabs.TabPane>  
169 - <Tabs.TabPane key="visualConfig" tab="可视化配置">  
170 - <VisualConfiguration v-model:value="frontId" @change="handleComponentCheckedChange" />  
171 - </Tabs.TabPane>  
172 - </Tabs>  
173 - </section>  
174 - </BasicModal>  
175 -</template>  
1 -<script lang="ts" setup>  
2 - import { ref, computed } from 'vue';  
3 - import { FrontComponent } from '../../../const/const';  
4 - import { dataSourceSchema } from '../../config/basicConfiguration';  
5 - import { FormActionType } from '/@/components/Form';  
6 - import BasicForm from '/@/components/Form/src/BasicForm.vue';  
7 - const formEl = ref<Nullable<FormActionType>>(null);  
8 -  
9 - const props = defineProps<{  
10 - frontId?: FrontComponent;  
11 - isEdit: boolean;  
12 - }>();  
13 -  
14 - defineExpose({ formActionType: formEl });  
15 -  
16 - const getDataSchema = computed(() => {  
17 - const { frontId, isEdit } = props;  
18 - if (!frontId) return [];  
19 - return dataSourceSchema(isEdit, frontId);  
20 - });  
21 -</script>  
22 -  
23 -<template>  
24 - <BasicForm  
25 - ref="formEl"  
26 - :schemas="getDataSchema"  
27 - class="w-full flex-1 data-source-form"  
28 - :show-action-button-group="false"  
29 - :row-props="{  
30 - gutter: 10,  
31 - }"  
32 - layout="horizontal"  
33 - :label-col="{ span: 0 }"  
34 - />  
35 -</template>  
1 -<script lang="ts" setup>  
2 - import { ref, unref } from 'vue';  
3 - import { BasicForm, FormActionType } from '/@/components/Form';  
4 - import { dataSourceSchema } from '../../config/basicConfiguration';  
5 - import { FrontComponent } from '../../../const/const';  
6 -  
7 - defineProps<{  
8 - isEdit: boolean;  
9 - frontId?: FrontComponent;  
10 - }>();  
11 -  
12 - const formEl = ref<Nullable<FormActionType>>();  
13 -  
14 - const setFormEl = (el: any) => {  
15 - formEl.value = el;  
16 - };  
17 -  
18 - const getFieldsValue = () => {  
19 - return unref(formEl)!.getFieldsValue();  
20 - };  
21 -  
22 - const validate = async () => {  
23 - await unref(formEl)!.validate();  
24 - };  
25 -  
26 - const setFieldsValue = async (record: Recordable) => {  
27 - await unref(formEl)!.setFieldsValue(record);  
28 - };  
29 -  
30 - const clearValidate = async (name?: string | string[]) => {  
31 - await unref(formEl)!.clearValidate(name);  
32 - };  
33 - defineExpose({  
34 - formActionType: { getFieldsValue, validate, setFieldsValue, clearValidate },  
35 - });  
36 -</script>  
37 -  
38 -<template>  
39 - <div class="w-full flex-1">  
40 - <BasicForm  
41 - :ref="(el) => setFormEl(el)"  
42 - :schemas="dataSourceSchema($props.isEdit, $props.frontId)"  
43 - class="w-full flex-1 data-source-form"  
44 - :show-action-button-group="false"  
45 - :row-props="{  
46 - gutter: 10,  
47 - }"  
48 - layout="horizontal"  
49 - :label-col="{ span: 0 }"  
50 - />  
51 - </div>  
52 -</template>  
1 -import { Component } from 'vue';  
2 -import { FrontComponent } from '../../../const/const';  
3 -import BasicDataSourceForm from './BasicDataSourceForm.vue';  
4 -// import ControlDataSourceForm from './ControlDataSourceForm.vue';  
5 -  
6 -const dataSourceComponentMap = new Map<FrontComponent, Component>();  
7 -  
8 -export const getDataSourceComponent = (frontId: FrontComponent) => {  
9 - if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!;  
10 - return BasicDataSourceForm;  
11 -};  
1 -<script lang="ts" setup>  
2 - import { computed, nextTick, Ref, ref, unref } from 'vue';  
3 - import { getDeviceHistoryInfo } from '/@/api/alarm/position';  
4 - import { Empty, Spin } from 'ant-design-vue';  
5 - import { useECharts } from '/@/hooks/web/useECharts';  
6 - import { AggregateDataEnum } from '/@/views/device/localtion/config.data';  
7 - import { useGridLayout } from '/@/hooks/component/useGridLayout';  
8 - import { ColEx } from '/@/components/Form/src/types';  
9 - import { DataSource } from '/@/api/dataBoard/model';  
10 - import { useForm, BasicForm } from '/@/components/Form';  
11 - import { formSchema, SchemaFiled } from '../config/historyTrend.config';  
12 - import BasicModal from '/@/components/Modal/src/BasicModal.vue';  
13 - import { useModalInner } from '/@/components/Modal';  
14 - import { getAllDeviceByOrg } from '/@/api/dataBoard';  
15 - import { useHistoryData } from '/@/views/device/list/hook/useHistoryData';  
16 - import { BasicTable, useTable } from '/@/components/Table';  
17 - import { formatToDateTime } from '/@/utils/dateUtil';  
18 - import {  
19 - ModeSwitchButton,  
20 - TABLE_CHART_MODE_LIST,  
21 - EnumTableChartMode,  
22 - } from '/@/components/Widget';  
23 -  
24 - type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;  
25 -  
26 - defineEmits(['register']);  
27 -  
28 - const mode = ref<EnumTableChartMode>(EnumTableChartMode.CHART);  
29 -  
30 - const chartRef = ref();  
31 -  
32 - const loading = ref(false);  
33 -  
34 - const isNull = ref(false);  
35 -  
36 - const historyData = ref<{ ts: number; value: string; name: string }[]>([]);  
37 -  
38 - const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } =  
39 - useHistoryData();  
40 -  
41 - const { setOptions, destory } = useECharts(chartRef as Ref<HTMLDivElement>);  
42 -  
43 - function hasDeviceAttr() {  
44 - return !!unref(deviceAttrs).length;  
45 - }  
46 -  
47 - const [register, method] = useForm({  
48 - schemas: formSchema(),  
49 - baseColProps: useGridLayout(2, 3, 4) as unknown as ColEx,  
50 - rowProps: {  
51 - gutter: 10,  
52 - },  
53 - labelWidth: 120,  
54 - fieldMapToTime: [  
55 - [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:ss'],  
56 - ],  
57 - submitButtonOptions: {  
58 - loading: loading as unknown as boolean,  
59 - },  
60 - async submitFunc() {  
61 - search();  
62 - },  
63 - });  
64 -  
65 - const search = async () => {  
66 - // 表单验证  
67 - await method.validate();  
68 - const value = method.getFieldsValue();  
69 - const searchParams = getSearchParams(value);  
70 - if (!hasDeviceAttr()) return;  
71 - // 发送请求  
72 - loading.value = true;  
73 - const res = await getDeviceHistoryInfo(searchParams);  
74 - historyData.value = getTableHistoryData(res);  
75 - loading.value = false;  
76 - // 判断数据对象是否为空  
77 - if (!Object.keys(res).length) {  
78 - isNull.value = false;  
79 - return;  
80 - } else {  
81 - isNull.value = true;  
82 - }  
83 -  
84 - const selectedKeys = unref(deviceAttrs).find(  
85 - (item) => item.identifier === value[SchemaFiled.KEYS]  
86 - );  
87 - setOptions(setChartOptions(res, selectedKeys));  
88 - };  
89 -  
90 - const getTableHistoryData = (record: Recordable<{ ts: number; value: string }[]>) => {  
91 - const keys = Object.keys(record);  
92 - const list = keys.reduce((prev, next) => {  
93 - const list = record[next].map((item) => {  
94 - return {  
95 - ...item,  
96 - name: next,  
97 - };  
98 - });  
99 - return [...prev, ...list];  
100 - }, []);  
101 - return list;  
102 - };  
103 -  
104 - const getIdentifierNameMapping = computed(() => {  
105 - const mapping = {};  
106 - unref(deviceAttrs).forEach((item) => {  
107 - const { identifier, name } = item;  
108 - mapping[identifier] = name;  
109 - });  
110 - return mapping;  
111 - });  
112 -  
113 - const [registerTable] = useTable({  
114 - showIndexColumn: false,  
115 - showTableSetting: false,  
116 - dataSource: historyData,  
117 - maxHeight: 300,  
118 - size: 'small',  
119 - columns: [  
120 - {  
121 - title: '属性',  
122 - dataIndex: 'name',  
123 - format: (value) => {  
124 - return unref(getIdentifierNameMapping)[value];  
125 - },  
126 - },  
127 - {  
128 - title: '值',  
129 - dataIndex: 'value',  
130 - },  
131 - {  
132 - title: '更新时间',  
133 - dataIndex: 'ts',  
134 - format: (val) => {  
135 - return formatToDateTime(val, 'YYYY-MM-DD HH:mm:ss');  
136 - },  
137 - },  
138 - ],  
139 - });  
140 -  
141 - const getDeviceDataKey = async (record: DeviceOption) => {  
142 - const { organizationId, value } = record;  
143 - try {  
144 - const options = await getAllDeviceByOrg(organizationId);  
145 - const record = options.find((item) => item.tbDeviceId === value);  
146 - await getDeviceAttribute(record!);  
147 - await nextTick();  
148 - method.updateSchema({  
149 - field: SchemaFiled.KEYS,  
150 - componentProps: {  
151 - options: unref(deviceAttrs).map((item) => ({ label: item.name, value: item.identifier })),  
152 - },  
153 - });  
154 - } catch (error) {  
155 - throw error;  
156 - }  
157 - };  
158 -  
159 - const handleModalOpen = async () => {  
160 - await nextTick();  
161 -  
162 - method.setFieldsValue({  
163 - [SchemaFiled.START_TS]: 1 * 24 * 60 * 60 * 1000,  
164 - [SchemaFiled.LIMIT]: 7,  
165 - [SchemaFiled.AGG]: AggregateDataEnum.NONE,  
166 - });  
167 -  
168 - if (!hasDeviceAttr()) return;  
169 -  
170 - const { deviceId } = method.getFieldsValue();  
171 -  
172 - const res = await getDeviceHistoryInfo({  
173 - entityId: deviceId,  
174 - keys: unref(getDeviceKeys).join(),  
175 - startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,  
176 - endTs: Date.now(),  
177 - agg: AggregateDataEnum.NONE,  
178 - limit: 7,  
179 - });  
180 - historyData.value = getTableHistoryData(res);  
181 - // 判断对象是否为空  
182 - if (!Object.keys(res).length) {  
183 - isNull.value = false;  
184 - return;  
185 - } else {  
186 - isNull.value = true;  
187 - }  
188 - setOptions(setChartOptions(res));  
189 - };  
190 -  
191 - const generateDeviceOptions = (dataSource: DataSource[]) => {  
192 - const record: { [key: string]: boolean } = {};  
193 -  
194 - const options: DeviceOption[] = [];  
195 - for (const item of dataSource) {  
196 - const { deviceName, gatewayDevice, slaveDeviceId, organizationId } = item;  
197 - let { deviceId } = item;  
198 - if (gatewayDevice && slaveDeviceId) {  
199 - deviceId = slaveDeviceId;  
200 - }  
201 - if (record[deviceId]) continue;  
202 - options.push({  
203 - label: deviceName,  
204 - value: deviceId,  
205 - organizationId,  
206 - });  
207 - record[deviceId] = true;  
208 - }  
209 -  
210 - return options;  
211 - };  
212 -  
213 - const [registerModal] = useModalInner(async (dataSource: DataSource[]) => {  
214 - deviceAttrs.value = [];  
215 - loading.value = false;  
216 - const options = generateDeviceOptions(dataSource);  
217 - await nextTick();  
218 - method.updateSchema({  
219 - field: SchemaFiled.DEVICE_ID,  
220 - componentProps({ formActionType }) {  
221 - const { setFieldsValue } = formActionType;  
222 - return {  
223 - options,  
224 - onChange(_, record: DeviceOption) {  
225 - getDeviceDataKey(record);  
226 - setFieldsValue({ [SchemaFiled.KEYS]: null });  
227 - },  
228 - };  
229 - },  
230 - });  
231 -  
232 - if (options.length && options.at(0)?.value) {  
233 - const record = options.at(0)!;  
234 - await getDeviceDataKey(record);  
235 - try {  
236 - await method.setFieldsValue({ [SchemaFiled.DEVICE_ID]: record.value });  
237 - } catch (error) {}  
238 - }  
239 -  
240 - await handleModalOpen();  
241 - });  
242 -  
243 - const handleCancel = () => {  
244 - destory();  
245 - };  
246 -  
247 - const switchMode = (flag: EnumTableChartMode) => {  
248 - mode.value = flag;  
249 - };  
250 -</script>  
251 -  
252 -<template>  
253 - <BasicModal  
254 - @register="registerModal"  
255 - @cancel="handleCancel"  
256 - :destroy-on-close="true"  
257 - :show-ok-btn="false"  
258 - cancel-text="关闭"  
259 - width="70%"  
260 - title="历史趋势"  
261 - >  
262 - <section  
263 - class="flex flex-col p-4 h-full w-full min-w-7/10"  
264 - style="color: #f0f2f5; background-color: #f0f2f5"  
265 - >  
266 - <section class="bg-white my-3 p-2">  
267 - <BasicForm @register="register" />  
268 - </section>  
269 - <section class="bg-white p-3" style="min-height: 350px">  
270 - <Spin :spinning="loading" :absolute="true">  
271 - <div  
272 - v-show="mode === EnumTableChartMode.CHART"  
273 - class="flex h-70px items-center justify-end p-2"  
274 - >  
275 - <ModeSwitchButton  
276 - v-model:value="mode"  
277 - :mode="TABLE_CHART_MODE_LIST"  
278 - @change="switchMode"  
279 - />  
280 - </div>  
281 -  
282 - <div  
283 - v-show="isNull && mode === EnumTableChartMode.CHART"  
284 - ref="chartRef"  
285 - :style="{ height: '350px', width: '100%' }"  
286 - >  
287 - </div>  
288 - <Empty  
289 - v-if="mode === EnumTableChartMode.CHART"  
290 - class="h-350px flex flex-col justify-center items-center"  
291 - description="暂无数据,请选择设备查询"  
292 - v-show="!isNull"  
293 - />  
294 -  
295 - <BasicTable v-show="mode === EnumTableChartMode.TABLE" @register="registerTable">  
296 - <template #toolbar>  
297 - <div class="flex h-70px items-center justify-end p-2">  
298 - <ModeSwitchButton  
299 - v-model:value="mode"  
300 - :mode="TABLE_CHART_MODE_LIST"  
301 - @change="switchMode"  
302 - />  
303 - </div>  
304 - </template>  
305 - </BasicTable>  
306 - </Spin>  
307 - </section>  
308 - </section>  
309 - </BasicModal>  
310 -</template>  
311 -  
312 -<style scoped></style>  
1 -<script lang="ts" setup>  
2 - import { Tabs, List } from 'ant-design-vue';  
3 - import VisualWidgetSelect from './VisualWidgetSelect.vue';  
4 - import { getComponentDefaultConfig } from '../../components/help';  
5 - import { frontComponentMap } from '../../components/help';  
6 - import { computed } from 'vue';  
7 - import {  
8 - FrontComponent,  
9 - FrontComponentCategory,  
10 - FrontComponentCategoryName,  
11 - } from '../../const/const';  
12 -  
13 - interface DataSource {  
14 - category: string;  
15 - categoryName: string;  
16 - list: Recordable[];  
17 - }  
18 - const props = defineProps<{  
19 - value?: string;  
20 - }>();  
21 - const emit = defineEmits(['update:value', 'change']);  
22 -  
23 - const grid = { gutter: 10, column: 1, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 4 };  
24 -  
25 - const getDataSource = computed(() => {  
26 - const _dataSource = Array.from(frontComponentMap.values());  
27 - const category = new Map<FrontComponentCategory, DataSource>();  
28 - for (const item of _dataSource) {  
29 - if (category.has(item.ComponentCategory)) {  
30 - const value = category.get(item.ComponentCategory)!;  
31 - value.list.push(item);  
32 - continue;  
33 - }  
34 - category.set(item.ComponentCategory, {  
35 - category: item.ComponentCategory,  
36 - categoryName: FrontComponentCategoryName[item.ComponentCategory],  
37 - list: [item],  
38 - });  
39 - }  
40 - return Array.from(category.values());  
41 - });  
42 -  
43 - const handleCheck = (checked: FrontComponent) => {  
44 - const defaultConfig = getComponentDefaultConfig(checked);  
45 - emit('update:value', checked);  
46 - emit('change', defaultConfig);  
47 - };  
48 -</script>  
49 -  
50 -<template>  
51 - <section>  
52 - <Tabs>  
53 - <Tabs.TabPane  
54 - v-for="category in getDataSource"  
55 - :key="category.category"  
56 - :tab="category.categoryName"  
57 - >  
58 - <List :grid="grid" :data-source="category.list">  
59 - <template #renderItem="{ item }">  
60 - <List.Item class="!flex !justify-center">  
61 - <VisualWidgetSelect  
62 - :checked-id="props.value"  
63 - :control-id="item.ComponentKey"  
64 - @change="handleCheck"  
65 - >  
66 - <template #default>  
67 - <component :is="item.Component" :random="true" :layout="item.ComponentConfig" />  
68 - </template>  
69 - <template #description>  
70 - {{ item.ComponentName || '选择' }}  
71 - </template>  
72 - </VisualWidgetSelect>  
73 - </List.Item>  
74 - </template>  
75 - </List>  
76 - </Tabs.TabPane>  
77 - </Tabs>  
78 - </section>  
79 -</template>  
1 -<script lang="ts" setup>  
2 - import { ref, unref } from 'vue';  
3 - import { schemasMap, VisualOptionParams } from '../config/visualOptions';  
4 - import { useForm, BasicForm } from '/@/components/Form';  
5 - import { BasicModal, useModalInner } from '/@/components/Modal';  
6 - import { ComponentInfo } from '/@/api/dataBoard/model';  
7 - import { computed } from '@vue/reactivity';  
8 - import { FrontComponent, Gradient, visualOptionField } from '../../const/const';  
9 -  
10 - const emit = defineEmits(['close', 'register']);  
11 -  
12 - const props = defineProps<{  
13 - value?: string;  
14 - }>();  
15 -  
16 - const recordId = ref('');  
17 -  
18 - const getSchemas = computed(() => {  
19 - return schemasMap.get((props.value as FrontComponent) || 'text-component-1');  
20 - });  
21 -  
22 - const [registerForm, method] = useForm({  
23 - showActionButtonGroup: false,  
24 - labelWidth: 120,  
25 - baseColProps: {  
26 - span: 12,  
27 - },  
28 - });  
29 -  
30 - const [register, { closeModal }] = useModalInner(  
31 - (data: { recordId: string; componentInfo: ComponentInfo }) => {  
32 - recordId.value = data.recordId;  
33 - const gradientInfo = data.componentInfo?.gradientInfo || [];  
34 - let gradientRecord = {};  
35 - if (gradientInfo && gradientInfo.length) {  
36 - const first = gradientInfo.find((item) => item.key === Gradient.FIRST);  
37 - const second = gradientInfo.find((item) => item.key === Gradient.SECOND);  
38 - const third = gradientInfo.find((item) => item.key === Gradient.THIRD);  
39 - gradientRecord = {  
40 - [visualOptionField.FIRST_PHASE_COLOR]: first?.color,  
41 - [visualOptionField.FIRST_PHASE_VALUE]: first?.value,  
42 - [visualOptionField.SECOND_PHASE_COLOR]: second?.color,  
43 - [visualOptionField.SECOND_PHASE_VALUE]: second?.value,  
44 - [visualOptionField.THIRD_PHASE_COLOR]: third?.color,  
45 - [visualOptionField.THIRD_PHASE_VALUE]: third?.value,  
46 - };  
47 - }  
48 -  
49 - method.setFieldsValue({ ...(data.componentInfo || {}), ...gradientRecord });  
50 - }  
51 - );  
52 -  
53 - const handleGetValue = () => {  
54 - const value = method.getFieldsValue();  
55 - emit('close', unref(recordId), transformValue(value));  
56 - };  
57 -  
58 - const transformValue = (value: Partial<VisualOptionParams>) => {  
59 - return {  
60 - fontColor: value.fontColor || null,  
61 - icon: value.icon || null,  
62 - iconColor: value.iconColor || null,  
63 - unit: value.unit || null,  
64 - showDeviceName: value.showDeviceName,  
65 - gradientInfo: [  
66 - { key: Gradient.FIRST, value: value.firstPhaseValue, color: value.firstPhaseColor },  
67 - { key: Gradient.SECOND, value: value.secondPhaseValue, color: value.secondPhaseColor },  
68 - { key: Gradient.THIRD, value: value.thirdPhaseValue, color: value.thirdPhaseColor },  
69 - ],  
70 - };  
71 - };  
72 -  
73 - const handleClose = () => {  
74 - handleGetValue();  
75 - closeModal();  
76 - };  
77 -</script>  
78 -  
79 -<template>  
80 - <BasicModal  
81 - v-bind="$attrs"  
82 - destroy-on-close  
83 - @register="register"  
84 - @ok="handleClose"  
85 - title="选项"  
86 - width="40%"  
87 - >  
88 - <BasicForm class="form" @register="registerForm" :schemas="getSchemas" />  
89 - </BasicModal>  
90 -</template>  
91 -  
92 -<style scoped>  
93 - .form:deep(.ant-input-number) {  
94 - width: 100%;  
95 - }  
96 -</style>  
1 -<script lang="ts" setup>  
2 - import { Card } from 'ant-design-vue';  
3 -  
4 - const props = defineProps({  
5 - controlId: {  
6 - type: String,  
7 - // required: true,  
8 - },  
9 - checkedId: {  
10 - type: String,  
11 - // required: true,  
12 - },  
13 - });  
14 - const emit = defineEmits(['change']);  
15 -  
16 - const handleClick = () => {  
17 - emit('change', props.controlId);  
18 - };  
19 -</script>  
20 -  
21 -<template>  
22 - <Card  
23 - :style="{ borderColor: props.controlId === props.checkedId ? '#3079FF' : '#fff' }"  
24 - hoverable  
25 - bordered  
26 - class="w-60 h-60 border-2 widget-select !bg-light-50 cursor-pointer"  
27 - @click="handleClick"  
28 - >  
29 - <div class="widget-container">  
30 - <slot></slot>  
31 - </div>  
32 - <Card.Meta>  
33 - <template #description>  
34 - <slot name="description"></slot>  
35 - </template>  
36 - </Card.Meta>  
37 - </Card>  
38 -</template>  
39 -  
40 -<style scoped>  
41 - .widget-select {  
42 - box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1);  
43 - border-width: 2px;  
44 - }  
45 -  
46 - .widget-select:deep(.ant-card-body) {  
47 - /* height: 240px; */  
48 -  
49 - /* width: 236px;  
50 - height: 196px; */  
51 -  
52 - /* width: 100%;  
53 - height: 100%; */  
54 - width: 236px;  
55 - height: 236px;  
56 - padding: 0;  
57 - box-sizing: border-box;  
58 - display: flex;  
59 - flex-direction: column;  
60 - align-items: center;  
61 - justify-content: center;  
62 - }  
63 -  
64 - .widget-select:deep(.ant-card-meta) {  
65 - border-top: 1px solid #f0f0f0;  
66 - width: 100%;  
67 - height: 40px;  
68 - text-align: center;  
69 - line-height: 40px;  
70 - margin: 0;  
71 - }  
72 -  
73 - .widget-select .widget-container {  
74 - width: 236px;  
75 - height: 196px;  
76 - display: flex;  
77 - justify-content: center;  
78 - align-items: center;  
79 - }  
80 -  
81 - [data-theme='dark'] .widget-select:deep(.ant-card-body) {  
82 - @apply bg-dark-900;  
83 - }  
84 -  
85 - [data-theme='dark'] .widget-select:deep(.ant-card) {  
86 - @apply border-dark-300;  
87 - }  
88 -</style>  
1 -import { getDeviceAttributes, getMeetTheConditionsDevice } from '/@/api/dataBoard';  
2 -import { getOrganizationList } from '/@/api/system/system';  
3 -import { FormSchema, useComponentRegister } from '/@/components/Form';  
4 -import { copyTransFun } from '/@/utils/fnUtils';  
5 -import { DeviceAttributeParams, MasterDeviceList } from '/@/api/dataBoard/model';  
6 -import { FrontComponent } from '../../const/const';  
7 -import { getDeviceProfile } from '/@/api/alarm/position';  
8 -import { getModelServices } from '/@/api/device/modelOfMatter';  
9 -import { findDictItemByCode } from '/@/api/system/dict';  
10 -import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';  
11 -import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';  
12 -import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';  
13 -import { JSONEditor } from '/@/components/CodeEditor';  
14 -import { CommandTypeEnum } from '/@/views/rule/linkedge/config/config.data';  
15 -import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';  
16 -  
17 -useComponentRegister('JSONEditor', JSONEditor);  
18 -  
19 -export enum BasicConfigField {  
20 - NAME = 'name',  
21 - REMARK = 'remark',  
22 -}  
23 -  
24 -const getDeviceAttribute = async (params: DeviceAttributeParams) => {  
25 - try {  
26 - const data = await getDeviceAttributes(params);  
27 - if (data) return data.map((item) => ({ label: item.name, value: item.identifier }));  
28 - } catch (error) {}  
29 - return [];  
30 -};  
31 -  
32 -const getDeviceService = async (deviceProfileId: string) => {  
33 - try {  
34 - const data = await getModelServices({ deviceProfileId });  
35 - if (data)  
36 - return data.map((item) => ({ ...item, label: item.functionName, value: item.identifier }));  
37 - } catch (error) {}  
38 - return [];  
39 -};  
40 -  
41 -export enum DataSourceField {  
42 - IS_GATEWAY_DEVICE = 'gatewayDevice',  
43 - DEVICE_TYPE = 'deviceType',  
44 - TRANSPORT_TYPE = 'transportType',  
45 - ORIGINATION_ID = 'organizationId',  
46 - DEVICE_ID = 'deviceId',  
47 - // SLAVE_DEVICE_ID = 'slaveDeviceId',  
48 - DEVICE_PROFILE_ID = 'deviceProfileId',  
49 - ATTRIBUTE = 'attribute',  
50 - ATTRIBUTE_RENAME = 'attributeRename',  
51 - DEVICE_NAME = 'deviceName',  
52 - DEVICE_RENAME = 'deviceRename',  
53 - LONGITUDE_ATTRIBUTE = 'longitudeAttribute',  
54 - LATITUDE_ATTRIBUTE = 'latitudeAttribute',  
55 -  
56 - COMMAND = 'command',  
57 - COMMAND_TYPE = 'commandType',  
58 - SERVICE = 'service',  
59 -}  
60 -  
61 -export const isControlComponent = (frontId: FrontComponent) => {  
62 - const list = [  
63 - FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH,  
64 - FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON,  
65 - FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH,  
66 - ];  
67 - return list.includes(frontId);  
68 -};  
69 -  
70 -export const isMapComponent = (frontId: FrontComponent) => {  
71 - const list = [  
72 - FrontComponent.MAP_COMPONENT_TRACK_HISTORY,  
73 - FrontComponent.MAP_COMPONENT_TRACK_REAL,  
74 - ];  
75 - return list.includes(frontId);  
76 -};  
77 -  
78 -const isTcpProfile = (transportType: string) => {  
79 - return transportType === TransportTypeEnum.TCP;  
80 -};  
81 -  
82 -export const basicSchema: FormSchema[] = [  
83 - {  
84 - field: BasicConfigField.NAME,  
85 - label: '组件名称',  
86 - component: 'Input',  
87 - // rules: [{ required: true, message: '组件名称为必填项' }],  
88 - componentProps: {  
89 - placeholder: '请输入组件名称',  
90 - maxLength: 32,  
91 - },  
92 - },  
93 - {  
94 - field: BasicConfigField.REMARK,  
95 - label: '组件备注',  
96 - component: 'InputTextArea',  
97 - // rules: [{ required: true, message: '组件备注为必填项' }],  
98 - componentProps: {  
99 - placeholder: '请输入组件备注',  
100 - maxLength: 255,  
101 - },  
102 - },  
103 -];  
104 -  
105 -export const dataSourceSchema = (isEdit: boolean, frontId?: string): FormSchema[] => {  
106 - // console.log(useSelectWidgetKeys());  
107 - // const isEdit = unref(useSelectWidgetMode()) === DataActionModeEnum.UPDATE;  
108 -  
109 - return [  
110 - {  
111 - field: DataSourceField.IS_GATEWAY_DEVICE,  
112 - component: 'Switch',  
113 - label: '是否是网关设备',  
114 - show: false,  
115 - },  
116 - {  
117 - field: DataSourceField.DEVICE_NAME,  
118 - component: 'Input',  
119 - label: '设备名',  
120 - show: false,  
121 - },  
122 - {  
123 - field: DataSourceField.TRANSPORT_TYPE,  
124 - component: 'Input',  
125 - label: '设备配置类型',  
126 - show: false,  
127 - },  
128 - {  
129 - field: DataSourceField.DEVICE_TYPE,  
130 - component: 'ApiSelect',  
131 - label: '设备类型',  
132 - colProps: { span: 8 },  
133 - rules: [{ message: '请选择设备类型', required: true }],  
134 - componentProps: ({ formActionType }) => {  
135 - const { setFieldsValue } = formActionType;  
136 - return {  
137 - api: findDictItemByCode,  
138 - params: {  
139 - dictCode: 'device_type',  
140 - },  
141 - valueField: 'itemValue',  
142 - labelField: 'itemText',  
143 - placeholder: '请选择设备类型',  
144 - onChange: (value: DeviceTypeEnum) => {  
145 - setFieldsValue({  
146 - [DataSourceField.IS_GATEWAY_DEVICE]: value === DeviceTypeEnum.GATEWAY,  
147 - [DataSourceField.DEVICE_PROFILE_ID]: null,  
148 - [DataSourceField.DEVICE_ID]: null,  
149 - [DataSourceField.ATTRIBUTE]: null,  
150 - [DataSourceField.TRANSPORT_TYPE]: null,  
151 - });  
152 - },  
153 - getPopupContainer: () => document.body,  
154 - };  
155 - },  
156 - },  
157 - {  
158 - field: DataSourceField.DEVICE_PROFILE_ID,  
159 - component: 'ApiSelect',  
160 - label: '产品',  
161 - colProps: { span: 8 },  
162 - rules: [{ required: true, message: '产品为必填项' }],  
163 - componentProps: ({ formActionType, formModel }) => {  
164 - const { setFieldsValue } = formActionType;  
165 - const deviceType = formModel[DataSourceField.DEVICE_TYPE];  
166 - return {  
167 - api: async () => {  
168 - if (!deviceType) return [];  
169 - const list = await getDeviceProfile(deviceType);  
170 - if (isEdit) {  
171 - const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];  
172 - const record = list.find((item) => item.id === deviceProfileId);  
173 - setFieldsValue({ [DataSourceField.TRANSPORT_TYPE]: record?.transportType });  
174 - }  
175 - return list;  
176 - },  
177 - labelField: 'name',  
178 - valueField: 'id',  
179 - placeholder: '请选择产品',  
180 - onChange: (_, option = {} as Record<'transportType', string>) => {  
181 - setFieldsValue({  
182 - [DataSourceField.DEVICE_ID]: null,  
183 - [DataSourceField.ATTRIBUTE]: null,  
184 - [DataSourceField.TRANSPORT_TYPE]: option[DataSourceField.TRANSPORT_TYPE],  
185 - });  
186 - },  
187 - getPopupContainer: () => document.body,  
188 - };  
189 - },  
190 - },  
191 - {  
192 - field: DataSourceField.ORIGINATION_ID,  
193 - component: 'ApiTreeSelect',  
194 - label: '组织',  
195 - colProps: { span: 8 },  
196 - rules: [{ required: true, message: '组织为必填项' }],  
197 - componentProps({ formActionType }) {  
198 - const { setFieldsValue } = formActionType;  
199 - return {  
200 - placeholder: '请选择组织',  
201 - api: async () => {  
202 - const data = await getOrganizationList();  
203 - copyTransFun(data as any as any[]);  
204 - return data;  
205 - },  
206 - onChange() {  
207 - setFieldsValue({  
208 - [DataSourceField.DEVICE_ID]: null,  
209 - });  
210 - },  
211 - getPopupContainer: () => document.body,  
212 - };  
213 - },  
214 - },  
215 - {  
216 - field: DataSourceField.DEVICE_PROFILE_ID,  
217 - component: 'Input',  
218 - label: '',  
219 - show: false,  
220 - },  
221 - {  
222 - field: DataSourceField.DEVICE_ID,  
223 - component: 'ApiSelect',  
224 - label: '设备',  
225 - colProps: { span: 8 },  
226 - rules: [{ required: true, message: '设备名称为必填项' }],  
227 - componentProps({ formModel, formActionType }) {  
228 - const { setFieldsValue } = formActionType;  
229 - const organizationId = formModel[DataSourceField.ORIGINATION_ID];  
230 - const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];  
231 - const deviceType = formModel[DataSourceField.DEVICE_TYPE];  
232 -  
233 - return {  
234 - api: async () => {  
235 - if (organizationId) {  
236 - try {  
237 - const data = await getMeetTheConditionsDevice({  
238 - organizationId,  
239 - deviceProfileId,  
240 - deviceType,  
241 - });  
242 - if (data)  
243 - return data.map((item) => ({  
244 - ...item,  
245 - label: item.alias || item.name,  
246 - value: item.tbDeviceId,  
247 - deviceType: item.deviceType,  
248 - }));  
249 - } catch (error) {}  
250 - }  
251 - return [];  
252 - },  
253 - onChange(_value, record: MasterDeviceList) {  
254 - setFieldsValue({  
255 - [DataSourceField.DEVICE_NAME]: record?.label,  
256 - });  
257 - },  
258 - placeholder: '请选择设备',  
259 - getPopupContainer: () => document.body,  
260 - };  
261 - },  
262 - },  
263 - {  
264 - field: DataSourceField.ATTRIBUTE,  
265 - component: 'ApiSelect',  
266 - label: '属性',  
267 - colProps: { span: 8 },  
268 - rules: [{ required: true, message: '请选择属性' }],  
269 - ifShow: ({ model }) =>  
270 - !(isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && isControlComponent(frontId!)),  
271 - componentProps({ formModel }) {  
272 - const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];  
273 - return {  
274 - api: async () => {  
275 - try {  
276 - if (deviceProfileId) {  
277 - return await getDeviceAttribute({  
278 - deviceProfileId,  
279 - dataType: isControlComponent(frontId!) ? DataTypeEnum.IS_BOOL : undefined,  
280 - });  
281 - }  
282 - } catch (error) {}  
283 - return [];  
284 - },  
285 - placeholder: '请选择属性',  
286 - getPopupContainer: () => document.body,  
287 - };  
288 - },  
289 - },  
290 - {  
291 - field: DataSourceField.COMMAND_TYPE,  
292 - component: 'ApiSelect',  
293 - label: '命令类型',  
294 - defaultValue: CommandTypeEnum.CUSTOM.toString(),  
295 - rules: [{ required: true, message: '请选择命令类型' }],  
296 - colProps: { span: 8 },  
297 - ifShow: ({ model }) =>  
298 - isControlComponent(frontId!) && isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),  
299 - componentProps: ({ formActionType }) => {  
300 - const { setFieldsValue } = formActionType;  
301 - return {  
302 - api: findDictItemByCode,  
303 - params: {  
304 - dictCode: 'custom_define',  
305 - },  
306 - labelField: 'itemText',  
307 - valueField: 'itemValue',  
308 - placeholder: '请选择命令类型',  
309 - onChange() {  
310 - setFieldsValue({ [DataSourceField.COMMAND]: null, [DataSourceField.SERVICE]: null });  
311 - },  
312 - };  
313 - },  
314 - },  
315 - {  
316 - field: DataSourceField.SERVICE,  
317 - component: 'ApiSelect',  
318 - label: '服务',  
319 - colProps: { span: 8 },  
320 - rules: [{ required: true, message: '请选择服务' }],  
321 - ifShow: ({ model }) =>  
322 - isControlComponent(frontId as FrontComponent) &&  
323 - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE.toString() &&  
324 - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),  
325 - componentProps({ formModel, formActionType }) {  
326 - const { setFieldsValue } = formActionType;  
327 - const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];  
328 - const transportType = formModel[DataSourceField.TRANSPORT_TYPE];  
329 - if (isEdit && ![deviceProfileId, transportType].every(Boolean))  
330 - return { placeholder: '请选择服务', getPopupContainer: () => document.body };  
331 - return {  
332 - api: async () => {  
333 - try {  
334 - if (deviceProfileId) {  
335 - return await getDeviceService(deviceProfileId);  
336 - }  
337 - } catch (error) {}  
338 - return [];  
339 - },  
340 - placeholder: '请选择服务',  
341 - getPopupContainer: () => document.body,  
342 - onChange(value: string, options: ModelOfMatterParams) {  
343 - const command = value ? (options.functionJson.inputData || [])[0].serviceCommand : null;  
344 - setFieldsValue({ [DataSourceField.COMMAND]: command });  
345 - },  
346 - };  
347 - },  
348 - },  
349 - {  
350 - field: DataSourceField.COMMAND,  
351 - component: 'Input',  
352 - label: '命令',  
353 - colProps: { span: 8 },  
354 - rules: [{ required: true, message: '请输入下发命令' }],  
355 - // 是控制组件 && 自定义命令 && 传输协议为TCP  
356 - ifShow: ({ model }) =>  
357 - isControlComponent(frontId!) &&  
358 - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM.toString() &&  
359 - model[DataSourceField.TRANSPORT_TYPE] &&  
360 - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),  
361 - componentProps: {  
362 - placeholder: '请输入下发命令',  
363 - },  
364 - },  
365 - {  
366 - field: DataSourceField.DEVICE_RENAME,  
367 - component: 'Input',  
368 - label: '设备名',  
369 - colProps: { span: 8 },  
370 - componentProps: {  
371 - placeholder: '设备重命名',  
372 - },  
373 - },  
374 - {  
375 - field: DataSourceField.ATTRIBUTE_RENAME,  
376 - component: 'Input',  
377 - label: '属性',  
378 - colProps: { span: 8 },  
379 - componentProps: {  
380 - placeholder: '属性重命名',  
381 - },  
382 - },  
383 - ];  
384 -};  
1 -import moment from 'moment';  
2 -import { Moment } from 'moment';  
3 -import { FormSchema } from '/@/components/Form';  
4 -import { ColEx } from '/@/components/Form/src/types';  
5 -import { useGridLayout } from '/@/hooks/component/useGridLayout';  
6 -import {  
7 - getPacketIntervalByRange,  
8 - getPacketIntervalByValue,  
9 - intervalOption,  
10 -} from '/@/views/device/localtion/cpns/TimePeriodForm/helper';  
11 -export enum QueryWay {  
12 - LATEST = 'latest',  
13 - TIME_PERIOD = 'timePeriod',  
14 -}  
15 -  
16 -export enum SchemaFiled {  
17 - DEVICE_ID = 'deviceId',  
18 - WAY = 'way',  
19 - TIME_PERIOD = 'timePeriod',  
20 - KEYS = 'keys',  
21 - DATE_RANGE = 'dataRange',  
22 - START_TS = 'startTs',  
23 - END_TS = 'endTs',  
24 - INTERVAL = 'interval',  
25 - LIMIT = 'limit',  
26 - AGG = 'agg',  
27 - ORDER_BY = 'orderBy',  
28 -}  
29 -  
30 -export enum AggregateDataEnum {  
31 - MIN = 'MIN',  
32 - MAX = 'MAX',  
33 - AVG = 'AVG',  
34 - SUM = 'SUM',  
35 - COUNT = 'COUNT',  
36 - NONE = 'NONE',  
37 -}  
38 -export const formSchema = (): FormSchema[] => {  
39 - return [  
40 - {  
41 - field: SchemaFiled.DEVICE_ID,  
42 - label: '设备名称',  
43 - component: 'Select',  
44 - rules: [{ required: true, message: '设备名称为必选项', type: 'string' }],  
45 - componentProps({ formActionType }) {  
46 - const { setFieldsValue } = formActionType;  
47 - return {  
48 - placeholder: '请选择设备',  
49 - onChange() {  
50 - setFieldsValue({ [SchemaFiled.KEYS]: null });  
51 - },  
52 - };  
53 - },  
54 - },  
55 - {  
56 - field: SchemaFiled.WAY,  
57 - label: '查询方式',  
58 - component: 'RadioGroup',  
59 - defaultValue: QueryWay.LATEST,  
60 - componentProps({ formActionType }) {  
61 - const { setFieldsValue } = formActionType;  
62 - return {  
63 - options: [  
64 - { label: '最后', value: QueryWay.LATEST },  
65 - { label: '时间段', value: QueryWay.TIME_PERIOD },  
66 - ],  
67 - onChange(event: ChangeEvent) {  
68 - (event.target as HTMLInputElement).value === QueryWay.LATEST  
69 - ? setFieldsValue({  
70 - [SchemaFiled.DATE_RANGE]: [],  
71 - [SchemaFiled.START_TS]: null,  
72 - [SchemaFiled.END_TS]: null,  
73 - })  
74 - : setFieldsValue({ [SchemaFiled.START_TS]: null });  
75 - },  
76 - getPopupContainer: () => document.body,  
77 - };  
78 - },  
79 - },  
80 - {  
81 - field: SchemaFiled.START_TS,  
82 - label: '最后数据',  
83 - component: 'Select',  
84 - ifShow({ values }) {  
85 - return values[SchemaFiled.WAY] === QueryWay.LATEST;  
86 - },  
87 - componentProps({ formActionType }) {  
88 - const { setFieldsValue } = formActionType;  
89 - return {  
90 - options: intervalOption,  
91 - onChange() {  
92 - setFieldsValue({ [SchemaFiled.INTERVAL]: null });  
93 - },  
94 - getPopupContainer: () => document.body,  
95 - };  
96 - },  
97 - rules: [{ required: true, message: '最后数据为必选项', type: 'number' }],  
98 - },  
99 - {  
100 - field: SchemaFiled.DATE_RANGE,  
101 - label: '时间段',  
102 - component: 'RangePicker',  
103 - ifShow({ values }) {  
104 - return values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD;  
105 - },  
106 - rules: [{ required: true, message: '时间段为必选项' }],  
107 - componentProps({ formActionType }) {  
108 - const { setFieldsValue } = formActionType;  
109 - let dates: Moment[] = [];  
110 - return {  
111 - showTime: {  
112 - defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],  
113 - },  
114 - onCalendarChange(value: Moment[]) {  
115 - dates = value;  
116 - },  
117 - disabledDate(current: Moment) {  
118 - if (!dates || dates.length === 0 || !current) {  
119 - return false;  
120 - }  
121 - const diffDate = current.diff(dates[0], 'years', true);  
122 - return Math.abs(diffDate) > 1;  
123 - },  
124 - onChange() {  
125 - dates = [];  
126 - setFieldsValue({ [SchemaFiled.INTERVAL]: null });  
127 - },  
128 - getPopupContainer: () => document.body,  
129 - };  
130 - },  
131 - colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx,  
132 - },  
133 - {  
134 - field: SchemaFiled.AGG,  
135 - label: '数据聚合功能',  
136 - component: 'Select',  
137 - componentProps: {  
138 - getPopupContainer: () => document.body,  
139 - options: [  
140 - { label: '最小值', value: AggregateDataEnum.MIN },  
141 - { label: '最大值', value: AggregateDataEnum.MAX },  
142 - { label: '平均值', value: AggregateDataEnum.AVG },  
143 - { label: '求和', value: AggregateDataEnum.SUM },  
144 - { label: '计数', value: AggregateDataEnum.COUNT },  
145 - { label: '空', value: AggregateDataEnum.NONE },  
146 - ],  
147 - },  
148 - },  
149 - {  
150 - field: SchemaFiled.INTERVAL,  
151 - label: '分组间隔',  
152 - component: 'Select',  
153 - dynamicRules: ({ model }) => {  
154 - return [  
155 - {  
156 - required: model[SchemaFiled.AGG] !== AggregateDataEnum.NONE,  
157 - message: '分组间隔为必填项',  
158 - type: 'number',  
159 - },  
160 - ];  
161 - },  
162 - ifShow({ values }) {  
163 - return values[SchemaFiled.AGG] !== AggregateDataEnum.NONE;  
164 - },  
165 - componentProps({ formModel, formActionType }) {  
166 - const options =  
167 - formModel[SchemaFiled.WAY] === QueryWay.LATEST  
168 - ? getPacketIntervalByValue(formModel[SchemaFiled.START_TS])  
169 - : getPacketIntervalByRange(formModel[SchemaFiled.DATE_RANGE]);  
170 - if (formModel[SchemaFiled.AGG] !== AggregateDataEnum.NONE) {  
171 - formActionType.setFieldsValue({ [SchemaFiled.LIMIT]: null });  
172 - }  
173 - return {  
174 - options,  
175 - getPopupContainer: () => document.body,  
176 - };  
177 - },  
178 - },  
179 - {  
180 - field: SchemaFiled.LIMIT,  
181 - label: '最大条数',  
182 - component: 'InputNumber',  
183 - ifShow({ values }) {  
184 - return values[SchemaFiled.AGG] === AggregateDataEnum.NONE;  
185 - },  
186 - helpMessage: ['根据查询条件,查出的数据条数不超过这个值'],  
187 - componentProps() {  
188 - return {  
189 - max: 50000,  
190 - min: 7,  
191 - getPopupContainer: () => document.body,  
192 - };  
193 - },  
194 - },  
195 - {  
196 - field: SchemaFiled.KEYS,  
197 - label: '设备属性',  
198 - component: 'Select',  
199 - componentProps: {  
200 - getPopupContainer: () => document.body,  
201 - },  
202 - },  
203 - ];  
204 -};  
205 -  
206 -export function getHistorySearchParams(value: Recordable) {  
207 - const { startTs, endTs, interval, agg, limit, way, keys, deviceId } = value;  
208 - if (way === QueryWay.LATEST) {  
209 - return {  
210 - keys,  
211 - entityId: deviceId,  
212 - startTs: moment().subtract(startTs, 'ms').valueOf(),  
213 - endTs: Date.now(),  
214 - interval,  
215 - agg,  
216 - limit,  
217 - };  
218 - } else {  
219 - return {  
220 - keys,  
221 - entityId: deviceId,  
222 - startTs: moment(startTs).valueOf(),  
223 - endTs: moment(endTs).valueOf(),  
224 - interval,  
225 - agg,  
226 - limit,  
227 - };  
228 - }  
229 -}