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 6 EXCEPTION_COMPONENT,
7 7 PAGE_NOT_FOUND_NAME,
8 8 } from '/@/router/constant';
9   -import { DATA_BOARD_SHARE_URL } from '../../views/visual/board/config/config';
10   -
11 9 // 404 on a page
12 10 export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
13 11 path: '/:path(.*)*',
... ... @@ -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 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 3 import { mainOutRoutes } from './mainOut';
4 4 import { PageEnum } from '/@/enums/pageEnum';
5 5 import { t } from '/@/hooks/web/useI18n';
... ... @@ -86,6 +86,5 @@ export const basicRoutes = [
86 86 ...mainOutRoutes,
87 87 REDIRECT_ROUTE,
88 88 PAGE_NOT_FOUND_ROUTE,
89   - DATA_BOARD_SHARE,
90 89 PUBLIC_PAGE_ROUTER,
91 90 ];
... ...
... ... @@ -161,7 +161,7 @@ const transform: AxiosTransform = {
161 161 throw new Error(error);
162 162 }
163 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 18 TABLE_CHART_MODE_LIST,
19 19 EnumTableChartMode,
20 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 23 interface DeviceDetail {
24 24 tbDeviceId: string;
... ...
... ... @@ -7,8 +7,8 @@ import { getDeviceAttributes } from '/@/api/dataBoard';
7 7 import { DeviceAttributeRecord } from '/@/api/dataBoard/model';
8 8 import { dateUtil } from '/@/utils/dateUtil';
9 9 import { isArray } from '/@/utils/is';
10   -import { QueryWay, SchemaFiled } from '/@/views/visual/board/detail/config/historyTrend.config';
11 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 13 interface DeviceOption {
14 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 1 export enum MoreActionEvent {
4 2 EDIT = 'edit',
5 3 COPY = 'copy',
... ... @@ -27,9 +25,6 @@ export const DEFAULT_WIDGET_HEIGHT = 6;
27 25 export const DEFAULT_MIN_HEIGHT = 5;
28 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 28 export const isBataBoardSharePage = (url: string) => {
34 29 const reg = /^\/data\/board\/share/g;
35 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   -}