Commit 128a5ebdb2fc8c44107a4752d92b422ac4e8c83c
1 parent
29b5c630
wip: implement digitalDashboardComponent && DashboardComponent
Showing
17 changed files
with
585 additions
and
108 deletions
... | ... | @@ -7,6 +7,7 @@ import { |
7 | 7 | Layout, |
8 | 8 | UpdateDataBoardLayoutParams, |
9 | 9 | UpdateDataBoardParams, |
10 | + UpdateDataComponentParams, | |
10 | 11 | } from './model'; |
11 | 12 | import { defHttp } from '/@/utils/http/axios'; |
12 | 13 | |
... | ... | @@ -22,6 +23,7 @@ enum DataComponentUrl { |
22 | 23 | GET_DATA_COMPONENT = '/data_component', |
23 | 24 | ADD_DATA_COMPONENT = '/data_component', |
24 | 25 | DELETE_DATA_COMPONENT = '/data_component', |
26 | + UPDATE_DATA_COMPONENT = '/data_component', | |
25 | 27 | } |
26 | 28 | |
27 | 29 | /** |
... | ... | @@ -118,3 +120,15 @@ export const deleteDataComponent = (params: string[]) => { |
118 | 120 | }, |
119 | 121 | }); |
120 | 122 | }; |
123 | + | |
124 | +/** | |
125 | + * @description 更新数据组件 | |
126 | + * @param params | |
127 | + * @returns | |
128 | + */ | |
129 | +export const updateDataComponent = (params: UpdateDataComponentParams) => { | |
130 | + return defHttp.post({ | |
131 | + url: `${DataComponentUrl.UPDATE_DATA_COMPONENT}/${params.boardId}/update`, | |
132 | + params: params.record, | |
133 | + }); | |
134 | +}; | ... | ... |
... | ... | @@ -3,67 +3,31 @@ |
3 | 3 | import type { PropType } from 'vue'; |
4 | 4 | import { nextTick, onMounted, onUnmounted, ref, unref } from 'vue'; |
5 | 5 | import { init } from 'echarts'; |
6 | - | |
7 | - interface DataSource { | |
8 | - id: string | number; | |
9 | - } | |
6 | + import { instrumentComponent1 } from './dashBoardComponent.config'; | |
7 | + import { dateUtil } from '/@/utils/dateUtil'; | |
10 | 8 | |
11 | 9 | const props = defineProps({ |
12 | - dataSource: { | |
13 | - type: Object as PropType<DataSource>, | |
14 | - required: true, | |
15 | - }, | |
16 | - chartOption: { | |
17 | - type: Object as PropType<EChartsOption>, | |
18 | - // required: true, | |
19 | - }, | |
20 | 10 | add: { |
21 | 11 | type: Function, |
22 | - required: true, | |
12 | + }, | |
13 | + layout: { | |
14 | + type: Object as PropType<Recordable>, | |
15 | + default: () => ({}), | |
16 | + }, | |
17 | + value: { | |
18 | + type: Object as PropType<Recordable>, | |
19 | + default: () => ({}), | |
23 | 20 | }, |
24 | 21 | }); |
25 | 22 | |
26 | - const getControlsWidgetId = () => `widget-chart-${props.dataSource.id}`; | |
23 | + const getControlsWidgetId = () => `widget-chart-${props.value.id}`; | |
27 | 24 | |
28 | 25 | const chartRef = ref<Nullable<ECharts>>(null); |
29 | 26 | |
30 | 27 | function initChart() { |
31 | 28 | const chartDom = document.getElementById(getControlsWidgetId())!; |
32 | 29 | chartRef.value = init(chartDom); |
33 | - const option: EChartsOption = props.chartOption || { | |
34 | - tooltip: { | |
35 | - trigger: 'item', | |
36 | - // confine: true, | |
37 | - extraCssText: 'position: fixed;', | |
38 | - position: (point, params, dom) => { | |
39 | - const parentEl = (dom as HTMLDivElement).parentElement!; | |
40 | - | |
41 | - const { top = 0, left = 0 } = parentEl.getBoundingClientRect()!; | |
42 | - return [left, top]; | |
43 | - }, | |
44 | - }, | |
45 | - series: [ | |
46 | - { | |
47 | - name: 'Access From', | |
48 | - type: 'pie', | |
49 | - radius: '50%', | |
50 | - data: [ | |
51 | - { value: 1048, name: 'Search Engine' }, | |
52 | - { value: 735, name: 'Direct' }, | |
53 | - { value: 580, name: 'Email' }, | |
54 | - { value: 484, name: 'Union Ads' }, | |
55 | - { value: 300, name: 'Video Ads' }, | |
56 | - ], | |
57 | - emphasis: { | |
58 | - itemStyle: { | |
59 | - shadowBlur: 10, | |
60 | - shadowOffsetX: 0, | |
61 | - shadowColor: 'rgba(0, 0, 0, 0.5)', | |
62 | - }, | |
63 | - }, | |
64 | - }, | |
65 | - ], | |
66 | - }; | |
30 | + const option: EChartsOption = props.layout || instrumentComponent1(); | |
67 | 31 | |
68 | 32 | nextTick(() => { |
69 | 33 | option && unref(chartRef)?.setOption(option); |
... | ... | @@ -76,7 +40,7 @@ |
76 | 40 | |
77 | 41 | onMounted(() => { |
78 | 42 | initChart(); |
79 | - props.add(props.dataSource.id, update); | |
43 | + props.add && props.add(props.value.id, update); | |
80 | 44 | }); |
81 | 45 | |
82 | 46 | onUnmounted(() => { |
... | ... | @@ -89,7 +53,13 @@ |
89 | 53 | <template> |
90 | 54 | <div class="flex flex-col w-full h-full min-w-3 min-h-3"> |
91 | 55 | <div :id="getControlsWidgetId()" class="widget-charts w-full h-full"></div> |
92 | - <div class="text-xs text-center text-gray-400">更新时间:</div> | |
56 | + <div>{{}}</div> | |
57 | + <div class="text-xs text-center text-gray-400"> | |
58 | + <span>更新时间:</span> | |
59 | + <span> | |
60 | + {{ props.value.updateTime || dateUtil().format('YYYY-MM-DD HH:mm:ss') }} | |
61 | + </span> | |
62 | + </div> | |
93 | 63 | </div> |
94 | 64 | </template> |
95 | 65 | ... | ... |
1 | -<script lang="ts" setup></script> | |
1 | +<script lang="ts" setup> | |
2 | + import { computed } from 'vue'; | |
3 | + import { Space } from 'ant-design-vue'; | |
4 | + import type { DigitalDashBoardLayout, DigitalDashBoardValue } from './digitalDashBoard.config'; | |
5 | + import { dateUtil } from '/@/utils/dateUtil'; | |
2 | 6 | |
3 | -<template> </template> | |
7 | + const props = defineProps<{ | |
8 | + layout: DigitalDashBoardLayout; | |
9 | + value: DigitalDashBoardValue; | |
10 | + }>(); | |
11 | + | |
12 | + const integerPart = computed(() => { | |
13 | + const { value = 0 } = props.value; | |
14 | + const { max = 5 } = props.layout; | |
15 | + let _value = value?.toFixed(2).split('.')[0]; | |
16 | + if (_value.length < max) _value = _value.padStart(5, '0'); | |
17 | + | |
18 | + if (_value.length > max) _value = ''.padStart(5, '9'); | |
19 | + | |
20 | + return _value; | |
21 | + }); | |
22 | + | |
23 | + const decimalPart = computed(() => { | |
24 | + const { value = 0 } = props.value; | |
25 | + const { keepNumber = 2 } = props.layout; | |
26 | + let _value = value?.toFixed(2).split('.')[1]; | |
27 | + if (_value.length < keepNumber) _value = _value.padStart(5, '0'); | |
28 | + | |
29 | + if (_value.length > keepNumber) _value = ''.padStart(5, '0'); | |
30 | + | |
31 | + return _value; | |
32 | + }); | |
33 | +</script> | |
34 | + | |
35 | +<template> | |
36 | + <section class="w-full h-full"> | |
37 | + <div class="flex flex-col w-full h-full"> | |
38 | + <div class="flex-1 flex justify-center items-center"> | |
39 | + <div class="flex flex-col"> | |
40 | + <Space justify="end" class="justify-end"> | |
41 | + <div | |
42 | + v-for="number in integerPart" | |
43 | + :key="number" | |
44 | + class="border border-gray-400 p-2" | |
45 | + :style="{ color: props.value.valueColor }" | |
46 | + > | |
47 | + {{ number }} | |
48 | + </div> | |
49 | + </Space> | |
50 | + <Space justify="end" class="justify-end mt-2"> | |
51 | + <div | |
52 | + v-for="number in decimalPart" | |
53 | + :key="number" | |
54 | + class="border border-gray-400 p-1" | |
55 | + :style="{ color: props.value.valueColor }" | |
56 | + > | |
57 | + {{ number }} | |
58 | + </div> | |
59 | + </Space> | |
60 | + </div> | |
61 | + </div> | |
62 | + | |
63 | + <div class="text-center"> | |
64 | + <span>{{ props.value.name || '电表' }}</span> | |
65 | + <span class="px-1">({{ props.value.unit || 'kw/h' }})</span> | |
66 | + </div> | |
67 | + <div class="text-center mt-1 text-gray-400 text-xs"> | |
68 | + <span class="mr-1">更新时间:</span> | |
69 | + <span>{{ props.value.updateTime || dateUtil().format('YYYY-MM-DD HH:mm:ss') }}</span> | |
70 | + </div> | |
71 | + </div> | |
72 | + <div></div> | |
73 | + </section> | |
74 | +</template> | ... | ... |
1 | +import { EChartsOption } from 'echarts'; | |
2 | +import { visualOptionField } from '../../detail/config/visualOptions'; | |
3 | + | |
4 | +export type InstrumentComponentType = 'instrument-component-1' | 'instrument-component-2'; | |
5 | + | |
6 | +export type GradientKey = | |
7 | + | visualOptionField.FIRST_PHASE_COLOR | |
8 | + | visualOptionField.FIRST_PHASE_VALUE | |
9 | + | visualOptionField.SECOND_PHASE_COLOR | |
10 | + | visualOptionField.SECOND_PHASE_VALUE | |
11 | + | visualOptionField.THIRD_PHASE_COLOR | |
12 | + | visualOptionField.THIRD_PHASE_VALUE; | |
13 | +export interface GradientInfoRecord { | |
14 | + key: GradientKey; | |
15 | + value: number | string; | |
16 | +} | |
17 | + | |
18 | +export interface DashBoardValue { | |
19 | + unit?: string; | |
20 | + name?: string; | |
21 | + updateTime?: string; | |
22 | + value?: number; | |
23 | + valueColor?: string; | |
24 | + gradientInfo?: GradientInfoRecord[]; | |
25 | +} | |
26 | + | |
27 | +export const instrumentComponent1 = (params?: { value: number; unit: string }): EChartsOption => { | |
28 | + const { value = 10, unit = '°C' } = params || {}; | |
29 | + return { | |
30 | + series: [ | |
31 | + { | |
32 | + type: 'gauge', | |
33 | + center: ['50%', '60%'], | |
34 | + startAngle: 200, | |
35 | + endAngle: -20, | |
36 | + min: 0, | |
37 | + max: 60, | |
38 | + splitNumber: 12, | |
39 | + itemStyle: { | |
40 | + color: '#FFAB91', | |
41 | + }, | |
42 | + progress: { | |
43 | + show: true, | |
44 | + width: 30, | |
45 | + }, | |
46 | + pointer: { | |
47 | + show: false, | |
48 | + }, | |
49 | + axisLine: { | |
50 | + lineStyle: { | |
51 | + width: 30, | |
52 | + }, | |
53 | + }, | |
54 | + axisTick: { | |
55 | + distance: -45, | |
56 | + splitNumber: 5, | |
57 | + lineStyle: { | |
58 | + width: 2, | |
59 | + color: '#999', | |
60 | + }, | |
61 | + }, | |
62 | + splitLine: { | |
63 | + distance: -52, | |
64 | + length: 14, | |
65 | + lineStyle: { | |
66 | + width: 3, | |
67 | + color: '#999', | |
68 | + }, | |
69 | + }, | |
70 | + axisLabel: { | |
71 | + distance: -20, | |
72 | + color: '#999', | |
73 | + fontSize: 20, | |
74 | + }, | |
75 | + anchor: { | |
76 | + show: false, | |
77 | + }, | |
78 | + title: { | |
79 | + show: false, | |
80 | + }, | |
81 | + detail: { | |
82 | + valueAnimation: true, | |
83 | + width: '60%', | |
84 | + lineHeight: 40, | |
85 | + borderRadius: 8, | |
86 | + offsetCenter: [0, '-15%'], | |
87 | + fontSize: 16, | |
88 | + fontWeight: 'bolder', | |
89 | + formatter: `{value} ${unit}`, | |
90 | + color: 'auto', | |
91 | + }, | |
92 | + data: [ | |
93 | + { | |
94 | + value: value, | |
95 | + }, | |
96 | + ], | |
97 | + }, | |
98 | + { | |
99 | + type: 'gauge', | |
100 | + center: ['50%', '60%'], | |
101 | + startAngle: 200, | |
102 | + endAngle: -20, | |
103 | + min: 0, | |
104 | + max: 60, | |
105 | + itemStyle: { | |
106 | + color: '#FD7347', | |
107 | + }, | |
108 | + progress: { | |
109 | + show: true, | |
110 | + width: 8, | |
111 | + }, | |
112 | + pointer: { | |
113 | + show: false, | |
114 | + }, | |
115 | + axisLine: { | |
116 | + show: false, | |
117 | + }, | |
118 | + axisTick: { | |
119 | + show: false, | |
120 | + }, | |
121 | + splitLine: { | |
122 | + show: false, | |
123 | + }, | |
124 | + axisLabel: { | |
125 | + show: false, | |
126 | + }, | |
127 | + detail: { | |
128 | + show: false, | |
129 | + }, | |
130 | + data: [ | |
131 | + { | |
132 | + value: value, | |
133 | + }, | |
134 | + ], | |
135 | + }, | |
136 | + ], | |
137 | + }; | |
138 | +}; | |
139 | + | |
140 | +export const instrumentComponent2 = (params?: { | |
141 | + gradient: GradientInfoRecord[]; | |
142 | + value: number; | |
143 | + unit: string; | |
144 | +}): EChartsOption => { | |
145 | + const { gradient = [], value = 0, unit = 'km/h' } = params || {}; | |
146 | + return { | |
147 | + series: [ | |
148 | + { | |
149 | + type: 'gauge', | |
150 | + axisLine: { | |
151 | + lineStyle: { | |
152 | + width: 30, | |
153 | + color: [ | |
154 | + [ | |
155 | + 0.3, | |
156 | + (getGradientValue(visualOptionField.FIRST_PHASE_COLOR, gradient) as string) || | |
157 | + '#67e0e3', | |
158 | + ], | |
159 | + [ | |
160 | + 0.7, | |
161 | + (getGradientValue(visualOptionField.SECOND_PHASE_COLOR, gradient) as string) || | |
162 | + '#37a2da', | |
163 | + ], | |
164 | + [ | |
165 | + 1, | |
166 | + (getGradientValue(visualOptionField.THIRD_PHASE_COLOR, gradient) as string) || | |
167 | + '#fd666d', | |
168 | + ], | |
169 | + ], | |
170 | + }, | |
171 | + }, | |
172 | + pointer: { | |
173 | + itemStyle: { | |
174 | + color: 'auto', | |
175 | + }, | |
176 | + }, | |
177 | + axisTick: { | |
178 | + distance: -30, | |
179 | + length: 8, | |
180 | + lineStyle: { | |
181 | + color: '#fff', | |
182 | + width: 2, | |
183 | + }, | |
184 | + }, | |
185 | + splitLine: { | |
186 | + distance: -30, | |
187 | + length: 30, | |
188 | + lineStyle: { | |
189 | + color: '#fff', | |
190 | + width: 4, | |
191 | + }, | |
192 | + }, | |
193 | + axisLabel: { | |
194 | + color: 'auto', | |
195 | + distance: 40, | |
196 | + fontSize: 14, | |
197 | + }, | |
198 | + detail: { | |
199 | + valueAnimation: true, | |
200 | + formatter: `{value} ${unit}`, | |
201 | + color: 'auto', | |
202 | + fontSize: '16', | |
203 | + }, | |
204 | + data: [ | |
205 | + { | |
206 | + value: value, | |
207 | + }, | |
208 | + ], | |
209 | + }, | |
210 | + ], | |
211 | + }; | |
212 | +}; | |
213 | + | |
214 | +export const getGradientValue = (key: GradientKey, record: GradientInfoRecord[]) => { | |
215 | + return record.find((item) => item.key === key)?.value; | |
216 | +}; | ... | ... |
1 | +export type DigitalDashBoardComponentType = 'digital-dashboard'; | |
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 | + valueColor?: string; | |
14 | +} | ... | ... |
1 | +import { Component } from 'vue'; | |
2 | +import { WidgetComponentType } from '../../detail/config/visualOptions'; | |
3 | +import { instrumentComponent1, instrumentComponent2 } from './dashBoardComponent.config'; | |
4 | +import DashBoardComponent from './DashBoardComponent.vue'; | |
5 | +import DigitalDashBoard from './DigitalDashBoard.vue'; | |
6 | +import { buildUUID } from '/@/utils/uuid'; | |
7 | + | |
8 | +interface InstrumentComponentConfig { | |
9 | + id: WidgetComponentType; | |
10 | + layout: Recordable; | |
11 | + component: Component; | |
12 | + value: Recordable; | |
13 | +} | |
14 | + | |
15 | +export const instrumentComponentConfig: InstrumentComponentConfig[] = [ | |
16 | + { | |
17 | + id: 'instrument-component-1', | |
18 | + layout: instrumentComponent1(), | |
19 | + component: DashBoardComponent, | |
20 | + value: { id: buildUUID() }, | |
21 | + }, | |
22 | + { | |
23 | + id: 'instrument-component-2', | |
24 | + layout: instrumentComponent2(), | |
25 | + component: DashBoardComponent, | |
26 | + value: { id: buildUUID() }, | |
27 | + }, | |
28 | + { | |
29 | + id: 'digital-dashboard', | |
30 | + layout: {}, | |
31 | + component: DigitalDashBoard, | |
32 | + value: {}, | |
33 | + }, | |
34 | +]; | ... | ... |
... | ... | @@ -18,6 +18,13 @@ export interface TextComponentValue { |
18 | 18 | iconColor?: string; |
19 | 19 | } |
20 | 20 | |
21 | +export type TextComponentType = | |
22 | + | 'text-component-1' | |
23 | + | 'text-component-2' | |
24 | + | 'text-component-3' | |
25 | + | 'text-component-4' | |
26 | + | 'text-component-5'; | |
27 | + | |
21 | 28 | type TextComponentDefault = TextComponentLayout & { value: TextComponentValue }; |
22 | 29 | |
23 | 30 | export const TextComponent1Config: TextComponentDefault = { | ... | ... |
... | ... | @@ -4,27 +4,28 @@ |
4 | 4 | import { FormActionType, useForm } from '/@/components/Form'; |
5 | 5 | import { basicSchema, dataSourceSchema } from '../config/basicConfiguration'; |
6 | 6 | import BasicForm from '/@/components/Form/src/BasicForm.vue'; |
7 | - import { onMounted, reactive, ref, shallowReactive, unref, nextTick } from 'vue'; | |
7 | + import { reactive, ref, shallowReactive, unref, nextTick, watch } from 'vue'; | |
8 | 8 | import VisualOptionsModal from './VisualOptionsModal.vue'; |
9 | 9 | import { useModal } from '/@/components/Modal'; |
10 | 10 | import { buildUUID } from '/@/utils/uuid'; |
11 | - import type { DataComponentRecord, ComponentInfo, DataSource } from '/@/api/dataBoard/model'; | |
11 | + import type { ComponentInfo, DataSource } from '/@/api/dataBoard/model'; | |
12 | 12 | import { useMessage } from '/@/hooks/web/useMessage'; |
13 | + import { DataBoardLayoutInfo } from '../../types/type'; | |
13 | 14 | |
14 | 15 | type DataSourceFormEL = { [key: string]: Nullable<FormActionType> }; |
15 | 16 | |
16 | 17 | type DataSourceEl = DataSource & { id: string }; |
17 | 18 | |
18 | 19 | const props = defineProps<{ |
19 | - record: DataComponentRecord; | |
20 | + record: DataBoardLayoutInfo; | |
20 | 21 | frontId?: string; |
21 | 22 | }>(); |
22 | 23 | |
23 | 24 | const { createMessage } = useMessage(); |
24 | 25 | |
25 | - const componentRecord = reactive<DataComponentRecord>({ | |
26 | - id: 'string', | |
27 | - } as unknown as DataComponentRecord); | |
26 | + // const componentRecord = reactive<DataBoardLayoutInfo>({ | |
27 | + // ...props.record, | |
28 | + // } as unknown as DataBoardLayoutInfo); | |
28 | 29 | |
29 | 30 | const dataSource = ref<DataSourceEl[]>([{ id: buildUUID() } as unknown as DataSourceEl]); |
30 | 31 | |
... | ... | @@ -38,7 +39,6 @@ |
38 | 39 | |
39 | 40 | const setFormEl = (el: any, id: string) => { |
40 | 41 | if (!dataSourceEl[id] && el) { |
41 | - console.log({ el, id }); | |
42 | 42 | const { formActionType } = el as unknown as { formActionType: FormActionType }; |
43 | 43 | dataSourceEl[id] = formActionType; |
44 | 44 | } |
... | ... | @@ -59,12 +59,12 @@ |
59 | 59 | for (const id of hasExistEl) { |
60 | 60 | const index = unref(dataSource).findIndex((item) => item.id === id); |
61 | 61 | const value = (dataSourceEl[id] as FormActionType).getFieldsValue() as DataSource; |
62 | + if (!~index) continue; | |
62 | 63 | const componentInfo = unref(dataSource)[index].componentInfo || {}; |
63 | - ~index && | |
64 | - _dataSource.push({ | |
65 | - ...value, | |
66 | - componentInfo: { ...componentInfo }, | |
67 | - }); | |
64 | + _dataSource.push({ | |
65 | + ...value, | |
66 | + componentInfo: { ...componentInfo }, | |
67 | + }); | |
68 | 68 | } |
69 | 69 | return _dataSource; |
70 | 70 | }; |
... | ... | @@ -113,19 +113,38 @@ |
113 | 113 | }; |
114 | 114 | |
115 | 115 | const echoDataSource = () => { |
116 | - basicMethod.setFieldsValue(props.record); | |
117 | - // dataSourceMethod.setFieldsValue(props.record); | |
116 | + basicMethod.setFieldsValue(props.record.record); | |
117 | + dataSource.value = []; | |
118 | + dataSource.value = props.record.record.dataSource.map((item) => { | |
119 | + const id = buildUUID(); | |
120 | + | |
121 | + dataSource.value.push({ | |
122 | + id, | |
123 | + ...item, | |
124 | + }); | |
125 | + | |
126 | + nextTick(() => { | |
127 | + (dataSourceEl[id] as FormActionType).setFieldsValue(item); | |
128 | + }); | |
129 | + return { | |
130 | + id, | |
131 | + ...item, | |
132 | + }; | |
133 | + }); | |
118 | 134 | }; |
119 | 135 | |
136 | + watch( | |
137 | + () => props.record, | |
138 | + () => { | |
139 | + if (Object.keys(props.record).length) echoDataSource(); | |
140 | + } | |
141 | + ); | |
142 | + | |
120 | 143 | const handleRowComponentInfo = (recordId: string, value: ComponentInfo) => { |
121 | 144 | const index = unref(dataSource).findIndex((item) => item.id === recordId); |
122 | 145 | ~index && (unref(dataSource)[index].componentInfo = value); |
123 | 146 | }; |
124 | 147 | |
125 | - onMounted(() => { | |
126 | - echoDataSource(); | |
127 | - }); | |
128 | - | |
129 | 148 | defineExpose({ |
130 | 149 | getAllDataSourceFieldValue, |
131 | 150 | }); |
... | ... | @@ -179,7 +198,11 @@ |
179 | 198 | <div class="text-center"> |
180 | 199 | <Button type="primary" @click="handleAdd">添加数据源</Button> |
181 | 200 | </div> |
182 | - <VisualOptionsModal @close="handleRowComponentInfo" @register="registerVisualOptionModal" /> | |
201 | + <VisualOptionsModal | |
202 | + :value="props.frontId" | |
203 | + @close="handleRowComponentInfo" | |
204 | + @register="registerVisualOptionModal" | |
205 | + /> | |
183 | 206 | </section> |
184 | 207 | </template> |
185 | 208 | ... | ... |
... | ... | @@ -4,11 +4,12 @@ |
4 | 4 | import BasicConfiguration from './BasicConfiguration.vue'; |
5 | 5 | import VisualConfiguration from './VisualConfiguration.vue'; |
6 | 6 | import { computed, ref, unref } from 'vue'; |
7 | - import type { DataComponentRecord } from '/@/api/dataBoard/model'; | |
8 | 7 | import { RouteParams, useRoute } from 'vue-router'; |
9 | - import { addDataComponent, updateDataBoardLayout } from '/@/api/dataBoard'; | |
8 | + import { addDataComponent, updateDataBoardLayout, updateDataComponent } from '/@/api/dataBoard'; | |
10 | 9 | import { useModalInner } from '/@/components/Modal'; |
11 | 10 | import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH } from '../../config/config'; |
11 | + import { DataBoardLayoutInfo } from '../../types/type'; | |
12 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
12 | 13 | |
13 | 14 | interface DataComponentRouteParams extends RouteParams { |
14 | 15 | id: string; |
... | ... | @@ -18,11 +19,7 @@ |
18 | 19 | |
19 | 20 | const ROUTE = useRoute(); |
20 | 21 | |
21 | - const [register, { closeModal }] = useModalInner(); | |
22 | - | |
23 | - const basicConfigurationEl = ref<{ | |
24 | - getAllDataSourceFieldValue: Fn<any, Recordable>; | |
25 | - }>(); | |
22 | + const { createMessage } = useMessage(); | |
26 | 23 | |
27 | 24 | const boardId = computed(() => { |
28 | 25 | return (ROUTE.params as DataComponentRouteParams).id; |
... | ... | @@ -30,12 +27,26 @@ |
30 | 27 | |
31 | 28 | const frontId = ref(''); |
32 | 29 | |
33 | - const componentRecord = ref<DataComponentRecord>({} as unknown as DataComponentRecord); | |
30 | + const isEdit = ref(false); | |
31 | + | |
32 | + const componentRecord = ref<DataBoardLayoutInfo>({} as unknown as DataBoardLayoutInfo); | |
33 | + | |
34 | + const [register, { closeModal }] = useModalInner( | |
35 | + (record: DataBoardLayoutInfo & { isEdit: boolean }) => { | |
36 | + componentRecord.value = record; | |
37 | + frontId.value = record.record.frontId; | |
38 | + isEdit.value = record.isEdit; | |
39 | + } | |
40 | + ); | |
41 | + | |
42 | + const basicConfigurationEl = ref<{ | |
43 | + getAllDataSourceFieldValue: Fn<any, Recordable>; | |
44 | + }>(); | |
34 | 45 | |
35 | 46 | const handleSubmit = () => { |
36 | 47 | const { getAllDataSourceFieldValue } = unref(basicConfigurationEl)!; |
37 | 48 | const value = getAllDataSourceFieldValue(); |
38 | - handleAddComponent(value); | |
49 | + unref(isEdit) ? handleUpdateComponent(value) : handleAddComponent(value); | |
39 | 50 | }; |
40 | 51 | |
41 | 52 | const handleAddComponent = async (value: Recordable) => { |
... | ... | @@ -44,6 +55,7 @@ |
44 | 55 | boardId: unref(boardId), |
45 | 56 | record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value }, |
46 | 57 | }); |
58 | + createMessage.success('创建成功'); | |
47 | 59 | const id = data.data.id; |
48 | 60 | await updateDataBoardLayout({ |
49 | 61 | boardId: unref(boardId), |
... | ... | @@ -51,7 +63,28 @@ |
51 | 63 | }); |
52 | 64 | closeModal(); |
53 | 65 | emit('submit'); |
54 | - } catch (error) {} | |
66 | + } catch (error) { | |
67 | + // createMessage.error('创建失败'); | |
68 | + } | |
69 | + }; | |
70 | + | |
71 | + const handleUpdateComponent = async (value: Recordable) => { | |
72 | + try { | |
73 | + await updateDataComponent({ | |
74 | + boardId: unref(boardId), | |
75 | + record: { | |
76 | + id: unref(componentRecord).i, | |
77 | + dataBoardId: unref(boardId), | |
78 | + frontId: unref(frontId), | |
79 | + ...value, | |
80 | + }, | |
81 | + }); | |
82 | + createMessage.success('修改成功'); | |
83 | + closeModal(); | |
84 | + emit('submit'); | |
85 | + } catch (error) { | |
86 | + // createMessage.error('修改失败'); | |
87 | + } | |
55 | 88 | }; |
56 | 89 | </script> |
57 | 90 | |
... | ... | @@ -61,6 +94,7 @@ |
61 | 94 | @register="register" |
62 | 95 | title="自定义组件" |
63 | 96 | width="70%" |
97 | + destroy-on-close | |
64 | 98 | @ok="handleSubmit" |
65 | 99 | > |
66 | 100 | <section> | ... | ... |
... | ... | @@ -3,6 +3,7 @@ |
3 | 3 | import VisualWidgetSelect from './VisualWidgetSelect.vue'; |
4 | 4 | import TextComponent from '../../components/TextComponent/TextComponent.vue'; |
5 | 5 | import { textComponentConfig } from '../../components/TextComponent/config'; |
6 | + import { instrumentComponentConfig } from '../../components/InstrumentComponent'; | |
6 | 7 | const props = defineProps<{ |
7 | 8 | value: string; |
8 | 9 | }>(); |
... | ... | @@ -35,7 +36,22 @@ |
35 | 36 | </List> |
36 | 37 | </Tabs.TabPane> |
37 | 38 | <Tabs.TabPane key="2" tab="仪表组件"> |
38 | - <div>仪表组件</div> | |
39 | + <List | |
40 | + :grid="{ gutter: 10, column: 3, xs: 3, sm: 3, md: 3, lg: 3, xl: 3, xxl: 3 }" | |
41 | + :data-source="instrumentComponentConfig" | |
42 | + > | |
43 | + <template #renderItem="{ item }"> | |
44 | + <List.Item class="!flex !justify-center"> | |
45 | + <VisualWidgetSelect | |
46 | + :checked-id="props.value" | |
47 | + :control-id="item.id" | |
48 | + @change="handleCheck" | |
49 | + > | |
50 | + <component :is="item.component" :layout="item.layout" :value="item.value" /> | |
51 | + </VisualWidgetSelect> | |
52 | + </List.Item> | |
53 | + </template> | |
54 | + </List> | |
39 | 55 | </Tabs.TabPane> |
40 | 56 | </Tabs> |
41 | 57 | </section> | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | - import { onMounted, ref, unref } from 'vue'; | |
3 | - import { modeOne, modeTwo, modeThree, modeFour } from '../config/visualOptions'; | |
2 | + import { ref, unref } from 'vue'; | |
3 | + import { WidgetComponentType, schemasMap } from '../config/visualOptions'; | |
4 | 4 | import { useForm, BasicForm } from '/@/components/Form'; |
5 | 5 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
6 | 6 | import { ComponentInfo } from '/@/api/dataBoard/model'; |
7 | + import { computed } from '@vue/reactivity'; | |
7 | 8 | |
8 | 9 | const emit = defineEmits(['close']); |
9 | 10 | |
11 | + const props = defineProps<{ | |
12 | + value?: string; | |
13 | + }>(); | |
14 | + | |
10 | 15 | const recordId = ref(''); |
11 | 16 | |
17 | + const getSchemas = computed(() => { | |
18 | + return schemasMap.get((props.value as WidgetComponentType) || 'text-component-1'); | |
19 | + }); | |
20 | + | |
12 | 21 | const [registerForm, method] = useForm({ |
13 | - schemas: modeTwo, | |
14 | 22 | showActionButtonGroup: false, |
15 | 23 | labelWidth: 120, |
16 | 24 | baseColProps: { |
... | ... | @@ -45,6 +53,6 @@ |
45 | 53 | title="选项" |
46 | 54 | width="60%" |
47 | 55 | > |
48 | - <BasicForm @register="registerForm" /> | |
56 | + <BasicForm @register="registerForm" :schemas="getSchemas" /> | |
49 | 57 | </BasicModal> |
50 | 58 | </template> | ... | ... |
1 | +import { InstrumentComponentType } from '../../components/InstrumentComponent/dashBoardComponent.config'; | |
2 | +import { DigitalDashBoardComponentType } from '../../components/InstrumentComponent/digitalDashBoard.config'; | |
3 | +import { TextComponentType } from '../../components/TextComponent/config'; | |
1 | 4 | import { FormSchema } from '/@/components/Form'; |
2 | 5 | export enum defaultOptions { |
3 | 6 | fontColor = '#rer', |
4 | 7 | } |
5 | 8 | |
9 | +export type WidgetComponentType = | |
10 | + | TextComponentType | |
11 | + | InstrumentComponentType | |
12 | + | DigitalDashBoardComponentType; | |
13 | + | |
6 | 14 | export enum visualOptionField { |
7 | 15 | FONT_COLOR = 'fontColor', |
8 | 16 | UNIT = 'unit', |
... | ... | @@ -137,3 +145,13 @@ export const modeFour: FormSchema[] = [ |
137 | 145 | }, |
138 | 146 | }, |
139 | 147 | ]; |
148 | + | |
149 | +export const schemasMap = new Map<WidgetComponentType, FormSchema[]>(); | |
150 | + | |
151 | +schemasMap.set('text-component-1', modeOne); | |
152 | +schemasMap.set('text-component-2', modeOne); | |
153 | +schemasMap.set('text-component-3', modeOne); | |
154 | +schemasMap.set('text-component-4', modeTwo); | |
155 | +schemasMap.set('text-component-4', modeTwo); | |
156 | +schemasMap.set('instrument-component-1', modeOne); | |
157 | +schemasMap.set('instrument-component-2', modeThree); | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | 2 | import { Button, PageHeader } from 'ant-design-vue'; |
3 | - import { GridItem, GridLayout, Layout } from 'vue3-grid-layout'; | |
3 | + import { GridItem, GridLayout } from 'vue3-grid-layout'; | |
4 | 4 | import { nextTick, onMounted, ref } from 'vue'; |
5 | 5 | import WidgetWrapper from '../components/WidgetWrapper/WidgetWrapper.vue'; |
6 | 6 | import BaseWidgetHeader from '../components/WidgetHeader/BaseWidgetHeader.vue'; |
7 | 7 | import { DropMenu } from '/@/components/Dropdown'; |
8 | 8 | import DataBindModal from './components/DataBindModal.vue'; |
9 | 9 | import { useModal } from '/@/components/Modal'; |
10 | - import { MoreActionEvent } from '../config/config'; | |
11 | - import { deleteDataComponent, getDataComponent } from '/@/api/dataBoard'; | |
10 | + import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, MoreActionEvent } from '../config/config'; | |
11 | + import { | |
12 | + addDataComponent, | |
13 | + deleteDataComponent, | |
14 | + getDataComponent, | |
15 | + updateDataBoardLayout, | |
16 | + } from '/@/api/dataBoard'; | |
12 | 17 | import { useRoute } from 'vue-router'; |
13 | 18 | import { computed, unref } from '@vue/reactivity'; |
14 | - import { DataComponentRecord, DataSource, Layout as LayoutRecord } from '/@/api/dataBoard/model'; | |
19 | + import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model'; | |
15 | 20 | import { frontComponentMap, FrontComponentType } from './config/help'; |
16 | 21 | import { useMessage } from '/@/hooks/web/useMessage'; |
17 | - const handleBack = () => {}; | |
18 | - | |
19 | - type DataBoardRecord = DataComponentRecord & { layout: LayoutRecord }; | |
20 | - | |
21 | - type DataBoardLayoutInfo = Layout & { | |
22 | - record: DataComponentRecord & { width: number; height: number }; | |
23 | - }; | |
22 | + import { DataBoardLayoutInfo } from '../types/type'; | |
24 | 23 | |
25 | 24 | const ROUTE = useRoute(); |
26 | 25 | |
27 | - const { createMessage } = useMessage(); | |
26 | + const { createMessage, createConfirm } = useMessage(); | |
28 | 27 | const getBoardId = computed(() => { |
29 | 28 | return (ROUTE.params as { id: string }).id; |
30 | 29 | }); |
... | ... | @@ -74,7 +73,7 @@ |
74 | 73 | height, |
75 | 74 | }; |
76 | 75 | }); |
77 | - console.log(unref(dataBoardList)); | |
76 | + | |
78 | 77 | nextTick(() => { |
79 | 78 | const updateFn = widgetEl.get(i); |
80 | 79 | if (updateFn) updateFn(); |
... | ... | @@ -83,6 +82,7 @@ |
83 | 82 | |
84 | 83 | const itemResized = (i: string, newH: number, newW: number, newHPx: number, newWPx: number) => { |
85 | 84 | updateSize(i, newH, newW, newHPx, newWPx); |
85 | + console.log({ newH, newW, newHPx, newWPx }); | |
86 | 86 | }; |
87 | 87 | |
88 | 88 | const itemContainerResized = ( |
... | ... | @@ -111,8 +111,15 @@ |
111 | 111 | const [register, { openModal }] = useModal(); |
112 | 112 | |
113 | 113 | const handleMoreAction = (event: DropMenu, id: string) => { |
114 | - if (event.event === MoreActionEvent.EDIT) openModal(true); | |
115 | - if (event.event === MoreActionEvent.DELETE) handleDelete(id); | |
114 | + if (event.event === MoreActionEvent.DELETE) { | |
115 | + createConfirm({ | |
116 | + iconType: 'warning', | |
117 | + content: '是否确认删除?', | |
118 | + onOk: () => handleDelete(id), | |
119 | + }); | |
120 | + } | |
121 | + if (event.event === MoreActionEvent.EDIT) handleUpdate(id); | |
122 | + if (event.event === MoreActionEvent.COPY) handleCopy(id); | |
116 | 123 | }; |
117 | 124 | |
118 | 125 | const handleOpenCreatePanel = () => { |
... | ... | @@ -124,7 +131,12 @@ |
124 | 131 | const data = await getDataComponent(unref(getBoardId)); |
125 | 132 | dataBoardList.value = data.data.componentData.map((item) => { |
126 | 133 | const index = data.data.componentLayout.findIndex((each) => item.id === each.id); |
127 | - const layout = data.data.componentLayout[index]; | |
134 | + let layout; | |
135 | + if (!~index) { | |
136 | + layout = {}; | |
137 | + } else { | |
138 | + layout = data.data.componentLayout[index]; | |
139 | + } | |
128 | 140 | return { |
129 | 141 | i: item.id, |
130 | 142 | w: layout.w || defaultWidth, |
... | ... | @@ -138,6 +150,7 @@ |
138 | 150 | }, |
139 | 151 | }; |
140 | 152 | }); |
153 | + console.log(unref(dataBoardList)); | |
141 | 154 | } catch (error) {} |
142 | 155 | }; |
143 | 156 | |
... | ... | @@ -153,13 +166,40 @@ |
153 | 166 | return component?.transformConfig(component.ComponentConfig, record, dataSourceRecord); |
154 | 167 | }; |
155 | 168 | |
169 | + const handleUpdate = async (id: string) => { | |
170 | + const record = unref(dataBoardList).find((item) => item.i === id); | |
171 | + openModal(true, { isEdit: true, ...record }); | |
172 | + }; | |
173 | + | |
174 | + const handleCopy = async (id: string) => { | |
175 | + const record = unref(dataBoardList).find((item) => item.i === id); | |
176 | + console.log({ record }); | |
177 | + try { | |
178 | + const data = await addDataComponent({ | |
179 | + boardId: unref(getBoardId), | |
180 | + record: { | |
181 | + dataBoardId: unref(getBoardId), | |
182 | + frontId: record?.record.frontId, | |
183 | + dataSource: record?.record.dataSource, | |
184 | + }, | |
185 | + }); | |
186 | + createMessage.success('复制成功'); | |
187 | + const id = data.data.id; | |
188 | + await updateDataBoardLayout({ | |
189 | + boardId: unref(getBoardId), | |
190 | + layout: [{ id, w: DEFAULT_WIDGET_WIDTH, h: DEFAULT_WIDGET_HEIGHT, x: 0, y: 0 }], | |
191 | + }); | |
192 | + getDataBoardComponent(); | |
193 | + } catch (error) {} | |
194 | + }; | |
195 | + | |
156 | 196 | const handleDelete = async (id: string) => { |
157 | 197 | try { |
158 | 198 | await deleteDataComponent([id]); |
159 | 199 | createMessage.success('删除成功'); |
160 | 200 | await getDataBoardComponent(); |
161 | 201 | } catch (error) { |
162 | - createMessage.error('删除失败'); | |
202 | + // createMessage.error('删除失败'); | |
163 | 203 | } |
164 | 204 | }; |
165 | 205 | |
... | ... | @@ -170,11 +210,14 @@ |
170 | 210 | |
171 | 211 | <template> |
172 | 212 | <section class="bg-light-50 flex flex-col overflow-hidden h-full w-full"> |
173 | - <PageHeader title="水电表看板" @back="handleBack"> | |
213 | + <PageHeader title="水电表看板"> | |
174 | 214 | <template #extra> |
175 | 215 | <Button type="primary" @click="handleOpenCreatePanel">创建组件</Button> |
176 | 216 | </template> |
177 | - <div>已创建组件: 3个</div> | |
217 | + <div> | |
218 | + <span class="mr-3 text-gray-400">已创建组件:</span> | |
219 | + <span class="text-cyan-400"> {{ dataBoardList.length }}个</span> | |
220 | + </div> | |
178 | 221 | </PageHeader> |
179 | 222 | <section class="flex-1"> |
180 | 223 | <GridLayout | ... | ... |
... | ... | @@ -159,7 +159,7 @@ |
159 | 159 | {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }} |
160 | 160 | </span> |
161 | 161 | <span v-if="item.viewType === ViewType.PUBLIC_VIEW"> |
162 | - <Tooltip title="分享链接"> | |
162 | + <Tooltip title="点击复制分享链接"> | |
163 | 163 | <ShareAltOutlined class="ml-2" @click="handleCopyShareUrl(item)" /> |
164 | 164 | </Tooltip> |
165 | 165 | </span> | ... | ... |