Commit 128a5ebdb2fc8c44107a4752d92b422ac4e8c83c

Authored by ww
1 parent 29b5c630

wip: implement digitalDashboardComponent && DashboardComponent

@@ -7,6 +7,7 @@ import { @@ -7,6 +7,7 @@ import {
7 Layout, 7 Layout,
8 UpdateDataBoardLayoutParams, 8 UpdateDataBoardLayoutParams,
9 UpdateDataBoardParams, 9 UpdateDataBoardParams,
  10 + UpdateDataComponentParams,
10 } from './model'; 11 } from './model';
11 import { defHttp } from '/@/utils/http/axios'; 12 import { defHttp } from '/@/utils/http/axios';
12 13
@@ -22,6 +23,7 @@ enum DataComponentUrl { @@ -22,6 +23,7 @@ enum DataComponentUrl {
22 GET_DATA_COMPONENT = '/data_component', 23 GET_DATA_COMPONENT = '/data_component',
23 ADD_DATA_COMPONENT = '/data_component', 24 ADD_DATA_COMPONENT = '/data_component',
24 DELETE_DATA_COMPONENT = '/data_component', 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,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 +};
@@ -111,3 +111,8 @@ export interface UpdateDataBoardLayoutParams { @@ -111,3 +111,8 @@ export interface UpdateDataBoardLayoutParams {
111 boardId: string; 111 boardId: string;
112 layout: Layout[]; 112 layout: Layout[];
113 } 113 }
  114 +
  115 +export interface UpdateDataComponentParams {
  116 + boardId: string;
  117 + record: Partial<DataComponentRecord>;
  118 +}
@@ -3,67 +3,31 @@ @@ -3,67 +3,31 @@
3 import type { PropType } from 'vue'; 3 import type { PropType } from 'vue';
4 import { nextTick, onMounted, onUnmounted, ref, unref } from 'vue'; 4 import { nextTick, onMounted, onUnmounted, ref, unref } from 'vue';
5 import { init } from 'echarts'; 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 const props = defineProps({ 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 add: { 10 add: {
21 type: Function, 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 const chartRef = ref<Nullable<ECharts>>(null); 25 const chartRef = ref<Nullable<ECharts>>(null);
29 26
30 function initChart() { 27 function initChart() {
31 const chartDom = document.getElementById(getControlsWidgetId())!; 28 const chartDom = document.getElementById(getControlsWidgetId())!;
32 chartRef.value = init(chartDom); 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 nextTick(() => { 32 nextTick(() => {
69 option && unref(chartRef)?.setOption(option); 33 option && unref(chartRef)?.setOption(option);
@@ -76,7 +40,7 @@ @@ -76,7 +40,7 @@
76 40
77 onMounted(() => { 41 onMounted(() => {
78 initChart(); 42 initChart();
79 - props.add(props.dataSource.id, update); 43 + props.add && props.add(props.value.id, update);
80 }); 44 });
81 45
82 onUnmounted(() => { 46 onUnmounted(() => {
@@ -89,7 +53,13 @@ @@ -89,7 +53,13 @@
89 <template> 53 <template>
90 <div class="flex flex-col w-full h-full min-w-3 min-h-3"> 54 <div class="flex flex-col w-full h-full min-w-3 min-h-3">
91 <div :id="getControlsWidgetId()" class="widget-charts w-full h-full"></div> 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 </div> 63 </div>
94 </template> 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,6 +18,13 @@ export interface TextComponentValue {
18 iconColor?: string; 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 type TextComponentDefault = TextComponentLayout & { value: TextComponentValue }; 28 type TextComponentDefault = TextComponentLayout & { value: TextComponentValue };
22 29
23 export const TextComponent1Config: TextComponentDefault = { 30 export const TextComponent1Config: TextComponentDefault = {
@@ -10,8 +10,6 @@ @@ -10,8 +10,6 @@
10 10
11 const slot = useSlots(); 11 const slot = useSlots();
12 12
13 - console.log({ dataSource: props.dataSource });  
14 -  
15 const { update, add, remove } = useUpdateCenter(); 13 const { update, add, remove } = useUpdateCenter();
16 14
17 onMounted(() => { 15 onMounted(() => {
@@ -4,27 +4,28 @@ @@ -4,27 +4,28 @@
4 import { FormActionType, useForm } from '/@/components/Form'; 4 import { FormActionType, useForm } from '/@/components/Form';
5 import { basicSchema, dataSourceSchema } from '../config/basicConfiguration'; 5 import { basicSchema, dataSourceSchema } from '../config/basicConfiguration';
6 import BasicForm from '/@/components/Form/src/BasicForm.vue'; 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 import VisualOptionsModal from './VisualOptionsModal.vue'; 8 import VisualOptionsModal from './VisualOptionsModal.vue';
9 import { useModal } from '/@/components/Modal'; 9 import { useModal } from '/@/components/Modal';
10 import { buildUUID } from '/@/utils/uuid'; 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 import { useMessage } from '/@/hooks/web/useMessage'; 12 import { useMessage } from '/@/hooks/web/useMessage';
  13 + import { DataBoardLayoutInfo } from '../../types/type';
13 14
14 type DataSourceFormEL = { [key: string]: Nullable<FormActionType> }; 15 type DataSourceFormEL = { [key: string]: Nullable<FormActionType> };
15 16
16 type DataSourceEl = DataSource & { id: string }; 17 type DataSourceEl = DataSource & { id: string };
17 18
18 const props = defineProps<{ 19 const props = defineProps<{
19 - record: DataComponentRecord; 20 + record: DataBoardLayoutInfo;
20 frontId?: string; 21 frontId?: string;
21 }>(); 22 }>();
22 23
23 const { createMessage } = useMessage(); 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 const dataSource = ref<DataSourceEl[]>([{ id: buildUUID() } as unknown as DataSourceEl]); 30 const dataSource = ref<DataSourceEl[]>([{ id: buildUUID() } as unknown as DataSourceEl]);
30 31
@@ -38,7 +39,6 @@ @@ -38,7 +39,6 @@
38 39
39 const setFormEl = (el: any, id: string) => { 40 const setFormEl = (el: any, id: string) => {
40 if (!dataSourceEl[id] && el) { 41 if (!dataSourceEl[id] && el) {
41 - console.log({ el, id });  
42 const { formActionType } = el as unknown as { formActionType: FormActionType }; 42 const { formActionType } = el as unknown as { formActionType: FormActionType };
43 dataSourceEl[id] = formActionType; 43 dataSourceEl[id] = formActionType;
44 } 44 }
@@ -59,12 +59,12 @@ @@ -59,12 +59,12 @@
59 for (const id of hasExistEl) { 59 for (const id of hasExistEl) {
60 const index = unref(dataSource).findIndex((item) => item.id === id); 60 const index = unref(dataSource).findIndex((item) => item.id === id);
61 const value = (dataSourceEl[id] as FormActionType).getFieldsValue() as DataSource; 61 const value = (dataSourceEl[id] as FormActionType).getFieldsValue() as DataSource;
  62 + if (!~index) continue;
62 const componentInfo = unref(dataSource)[index].componentInfo || {}; 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 return _dataSource; 69 return _dataSource;
70 }; 70 };
@@ -113,19 +113,38 @@ @@ -113,19 +113,38 @@
113 }; 113 };
114 114
115 const echoDataSource = () => { 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 const handleRowComponentInfo = (recordId: string, value: ComponentInfo) => { 143 const handleRowComponentInfo = (recordId: string, value: ComponentInfo) => {
121 const index = unref(dataSource).findIndex((item) => item.id === recordId); 144 const index = unref(dataSource).findIndex((item) => item.id === recordId);
122 ~index && (unref(dataSource)[index].componentInfo = value); 145 ~index && (unref(dataSource)[index].componentInfo = value);
123 }; 146 };
124 147
125 - onMounted(() => {  
126 - echoDataSource();  
127 - });  
128 -  
129 defineExpose({ 148 defineExpose({
130 getAllDataSourceFieldValue, 149 getAllDataSourceFieldValue,
131 }); 150 });
@@ -179,7 +198,11 @@ @@ -179,7 +198,11 @@
179 <div class="text-center"> 198 <div class="text-center">
180 <Button type="primary" @click="handleAdd">添加数据源</Button> 199 <Button type="primary" @click="handleAdd">添加数据源</Button>
181 </div> 200 </div>
182 - <VisualOptionsModal @close="handleRowComponentInfo" @register="registerVisualOptionModal" /> 201 + <VisualOptionsModal
  202 + :value="props.frontId"
  203 + @close="handleRowComponentInfo"
  204 + @register="registerVisualOptionModal"
  205 + />
183 </section> 206 </section>
184 </template> 207 </template>
185 208
@@ -4,11 +4,12 @@ @@ -4,11 +4,12 @@
4 import BasicConfiguration from './BasicConfiguration.vue'; 4 import BasicConfiguration from './BasicConfiguration.vue';
5 import VisualConfiguration from './VisualConfiguration.vue'; 5 import VisualConfiguration from './VisualConfiguration.vue';
6 import { computed, ref, unref } from 'vue'; 6 import { computed, ref, unref } from 'vue';
7 - import type { DataComponentRecord } from '/@/api/dataBoard/model';  
8 import { RouteParams, useRoute } from 'vue-router'; 7 import { RouteParams, useRoute } from 'vue-router';
9 - import { addDataComponent, updateDataBoardLayout } from '/@/api/dataBoard'; 8 + import { addDataComponent, updateDataBoardLayout, updateDataComponent } from '/@/api/dataBoard';
10 import { useModalInner } from '/@/components/Modal'; 9 import { useModalInner } from '/@/components/Modal';
11 import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH } from '../../config/config'; 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 interface DataComponentRouteParams extends RouteParams { 14 interface DataComponentRouteParams extends RouteParams {
14 id: string; 15 id: string;
@@ -18,11 +19,7 @@ @@ -18,11 +19,7 @@
18 19
19 const ROUTE = useRoute(); 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 const boardId = computed(() => { 24 const boardId = computed(() => {
28 return (ROUTE.params as DataComponentRouteParams).id; 25 return (ROUTE.params as DataComponentRouteParams).id;
@@ -30,12 +27,26 @@ @@ -30,12 +27,26 @@
30 27
31 const frontId = ref(''); 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 const handleSubmit = () => { 46 const handleSubmit = () => {
36 const { getAllDataSourceFieldValue } = unref(basicConfigurationEl)!; 47 const { getAllDataSourceFieldValue } = unref(basicConfigurationEl)!;
37 const value = getAllDataSourceFieldValue(); 48 const value = getAllDataSourceFieldValue();
38 - handleAddComponent(value); 49 + unref(isEdit) ? handleUpdateComponent(value) : handleAddComponent(value);
39 }; 50 };
40 51
41 const handleAddComponent = async (value: Recordable) => { 52 const handleAddComponent = async (value: Recordable) => {
@@ -44,6 +55,7 @@ @@ -44,6 +55,7 @@
44 boardId: unref(boardId), 55 boardId: unref(boardId),
45 record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value }, 56 record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value },
46 }); 57 });
  58 + createMessage.success('创建成功');
47 const id = data.data.id; 59 const id = data.data.id;
48 await updateDataBoardLayout({ 60 await updateDataBoardLayout({
49 boardId: unref(boardId), 61 boardId: unref(boardId),
@@ -51,7 +63,28 @@ @@ -51,7 +63,28 @@
51 }); 63 });
52 closeModal(); 64 closeModal();
53 emit('submit'); 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 </script> 89 </script>
57 90
@@ -61,6 +94,7 @@ @@ -61,6 +94,7 @@
61 @register="register" 94 @register="register"
62 title="自定义组件" 95 title="自定义组件"
63 width="70%" 96 width="70%"
  97 + destroy-on-close
64 @ok="handleSubmit" 98 @ok="handleSubmit"
65 > 99 >
66 <section> 100 <section>
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 import VisualWidgetSelect from './VisualWidgetSelect.vue'; 3 import VisualWidgetSelect from './VisualWidgetSelect.vue';
4 import TextComponent from '../../components/TextComponent/TextComponent.vue'; 4 import TextComponent from '../../components/TextComponent/TextComponent.vue';
5 import { textComponentConfig } from '../../components/TextComponent/config'; 5 import { textComponentConfig } from '../../components/TextComponent/config';
  6 + import { instrumentComponentConfig } from '../../components/InstrumentComponent';
6 const props = defineProps<{ 7 const props = defineProps<{
7 value: string; 8 value: string;
8 }>(); 9 }>();
@@ -35,7 +36,22 @@ @@ -35,7 +36,22 @@
35 </List> 36 </List>
36 </Tabs.TabPane> 37 </Tabs.TabPane>
37 <Tabs.TabPane key="2" tab="仪表组件"> 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 </Tabs.TabPane> 55 </Tabs.TabPane>
40 </Tabs> 56 </Tabs>
41 </section> 57 </section>
1 <script lang="ts" setup> 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 import { useForm, BasicForm } from '/@/components/Form'; 4 import { useForm, BasicForm } from '/@/components/Form';
5 import { BasicModal, useModalInner } from '/@/components/Modal'; 5 import { BasicModal, useModalInner } from '/@/components/Modal';
6 import { ComponentInfo } from '/@/api/dataBoard/model'; 6 import { ComponentInfo } from '/@/api/dataBoard/model';
  7 + import { computed } from '@vue/reactivity';
7 8
8 const emit = defineEmits(['close']); 9 const emit = defineEmits(['close']);
9 10
  11 + const props = defineProps<{
  12 + value?: string;
  13 + }>();
  14 +
10 const recordId = ref(''); 15 const recordId = ref('');
11 16
  17 + const getSchemas = computed(() => {
  18 + return schemasMap.get((props.value as WidgetComponentType) || 'text-component-1');
  19 + });
  20 +
12 const [registerForm, method] = useForm({ 21 const [registerForm, method] = useForm({
13 - schemas: modeTwo,  
14 showActionButtonGroup: false, 22 showActionButtonGroup: false,
15 labelWidth: 120, 23 labelWidth: 120,
16 baseColProps: { 24 baseColProps: {
@@ -45,6 +53,6 @@ @@ -45,6 +53,6 @@
45 title="选项" 53 title="选项"
46 width="60%" 54 width="60%"
47 > 55 >
48 - <BasicForm @register="registerForm" /> 56 + <BasicForm @register="registerForm" :schemas="getSchemas" />
49 </BasicModal> 57 </BasicModal>
50 </template> 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 import { FormSchema } from '/@/components/Form'; 4 import { FormSchema } from '/@/components/Form';
2 export enum defaultOptions { 5 export enum defaultOptions {
3 fontColor = '#rer', 6 fontColor = '#rer',
4 } 7 }
5 8
  9 +export type WidgetComponentType =
  10 + | TextComponentType
  11 + | InstrumentComponentType
  12 + | DigitalDashBoardComponentType;
  13 +
6 export enum visualOptionField { 14 export enum visualOptionField {
7 FONT_COLOR = 'fontColor', 15 FONT_COLOR = 'fontColor',
8 UNIT = 'unit', 16 UNIT = 'unit',
@@ -137,3 +145,13 @@ export const modeFour: FormSchema[] = [ @@ -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 <script lang="ts" setup> 1 <script lang="ts" setup>
2 import { Button, PageHeader } from 'ant-design-vue'; 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 import { nextTick, onMounted, ref } from 'vue'; 4 import { nextTick, onMounted, ref } from 'vue';
5 import WidgetWrapper from '../components/WidgetWrapper/WidgetWrapper.vue'; 5 import WidgetWrapper from '../components/WidgetWrapper/WidgetWrapper.vue';
6 import BaseWidgetHeader from '../components/WidgetHeader/BaseWidgetHeader.vue'; 6 import BaseWidgetHeader from '../components/WidgetHeader/BaseWidgetHeader.vue';
7 import { DropMenu } from '/@/components/Dropdown'; 7 import { DropMenu } from '/@/components/Dropdown';
8 import DataBindModal from './components/DataBindModal.vue'; 8 import DataBindModal from './components/DataBindModal.vue';
9 import { useModal } from '/@/components/Modal'; 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 import { useRoute } from 'vue-router'; 17 import { useRoute } from 'vue-router';
13 import { computed, unref } from '@vue/reactivity'; 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 import { frontComponentMap, FrontComponentType } from './config/help'; 20 import { frontComponentMap, FrontComponentType } from './config/help';
16 import { useMessage } from '/@/hooks/web/useMessage'; 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 const ROUTE = useRoute(); 24 const ROUTE = useRoute();
26 25
27 - const { createMessage } = useMessage(); 26 + const { createMessage, createConfirm } = useMessage();
28 const getBoardId = computed(() => { 27 const getBoardId = computed(() => {
29 return (ROUTE.params as { id: string }).id; 28 return (ROUTE.params as { id: string }).id;
30 }); 29 });
@@ -74,7 +73,7 @@ @@ -74,7 +73,7 @@
74 height, 73 height,
75 }; 74 };
76 }); 75 });
77 - console.log(unref(dataBoardList)); 76 +
78 nextTick(() => { 77 nextTick(() => {
79 const updateFn = widgetEl.get(i); 78 const updateFn = widgetEl.get(i);
80 if (updateFn) updateFn(); 79 if (updateFn) updateFn();
@@ -83,6 +82,7 @@ @@ -83,6 +82,7 @@
83 82
84 const itemResized = (i: string, newH: number, newW: number, newHPx: number, newWPx: number) => { 83 const itemResized = (i: string, newH: number, newW: number, newHPx: number, newWPx: number) => {
85 updateSize(i, newH, newW, newHPx, newWPx); 84 updateSize(i, newH, newW, newHPx, newWPx);
  85 + console.log({ newH, newW, newHPx, newWPx });
86 }; 86 };
87 87
88 const itemContainerResized = ( 88 const itemContainerResized = (
@@ -111,8 +111,15 @@ @@ -111,8 +111,15 @@
111 const [register, { openModal }] = useModal(); 111 const [register, { openModal }] = useModal();
112 112
113 const handleMoreAction = (event: DropMenu, id: string) => { 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 const handleOpenCreatePanel = () => { 125 const handleOpenCreatePanel = () => {
@@ -124,7 +131,12 @@ @@ -124,7 +131,12 @@
124 const data = await getDataComponent(unref(getBoardId)); 131 const data = await getDataComponent(unref(getBoardId));
125 dataBoardList.value = data.data.componentData.map((item) => { 132 dataBoardList.value = data.data.componentData.map((item) => {
126 const index = data.data.componentLayout.findIndex((each) => item.id === each.id); 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 return { 140 return {
129 i: item.id, 141 i: item.id,
130 w: layout.w || defaultWidth, 142 w: layout.w || defaultWidth,
@@ -138,6 +150,7 @@ @@ -138,6 +150,7 @@
138 }, 150 },
139 }; 151 };
140 }); 152 });
  153 + console.log(unref(dataBoardList));
141 } catch (error) {} 154 } catch (error) {}
142 }; 155 };
143 156
@@ -153,13 +166,40 @@ @@ -153,13 +166,40 @@
153 return component?.transformConfig(component.ComponentConfig, record, dataSourceRecord); 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 const handleDelete = async (id: string) => { 196 const handleDelete = async (id: string) => {
157 try { 197 try {
158 await deleteDataComponent([id]); 198 await deleteDataComponent([id]);
159 createMessage.success('删除成功'); 199 createMessage.success('删除成功');
160 await getDataBoardComponent(); 200 await getDataBoardComponent();
161 } catch (error) { 201 } catch (error) {
162 - createMessage.error('删除失败'); 202 + // createMessage.error('删除失败');
163 } 203 }
164 }; 204 };
165 205
@@ -170,11 +210,14 @@ @@ -170,11 +210,14 @@
170 210
171 <template> 211 <template>
172 <section class="bg-light-50 flex flex-col overflow-hidden h-full w-full"> 212 <section class="bg-light-50 flex flex-col overflow-hidden h-full w-full">
173 - <PageHeader title="水电表看板" @back="handleBack"> 213 + <PageHeader title="水电表看板">
174 <template #extra> 214 <template #extra>
175 <Button type="primary" @click="handleOpenCreatePanel">创建组件</Button> 215 <Button type="primary" @click="handleOpenCreatePanel">创建组件</Button>
176 </template> 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 </PageHeader> 221 </PageHeader>
179 <section class="flex-1"> 222 <section class="flex-1">
180 <GridLayout 223 <GridLayout
@@ -159,7 +159,7 @@ @@ -159,7 +159,7 @@
159 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }} 159 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
160 </span> 160 </span>
161 <span v-if="item.viewType === ViewType.PUBLIC_VIEW"> 161 <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
162 - <Tooltip title="分享链接"> 162 + <Tooltip title="点击复制分享链接">
163 <ShareAltOutlined class="ml-2" @click="handleCopyShareUrl(item)" /> 163 <ShareAltOutlined class="ml-2" @click="handleCopyShareUrl(item)" />
164 </Tooltip> 164 </Tooltip>
165 </span> 165 </span>
  1 +import { Layout } from 'vue3-grid-layout';
  2 +import { DataComponentRecord } from '/@/api/dataBoard/model';
  3 +
  4 +export type DataBoardLayoutInfo = Layout & {
  5 + record: DataComponentRecord & { width: number; height: number };
  6 +};