Commit 29b5c630ec03930440ad66f506f53697f0c2084f

Authored by ww
1 parent c158d755

wip: data board component

1 1 import {
2 2 AddDataBoardParams,
  3 + AddDataComponentParams,
3 4 DataBoardList,
  5 + DataComponentRecord,
4 6 GetDataBoardParams,
  7 + Layout,
  8 + UpdateDataBoardLayoutParams,
5 9 UpdateDataBoardParams,
6 10 } from './model';
7 11 import { defHttp } from '/@/utils/http/axios';
... ... @@ -11,6 +15,13 @@ enum DataBoardUrl {
11 15 ADD_DATA_BOARD = '/data_board/add',
12 16 DELETE_DATA_BOARD = '/data_board',
13 17 UPDATE_DATA_BOARD = '/data_board/update',
  18 + UPDATE_DATA_BOARD_LAYOUT = '/data_board',
  19 +}
  20 +
  21 +enum DataComponentUrl {
  22 + GET_DATA_COMPONENT = '/data_component',
  23 + ADD_DATA_COMPONENT = '/data_component',
  24 + DELETE_DATA_COMPONENT = '/data_component',
14 25 }
15 26
16 27 /**
... ... @@ -60,3 +71,50 @@ export const deleteDataBoard = (params: string[]) => {
60 71 params: { ids: params },
61 72 });
62 73 };
  74 +
  75 +/**
  76 + * @description 更新数据组件位置
  77 + * @param params
  78 + * @returns
  79 + */
  80 +export const updateDataBoardLayout = (params: UpdateDataBoardLayoutParams) => {
  81 + return defHttp.post({
  82 + url: `${DataBoardUrl.UPDATE_DATA_BOARD_LAYOUT}/${params.boardId}/layout/`,
  83 + params: params.layout,
  84 + });
  85 +};
  86 +
  87 +/**
  88 + * @description 获取数据组件
  89 + * @param params
  90 + * @returns
  91 + */
  92 +export const getDataComponent = (params: string) => {
  93 + return defHttp.get<{ data: { componentData: DataComponentRecord[]; componentLayout: Layout[] } }>(
  94 + {
  95 + url: `${DataComponentUrl.GET_DATA_COMPONENT}/${params}`,
  96 + // params: { boardId: params },
  97 + }
  98 + );
  99 +};
  100 +
  101 +export const addDataComponent = (params: AddDataComponentParams) => {
  102 + return defHttp.post<{ data: DataComponentRecord }>({
  103 + url: `${DataComponentUrl.ADD_DATA_COMPONENT}/${params.boardId}/add`,
  104 + params: params.record,
  105 + });
  106 +};
  107 +
  108 +/**
  109 + * @description 删除数据组件
  110 + * @param params
  111 + * @returns
  112 + */
  113 +export const deleteDataComponent = (params: string[]) => {
  114 + return defHttp.delete({
  115 + url: DataComponentUrl.DELETE_DATA_COMPONENT,
  116 + params: {
  117 + ids: params,
  118 + },
  119 + });
  120 +};
... ...
... ... @@ -23,14 +23,6 @@ export interface Layout {
23 23 y: number;
24 24 }
25 25
26   -export interface Layout {
27   - h: number;
28   - id: string;
29   - w: number;
30   - x: number;
31   - y: number;
32   -}
33   -
34 26 export interface DataBoardRecord {
35 27 name: string;
36 28 roleIds: string[];
... ... @@ -57,3 +49,65 @@ export interface DataBoardList {
57 49 items: DataBoardRecord[];
58 50 total: number;
59 51 }
  52 +
  53 +export interface GradientInfo {
  54 + value: number;
  55 + key: string;
  56 +}
  57 +
  58 +export interface ComponentInfo {
  59 + fontColor: string;
  60 + unit: string;
  61 + gradientInfo: GradientInfo[];
  62 + iconColor: string;
  63 + icon: string;
  64 +}
  65 +
  66 +export interface DataSource {
  67 + attribute: string;
  68 + deviceId: string;
  69 + organizationId: string;
  70 + attributeRename: string;
  71 + deviceRename: string;
  72 + componentInfo: ComponentInfo;
  73 +}
  74 +
  75 +export interface DataComponentRecord {
  76 + dataBoardId: string;
  77 + dataSource: DataSource[];
  78 + frontId: string;
  79 + name: string;
  80 + tenantId: string;
  81 + tenantStatus: string;
  82 + description: string;
  83 + roleIds: string[];
  84 + updater: string;
  85 + enabled: boolean;
  86 + id: string;
  87 + defaultConfig: string;
  88 + tenantProfileId: string;
  89 + remark: string;
  90 + icon: string;
  91 + tenantExpireTime: string;
  92 + updateTime: string;
  93 + creator: string;
  94 + createTime: string;
  95 +}
  96 +
  97 +export interface AddDataComponentParams {
  98 + boardId: string;
  99 + record: Partial<DataComponentRecord>;
  100 +}
  101 +
  102 +export interface Layout {
  103 + id: string;
  104 + w: number;
  105 + h: number;
  106 + x: number;
  107 + y: number;
  108 +}
  109 +
  110 +export interface UpdateDataBoardLayoutParams {
  111 + boardId: string;
  112 + layout: Layout[];
  113 +}
... ...
  1 +/**
  2 + * @description use to function capture await throw error
  3 + * @param promise
  4 + * @param errorExt - Additional Information you can pass to the err object
  5 + */
  6 +export function to<DATA = any, ERROR = any>(
  7 + promise: any,
  8 + errorExt?: string
  9 +): Promise<[ERROR, DATA]> {
  10 + return promise
  11 + .then((data) => [null, data])
  12 + .catch((err) => {
  13 + if (errorExt) {
  14 + const parsedError = Object.assign({}, err, errorExt);
  15 + return [parsedError, undefined];
  16 + }
  17 + return [err, undefined];
  18 + });
  19 +}
... ...
  1 +<script lang="ts" setup>
  2 + import type { ECharts, EChartsOption } from 'echarts';
  3 + import type { PropType } from 'vue';
  4 + import { nextTick, onMounted, onUnmounted, ref, unref } from 'vue';
  5 + import { init } from 'echarts';
  6 +
  7 + interface DataSource {
  8 + id: string | number;
  9 + }
  10 +
  11 + 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: {
  21 + type: Function,
  22 + required: true,
  23 + },
  24 + });
  25 +
  26 + const getControlsWidgetId = () => `widget-chart-${props.dataSource.id}`;
  27 +
  28 + const chartRef = ref<Nullable<ECharts>>(null);
  29 +
  30 + function initChart() {
  31 + const chartDom = document.getElementById(getControlsWidgetId())!;
  32 + 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 + };
  67 +
  68 + nextTick(() => {
  69 + option && unref(chartRef)?.setOption(option);
  70 + });
  71 + }
  72 +
  73 + function update() {
  74 + unref(chartRef)?.resize();
  75 + }
  76 +
  77 + onMounted(() => {
  78 + initChart();
  79 + props.add(props.dataSource.id, update);
  80 + });
  81 +
  82 + onUnmounted(() => {
  83 + unref(chartRef)?.clear();
  84 + });
  85 +
  86 + defineExpose({ update });
  87 +</script>
  88 +
  89 +<template>
  90 + <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>
  92 + <div class="text-xs text-center text-gray-400">更新时间:</div>
  93 + </div>
  94 +</template>
  95 +
  96 +<style scoped>
  97 + .widget-charts > div {
  98 + width: 100%;
  99 + height: 100%;
  100 + }
  101 +</style>
... ...
  1 +<script lang="ts" setup></script>
  2 +
  3 +<template> </template>
... ...
... ... @@ -44,16 +44,21 @@
44 44 <div class="w-1/2">
45 45 <div>
46 46 <div v-if="getShowIcon">
47   - <SvgIcon name="CO2" prefix="iconfont" class="!w-1/2 !h-[2em]" />
  47 + <SvgIcon
  48 + :name="props.value.icon || 'CO2'"
  49 + prefix="iconfont"
  50 + class="!w-1/2 !h-[2em]"
  51 + :style="{ color: props.value.iconColor }"
  52 + />
48 53 </div>
49 54 <div>{{ props.value.name }}</div>
50 55 </div>
51 56 </div>
52 57 <div class="w-1/2 flex justify-center">
53 58 <Statistic
54   - value="123"
  59 + :value="props.value.value || '123'"
55 60 :suffix="getShowUnit ? props.value.unit : ''"
56   - :value-style="{ fontSize: '1.3em' }"
  61 + :value-style="{ fontSize: '1.3em', color: props.value.fontColor }"
57 62 />
58 63 </div>
59 64 </div>
... ...
1 1 import { formatToDateTime } from '/@/utils/dateUtil';
2   -
  2 +import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
3 3 export interface TextComponentLayout {
4 4 id: string;
5 5 base?: boolean;
... ... @@ -20,37 +20,73 @@ export interface TextComponentValue {
20 20
21 21 type TextComponentDefault = TextComponentLayout & { value: TextComponentValue };
22 22
23   -export const textComponentConfig: TextComponentDefault[] = [
24   - { id: 'text-component-1', base: true, value: { value: 123, name: '温度' } },
25   - { id: 'text-component-2', base: false, value: { value: 123, name: '温度' } },
26   - {
27   - id: 'text-component-3',
28   - base: false,
29   - showUpdate: true,
30   - value: {
31   - value: 123,
32   - name: '温度',
33   - updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
34   - },
35   - },
36   - {
37   - id: 'text-component-4',
38   - base: false,
39   - showIcon: true,
40   - showUpdate: true,
41   - showUnit: true,
42   - value: {
43   - value: 123,
44   - name: '温度',
45   - updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
46   - unit: '℃',
47   - },
  23 +export const TextComponent1Config: TextComponentDefault = {
  24 + id: 'text-component-1',
  25 + base: true,
  26 + value: { value: 123, name: '温度' },
  27 +};
  28 +
  29 +export const TextComponent2Config: TextComponentDefault = {
  30 + id: 'text-component-2',
  31 + base: false,
  32 + value: { value: 123, name: '温度' },
  33 +};
  34 +export const TextComponent3Config: TextComponentDefault = {
  35 + id: 'text-component-3',
  36 + base: false,
  37 + showUpdate: true,
  38 + value: {
  39 + value: 123,
  40 + name: '温度',
  41 + updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
48 42 },
49   - {
50   - id: 'text-component-5',
51   - base: false,
52   - showIcon: true,
53   - showUnit: true,
54   - value: { value: 123, name: '温度', unit: '℃' },
  43 +};
  44 +export const TextComponent4Config: TextComponentDefault = {
  45 + id: 'text-component-4',
  46 + base: false,
  47 + showIcon: true,
  48 + showUpdate: true,
  49 + showUnit: true,
  50 + value: {
  51 + value: 123,
  52 + name: '温度',
  53 + updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
  54 + unit: '℃',
55 55 },
  56 +};
  57 +export const TextComponent5Config: TextComponentDefault = {
  58 + id: 'text-component-5',
  59 + base: false,
  60 + showIcon: true,
  61 + showUnit: true,
  62 + value: { value: 123, name: '温度', unit: '℃' },
  63 +};
  64 +
  65 +export const textComponentConfig: TextComponentDefault[] = [
  66 + TextComponent1Config,
  67 + TextComponent2Config,
  68 + TextComponent3Config,
  69 + TextComponent4Config,
  70 + TextComponent5Config,
56 71 ];
  72 +
  73 +export const transformTextComponentConfig = (
  74 + config: TextComponentDefault,
  75 + record: DataComponentRecord,
  76 + dataSourceRecord: DataSource
  77 +) => {
  78 + return {
  79 + layout: {
  80 + ...config,
  81 + } as TextComponentLayout,
  82 + value: {
  83 + name: dataSourceRecord.attributeRename || dataSourceRecord.attribute,
  84 + // value: record.va
  85 + icon: dataSourceRecord.componentInfo.icon,
  86 + unit: dataSourceRecord.componentInfo.unit,
  87 + updateTime: record.updateTime || record.createTime,
  88 + fontColor: dataSourceRecord.componentInfo.fontColor,
  89 + iconColor: dataSourceRecord.componentInfo.iconColor,
  90 + } as TextComponentValue,
  91 + };
  92 +};
... ...
... ... @@ -7,6 +7,9 @@
7 7 import { MoreActionEvent } from '../../config/config';
8 8
9 9 const emit = defineEmits(['action']);
  10 + const props = defineProps<{
  11 + id: string;
  12 + }>();
10 13
11 14 const dropMenuList: DropMenu[] = [
12 15 {
... ... @@ -27,14 +30,14 @@
27 30 ];
28 31
29 32 const handleMenuEvent = (event: DropMenu) => {
30   - emit('action', event);
  33 + emit('action', event, props.id);
31 34 };
32 35 </script>
33 36
34 37 <template>
35 38 <div class="flex justify-between">
36 39 <div class="flex flex-auto">
37   - <div v-for="item in 3" class="flex mx-2" :key="item">
  40 + <div v-for="item in 1" class="flex mx-2" :key="item">
38 41 <div class="flex items-center">
39 42 <Tooltip>
40 43 <SvgIcon name="online" prefix="iconfont" class="!fill-emerald-400" />
... ...
1 1 <script lang="ts" setup>
2   - import { onMounted } from 'vue';
  2 + import { onMounted, useSlots } from 'vue';
3 3 import { useUpdateCenter } from '../../hook/useUpdateCenter';
4 4 import type { DataSource, WidgetWrapperRegister } from './type';
5 5
... ... @@ -8,6 +8,10 @@
8 8 register?: WidgetWrapperRegister;
9 9 }>();
10 10
  11 + const slot = useSlots();
  12 +
  13 + console.log({ dataSource: props.dataSource });
  14 +
11 15 const { update, add, remove } = useUpdateCenter();
12 16
13 17 onMounted(() => {
... ... @@ -29,7 +33,10 @@
29 33 class="widget-item"
30 34 >
31 35 <div class="widget-box">
32   - <div class="widget-controls-container">
  36 + <div
  37 + class="widget-controls-container"
  38 + :style="{ height: slot.footer ? 'calc(100% - 20px)' : '100%' }"
  39 + >
33 40 <slot
34 41 name="controls"
35 42 :record="item"
... ...
  1 +import { DataComponentRecord } from '/@/api/dataBoard/model';
  2 +
1 3 export interface DataSource {
2   - id: number | string
3   - width: number
4   - height: number
  4 + id: number | string;
  5 + width: number;
  6 + height: number;
5 7
6   - [key: string]: any
  8 + [key: string]: any;
7 9 }
8 10
9   -export type WidgetWrapperRegister = (dataSource: DataSource[]) => any
  11 +export type WidgetWrapperRegister = (dataSource: DataComponentRecord[]) => any;
... ...
... ... @@ -5,3 +5,6 @@ export enum MoreActionEvent {
5 5 }
6 6
7 7 // export enum
  8 +
  9 +export const DEFAULT_WIDGET_WIDTH = 6;
  10 +export const DEFAULT_WIDGET_HEIGHT = 6;
... ...
1 1 <script lang="ts" setup>
2 2 import { CopyOutlined, DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
3 3 import { Tooltip, Button } from 'ant-design-vue';
4   - import { useForm } from '/@/components/Form';
  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 { ref, unref } from 'vue';
  7 + import { onMounted, reactive, ref, shallowReactive, unref, nextTick } 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';
  12 + import { useMessage } from '/@/hooks/web/useMessage';
11 13
12   - const props = defineProps();
  14 + type DataSourceFormEL = { [key: string]: Nullable<FormActionType> };
13 15
14   - const dataSource = ref([{ id: 'string' }]);
  16 + type DataSourceEl = DataSource & { id: string };
  17 +
  18 + const props = defineProps<{
  19 + record: DataComponentRecord;
  20 + frontId?: string;
  21 + }>();
  22 +
  23 + const { createMessage } = useMessage();
  24 +
  25 + const componentRecord = reactive<DataComponentRecord>({
  26 + id: 'string',
  27 + } as unknown as DataComponentRecord);
  28 +
  29 + const dataSource = ref<DataSourceEl[]>([{ id: buildUUID() } as unknown as DataSourceEl]);
15 30
16 31 const [basicRegister, basicMethod] = useForm({
17 32 schemas: basicSchema,
... ... @@ -19,70 +34,157 @@
19 34 labelWidth: 96,
20 35 });
21 36
22   - const [dataSourceRegister, dataSourceMethod] = useForm({
23   - schemas: dataSourceSchema,
24   - showActionButtonGroup: false,
25   - layout: 'inline',
26   - labelCol: {
27   - span: 0,
28   - },
29   - baseColProps: {
30   - span: 4,
31   - },
32   - });
  37 + const dataSourceEl = shallowReactive<DataSourceFormEL>({} as unknown as DataSourceFormEL);
  38 +
  39 + const setFormEl = (el: any, id: string) => {
  40 + if (!dataSourceEl[id] && el) {
  41 + console.log({ el, id });
  42 + const { formActionType } = el as unknown as { formActionType: FormActionType };
  43 + dataSourceEl[id] = formActionType;
  44 + }
  45 + };
  46 +
  47 + const getAllDataSourceFieldValue = () => {
  48 + const _dataSource = getDataSourceField();
  49 + const basicInfo = basicMethod.getFieldsValue();
  50 + return {
  51 + ...basicInfo,
  52 + dataSource: _dataSource,
  53 + };
  54 + };
  55 +
  56 + const getDataSourceField = () => {
  57 + const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);
  58 + const _dataSource: DataSource[] = [];
  59 + for (const id of hasExistEl) {
  60 + const index = unref(dataSource).findIndex((item) => item.id === id);
  61 + const value = (dataSourceEl[id] as FormActionType).getFieldsValue() as DataSource;
  62 + const componentInfo = unref(dataSource)[index].componentInfo || {};
  63 + ~index &&
  64 + _dataSource.push({
  65 + ...value,
  66 + componentInfo: { ...componentInfo },
  67 + });
  68 + }
  69 + return _dataSource;
  70 + };
  71 +
  72 + const handleCopy = async (data: DataSourceEl) => {
  73 + const value = (dataSourceEl[data.id] as FormActionType).getFieldsValue() as DataSource;
  74 + const index = unref(dataSource).findIndex((item) => item.id === data.id);
33 75
34   - const handleCopy = (data: Recordable) => {
  76 + const componentInfo = ~index
  77 + ? unref(dataSource)[index].componentInfo
  78 + : ({} as unknown as ComponentInfo);
  79 +
  80 + const copyRecordId = buildUUID();
35 81 unref(dataSource).push({
36   - id: data.id + 1,
  82 + ...value,
  83 + id: copyRecordId,
  84 + componentInfo,
37 85 });
  86 + await nextTick();
  87 + (dataSourceEl[copyRecordId] as FormActionType).setFieldsValue(value);
38 88 };
39 89
40 90 const [registerVisualOptionModal, { openModal }] = useModal();
41 91
42   - const handleSetting = () => {
43   - openModal(true);
  92 + const handleSetting = (item: DataSourceEl) => {
  93 + if (!props.frontId) {
  94 + createMessage.warning('请先选择可视化组件');
  95 + return;
  96 + }
  97 + openModal(true, {
  98 + recordId: item.id,
  99 + componentInfo: item.componentInfo,
  100 + });
44 101 };
45 102
46   - const handleDelete = (data: Recordable) => {
  103 + const handleDelete = (data: DataSourceEl) => {
47 104 const index = unref(dataSource).findIndex((item) => item.id === data.id);
48   -
49 105 ~index && unref(dataSource).splice(index, 1);
  106 + dataSourceEl[data.id] = null;
50 107 };
51 108
52 109 const handleAdd = () => {
53 110 unref(dataSource).push({
54 111 id: buildUUID(),
55   - });
  112 + } as unknown as DataSourceEl);
  113 + };
  114 +
  115 + const echoDataSource = () => {
  116 + basicMethod.setFieldsValue(props.record);
  117 + // dataSourceMethod.setFieldsValue(props.record);
  118 + };
  119 +
  120 + const handleRowComponentInfo = (recordId: string, value: ComponentInfo) => {
  121 + const index = unref(dataSource).findIndex((item) => item.id === recordId);
  122 + ~index && (unref(dataSource)[index].componentInfo = value);
56 123 };
  124 +
  125 + onMounted(() => {
  126 + echoDataSource();
  127 + });
  128 +
  129 + defineExpose({
  130 + getAllDataSourceFieldValue,
  131 + });
57 132 </script>
58 133
59 134 <template>
60 135 <section>
61 136 <h3 class="w-24 text-right pr-2 my-4">基础信息</h3>
62 137 <div class="w-3/4">
63   - <BasicForm @register="basicRegister" />
  138 + <BasicForm @register="basicRegister" class="w-full" />
64 139 </div>
65 140 <h3 class="w-24 flex-shrink-0 text-right pr-2 my-4">选择数据源</h3>
66 141 <div v-for="item in dataSource" :key="item.id" class="flex">
67   - <div class="w-24 text-right" style="flex: 0 0 96px; padding-right: 8px">选择设备</div>
  142 + <div
  143 + class="w-24 text-right leading-30px pr-8px flex right"
  144 + style="flex: 0 0 96px; justify-content: right"
  145 + >
  146 + 选择设备
  147 + </div>
68 148 <div class="pl-2 flex-auto">
69   - <BasicForm @register="dataSourceRegister" />
  149 + <BasicForm
  150 + :ref="(el) => setFormEl(el, item.id)"
  151 + :schemas="dataSourceSchema"
  152 + class="w-full flex-1 data-source-form"
  153 + :show-action-button-group="false"
  154 + :row-props="{
  155 + gutter: 10,
  156 + }"
  157 + layout="inline"
  158 + :label-col="{ span: 0 }"
  159 + />
70 160 </div>
71   - <div class="flex justify-center gap-3 w-18">
  161 + <div class="flex justify-center gap-3 w-24">
72 162 <Tooltip title="复制">
73   - <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg" />
  163 + <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-30px" />
74 164 </Tooltip>
75 165 <Tooltip title="设置">
76   - <SettingOutlined @click="handleSetting()" class="cursor-pointer text-lg" />
  166 + <SettingOutlined
  167 + @click="handleSetting(item)"
  168 + class="cursor-pointer text-lg !leading-30px"
  169 + />
77 170 </Tooltip>
78 171 <Tooltip title="删除">
79   - <DeleteOutlined @click="handleDelete(item)" class="cursor-pointer text-lg" />
  172 + <DeleteOutlined
  173 + @click="handleDelete(item)"
  174 + class="cursor-pointer text-lg !leading-30px"
  175 + />
80 176 </Tooltip>
81 177 </div>
82 178 </div>
83 179 <div class="text-center">
84 180 <Button type="primary" @click="handleAdd">添加数据源</Button>
85 181 </div>
86   - <VisualOptionsModal @register="registerVisualOptionModal" />
  182 + <VisualOptionsModal @close="handleRowComponentInfo" @register="registerVisualOptionModal" />
87 183 </section>
88 184 </template>
  185 +
  186 +<style scoped>
  187 + .data-source-form:deep(.ant-row) {
  188 + width: 100%;
  189 + }
  190 +</style>
... ...
... ... @@ -3,26 +3,77 @@
3 3 import BasicModal from '/@/components/Modal/src/BasicModal.vue';
4 4 import BasicConfiguration from './BasicConfiguration.vue';
5 5 import VisualConfiguration from './VisualConfiguration.vue';
6   - import { ref } from 'vue';
  6 + import { computed, ref, unref } from 'vue';
  7 + import type { DataComponentRecord } from '/@/api/dataBoard/model';
  8 + import { RouteParams, useRoute } from 'vue-router';
  9 + import { addDataComponent, updateDataBoardLayout } from '/@/api/dataBoard';
  10 + import { useModalInner } from '/@/components/Modal';
  11 + import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH } from '../../config/config';
7 12
8   - const componentId = ref('');
9   -
10   - interface SettingOption {
11   - color?: string;
  13 + interface DataComponentRouteParams extends RouteParams {
  14 + id: string;
12 15 }
13 16
14   - const handleSettingUpdate = (value: SettingOption) => {};
  17 + const emit = defineEmits(['submit']);
  18 +
  19 + const ROUTE = useRoute();
  20 +
  21 + const [register, { closeModal }] = useModalInner();
  22 +
  23 + const basicConfigurationEl = ref<{
  24 + getAllDataSourceFieldValue: Fn<any, Recordable>;
  25 + }>();
  26 +
  27 + const boardId = computed(() => {
  28 + return (ROUTE.params as DataComponentRouteParams).id;
  29 + });
  30 +
  31 + const frontId = ref('');
  32 +
  33 + const componentRecord = ref<DataComponentRecord>({} as unknown as DataComponentRecord);
  34 +
  35 + const handleSubmit = () => {
  36 + const { getAllDataSourceFieldValue } = unref(basicConfigurationEl)!;
  37 + const value = getAllDataSourceFieldValue();
  38 + handleAddComponent(value);
  39 + };
  40 +
  41 + const handleAddComponent = async (value: Recordable) => {
  42 + try {
  43 + const data = await addDataComponent({
  44 + boardId: unref(boardId),
  45 + record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value },
  46 + });
  47 + const id = data.data.id;
  48 + await updateDataBoardLayout({
  49 + boardId: unref(boardId),
  50 + layout: [{ id, w: DEFAULT_WIDGET_WIDTH, h: DEFAULT_WIDGET_HEIGHT, x: 0, y: 0 }],
  51 + });
  52 + closeModal();
  53 + emit('submit');
  54 + } catch (error) {}
  55 + };
15 56 </script>
16 57
17 58 <template>
18   - <BasicModal v-bind="$attrs" title="自定义组件" width="70%">
  59 + <BasicModal
  60 + v-bind="$attrs"
  61 + @register="register"
  62 + title="自定义组件"
  63 + width="70%"
  64 + @ok="handleSubmit"
  65 + >
19 66 <section>
20 67 <Tabs type="card">
21 68 <Tabs.TabPane key="1" tab="基础配置">
22   - <BasicConfiguration @change="handleSettingUpdate" />
  69 + <BasicConfiguration
  70 + ref="basicConfigurationEl"
  71 + :front-id="frontId"
  72 + :record="componentRecord"
  73 + />
23 74 </Tabs.TabPane>
24 75 <Tabs.TabPane key="2" tab="可视化配置">
25   - <VisualConfiguration v-model:value="componentId" />
  76 + <VisualConfiguration v-model:value="frontId" />
26 77 </Tabs.TabPane>
27 78 </Tabs>
28 79 </section>
... ...
... ... @@ -22,7 +22,7 @@
22 22 :data-source="textComponentConfig"
23 23 >
24 24 <template #renderItem="{ item }">
25   - <List.Item>
  25 + <List.Item class="!flex !justify-center">
26 26 <VisualWidgetSelect
27 27 :checked-id="props.value"
28 28 :control-id="item.id"
... ...
1 1 <script lang="ts" setup>
  2 + import { onMounted, ref, unref } from 'vue';
2 3 import { modeOne, modeTwo, modeThree, modeFour } from '../config/visualOptions';
3 4 import { useForm, BasicForm } from '/@/components/Form';
4 5 import { BasicModal, useModalInner } from '/@/components/Modal';
  6 + import { ComponentInfo } from '/@/api/dataBoard/model';
  7 +
  8 + const emit = defineEmits(['close']);
  9 +
  10 + const recordId = ref('');
5 11
6 12 const [registerForm, method] = useForm({
7 13 schemas: modeTwo,
... ... @@ -12,11 +18,16 @@
12 18 },
13 19 });
14 20
15   - const [register, { closeModal }] = useModalInner();
  21 + const [register, { closeModal }] = useModalInner(
  22 + (data: { recordId: string; componentInfo: ComponentInfo }) => {
  23 + recordId.value = data.recordId;
  24 + method.setFieldsValue(data.componentInfo || {});
  25 + }
  26 + );
16 27
17 28 const handleGetValue = () => {
18 29 const value = method.getFieldsValue();
19   - console.log(value);
  30 + emit('close', unref(recordId), value);
20 31 };
21 32
22 33 const handleClose = () => {
... ... @@ -26,7 +37,14 @@
26 37 </script>
27 38
28 39 <template>
29   - <BasicModal v-bind="$attrs" @register="register" @ok="handleClose" title="选项" width="60%">
  40 + <BasicModal
  41 + v-bind="$attrs"
  42 + destroy-on-close
  43 + @register="register"
  44 + @ok="handleClose"
  45 + title="选项"
  46 + width="60%"
  47 + >
30 48 <BasicForm @register="registerForm" />
31 49 </BasicModal>
32 50 </template>
... ...
  1 +import { byOganizationIdGetMasterDevice, getAttribute } from '/@/api/ruleengine/ruleengineApi';
1 2 import { getOrganizationList } from '/@/api/system/system';
2 3 import { FormSchema } from '/@/components/Form';
  4 +import { copyTransFun } from '/@/utils/fnUtils';
  5 +import { to } from '/@/utils/to';
  6 +
  7 +export enum DataSourceField {
  8 + DEVICE_ID = 'deviceId',
  9 +}
3 10
4 11 export const basicSchema: FormSchema[] = [
5 12 {
6 13 field: 'name',
7 14 label: '组件名称',
8 15 component: 'Input',
  16 + componentProps: {
  17 + placeholder: '请输入组件名称',
  18 + },
9 19 },
10 20 {
11 21 field: 'remake',
12 22 label: '组件备注',
13 23 component: 'InputTextArea',
  24 + componentProps: {
  25 + placeholder: '请输入组件备注',
  26 + },
14 27 },
15 28 ];
16 29
17 30 export const dataSourceSchema: FormSchema[] = [
18 31 {
19   - field: 'org',
  32 + field: 'organizationId',
20 33 component: 'ApiTreeSelect',
21 34 label: '组织',
22 35 colProps: { span: 6 },
23   - componentProps() {
  36 + componentProps({ formActionType }) {
  37 + const { setFieldsValue } = formActionType;
24 38 return {
25 39 placeholder: '请选择组织',
26 40 api: async () => {
27 41 const data = await getOrganizationList();
  42 + copyTransFun(data as any as any[]);
28 43 return data;
29 44 },
30   -
31   - onChange() {},
  45 + onChange() {
  46 + setFieldsValue({ device: null, attr: null });
  47 + },
  48 + getPopupContainer: () => document.body,
32 49 };
33 50 },
34 51 },
35 52 {
36   - field: 'device',
37   - component: 'Select',
  53 + field: 'deviceId',
  54 + component: 'ApiSelect',
38 55 label: '设备',
39 56 colProps: { span: 5 },
40   - componentProps: {
41   - placeholder: '请选择设备',
  57 + componentProps({ formModel, formActionType }) {
  58 + const { setFieldsValue } = formActionType;
  59 + const orgId = formModel['organizationId'];
  60 + return {
  61 + api: async () => {
  62 + if (orgId) {
  63 + const [, data] = await to<Record<'id' | 'name', string>[]>(
  64 + byOganizationIdGetMasterDevice(orgId)
  65 + );
  66 + if (data) return data.map((item) => ({ label: item.name, value: item.id }));
  67 + }
  68 + return [];
  69 + },
  70 + onChange() {
  71 + setFieldsValue({ attr: null });
  72 + },
  73 + placeholder: '请选择设备',
  74 + getPopupContainer: () => document.body,
  75 + };
42 76 },
43 77 },
44 78 {
45   - field: 'attr',
46   - component: 'Select',
  79 + field: 'attribute',
  80 + component: 'ApiSelect',
47 81 label: '属性',
48 82 colProps: { span: 5 },
49   - componentProps: {
50   - placeholder: '请选择属性',
  83 + componentProps({ formModel }) {
  84 + const orgId = formModel['organizationId'];
  85 + const deviceId = formModel['deviceId'];
  86 + return {
  87 + api: async () => {
  88 + if (orgId && deviceId) {
  89 + const [, data] = await to<string[]>(getAttribute(orgId, deviceId));
  90 + // TODO attribute exist null
  91 + if (data) return data.filter(Boolean).map((item) => ({ label: item, value: item }));
  92 + }
  93 + return [];
  94 + },
  95 + placeholder: '请选择属性',
  96 + getPopupContainer: () => document.body,
  97 + };
51 98 },
52 99 },
53 100 {
... ... @@ -60,7 +107,7 @@ export const dataSourceSchema: FormSchema[] = [
60 107 },
61 108 },
62 109 {
63   - field: 'attrRename',
  110 + field: 'attributeRename',
64 111 component: 'Input',
65 112 label: '属性',
66 113 colProps: { span: 4 },
... ...
  1 +import { Component } from 'vue';
  2 +import TextComponent from '../../components/TextComponent/TextComponent.vue';
  3 +import {
  4 + TextComponent1Config,
  5 + TextComponent2Config,
  6 + TextComponent3Config,
  7 + TextComponent4Config,
  8 + TextComponent5Config,
  9 + transformTextComponentConfig,
  10 +} from '../../components/TextComponent/config';
  11 +import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
  12 +export enum FrontComponent {
  13 + TEXT_COMPONENT_1 = 'text-component-1',
  14 + TEXT_COMPONENT_2 = 'text-component-2',
  15 + TEXT_COMPONENT_3 = 'text-component-3',
  16 + TEXT_COMPONENT_4 = 'text-component-4',
  17 + TEXT_COMPONENT_5 = 'text-component-5',
  18 +}
  19 +
  20 +export type FrontComponentType =
  21 + | 'text-component-1'
  22 + | 'text-component-2'
  23 + | 'text-component-3'
  24 + | 'text-component-4'
  25 + | 'text-component-5';
  26 +
  27 +export interface ComponentConfig {
  28 + Component: Component;
  29 + ComponentConfig: Recordable;
  30 + transformConfig: (
  31 + ComponentConfig: Recordable,
  32 + record: DataComponentRecord,
  33 + dataSourceRecord: DataSource
  34 + ) => Recordable;
  35 +}
  36 +
  37 +export const frontComponentMap = new Map<FrontComponentType, ComponentConfig>();
  38 +
  39 +frontComponentMap.set(FrontComponent.TEXT_COMPONENT_1, {
  40 + Component: TextComponent,
  41 + ComponentConfig: TextComponent1Config,
  42 + transformConfig: transformTextComponentConfig,
  43 +});
  44 +
  45 +frontComponentMap.set(FrontComponent.TEXT_COMPONENT_2, {
  46 + Component: TextComponent,
  47 + ComponentConfig: TextComponent2Config,
  48 + transformConfig: transformTextComponentConfig,
  49 +});
  50 +
  51 +frontComponentMap.set(FrontComponent.TEXT_COMPONENT_3, {
  52 + Component: TextComponent,
  53 + ComponentConfig: TextComponent3Config,
  54 + transformConfig: transformTextComponentConfig,
  55 +});
  56 +
  57 +frontComponentMap.set(FrontComponent.TEXT_COMPONENT_4, {
  58 + Component: TextComponent,
  59 + ComponentConfig: TextComponent4Config,
  60 + transformConfig: transformTextComponentConfig,
  61 +});
  62 +
  63 +frontComponentMap.set(FrontComponent.TEXT_COMPONENT_5, {
  64 + Component: TextComponent,
  65 + ComponentConfig: TextComponent5Config,
  66 + transformConfig: transformTextComponentConfig,
  67 +});
... ...
1 1 <script lang="ts" setup>
2 2 import { Button, PageHeader } from 'ant-design-vue';
3 3 import { GridItem, GridLayout, Layout } from 'vue3-grid-layout';
4   - import { nextTick, ref } from 'vue';
  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   - import InformationPanel from '../components/Other/InformationPanel.vue';
8 7 import { DropMenu } from '/@/components/Dropdown';
9 8 import DataBindModal from './components/DataBindModal.vue';
10 9 import { useModal } from '/@/components/Modal';
11 10 import { MoreActionEvent } from '../config/config';
  11 + import { deleteDataComponent, getDataComponent } from '/@/api/dataBoard';
  12 + import { useRoute } from 'vue-router';
  13 + import { computed, unref } from '@vue/reactivity';
  14 + import { DataComponentRecord, DataSource, Layout as LayoutRecord } from '/@/api/dataBoard/model';
  15 + import { frontComponentMap, FrontComponentType } from './config/help';
  16 + import { useMessage } from '/@/hooks/web/useMessage';
12 17 const handleBack = () => {};
13 18
14   - interface ChartAttr {
15   - id: string | number;
16   - width: number;
17   - height: number;
18   - }
  19 + type DataBoardRecord = DataComponentRecord & { layout: LayoutRecord };
19 20
20   - interface ChartSetting extends Layout {
21   - chart: ChartAttr[];
22   - }
  21 + type DataBoardLayoutInfo = Layout & {
  22 + record: DataComponentRecord & { width: number; height: number };
  23 + };
  24 +
  25 + const ROUTE = useRoute();
  26 +
  27 + const { createMessage } = useMessage();
  28 + const getBoardId = computed(() => {
  29 + return (ROUTE.params as { id: string }).id;
  30 + });
23 31
24 32 const widgetEl = new Map<string, Fn>();
25 33
26   - const id = '296Charts';
27   - // GridItem.
28   - const layout = ref<ChartSetting[]>([
29   - {
30   - x: 0,
31   - y: 0,
32   - w: 6,
33   - h: 6,
34   - i: id,
35   - static: false,
36   - chart: [
37   - { id: 'a', width: 100, height: 100 },
38   - { id: 'b', width: 100, height: 100 },
39   - // { id: 'c', width: 100, height: 100 },
40   - // { id: 'd', width: 100, height: 100 },
41   - // { id: 'e', width: 100, height: 100 },
42   - // { id: 'f', width: 100, height: 100 },
43   - // { id: 'g', width: 100, height: 100 },
44   - // { id: 'h', width: 100, height: 100 },
45   - // { id: 'i', width: 100, height: 100 },
46   - ],
47   - },
48   - {
49   - x: 0,
50   - y: 0,
51   - w: 6,
52   - h: 6,
53   - i: 'sdasdf',
54   - static: false,
55   - chart: [
56   - { id: 'j', width: 100, height: 100 },
57   - { id: 'k', width: 100, height: 100 },
58   - { id: 'l', width: 100, height: 100 },
59   - // { id: 'm', width: 100, height: 100 },
60   - // { id: 'n', width: 100, height: 100 },
61   - // { id: 'o', width: 100, height: 100 },
62   - // { id: 'p', width: 100, height: 100 },
63   - // { id: 'q', width: 100, height: 100 },
64   - // { id: 'r', width: 100, height: 100 },
65   - ],
66   - },
67   - ]);
  34 + const dataBoardList = ref<DataBoardLayoutInfo[]>([]);
  35 +
68 36 const draggable = ref(true);
69 37 const resizable = ref(true);
70 38
71 39 const GirdLayoutColNum = 24;
72   -
73 40 const GridLayoutMargin = 10;
74 41
  42 + const defaultWidth = 6;
  43 + const defaultHeight = 6;
  44 +
75 45 function updateSize(i: string, newH: number, newW: number, newHPx: number, newWPx: number) {
76 46 newWPx = Number(newWPx);
77 47 newHPx = Number(newHPx);
78 48
79   - const data = layout.value.find((item) => item.i === i)!;
80   - const border = 2;
81   - const length = data.chart.length || 0;
  49 + const data = dataBoardList.value.find((item) => item.i === i)!;
  50 + const length = data.record.dataSource.length || 0;
82 51
83 52 const row = Math.floor(Math.pow(length, 0.5));
84 53 const col = Math.floor(length / row);
... ... @@ -98,13 +67,14 @@
98 67 width = 100;
99 68 }
100 69
101   - data.chart = data?.chart.map((item) => {
  70 + data.record.dataSource = data?.record.dataSource.map((item) => {
102 71 return {
103 72 ...item,
104 73 width,
105 74 height,
106 75 };
107 76 });
  77 + console.log(unref(dataBoardList));
108 78 nextTick(() => {
109 79 const updateFn = widgetEl.get(i);
110 80 if (updateFn) updateFn();
... ... @@ -132,7 +102,7 @@
132 102 });
133 103 };
134 104
135   - const setComponentRef = (el: Element, record: ChartSetting) => {
  105 + const setComponentRef = (el: Element, record: DataBoardLayoutInfo) => {
136 106 if (widgetEl.has(record.i)) widgetEl.delete(record.i);
137 107 if (el && (el as unknown as { update: Fn }).update)
138 108 widgetEl.set(record.i, (el as unknown as { update: Fn }).update);
... ... @@ -140,28 +110,75 @@
140 110
141 111 const [register, { openModal }] = useModal();
142 112
143   - const handleMoreAction = (event: DropMenu) => {
  113 + const handleMoreAction = (event: DropMenu, id: string) => {
144 114 if (event.event === MoreActionEvent.EDIT) openModal(true);
  115 + if (event.event === MoreActionEvent.DELETE) handleDelete(id);
145 116 };
  117 +
  118 + const handleOpenCreatePanel = () => {
  119 + openModal(true);
  120 + };
  121 +
  122 + const getDataBoardComponent = async () => {
  123 + try {
  124 + const data = await getDataComponent(unref(getBoardId));
  125 + dataBoardList.value = data.data.componentData.map((item) => {
  126 + const index = data.data.componentLayout.findIndex((each) => item.id === each.id);
  127 + const layout = data.data.componentLayout[index];
  128 + return {
  129 + i: item.id,
  130 + w: layout.w || defaultWidth,
  131 + h: layout.h || defaultHeight,
  132 + x: layout.x || 0,
  133 + y: layout.y || 0,
  134 + record: {
  135 + ...item,
  136 + width: 100,
  137 + height: 100,
  138 + },
  139 + };
  140 + });
  141 + } catch (error) {}
  142 + };
  143 +
  144 + const getComponent = (record: DataComponentRecord) => {
  145 + const frontComponent = record.frontId;
  146 + const component = frontComponentMap.get(frontComponent as FrontComponentType);
  147 + return component?.Component;
  148 + };
  149 +
  150 + const getComponentConfig = (record: DataComponentRecord, dataSourceRecord: DataSource) => {
  151 + const frontComponent = record.frontId;
  152 + const component = frontComponentMap.get(frontComponent as FrontComponentType);
  153 + return component?.transformConfig(component.ComponentConfig, record, dataSourceRecord);
  154 + };
  155 +
  156 + const handleDelete = async (id: string) => {
  157 + try {
  158 + await deleteDataComponent([id]);
  159 + createMessage.success('删除成功');
  160 + await getDataBoardComponent();
  161 + } catch (error) {
  162 + createMessage.error('删除失败');
  163 + }
  164 + };
  165 +
  166 + onMounted(() => {
  167 + getDataBoardComponent();
  168 + });
146 169 </script>
147 170
148 171 <template>
149   - <!-- <PageWrapper title="水电表看板" @back="handleBack" content="已创建组件: 3个">
150   - <template #extra>
151   - <Button type="primary">创建组件</Button>
152   - </template>
153   -
154   - </PageWrapper> -->
155 172 <section class="bg-light-50 flex flex-col overflow-hidden h-full w-full">
156 173 <PageHeader title="水电表看板" @back="handleBack">
157 174 <template #extra>
158   - <Button type="primary">创建组件</Button>
  175 + <Button type="primary" @click="handleOpenCreatePanel">创建组件</Button>
159 176 </template>
160 177 <div>已创建组件: 3个</div>
161 178 </PageHeader>
162 179 <section class="flex-1">
163 180 <GridLayout
164   - v-model:layout="layout"
  181 + v-model:layout="dataBoardList"
165 182 :col-num="GirdLayoutColNum"
166 183 :row-height="30"
167 184 :margin="[GridLayoutMargin, GridLayoutMargin]"
... ... @@ -172,7 +189,7 @@
172 189 style="width: 100%"
173 190 >
174 191 <GridItem
175   - v-for="item in layout"
  192 + v-for="item in dataBoardList"
176 193 :key="item.i"
177 194 :static="item.static"
178 195 :x="item.x"
... ... @@ -190,24 +207,27 @@
190 207 <WidgetWrapper
191 208 :key="item.i"
192 209 :ref="(el: Element) => setComponentRef(el, item)"
193   - :data-source="item.chart"
  210 + :data-source="item.record.dataSource"
194 211 >
195 212 <template #header>
196 213 <!-- <div>header</div> -->
197   - <BaseWidgetHeader @action="handleMoreAction" />
  214 + <BaseWidgetHeader :id="item.record.id" @action="handleMoreAction" />
198 215 </template>
199   - <template #controls="{ record, add }">
200   - <InformationPanel />
201   - <!-- <LightBulbSwitch @change="handleChange" /> -->
202   - <!-- <div :id="getControlsWidgetId(record.id)" class="widget-charts" /> -->
203   - <!-- <BaseDashboard :data-source="record" :add="add" /> -->
  216 + <template #controls="{ record, add, remove, update }">
  217 + <component
  218 + :is="getComponent(item.record)"
  219 + :add="add"
  220 + :remove="remove"
  221 + :update="update"
  222 + v-bind="getComponentConfig(item.record, record)"
  223 + />
204 224 </template>
205 225 </WidgetWrapper>
206 226 </GridItem>
207 227 </GridLayout>
208 228 </section>
209 229 </section>
210   - <DataBindModal @register="register" />
  230 + <DataBindModal @register="register" @submit="getDataBoardComponent" />
211 231 </template>
212 232
213 233 <style>
... ...
1 1 <script lang="ts" setup>
2   - import { List, Card, Statistic, Button, Tooltip } from 'ant-design-vue';
  2 + import { List, Card, Statistic, Button, Tooltip, Spin } from 'ant-design-vue';
3 3 import { onMounted, ref, unref } from 'vue';
4 4 import { PageWrapper } from '/@/components/Page';
5 5 import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue';
... ... @@ -20,6 +20,7 @@
20 20
21 21 const { createMessage } = useMessage();
22 22
  23 + const loading = ref(false);
23 24 const dataBoardList = ref<DataBoardRecord[]>([]);
24 25 //分页相关
25 26 const page = ref(1);
... ... @@ -65,6 +66,7 @@
65 66
66 67 const getDatasource = async () => {
67 68 try {
  69 + loading.value = true;
68 70 const { total, items } = await getDataBoardList({
69 71 page: unref(paginationProp).current,
70 72 pageSize: unref(paginationProp).pageSize,
... ... @@ -73,6 +75,7 @@
73 75 paginationProp.value.total = total;
74 76 } catch (error) {
75 77 } finally {
  78 + loading.value = false;
76 79 }
77 80 };
78 81
... ... @@ -120,53 +123,55 @@
120 123 <div class="text-lg mr-6 font-bold">自定义看板</div>
121 124 <Button type="primary" @click="handleOpenDetailModal">创建看板</Button>
122 125 </div>
123   - <List
124   - :pagination="paginationProp"
125   - :data-source="dataBoardList"
126   - :grid="{ gutter: 5, column: 4, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 3 }"
127   - >
128   - <template #renderItem="{ item }">
129   - <ListItem>
130   - <Card class="data-card cursor-pointer" @click="handleViewBoard(item)">
131   - <template #extra>
132   - <Dropdown
133   - :trigger="['click']"
134   - @menu-event="(event) => handleMenuEvent(event, item)"
135   - :drop-menu-list="dropMenuList"
136   - >
137   - <MoreOutlined class="rotate-90 transform cursor-pointer" />
138   - </Dropdown>
139   - </template>
140   - <!-- <template #cover>title</template> -->
141   - <section>
142   - <div class="flex justify-between items-center">
143   - <div>{{ item.name }}</div>
144   - <div class="flex content-center">
145   - <Statistic value="12">
146   - <template #suffix>
147   - <span class="text-sm">个组件</span>
148   - </template>
149   - </Statistic>
  126 + <Spin :spinning="loading">
  127 + <List
  128 + :pagination="paginationProp"
  129 + :data-source="dataBoardList"
  130 + :grid="{ gutter: 5, column: 4, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 3 }"
  131 + >
  132 + <template #renderItem="{ item }">
  133 + <ListItem>
  134 + <Card class="data-card cursor-pointer">
  135 + <template #extra>
  136 + <Dropdown
  137 + :trigger="['click']"
  138 + @menu-event="(event) => handleMenuEvent(event, item)"
  139 + :drop-menu-list="dropMenuList"
  140 + >
  141 + <MoreOutlined class="rotate-90 transform cursor-pointer" />
  142 + </Dropdown>
  143 + </template>
  144 + <!-- <template #cover>title</template> -->
  145 + <section @click="handleViewBoard(item)">
  146 + <div class="flex justify-between items-center">
  147 + <div>{{ item.name }}</div>
  148 + <div class="flex content-center">
  149 + <Statistic value="12">
  150 + <template #suffix>
  151 + <span class="text-sm">个组件</span>
  152 + </template>
  153 + </Statistic>
  154 + </div>
150 155 </div>
151   - </div>
152   - <div class="flex justify-between mt-4">
153   - <div>
154   - <span>
155   - {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
156   - </span>
157   - <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
158   - <Tooltip title="分享链接">
159   - <ShareAltOutlined class="ml-2" @click="handleCopyShareUrl(item)" />
160   - </Tooltip>
161   - </span>
  156 + <div class="flex justify-between mt-4">
  157 + <div>
  158 + <span>
  159 + {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
  160 + </span>
  161 + <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
  162 + <Tooltip title="分享链接">
  163 + <ShareAltOutlined class="ml-2" @click="handleCopyShareUrl(item)" />
  164 + </Tooltip>
  165 + </span>
  166 + </div>
  167 + <div>{{ item.updateTime || item.createTime }}</div>
162 168 </div>
163   - <div>{{ item.updateTime || item.createTime }}</div>
164   - </div>
165   - </section>
166   - </Card>
167   - </ListItem>
168   - </template>
169   - </List>
  169 + </section>
  170 + </Card>
  171 + </ListItem>
  172 + </template>
  173 + </List>
  174 + </Spin>
170 175 <PanelDetailModal @register="registerModal" @change="getDatasource" />
171 176 </PageWrapper>
172 177 </template>
... ...