Commit 0f7e9f5e0b28608f4d5e638f7f5044086315b1b4

Authored by fengwotao
2 parents cd51bd75 bc34d678

Merge branch 'main' into dev-ft

... ... @@ -14,8 +14,8 @@ VITE_PUBLIC_PATH = /
14 14 # VITE_PROXY = [["/api","http://101.133.234.90:8080/api"]]
15 15 # 线上测试环境
16 16 # VITE_PROXY = [["/api","http://localhost:8080/api"],["/thingskit-drawio","http://localhost:3000/"]]
17   -# VITE_PROXY = [["/api","https://dev.thingskit.com/api"],["/thingskit-drawio","http://localhost:3000/"]]
18   -VITE_PROXY = [["/api","http://121.37.251.8:8080/api"],["/thingskit-drawio","http://localhost:3000/"]]
  17 +VITE_PROXY = [["/api","https://dev.thingskit.com/api"],["/thingskit-drawio","http://localhost:3000/"]]
  18 +# VITE_PROXY = [["/api","http://121.37.251.8:8080/api"],["/thingskit-drawio","http://localhost:3000/"]]
19 19 # VITE_PROXY = [["/api","http://192.168.10.106:8080/api"],["/thingskit-drawio","http://192.168.10.106:8080/api"]]
20 20
21 21 # 实时数据的ws地址
... ...
1 1 import {
2 2 AddDataBoardParams,
3 3 AddDataComponentParams,
  4 + ComponentInfoDetail,
4 5 DataBoardList,
5 6 DataComponentRecord,
6 7 GetDataBoardParams,
... ... @@ -103,12 +104,10 @@ export const updateDataBoardLayout = (params: UpdateDataBoardLayoutParams) => {
103 104 * @returns
104 105 */
105 106 export const getDataComponent = (params: string) => {
106   - return defHttp.get<{ data: { componentData: DataComponentRecord[]; componentLayout: Layout[] } }>(
107   - {
108   - url: `${DataComponentUrl.GET_DATA_COMPONENT}/${params}`,
109   - // params: { boardId: params },
110   - }
111   - );
  107 + return defHttp.get<ComponentInfoDetail>({
  108 + url: `${DataComponentUrl.GET_DATA_COMPONENT}/${params}`,
  109 + // params: { boardId: params },
  110 + });
112 111 };
113 112
114 113 export const addDataComponent = (params: AddDataComponentParams) => {
... ... @@ -151,7 +150,7 @@ export const updateDataComponent = (params: UpdateDataComponentParams) => {
151 150 */
152 151 export const getShareBoardComponentInfo = (params: { boardId: string; tenantId: string }) => {
153 152 const { boardId, tenantId } = params;
154   - return defHttp.get({
  153 + return defHttp.get<ComponentInfoDetail>({
155 154 url: `${DataBoardShareUrl.GET_DATA_COMPONENT}/${boardId}/${tenantId}`,
156 155 });
157 156 };
... ...
... ... @@ -136,3 +136,7 @@ export interface MasterDeviceList {
136 136 id: string;
137 137 name: string;
138 138 }
  139 +
  140 +export interface ComponentInfoDetail {
  141 + data: { componentData: DataComponentRecord[]; componentLayout: Layout[] };
  142 +}
... ...
... ... @@ -38,6 +38,10 @@
38 38 type: Object as PropType<RadioRecord>,
39 39 default: () => DEFAULT_RADIO_RECORD,
40 40 },
  41 + random: {
  42 + type: Boolean,
  43 + default: true,
  44 + },
41 45 });
42 46
43 47 const getControlsWidgetId = () => `widget-chart-${props.value.id}`;
... ... @@ -85,13 +89,24 @@
85 89 }
86 90 );
87 91
  92 + let timeout: Nullable<number> = null;
  93 +
  94 + function handleRandomValue() {
  95 + const newValue = Math.floor(Math.random() * 100);
  96 + const updateFn = getUpdateValueFn(props.layout.componentType);
  97 + unref(chartRef)?.setOption((updateFn(newValue) as unknown as EChartsOption) || {});
  98 + }
  99 +
88 100 onMounted(() => {
89 101 initChart();
90 102 props.add && props.add(props.value.id, update);
  103 + if (props.random) timeout = setInterval(handleRandomValue, 2000) as unknown as number;
91 104 });
92 105
93 106 onUnmounted(() => {
94 107 unref(chartRef)?.clear();
  108 + clearInterval(timeout as number);
  109 + timeout = null;
95 110 });
96 111
97 112 defineExpose({ update });
... ...
1 1 <script lang="ts" setup>
2   - import { computed } from 'vue';
  2 + import { computed, onMounted, onUnmounted, ref, unref } from 'vue';
3 3 import { Space, Tooltip } from 'ant-design-vue';
4   - import type { DigitalDashBoardLayout, DigitalDashBoardValue } from './digitalDashBoard.config';
  4 + import {
  5 + DigitalComponentDefaultConfig,
  6 + DigitalDashBoardLayout,
  7 + DigitalDashBoardValue,
  8 + } from './digitalDashBoard.config';
5 9 import { dateUtil } from '/@/utils/dateUtil';
6 10 import {
7 11 fontSize,
8 12 RadioRecord,
9 13 DEFAULT_DATE_FORMAT,
10 14 DEFAULT_RADIO_RECORD,
  15 + DEFAULT_ANIMATION_INTERVAL,
11 16 } from '../../detail/config/util';
  17 + import { isNaN } from 'lodash';
12 18
13 19 const props = defineProps<{
14 20 layout: DigitalDashBoardLayout;
15 21 value: DigitalDashBoardValue;
16 22 radio?: RadioRecord;
  23 + random?: boolean;
17 24 }>();
18 25
  26 + const changeValue = ref(0);
  27 +
  28 + const getPropsValue = computed(() => {
  29 + return { value: unref(changeValue), ...DigitalComponentDefaultConfig, ...props.value };
  30 + });
  31 +
19 32 const integerPart = computed(() => {
20   - let { value = 0 } = props.value;
  33 + let { value = 0 } = unref(getPropsValue);
21 34 const { max = 5 } = props.layout;
22 35 if (isNaN(value)) value = 0;
23 36 let _value = Number(value).toFixed(2).split('.')[0];
... ... @@ -28,7 +41,7 @@
28 41 });
29 42
30 43 const decimalPart = computed(() => {
31   - let { value = 0 } = props.value;
  44 + let { value = 0 } = unref(getPropsValue);
32 45 const { keepNumber = 2 } = props.layout;
33 46 if (isNaN(value)) value = 0;
34 47 let _value = Number(value)?.toFixed(2).split('.')[1];
... ... @@ -43,6 +56,23 @@
43 56 const { radio } = props.radio || DEFAULT_RADIO_RECORD;
44 57 return radio;
45 58 });
  59 +
  60 + let timeout: Nullable<number> = null;
  61 +
  62 + const handleRandom = () => {
  63 + const newValue = Math.floor(Math.random() * 100);
  64 + changeValue.value = newValue;
  65 + };
  66 +
  67 + onMounted(() => {
  68 + if (props.random)
  69 + timeout = setInterval(handleRandom, DEFAULT_ANIMATION_INTERVAL) as unknown as number;
  70 + });
  71 +
  72 + onUnmounted(() => {
  73 + clearInterval(timeout as number);
  74 + timeout = null;
  75 + });
46 76 </script>
47 77
48 78 <template>
... ... @@ -56,7 +86,7 @@
56 86 :key="number"
57 87 class="border border-gray-400 p-2"
58 88 :style="{
59   - color: props.value.fontColor,
  89 + color: getPropsValue.fontColor,
60 90 fontSize: fontSize({ radio: getRadio, basic: 20 }),
61 91 }"
62 92 >
... ... @@ -69,7 +99,7 @@
69 99 :key="number"
70 100 class="border border-gray-400 p-1"
71 101 :style="{
72   - color: props.value.fontColor,
  102 + color: getPropsValue.fontColor,
73 103 fontSize: fontSize({ radio: getRadio, basic: 18 }),
74 104 }"
75 105 >
... ... @@ -84,7 +114,7 @@
84 114 :style="{ fontSize: fontSize({ radio: getRadio, basic: 18 }) }"
85 115 >
86 116 <span>{{ props.value.name || '电表' }}</span>
87   - <span class="px-1">({{ 'kw/h' }})</span>
  117 + <span class="px-1">({{ getPropsValue.unit }})</span>
88 118 </div>
89 119 <div
90 120 class="text-center mt-1 text-gray-400 text-xs truncate"
... ...
1 1 import { EChartsOption } from 'echarts';
2 2 import { fontSize } from '../../detail/config/util';
3 3 import { Gradient, visualOptionField } from '../../detail/config/visualOptions';
4   -import { DataComponentRecord, DataSource, GradientInfo } from '/@/api/dataBoard/model';
  4 +import {
  5 + ComponentInfo,
  6 + DataComponentRecord,
  7 + DataSource,
  8 + GradientInfo,
  9 +} from '/@/api/dataBoard/model';
5 10 import { isArray } from '/@/utils/is';
6 11 import { buildUUID } from '/@/utils/uuid';
7 12
... ... @@ -36,11 +41,13 @@ export interface DashboardComponentLayout {
36 41 componentType: InstrumentComponentType;
37 42 }
38 43
39   -export const instrumentComponent1 = (params?: {
40   - value: number;
41   - unit: string;
42   - fontColor: string;
43   -}): EChartsOption => {
  44 +export enum GradientColor {
  45 + FIRST = '#67e0e3',
  46 + SECOND = '#37a2da',
  47 + THIRD = '#fd666d',
  48 +}
  49 +
  50 +export const instrumentComponent1 = (params?: Partial<ComponentInfo>): EChartsOption => {
44 51 const { value = 10, unit = '°C' } = params || {};
45 52 return {
46 53 series: [
... ... @@ -108,7 +115,7 @@ export const instrumentComponent1 = (params?: {
108 115 },
109 116 data: [
110 117 {
111   - value: value,
  118 + value: value as number,
112 119 },
113 120 ],
114 121 },
... ... @@ -146,7 +153,7 @@ export const instrumentComponent1 = (params?: {
146 153 },
147 154 data: [
148 155 {
149   - value: value,
  156 + value: value as number,
150 157 },
151 158 ],
152 159 },
... ... @@ -154,16 +161,11 @@ export const instrumentComponent1 = (params?: {
154 161 };
155 162 };
156 163
157   -export const instrumentComponent2 = (params?: {
158   - gradient: GradientInfo[];
159   - value: number;
160   - unit: string;
161   - fontColor: string;
162   -}): EChartsOption => {
163   - const { gradient = [], value = 0, unit = 'km/h' } = params || {};
164   - const firstRecord = getGradient(Gradient.FIRST, gradient);
165   - const secondRecord = getGradient(Gradient.SECOND, gradient);
166   - const thirdRecord = getGradient(Gradient.THIRD, gradient);
  164 +export const instrumentComponent2 = (params?: Partial<ComponentInfo>): EChartsOption => {
  165 + const { gradientInfo = [], value = 0, unit = 'km/h' } = params || {};
  166 + const firstRecord = getGradient(Gradient.FIRST, gradientInfo);
  167 + const secondRecord = getGradient(Gradient.SECOND, gradientInfo);
  168 + const thirdRecord = getGradient(Gradient.THIRD, gradientInfo);
167 169
168 170 let max = thirdRecord?.value || secondRecord?.value || firstRecord?.value || 70;
169 171 max = Number(1 + Array(String(max).length).fill(0).join(''));
... ... @@ -182,9 +184,9 @@ export const instrumentComponent2 = (params?: {
182 184 lineStyle: {
183 185 width: 20,
184 186 color: [
185   - [firstGradient, firstRecord?.color || '#67e0e3'],
186   - [secondGradient, secondRecord?.color || '#37a2da'],
187   - [1, thirdRecord?.color || '#fd666d'],
  187 + [firstGradient, firstRecord?.color || GradientColor.FIRST],
  188 + [secondGradient, secondRecord?.color || GradientColor.SECOND],
  189 + [1, thirdRecord?.color || GradientColor.THIRD],
188 190 ],
189 191 },
190 192 },
... ... @@ -223,7 +225,7 @@ export const instrumentComponent2 = (params?: {
223 225 },
224 226 data: [
225 227 {
226   - value: value,
  228 + value: value as number,
227 229 },
228 230 ],
229 231 },
... ... @@ -293,14 +295,23 @@ export const update_instrument_2_value = (value) => {
293 295
294 296 function setGradientInfo(dataSource: DataSource) {
295 297 const componentInfo = dataSource.componentInfo;
296   - return instrumentComponent2({
297   - unit: componentInfo.unit,
298   - value: 0,
299   - gradient: componentInfo.gradientInfo,
300   - fontColor: componentInfo.fontColor,
301   - });
  298 + return instrumentComponent2(componentInfo);
302 299 }
303 300
  301 +export const Instrument1DefaultConfig: Partial<ComponentInfo> = {
  302 + fontColor: '#FD7347',
  303 +};
  304 +
  305 +export const Instrument2DefaultConfig: Partial<ComponentInfo> = {
  306 + fontColor: GradientColor.FIRST,
  307 + unit: 'km/h',
  308 + gradientInfo: [
  309 + { key: Gradient.FIRST, value: 30, color: GradientColor.FIRST },
  310 + { key: Gradient.SECOND, value: 70, color: GradientColor.SECOND },
  311 + { key: Gradient.THIRD, value: 80, color: GradientColor.THIRD },
  312 + ],
  313 +};
  314 +
304 315 export const transformDashboardComponentConfig = (
305 316 config: DashboardComponentLayout,
306 317 record: DataComponentRecord,
... ...
  1 +import { ComponentInfo } from '/@/api/dataBoard/model';
  2 +
1 3 export type DigitalDashBoardComponentType = 'digital-dashboard-component';
2 4
3 5 export interface DigitalDashBoardLayout {
... ... @@ -12,3 +14,8 @@ export interface DigitalDashBoardValue {
12 14 value?: number;
13 15 fontColor?: string;
14 16 }
  17 +
  18 +export const DigitalComponentDefaultConfig: Partial<ComponentInfo> = {
  19 + fontColor: '#000',
  20 + unit: 'kw/h',
  21 +};
... ...
1 1 import { EChartsOption } from 'echarts';
2 2 import { Component } from 'vue';
3 3 import { WidgetComponentType } from '../../detail/config/visualOptions';
4   -import { instrumentComponent1, instrumentComponent2 } from './dashBoardComponent.config';
  4 +import {
  5 + Instrument1DefaultConfig,
  6 + Instrument2DefaultConfig,
  7 + instrumentComponent1,
  8 + instrumentComponent2,
  9 + InstrumentComponentType,
  10 +} from './dashBoardComponent.config';
5 11 import DashBoardComponent from './DashBoardComponent.vue';
6 12 import DigitalDashBoard from './DigitalDashBoard.vue';
7 13 import { buildUUID } from '/@/utils/uuid';
8 14
9 15 export interface DashboardComponentLayout {
10 16 chartOption: EChartsOption;
  17 + componentType: InstrumentComponentType;
11 18 }
12 19
13 20 interface InstrumentComponentConfig {
... ... @@ -20,13 +27,19 @@ interface InstrumentComponentConfig {
20 27 export const instrumentComponentConfig: InstrumentComponentConfig[] = [
21 28 {
22 29 id: 'instrument-component-1',
23   - layout: { chartOption: instrumentComponent1() },
  30 + layout: {
  31 + chartOption: instrumentComponent1(Instrument1DefaultConfig),
  32 + componentType: 'instrument-component-1',
  33 + },
24 34 component: DashBoardComponent,
25 35 value: { id: buildUUID() },
26 36 },
27 37 {
28 38 id: 'instrument-component-2',
29   - layout: { chartOption: instrumentComponent2() },
  39 + layout: {
  40 + chartOption: instrumentComponent2(Instrument2DefaultConfig),
  41 + componentType: 'instrument-component-2',
  42 + },
30 43 component: DashBoardComponent,
31 44 value: { id: buildUUID() },
32 45 },
... ...
... ... @@ -7,7 +7,7 @@
7 7 DEFAULT_RADIO_RECORD,
8 8 DEFAULT_DATE_FORMAT,
9 9 } from '../../detail/config/util';
10   - import type { TextComponentLayout, TextComponentValue } from './config';
  10 + import { TextComponentDefaultConfig, TextComponentLayout, TextComponentValue } from './config';
11 11 import { SvgIcon } from '/@/components/Icon';
12 12 import { dateUtil } from '/@/utils/dateUtil';
13 13 const props = defineProps({
... ... @@ -17,7 +17,8 @@
17 17 },
18 18 value: {
19 19 type: Object as PropType<TextComponentValue>,
20   - default: () => ({ name: '温度', value: 123 } as TextComponentValue),
  20 + default: () =>
  21 + ({ ...TextComponentDefaultConfig, name: '温度', value: 123 } as TextComponentValue),
21 22 },
22 23 radio: {
23 24 type: Object as PropType<RadioRecord>,
... ... @@ -65,8 +66,8 @@
65 66 prefix="iconfont"
66 67 :style="{
67 68 color: props.value.iconColor,
68   - width: fontSize({ radio: getRadio, basic: 50 }),
69   - height: fontSize({ radio: getRadio, basic: 50 }),
  69 + width: fontSize({ radio: getRadio, basic: 50, min: 16 }),
  70 + height: fontSize({ radio: getRadio, basic: 50, min: 16 }),
70 71 }"
71 72 />
72 73 </div>
... ...
1   -import { formatToDateTime } from '/@/utils/dateUtil';
2   -import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
  1 +import { ComponentInfo, DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
3 2 export interface TextComponentLayout {
4 3 id: string;
5 4 base?: boolean;
... ... @@ -26,28 +25,21 @@ export type TextComponentType =
26 25 | 'text-component-4'
27 26 | 'text-component-5';
28 27
29   -type TextComponentDefault = TextComponentLayout & { value: TextComponentValue };
  28 +type TextComponentDefault = TextComponentLayout;
30 29
31 30 export const TextComponent1Config: TextComponentDefault = {
32 31 id: 'text-component-1',
33 32 base: true,
34   - value: { value: 123, name: '温度' },
35 33 };
36 34
37 35 export const TextComponent2Config: TextComponentDefault = {
38 36 id: 'text-component-2',
39 37 base: false,
40   - value: { value: 123, name: '温度' },
41 38 };
42 39 export const TextComponent3Config: TextComponentDefault = {
43 40 id: 'text-component-3',
44 41 base: false,
45 42 showUpdate: true,
46   - value: {
47   - value: 123,
48   - name: '温度',
49   - updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
50   - },
51 43 };
52 44 export const TextComponent4Config: TextComponentDefault = {
53 45 id: 'text-component-4',
... ... @@ -55,19 +47,19 @@ export const TextComponent4Config: TextComponentDefault = {
55 47 showIcon: true,
56 48 showUpdate: true,
57 49 showUnit: true,
58   - value: {
59   - value: 123,
60   - name: '温度',
61   - updateTime: formatToDateTime(new Date(), 'YYYY-MM-DD HH:mm:ss'),
62   - unit: '℃',
63   - },
64 50 };
65 51 export const TextComponent5Config: TextComponentDefault = {
66 52 id: 'text-component-5',
67 53 base: false,
68 54 showIcon: true,
69 55 showUnit: true,
70   - value: { value: 123, name: '温度', unit: '℃' },
  56 +};
  57 +
  58 +export const TextComponentDefaultConfig: Partial<ComponentInfo> = {
  59 + fontColor: '#000',
  60 + unit: '℃',
  61 + iconColor: '#000',
  62 + icon: 'CO2',
71 63 };
72 64
73 65 export const textComponentConfig: TextComponentDefault[] = [
... ...
... ... @@ -18,8 +18,8 @@
18 18 const { hasPermission } = usePermission();
19 19 const dropMenuList = computed<DropMenu[]>(() => {
20 20 const basicMenu: DropMenu[] = [];
21   - const hasUpdatePermission = hasPermission('api:yt:dataBoardDetail:update');
22   - const hasDeletePermission = hasPermission('api:yt:dataBoardDetail:delete');
  21 + const hasUpdatePermission = hasPermission('api:yt:data_component:update:update');
  22 + const hasDeletePermission = hasPermission('api:yt:data_component:delete');
23 23 const hasCopyPermission = hasPermission('api:yt:dataBoardDetail:copy');
24 24 if (hasUpdatePermission)
25 25 basicMenu.push({
... ...
... ... @@ -21,6 +21,7 @@
21 21 const props = defineProps<{
22 22 record: DataBoardLayoutInfo;
23 23 frontId?: string;
  24 + defaultConfig?: Partial<ComponentInfo>;
24 25 }>();
25 26
26 27 const { createMessage } = useMessage();
... ... @@ -29,7 +30,9 @@
29 30 // ...props.record,
30 31 // } as unknown as DataBoardLayoutInfo);
31 32
32   - const dataSource = ref<DataSourceEl[]>([{ id: buildUUID() } as unknown as DataSourceEl]);
  33 + const dataSource = ref<DataSourceEl[]>([
  34 + { id: buildUUID(), componentInfo: props.defaultConfig || {} } as unknown as DataSourceEl,
  35 + ]);
33 36
34 37 const [basicRegister, basicMethod] = useForm({
35 38 schemas: basicSchema,
... ... @@ -65,7 +68,7 @@
65 68 const componentInfo = unref(dataSource)[index].componentInfo || {};
66 69 _dataSource.push({
67 70 ...value,
68   - componentInfo: { ...componentInfo },
  71 + componentInfo: { ...(props.defaultConfig || {}), ...componentInfo },
69 72 });
70 73 }
71 74 return _dataSource;
... ... @@ -96,9 +99,17 @@
96 99 createMessage.warning('请先选择可视化组件');
97 100 return;
98 101 }
  102 +
  103 + // const defaultConfig = getComponentDefaultConfig(props.frontId as WidgetComponentType);
  104 +
  105 + const componentInfo: ComponentInfo = {
  106 + ...(props.defaultConfig || {}),
  107 + ...(item.componentInfo || {}),
  108 + };
  109 +
99 110 openModal(true, {
100 111 recordId: item.id,
101   - componentInfo: item.componentInfo,
  112 + componentInfo,
102 113 });
103 114 };
104 115
... ... @@ -111,6 +122,7 @@
111 122 const handleAdd = () => {
112 123 unref(dataSource).push({
113 124 id: buildUUID(),
  125 + componentInfo: props.defaultConfig || {},
114 126 } as unknown as DataSourceEl);
115 127 };
116 128
... ... @@ -185,19 +197,19 @@
185 197 </div>
186 198 <div class="flex justify-center gap-3 w-24">
187 199 <Tooltip title="复制">
188   - <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-52px" />
  200 + <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-32px" />
189 201 </Tooltip>
190 202 <Tooltip title="设置">
191 203 <SettingOutlined
192 204 v-show="showSettingButton"
193 205 @click="handleSetting(item)"
194   - class="cursor-pointer text-lg !leading-52px"
  206 + class="cursor-pointer text-lg !leading-32px"
195 207 />
196 208 </Tooltip>
197 209 <Tooltip title="删除">
198 210 <DeleteOutlined
199 211 @click="handleDelete(item)"
200   - class="cursor-pointer text-lg !leading-52px"
  212 + class="cursor-pointer text-lg !leading-32px"
201 213 />
202 214 </Tooltip>
203 215 </div>
... ...
... ... @@ -10,12 +10,13 @@
10 10 import { DataBoardLayoutInfo } from '../../types/type';
11 11 import { useMessage } from '/@/hooks/web/useMessage';
12 12 import { decode } from '../../config/config';
  13 + import { ComponentInfo } from '/@/api/dataBoard/model';
13 14
14 15 interface DataComponentRouteParams extends RouteParams {
15 16 id: string;
16 17 }
17 18
18   - const emit = defineEmits(['submit', 'register']);
  19 + const emit = defineEmits(['update', 'create', 'register']);
19 20
20 21 const ROUTE = useRoute();
21 22
... ... @@ -31,12 +32,13 @@
31 32
32 33 const componentRecord = ref<DataBoardLayoutInfo>({} as unknown as DataBoardLayoutInfo);
33 34
  35 + const componentDefaultConfig = ref<Partial<ComponentInfo>>({});
  36 +
34 37 const [register, { closeModal, changeOkLoading }] = useModalInner(
35 38 (data: { isEdit: boolean; record?: DataBoardLayoutInfo }) => {
36 39 componentRecord.value = data.record || ({} as unknown as DataBoardLayoutInfo);
37 40 frontId.value = data.record?.record?.frontId || '';
38 41 isEdit.value = data.isEdit || false;
39   - console.log(unref(isEdit));
40 42 }
41 43 );
42 44
... ... @@ -54,6 +56,10 @@
54 56
55 57 const handleAddComponent = async (value: Recordable) => {
56 58 try {
  59 + if (!unref(frontId)) {
  60 + createMessage.warning('请选择可视化组件');
  61 + return;
  62 + }
57 63 changeOkLoading(true);
58 64 await addDataComponent({
59 65 boardId: unref(boardId),
... ... @@ -61,7 +67,7 @@
61 67 });
62 68 createMessage.success('创建成功');
63 69 closeModal();
64   - emit('submit');
  70 + emit('create');
65 71 } catch (error) {
66 72 // createMessage.error('创建失败');
67 73 } finally {
... ... @@ -72,7 +78,7 @@
72 78 const handleUpdateComponent = async (value: Recordable) => {
73 79 try {
74 80 changeOkLoading(true);
75   - await updateDataComponent({
  81 + const res = await updateDataComponent({
76 82 boardId: unref(boardId),
77 83 record: {
78 84 id: unref(componentRecord).i,
... ... @@ -83,13 +89,18 @@
83 89 });
84 90 createMessage.success('修改成功');
85 91 closeModal();
86   - emit('submit');
  92 + // emit('submit');
  93 + emit('update', res.data.id);
87 94 } catch (error) {
88 95 // createMessage.error('修改失败');
89 96 } finally {
90 97 changeOkLoading(false);
91 98 }
92 99 };
  100 +
  101 + const handleComponentCheckedChange = (record: ComponentInfo) => {
  102 + componentDefaultConfig.value = record;
  103 + };
93 104 </script>
94 105
95 106 <template>
... ... @@ -108,10 +119,11 @@
108 119 ref="basicConfigurationEl"
109 120 :front-id="frontId"
110 121 :record="componentRecord"
  122 + :defaultConfig="componentDefaultConfig"
111 123 />
112 124 </Tabs.TabPane>
113 125 <Tabs.TabPane key="2" tab="可视化配置">
114   - <VisualConfiguration v-model:value="frontId" />
  126 + <VisualConfiguration v-model:value="frontId" @change="handleComponentCheckedChange" />
115 127 </Tabs.TabPane>
116 128 </Tabs>
117 129 </section>
... ...
... ... @@ -105,6 +105,7 @@
105 105 // 发送请求
106 106 loading.value = true;
107 107 const res = await getDeviceHistoryInfo(searchParams);
  108 + loading.value = false;
108 109 // 判断数据对象是否为空
109 110 if (!Object.keys(res).length) {
110 111 isNull.value = false;
... ... @@ -113,7 +114,6 @@
113 114 isNull.value = true;
114 115 }
115 116 setChartOptions(res, value.keys);
116   - loading.value = false;
117 117 },
118 118 });
119 119
... ... @@ -150,6 +150,7 @@
150 150 startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
151 151 endTs: Date.now(),
152 152 agg: AggregateDataEnum.NONE,
  153 + limit: 7,
153 154 });
154 155
155 156 // 判断对象是否为空
... ... @@ -218,7 +219,11 @@
218 219 <div v-show="isNull" ref="chartRef" :style="{ height: '350px', width: '100%' }">
219 220 <Loading :loading="loading" :absolute="true" />
220 221 </div>
221   - <Empty description="暂无数据,请选择设备查询" v-show="!isNull" />
  222 + <Empty
  223 + class="h-350px flex flex-col justify-center items-center"
  224 + description="暂无数据,请选择设备查询"
  225 + v-show="!isNull"
  226 + />
222 227 </section>
223 228 </section>
224 229 </BasicModal>
... ...
... ... @@ -5,15 +5,19 @@
5 5 import { textComponentConfig } from '../../components/TextComponent/config';
6 6 import { instrumentComponentConfig } from '../../components/InstrumentComponent';
7 7 import { pictureComponentList } from '../../components/PictureComponent';
  8 + import { getComponentDefaultConfig } from '../config/help';
  9 + import { WidgetComponentType } from '../config/visualOptions';
8 10 const props = defineProps<{
9 11 value: string;
10 12 }>();
11   - const emit = defineEmits(['update:value']);
  13 + const emit = defineEmits(['update:value', 'change']);
12 14
13 15 const grid = { gutter: 10, column: 1, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 4 };
14 16
15   - const handleCheck = (checked: string) => {
  17 + const handleCheck = (checked: WidgetComponentType) => {
  18 + const defaultConfig = getComponentDefaultConfig(checked);
16 19 emit('update:value', checked);
  20 + emit('change', defaultConfig);
17 21 };
18 22 </script>
19 23
... ... @@ -59,7 +63,12 @@
59 63 :control-id="item.id"
60 64 @change="handleCheck"
61 65 >
62   - <component :is="item.component" :layout="item.layout" :value="item.value" />
  66 + <component
  67 + :is="item.component"
  68 + :random="true"
  69 + :layout="item.layout"
  70 + :value="item.value"
  71 + />
63 72 </VisualWidgetSelect>
64 73 </List.Item>
65 74 </template>
... ...
... ... @@ -87,7 +87,7 @@
87 87 @register="register"
88 88 @ok="handleClose"
89 89 title="选项"
90   - width="60%"
  90 + width="40%"
91 91 >
92 92 <BasicForm class="form" @register="registerForm" :schemas="getSchemas" />
93 93 </BasicModal>
... ...
... ... @@ -20,10 +20,10 @@
20 20
21 21 <template>
22 22 <Card
23   - :style="{ borderColor: props.controlId === props.checkedId ? '#1a74e8' : '#f0f0f0' }"
  23 + :style="{ borderColor: props.controlId === props.checkedId ? '#1a74e8' : '#dcdfe6' }"
24 24 hoverable
25 25 bordered
26   - class="w-60 h-60 widget-select !bg-light-50"
  26 + class="w-60 h-60 widget-select !bg-light-50 cursor-pointer"
27 27 @click="handleClick"
28 28 >
29 29 <div class="widget-container">
... ... @@ -48,7 +48,7 @@
48 48 }
49 49
50 50 .widget-select:deep(.ant-card-meta) {
51   - border-top: 1px solid #efefef;
  51 + border-top: 1px solid #dcdfe6;
52 52 width: 100%;
53 53 height: 40px;
54 54 text-align: center;
... ...
... ... @@ -6,20 +6,24 @@ import {
6 6 TextComponent3Config,
7 7 TextComponent4Config,
8 8 TextComponent5Config,
  9 + TextComponentDefaultConfig,
9 10 transformTextComponentConfig,
10 11 } from '../../components/TextComponent/config';
11   -import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
  12 +import { ComponentInfo, DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
12 13 import DashBoardComponent from '../../components/InstrumentComponent/DashBoardComponent.vue';
13 14 import PictureComponent from '../../components/PictureComponent/PictureComponent.vue';
14 15 import { transformPictureConfig } from '../../components/PictureComponent/pictureComponent.config';
15 16 import { WidgetComponentType } from './visualOptions';
16 17 import {
17 18 DashboardComponentLayout,
  19 + Instrument1DefaultConfig,
  20 + Instrument2DefaultConfig,
18 21 instrumentComponent1,
19 22 instrumentComponent2,
20 23 transformDashboardComponentConfig,
21 24 } from '../../components/InstrumentComponent/dashBoardComponent.config';
22 25 import DigitalDashBoard from '../../components/InstrumentComponent/DigitalDashBoard.vue';
  26 +import { DigitalComponentDefaultConfig } from '../../components/InstrumentComponent/digitalDashBoard.config';
23 27 export enum FrontComponent {
24 28 TEXT_COMPONENT_1 = 'text-component-1',
25 29 TEXT_COMPONENT_2 = 'text-component-2',
... ... @@ -42,6 +46,11 @@ export interface ComponentConfig {
42 46 ) => Recordable;
43 47 }
44 48
  49 +export const frontComponentDefaultConfigMap = new Map<
  50 + WidgetComponentType,
  51 + Partial<ComponentInfo>
  52 +>();
  53 +
45 54 export const frontComponentMap = new Map<WidgetComponentType, ComponentConfig>();
46 55
47 56 frontComponentMap.set(FrontComponent.TEXT_COMPONENT_1, {
... ... @@ -103,3 +112,21 @@ frontComponentMap.set(FrontComponent.PICTURE_COMPONENT_1, {
103 112 ComponentConfig: {},
104 113 transformConfig: transformPictureConfig,
105 114 });
  115 +
  116 +frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_1, TextComponentDefaultConfig);
  117 +frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_2, TextComponentDefaultConfig);
  118 +frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_3, TextComponentDefaultConfig);
  119 +frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_4, TextComponentDefaultConfig);
  120 +frontComponentDefaultConfigMap.set(FrontComponent.TEXT_COMPONENT_5, TextComponentDefaultConfig);
  121 +
  122 +frontComponentDefaultConfigMap.set(FrontComponent.INSTRUMENT_COMPONENT_1, Instrument1DefaultConfig);
  123 +frontComponentDefaultConfigMap.set(FrontComponent.INSTRUMENT_COMPONENT_2, Instrument2DefaultConfig);
  124 +
  125 +frontComponentDefaultConfigMap.set(
  126 + FrontComponent.DIGITAL_DASHBOARD_COMPONENT,
  127 + DigitalComponentDefaultConfig
  128 +);
  129 +
  130 +export const getComponentDefaultConfig = (key: WidgetComponentType) => {
  131 + return frontComponentDefaultConfigMap.get(key) || {};
  132 +};
... ...
... ... @@ -5,6 +5,8 @@ export interface RadioRecord {
5 5 radio: number;
6 6 }
7 7
  8 +export const DEFAULT_ANIMATION_INTERVAL = 2000;
  9 +
8 10 export const DEFAULT_RADIO_RECORD: RadioRecord = {
9 11 width: 300,
10 12 height: 300,
... ... @@ -37,8 +39,19 @@ export const calcScale = (
37 39 };
38 40 };
39 41
40   -export const fontSize = ({ radio, basic, max }: { radio: number; basic: number; max?: number }) => {
  42 +export const fontSize = ({
  43 + radio,
  44 + basic,
  45 + max,
  46 + min,
  47 +}: {
  48 + radio: number;
  49 + basic: number;
  50 + max?: number;
  51 + min?: number;
  52 +}) => {
41 53 let res = basic * radio;
42 54 if (max && res > max) res = max;
  55 + if (min && res < min) res = min;
43 56 return res + 'px';
44 57 };
... ...
... ... @@ -175,7 +175,7 @@ schemasMap.set('text-component-1', modeOne);
175 175 schemasMap.set('text-component-2', modeOne);
176 176 schemasMap.set('text-component-3', modeOne);
177 177 schemasMap.set('text-component-4', modeTwo);
178   -schemasMap.set('text-component-4', modeTwo);
  178 +schemasMap.set('text-component-5', modeTwo);
179 179 schemasMap.set('instrument-component-1', modeOne);
180 180 schemasMap.set('instrument-component-2', modeThree);
181 181 schemasMap.set('digital-dashboard-component', modeFour);
... ...
... ... @@ -24,7 +24,12 @@
24 24 } from '/@/api/dataBoard';
25 25 import { useRoute, useRouter } from 'vue-router';
26 26 import { computed, unref } from '@vue/reactivity';
27   - import { DataComponentRecord, DataSource, Layout } from '/@/api/dataBoard/model';
  27 + import {
  28 + ComponentInfoDetail,
  29 + DataComponentRecord,
  30 + DataSource,
  31 + Layout,
  32 + } from '/@/api/dataBoard/model';
28 33 import { frontComponentMap } from './config/help';
29 34 import { calcScale } from './config/util';
30 35 import { useMessage } from '/@/hooks/web/useMessage';
... ... @@ -98,6 +103,9 @@
98 103 width = 100;
99 104 }
100 105
  106 + data.width = newWPx;
  107 + data.height = newHPx;
  108 +
101 109 data.record.dataSource = data?.record.dataSource.map((item) => {
102 110 if (!item.uuid) item.uuid = buildUUID();
103 111 return {
... ... @@ -194,7 +202,7 @@
194 202 try {
195 203 return await getDataComponent(unref(getBoardId));
196 204 } catch (error) {}
197   - return [];
  205 + return {} as ComponentInfoDetail;
198 206 };
199 207
200 208 const getSharePageComponentData = async () => {
... ... @@ -202,7 +210,7 @@
202 210 const params = unref(getSharePageParams);
203 211 return await getShareBoardComponentInfo(params);
204 212 } catch (error) {}
205   - return [];
  213 + return {} as ComponentInfoDetail;
206 214 };
207 215
208 216 const getDataBoradDetail = async () => {
... ... @@ -211,7 +219,7 @@
211 219 ? await getSharePageComponentData()
212 220 : await getBasePageComponentData();
213 221 } catch (error) {}
214   - return [];
  222 + return {} as ComponentInfoDetail;
215 223 };
216 224
217 225 const loading = ref(false);
... ... @@ -245,6 +253,35 @@
245 253 }
246 254 };
247 255
  256 + const handleUpdateComponent = async (id: string) => {
  257 + try {
  258 + loading.value = true;
  259 + const data = await getDataBoradDetail();
  260 + const updateIndex = data.data.componentData.findIndex((item) => item.id === id);
  261 + const originalIndex = unref(dataBoardList).findIndex((item) => item.i === id);
  262 +
  263 + const newUpdateData = data.data.componentData[updateIndex];
  264 + const originalData = unref(dataBoardList)[originalIndex];
  265 + dataBoardList.value[originalIndex] = {
  266 + i: id,
  267 + w: originalData.w || DEFAULT_WIDGET_WIDTH,
  268 + h: originalData.h || DEFAULT_WIDGET_HEIGHT,
  269 + x: originalData.x || 0,
  270 + y: originalData.y || 0,
  271 + width: originalData.width,
  272 + height: originalData.height,
  273 + record: newUpdateData,
  274 + };
  275 +
  276 + updateSize(id, 0, 0, originalData.height || 0, originalData.width || 0);
  277 +
  278 + beginSendMessage();
  279 + } catch (error) {
  280 + } finally {
  281 + loading.value = false;
  282 + }
  283 + };
  284 +
248 285 const getComponent = (record: DataComponentRecord) => {
249 286 const frontComponent = record.frontId;
250 287 const component = frontComponentMap.get(frontComponent as WidgetComponentType);
... ... @@ -312,7 +349,7 @@
312 349 </div>
313 350 </template>
314 351 <template #extra>
315   - <Authority value="api:yt:dataBoardDetail:post">
  352 + <Authority value="api:yt:data_component:add:post">
316 353 <Button v-if="!getIsSharePage" type="primary" @click="handleOpenCreatePanel"
317 354 >创建组件</Button
318 355 >
... ... @@ -383,6 +420,7 @@
383 420 :update="update"
384 421 :radio="record.radio || {}"
385 422 v-bind="getComponentConfig(item.record, record)"
  423 + :random="false"
386 424 />
387 425 </template>
388 426 </WidgetWrapper>
... ... @@ -391,7 +429,11 @@
391 429 <Empty v-if="!dataBoardList.length" />
392 430 </Spin>
393 431 </section>
394   - <DataBindModal @register="register" @submit="getDataBoardComponent" />
  432 + <DataBindModal
  433 + @register="register"
  434 + @update="handleUpdateComponent"
  435 + @create="getDataBoardComponent"
  436 + />
395 437 <HistoryTrendModal @register="registerHistoryDataModal" />
396 438 </section>
397 439 </template>
... ...
... ... @@ -88,9 +88,9 @@ export function useSocketConnect(dataSourceRef: Ref<DataBoardLayoutInfo[]>) {
88 88 throw Error(error as string);
89 89 }
90 90 },
91   - onDisconnected() {
92   - close();
93   - },
  91 + // onDisconnected() {
  92 + // close();
  93 + // },
94 94 });
95 95
96 96 const setCmdId = (cmdId: number, record: CmdMapping) => {
... ...
... ... @@ -64,8 +64,8 @@
64 64
65 65 const { hasPermission } = usePermission();
66 66 const dropMenuList = computed<DropMenu[]>(() => {
67   - const hasUpdatePermission = hasPermission('api:yt:dataBoard:update');
68   - const hasDeletePermission = hasPermission('api:yt:dataBoard:delete');
  67 + const hasUpdatePermission = hasPermission('api:yt:data_board:update:update');
  68 + const hasDeletePermission = hasPermission('api:yt:data_board:delete');
69 69 const basicMenu: DropMenu[] = [];
70 70 if (hasUpdatePermission)
71 71 basicMenu.push({
... ... @@ -126,16 +126,17 @@
126 126 createMessage.success('删除成功');
127 127 await getDatasource();
128 128 } catch (error) {
129   - createMessage.error('删除失败');
  129 + // createMessage.error('删除失败');
130 130 }
131 131 };
132 132
133 133 const [registerModal, { openModal }] = useModal();
134 134
135 135 const handleViewBoard = (record: DataBoardRecord) => {
136   - const hasDetailPermission = hasPermission('api:yt:dataBoard:detail');
  136 + const hasDetailPermission = hasPermission('api:yt:data_component:list');
137 137 if (hasDetailPermission)
138 138 router.push(`/data/board/detail/${encode(record.id)}/${encode(record.name)}`);
  139 + else createMessage.warning('没有权限');
139 140 };
140 141
141 142 const handlePagenationPosition = () => {
... ... @@ -161,7 +162,7 @@
161 162 <PageWrapper>
162 163 <div class="flex mb-6 items-center">
163 164 <div class="text-lg mr-6 font-bold">自定义看板</div>
164   - <Authority value="api:yt:dataBoard:post">
  165 + <Authority value="api:yt:data_board:add:post">
165 166 <Button type="primary" @click="handleOpenDetailModal">创建看板</Button>
166 167 </Authority>
167 168 </div>
... ... @@ -202,11 +203,11 @@
202 203 <span>
203 204 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
204 205 </span>
205   - <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
  206 + <!-- <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
206 207 <Tooltip title="点击复制分享链接">
207 208 <ShareAltOutlined class="ml-2" @click.stop="handleCopyShareUrl(item)" />
208 209 </Tooltip>
209   - </span>
  210 + </span> -->
210 211 </div>
211 212 <div>{{ item.updateTime || item.createTime }}</div>
212 213 </div>
... ...
... ... @@ -5,4 +5,6 @@ export type FrontDataSourceRecord = DataSource;
5 5
6 6 export type DataBoardLayoutInfo = Layout & {
7 7 record: DataComponentRecord;
  8 + width?: number;
  9 + height?: number;
8 10 };
... ...
... ... @@ -4,19 +4,22 @@
4 4 <div class="right-wrap">
5 5 <BasicTable style="cursor: pointer" @register="registerTable" @rowClick="deviceRowClick">
6 6 <template #deviceState="{ record }">
7   - <Tag :color="
8   - record.deviceState == DeviceState.INACTIVE
9   - ? 'warning'
10   - : record.deviceState == DeviceState.ONLINE
11   - ? 'success'
12   - : 'error'
13   - " class="ml-2">
  7 + <Tag
  8 + :color="
  9 + record.deviceState == DeviceState.INACTIVE
  10 + ? 'warning'
  11 + : record.deviceState == DeviceState.ONLINE
  12 + ? 'success'
  13 + : 'error'
  14 + "
  15 + class="ml-2"
  16 + >
14 17 {{
15   - record.deviceState == DeviceState.INACTIVE
16   - ? '待激活'
17   - : record.deviceState == DeviceState.ONLINE
18   - ? '在线'
19   - : '离线'
  18 + record.deviceState == DeviceState.INACTIVE
  19 + ? '待激活'
  20 + : record.deviceState == DeviceState.ONLINE
  21 + ? '在线'
  22 + : '离线'
20 23 }}
21 24 </Tag>
22 25 </template>
... ... @@ -27,393 +30,406 @@
27 30 </template>
28 31 </BasicTable>
29 32 </div>
30   - <BasicModal @register="registerModal" title="历史数据" width="70%" :minHeight="400" :footer="null"
31   - :canFullscreen="false" @cancel="handleCancelModal">
  33 + <BasicModal
  34 + @register="registerModal"
  35 + title="历史数据"
  36 + width="70%"
  37 + :minHeight="400"
  38 + :footer="null"
  39 + :canFullscreen="false"
  40 + @cancel="handleCancelModal"
  41 + >
32 42 <TimePeriodForm @register="timePeriodRegister" />
33 43 <!-- <BasicForm @register="registerForm" /> -->
34 44 <div v-show="isNull" ref="chartRef" :style="{ height: '550px', width }">
35 45 <Loading :loading="loading" :absolute="true" />
36 46 </div>
37   - <Empty v-show="!isNull" />
  47 + <Empty
  48 + :style="{ height: '550px', width }"
  49 + class="flex flex-col justify-center items-center"
  50 + v-show="!isNull"
  51 + />
38 52 </BasicModal>
39 53 <DeviceDetailDrawer @register="registerDetailDrawer" />
40 54 </div>
41 55 </template>
42 56 <script lang="ts">
43   -import { defineComponent, ref, nextTick, unref, onMounted, Ref, onUnmounted } from 'vue';
44   -import { useScript } from '/@/hooks/web/useScript';
45   -import { formSchema, columns } from './config.data';
46   -import { BasicTable, useTable } from '/@/components/Table';
47   -import { devicePage } from '/@/api/alarm/contact/alarmContact';
48   -import { Tag, Empty } from 'ant-design-vue';
49   -import { DeviceState } from '/@/api/device/model/deviceModel';
50   -import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
51   -import { useModal, BasicModal } from '/@/components/Modal';
52   -import { useECharts } from '/@/hooks/web/useECharts';
53   -import {
54   - getDeviceHistoryInfo,
55   - getDeviceDataKeys,
56   - getDeviceActiveTime,
57   -} from '/@/api/alarm/position';
58   -import { useDrawer } from '/@/components/Drawer';
59   -import DeviceDetailDrawer from '/@/views/device/list/cpns/modal/DeviceDetailDrawer.vue';
60   -import moment from 'moment';
61   -// 导入一些静态图片,避免打包时不能正确解析
62   -import djx from '/@/assets/images/djx.png';
63   -import zx from '/@/assets/images/zx.png';
64   -import lx from '/@/assets/images/lx.png';
65   -import djh from '/@/assets/images/djh.png';
66   -import online from '/@/assets/images/online1.png';
67   -import lx1 from '/@/assets/images/lx1.png';
68   -import Loading from '/@/components/Loading/src/Loading.vue';
69   -import { TimePeriodForm, useTimePeriodForm } from './cpns/TimePeriodForm';
70   -import { selectDeviceAttrSchema, eChartOptions } from './config.data';
71   -import { defaultSchemas } from './cpns/TimePeriodForm/config';
72   -import { QueryWay, SchemaFiled, AggregateDataEnum } from './cpns/TimePeriodForm/config';
73   -import { dateUtil } from '/@/utils/dateUtil';
74   -export default defineComponent({
75   - name: 'BaiduMap',
76   - components: {
77   - BasicTable,
78   - Tag,
79   - Empty,
80   - BasicModal,
81   - DeviceDetailDrawer,
82   - Loading,
83   - TimePeriodForm,
84   - },
85   - props: {
86   - width: {
87   - type: String,
88   - default: '100%',
  57 + import { defineComponent, ref, nextTick, unref, onMounted, Ref, onUnmounted } from 'vue';
  58 + import { useScript } from '/@/hooks/web/useScript';
  59 + import { formSchema, columns } from './config.data';
  60 + import { BasicTable, useTable } from '/@/components/Table';
  61 + import { devicePage } from '/@/api/alarm/contact/alarmContact';
  62 + import { Tag, Empty } from 'ant-design-vue';
  63 + import { DeviceState } from '/@/api/device/model/deviceModel';
  64 + import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
  65 + import { useModal, BasicModal } from '/@/components/Modal';
  66 + import { useECharts } from '/@/hooks/web/useECharts';
  67 + import {
  68 + getDeviceHistoryInfo,
  69 + getDeviceDataKeys,
  70 + getDeviceActiveTime,
  71 + } from '/@/api/alarm/position';
  72 + import { useDrawer } from '/@/components/Drawer';
  73 + import DeviceDetailDrawer from '/@/views/device/list/cpns/modal/DeviceDetailDrawer.vue';
  74 + import moment from 'moment';
  75 + // 导入一些静态图片,避免打包时不能正确解析
  76 + import djx from '/@/assets/images/djx.png';
  77 + import zx from '/@/assets/images/zx.png';
  78 + import lx from '/@/assets/images/lx.png';
  79 + import djh from '/@/assets/images/djh.png';
  80 + import online from '/@/assets/images/online1.png';
  81 + import lx1 from '/@/assets/images/lx1.png';
  82 + import Loading from '/@/components/Loading/src/Loading.vue';
  83 + import { TimePeriodForm, useTimePeriodForm } from './cpns/TimePeriodForm';
  84 + import { selectDeviceAttrSchema, eChartOptions } from './config.data';
  85 + import { defaultSchemas } from './cpns/TimePeriodForm/config';
  86 + import { QueryWay, SchemaFiled, AggregateDataEnum } from './cpns/TimePeriodForm/config';
  87 + import { dateUtil } from '/@/utils/dateUtil';
  88 + export default defineComponent({
  89 + name: 'BaiduMap',
  90 + components: {
  91 + BasicTable,
  92 + Tag,
  93 + Empty,
  94 + BasicModal,
  95 + DeviceDetailDrawer,
  96 + Loading,
  97 + TimePeriodForm,
89 98 },
90   - height: {
91   - type: String,
92   - default: 'calc(100vh - 78px)',
93   - },
94   - },
95   - setup() {
96   - let entityId = '';
97   - let globalRecord: any = {};
98   - const wrapRef = ref<HTMLDivElement | null>(null);
99   - const chartRef = ref<HTMLDivElement | null>(null);
100   - const deviceAttrs = ref<string[]>([]);
101   - const { setOptions, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
102   - const isNull = ref(true);
103   - const { toPromise } = useScript({ src: BAI_DU_MAP_URL });
104   - const [registerDetailDrawer, { openDrawer }] = useDrawer();
105   - const [registerModal, { openModal }] = useModal();
106   - const loading = ref(false);
107   -
108   - const [registerTable] = useTable({
109   - api: devicePage,
110   - columns,
111   - resizeHeightOffset: 32,
112   - formConfig: {
113   - schemas: formSchema,
114   - labelAlign: 'left',
  99 + props: {
  100 + width: {
  101 + type: String,
  102 + default: '100%',
115 103 },
116   - showIndexColumn: false,
117   - useSearchForm: true,
118   - pagination: {
119   - showSizeChanger: false,
  104 + height: {
  105 + type: String,
  106 + default: 'calc(100vh - 78px)',
120 107 },
121   - });
  108 + },
  109 + setup() {
  110 + let entityId = '';
  111 + let globalRecord: any = {};
  112 + const wrapRef = ref<HTMLDivElement | null>(null);
  113 + const chartRef = ref<HTMLDivElement | null>(null);
  114 + const deviceAttrs = ref<string[]>([]);
  115 + const { setOptions, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
  116 + const isNull = ref(true);
  117 + const { toPromise } = useScript({ src: BAI_DU_MAP_URL });
  118 + const [registerDetailDrawer, { openDrawer }] = useDrawer();
  119 + const [registerModal, { openModal }] = useModal();
  120 + const loading = ref(false);
122 121
123   - async function initMap() {
124   - await toPromise();
125   - await nextTick();
126   - const wrapEl = unref(wrapRef);
127   - const BMap = (window as any).BMap;
128   - if (!wrapEl) return;
129   - const map = new BMap.Map(wrapEl);
  122 + const [registerTable] = useTable({
  123 + api: devicePage,
  124 + columns,
  125 + resizeHeightOffset: 32,
  126 + formConfig: {
  127 + schemas: formSchema,
  128 + labelAlign: 'left',
  129 + },
  130 + showIndexColumn: false,
  131 + useSearchForm: true,
  132 + pagination: {
  133 + showSizeChanger: false,
  134 + },
  135 + });
  136 +
  137 + async function initMap() {
  138 + await toPromise();
  139 + await nextTick();
  140 + const wrapEl = unref(wrapRef);
  141 + const BMap = (window as any).BMap;
  142 + if (!wrapEl) return;
  143 + const map = new BMap.Map(wrapEl);
130 144
131   - const getLocation = new BMap.Geolocation();
132   - getLocation.getCurrentPosition((position) => {
133   - if (position) {
134   - let preMarker = null;
135   - const point = new BMap.Point(position.point.lng, position.point.lat);
136   - let marker = new BMap.Marker(point, { size: 30 });
137   - if (marker) {
138   - map.removeOverlay(preMarker);
  145 + const getLocation = new BMap.Geolocation();
  146 + getLocation.getCurrentPosition((position) => {
  147 + if (position) {
  148 + let preMarker = null;
  149 + const point = new BMap.Point(position.point.lng, position.point.lat);
  150 + let marker = new BMap.Marker(point, { size: 30 });
  151 + if (marker) {
  152 + map.removeOverlay(preMarker);
  153 + }
  154 + map.addOverlay(marker);
  155 + map.centerAndZoom(point, 15);
  156 + map.enableScrollWheelZoom(true);
  157 + } else {
  158 + const point = new BMap.Point(104.04666605565338, 30.543516387560476);
  159 + map.centerAndZoom(point, 15);
  160 + map.enableScrollWheelZoom(true);
139 161 }
140   - map.addOverlay(marker);
141   - map.centerAndZoom(point, 15);
142   - map.enableScrollWheelZoom(true);
143   - } else {
144   - const point = new BMap.Point(104.04666605565338, 30.543516387560476);
145   - map.centerAndZoom(point, 15);
146   - map.enableScrollWheelZoom(true);
147   - }
148   - })
149   - }
150   - // 点击表格某一行触发
151   - const deviceRowClick = async (record) => {
152   - entityId = record.tbDeviceId;
153   - globalRecord = record;
154   - const BMap = (window as any).BMap;
155   - const wrapEl = unref(wrapRef);
156   - const map = new BMap.Map(wrapEl);
157   - // if (record.deviceInfo.address) {
158   - // keys = await getDeviceDataKeys(entityId);
159   - try {
160   - deviceAttrs.value = (await getDeviceDataKeys(entityId)) || [];
161   - } catch (error) { }
  162 + });
  163 + }
  164 + // 点击表格某一行触发
  165 + const deviceRowClick = async (record) => {
  166 + entityId = record.tbDeviceId;
  167 + globalRecord = record;
  168 + const BMap = (window as any).BMap;
  169 + const wrapEl = unref(wrapRef);
  170 + const map = new BMap.Map(wrapEl);
  171 + // if (record.deviceInfo.address) {
  172 + // keys = await getDeviceDataKeys(entityId);
  173 + try {
  174 + deviceAttrs.value = (await getDeviceDataKeys(entityId)) || [];
  175 + } catch (error) {}
162 176
163   - const { name, organizationDTO, deviceState, deviceProfile } = record;
164   - const { longitude, latitude, address } = record.deviceInfo;
165   - //这里如果没有地理位置 最好设置一个默认位置 不然地图会全蓝
166   - const point = new BMap.Point(
167   - longitude == '' ? 104.04666605565338 : longitude,
168   - latitude == '' ? 30.543516387560476 : latitude
169   - );
170   - let options = {
171   - width: 330, // 信息窗口宽度
172   - height: 0, // 信息窗口高度
173   - };
174   - map.centerAndZoom(point, 15);
175   - map.enableScrollWheelZoom(true);
176   - // 创建信息窗口对象
177   - const res = await getDeviceActiveTime(entityId);
  177 + const { name, organizationDTO, deviceState, deviceProfile } = record;
  178 + const { longitude, latitude, address } = record.deviceInfo;
  179 + //这里如果没有地理位置 最好设置一个默认位置 不然地图会全蓝
  180 + const point = new BMap.Point(
  181 + longitude == '' ? 104.04666605565338 : longitude,
  182 + latitude == '' ? 30.543516387560476 : latitude
  183 + );
  184 + let options = {
  185 + width: 330, // 信息窗口宽度
  186 + height: 0, // 信息窗口高度
  187 + };
  188 + map.centerAndZoom(point, 15);
  189 + map.enableScrollWheelZoom(true);
  190 + // 创建信息窗口对象
  191 + const res = await getDeviceActiveTime(entityId);
178 192
179   - let { value: activeStatus, lastUpdateTs } = res[0];
180   - lastUpdateTs = moment(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss');
181   - let infoWindow = new BMap.InfoWindow(
182   - `
  193 + let { value: activeStatus, lastUpdateTs } = res[0];
  194 + lastUpdateTs = moment(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss');
  195 + let infoWindow = new BMap.InfoWindow(
  196 + `
183 197 <div style="display:flex;justify-content:space-between; margin:20px 0px;">
184 198 <div style="font-size:16px;font-weight:bold">${name}</div>
185   - ${deviceState === 'INACTIVE'
186   - ? `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${djh}" class="mr-1">待激活</div>`
187   - : deviceState === 'ONLINE'
188   - ? `<div style="display:flex;align-items:center; ">
  199 + ${
  200 + deviceState === 'INACTIVE'
  201 + ? `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${djh}" class="mr-1">待激活</div>`
  202 + : deviceState === 'ONLINE'
  203 + ? `<div style="display:flex;align-items:center; ">
189 204 <img style="width:15px;height:15px" src="${online}" class="mr-1">在线</div>`
190   - : `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${lx1}" class="mr-1">离线</div>`
191   - }
  205 + : `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${lx1}" class="mr-1">离线</div>`
  206 + }
192 207 </div>
193 208 <div>所属组织:${organizationDTO.name}</div>
194 209 <div style="margin-top:6px;">接入协议:${deviceProfile.transportType}</div>
195   - <div style="margin-top:6px;">设备位置:${address == '' ? '该设备暂无地理位置' : address
196   - }</div>
  210 + <div style="margin-top:6px;">设备位置:${
  211 + address == '' ? '该设备暂无地理位置' : address
  212 + }</div>
197 213 <div style="margin-top:6px;">${activeStatus ? '在' : '离'}线时间:${lastUpdateTs}</div>
198 214 <div style="display:flex;justify-content:end; margin-top:10px">
199 215 <button onclick="openDeviceInfoDrawer()" style="margin-right:10px;color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">设备信息</button>
200 216 <button onclick="openHistoryModal()" style="color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">历史数据</button>
201 217 </div>
202 218 `,
203   - options
204   - );
  219 + options
  220 + );
205 221
206   - map.openInfoWindow(infoWindow, map.getCenter());
207   - let preMarker = null;
  222 + map.openInfoWindow(infoWindow, map.getCenter());
  223 + let preMarker = null;
208 224
209   - const rivet = deviceState === 'INACTIVE' ? djx : deviceState === 'ONLINE' ? zx : lx;
210   - let myIcon = new BMap.Icon(rivet, new BMap.Size(20, 30));
211   - let marker = new BMap.Marker(point, { icon: myIcon });
212   - if (marker) {
213   - map.removeOverlay(preMarker);
214   - }
215   - map.addOverlay(marker);
216   - //标注监听事件
217   - marker.addEventListener('click', function (e) {
218   - const point = e.point;
219   - map.openInfoWindow(infoWindow, point);
220   - });
221   - //标注监听事件
222   - // } else {
223   - // const point = new BMap.Point(106.63028229687498, 36.06735821600903);
224   - // let options = {
225   - // width: 100, // 信息窗口宽度
226   - // height: 100, // 信息窗口高度
227   - // title: '提示', // 信息窗口标题
228   - // };
229   - // map.centerAndZoom(point, 5);
230   - // map.enableScrollWheelZoom(true);
231   - // let infoWindow = new BMap.InfoWindow('该设备暂无地理位置', options); // 创建信息窗口对象
232   - // map.openInfoWindow(infoWindow, map.getCenter());
233   - // }
234   - };
  225 + const rivet = deviceState === 'INACTIVE' ? djx : deviceState === 'ONLINE' ? zx : lx;
  226 + let myIcon = new BMap.Icon(rivet, new BMap.Size(20, 30));
  227 + let marker = new BMap.Marker(point, { icon: myIcon });
  228 + if (marker) {
  229 + map.removeOverlay(preMarker);
  230 + }
  231 + map.addOverlay(marker);
  232 + //标注监听事件
  233 + marker.addEventListener('click', function (e) {
  234 + const point = e.point;
  235 + map.openInfoWindow(infoWindow, point);
  236 + });
  237 + //标注监听事件
  238 + // } else {
  239 + // const point = new BMap.Point(106.63028229687498, 36.06735821600903);
  240 + // let options = {
  241 + // width: 100, // 信息窗口宽度
  242 + // height: 100, // 信息窗口高度
  243 + // title: '提示', // 信息窗口标题
  244 + // };
  245 + // map.centerAndZoom(point, 5);
  246 + // map.enableScrollWheelZoom(true);
  247 + // let infoWindow = new BMap.InfoWindow('该设备暂无地理位置', options); // 创建信息窗口对象
  248 + // map.openInfoWindow(infoWindow, map.getCenter());
  249 + // }
  250 + };
235 251
236   - // 设备信息
237   - const openDeviceInfoDrawer = async () => {
238   - const { id, tbDeviceId } = globalRecord;
239   - openDrawer(true, {
240   - id,
241   - tbDeviceId,
242   - });
243   - };
  252 + // 设备信息
  253 + const openDeviceInfoDrawer = async () => {
  254 + const { id, tbDeviceId } = globalRecord;
  255 + openDrawer(true, {
  256 + id,
  257 + tbDeviceId,
  258 + });
  259 + };
244 260
245   - const [timePeriodRegister, method] = useTimePeriodForm({
246   - schemas: [...defaultSchemas, ...selectDeviceAttrSchema],
247   - async submitFunc() {
248   - // 表单验证
249   - await method.validate();
250   - const value = method.getFieldsValue();
251   - const searchParams = getSearchParams(value);
  261 + const [timePeriodRegister, method] = useTimePeriodForm({
  262 + schemas: [...defaultSchemas, ...selectDeviceAttrSchema],
  263 + async submitFunc() {
  264 + // 表单验证
  265 + await method.validate();
  266 + const value = method.getFieldsValue();
  267 + const searchParams = getSearchParams(value);
252 268
253   - if (!hasDeviceAttr()) return;
254   - // 发送请求
255   - loading.value = true;
256   - const res = await getDeviceHistoryInfo(searchParams);
257   - // 判断数据对象是否为空
258   - if (!Object.keys(res).length) {
259   - isNull.value = false;
260   - return;
  269 + if (!hasDeviceAttr()) return;
  270 + // 发送请求
  271 + loading.value = true;
  272 + const res = await getDeviceHistoryInfo(searchParams);
  273 + // 判断数据对象是否为空
  274 + if (!Object.keys(res).length) {
  275 + isNull.value = false;
  276 + return;
  277 + } else {
  278 + isNull.value = true;
  279 + }
  280 + setChartOptions(res, value.keys);
  281 + loading.value = false;
  282 + },
  283 + });
  284 +
  285 + function getSearchParams(value: Recordable) {
  286 + const { startTs, endTs, interval, agg, limit, keys, way } = value;
  287 + if (way === QueryWay.LATEST) {
  288 + return {
  289 + entityId,
  290 + keys: keys ? keys : unref(deviceAttrs).join(),
  291 + startTs: moment().subtract(startTs, 'ms').valueOf(),
  292 + endTs: Date.now(),
  293 + interval,
  294 + agg,
  295 + limit,
  296 + };
261 297 } else {
262   - isNull.value = true;
  298 + return {
  299 + entityId,
  300 + keys: keys ? keys : unref(deviceAttrs).join(),
  301 + startTs: moment(startTs).valueOf(),
  302 + endTs: moment(endTs).valueOf(),
  303 + interval,
  304 + agg,
  305 + limit,
  306 + };
263 307 }
264   - setChartOptions(res, value.keys);
265   - loading.value = false;
266   - },
267   - });
268   -
269   - function getSearchParams(value: Recordable) {
270   - const { startTs, endTs, interval, agg, limit, keys, way } = value;
271   - if (way === QueryWay.LATEST) {
272   - return {
273   - entityId,
274   - keys: keys ? keys : unref(deviceAttrs).join(),
275   - startTs: moment().subtract(startTs, 'ms').valueOf(),
276   - endTs: Date.now(),
277   - interval,
278   - agg,
279   - limit,
280   - };
281   - } else {
282   - return {
283   - entityId,
284   - keys: keys ? keys : unref(deviceAttrs).join(),
285   - startTs: moment(startTs).valueOf(),
286   - endTs: moment(endTs).valueOf(),
287   - interval,
288   - agg,
289   - limit,
290   - };
291 308 }
292   - }
293 309
294   - const openHistoryModal = async () => {
295   - openModal(true);
  310 + const openHistoryModal = async () => {
  311 + openModal(true);
296 312
297   - await nextTick();
298   - method.updateSchema({
299   - field: 'keys',
300   - componentProps: {
301   - options: unref(deviceAttrs).map((item) => ({ label: item, value: item })),
302   - },
303   - });
  313 + await nextTick();
  314 + method.updateSchema({
  315 + field: 'keys',
  316 + componentProps: {
  317 + options: unref(deviceAttrs).map((item) => ({ label: item, value: item })),
  318 + },
  319 + });
304 320
305   - method.setFieldsValue({
306   - [SchemaFiled.START_TS]: 1 * 24 * 60 * 60 * 1000,
307   - [SchemaFiled.LIMIT]: 7,
308   - [SchemaFiled.AGG]: AggregateDataEnum.NONE,
309   - });
  321 + method.setFieldsValue({
  322 + [SchemaFiled.START_TS]: 1 * 24 * 60 * 60 * 1000,
  323 + [SchemaFiled.LIMIT]: 7,
  324 + [SchemaFiled.AGG]: AggregateDataEnum.NONE,
  325 + });
310 326
311   - if (!hasDeviceAttr()) return;
  327 + if (!hasDeviceAttr()) return;
312 328
313   - const res = await getDeviceHistoryInfo({
314   - entityId,
315   - keys: unref(deviceAttrs).join(),
316   - startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
317   - endTs: Date.now(),
318   - agg: AggregateDataEnum.NONE,
319   - });
  329 + const res = await getDeviceHistoryInfo({
  330 + entityId,
  331 + keys: unref(deviceAttrs).join(),
  332 + startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
  333 + endTs: Date.now(),
  334 + agg: AggregateDataEnum.NONE,
  335 + });
320 336
321   - // 判断对象是否为空
322   - if (!Object.keys(res).length) {
323   - isNull.value = false;
324   - return;
325   - } else {
326   - isNull.value = true;
327   - }
328   - setChartOptions(res);
329   - };
330   - function hasDeviceAttr() {
331   - if (!unref(deviceAttrs).length) {
332   - isNull.value = false;
333   - return false;
334   - } else {
335   - isNull.value = true;
336   - return true;
  337 + // 判断对象是否为空
  338 + if (!Object.keys(res).length) {
  339 + isNull.value = false;
  340 + return;
  341 + } else {
  342 + isNull.value = true;
  343 + }
  344 + setChartOptions(res);
  345 + };
  346 + function hasDeviceAttr() {
  347 + if (!unref(deviceAttrs).length) {
  348 + isNull.value = false;
  349 + return false;
  350 + } else {
  351 + isNull.value = true;
  352 + return true;
  353 + }
337 354 }
338   - }
339 355
340   - function setChartOptions(data, keys?) {
341   - const dataArray: any[] = [];
342   - for (const key in data) {
343   - for (const item1 of data[key]) {
344   - let { ts, value } = item1;
345   - const time = dateUtil(ts).format('YYYY-MM-DD HH:mm:ss');
346   - value = Number(value).toFixed(2);
347   - dataArray.push([time, value, key]);
  356 + function setChartOptions(data, keys?) {
  357 + const dataArray: any[] = [];
  358 + for (const key in data) {
  359 + for (const item1 of data[key]) {
  360 + let { ts, value } = item1;
  361 + const time = dateUtil(ts).format('YYYY-MM-DD HH:mm:ss');
  362 + value = Number(value).toFixed(2);
  363 + dataArray.push([time, value, key]);
  364 + }
348 365 }
  366 + keys = keys ? [keys] : unref(deviceAttrs);
  367 + const series: any = keys.map((item) => {
  368 + return {
  369 + name: item,
  370 + type: 'line',
  371 + data: dataArray.filter((item1) => item1[2] === item),
  372 + };
  373 + });
  374 + // 设置数据
  375 + setOptions(eChartOptions(series, keys));
349 376 }
350   - keys = keys ? [keys] : unref(deviceAttrs);
351   - const series: any = keys.map((item) => {
352   - return {
353   - name: item,
354   - type: 'line',
355   - data: dataArray.filter((item1) => item1[2] === item),
356   - };
357   - });
358   - // 设置数据
359   - setOptions(eChartOptions(series, keys));
360   - }
361 377
362   - const handleCancelModal = () => {
363   - method.setFieldsValue({
364   - [SchemaFiled.WAY]: QueryWay.LATEST,
365   - [SchemaFiled.KEYS]: null,
366   - [SchemaFiled.DATE_RANGE]: [],
367   - [SchemaFiled.INTERVAL]: null,
368   - [SchemaFiled.LIMIT]: 7,
369   - [SchemaFiled.AGG]: AggregateDataEnum.NONE,
370   - });
371   - getInstance()?.clear();
372   - };
  378 + const handleCancelModal = () => {
  379 + method.setFieldsValue({
  380 + [SchemaFiled.WAY]: QueryWay.LATEST,
  381 + [SchemaFiled.KEYS]: null,
  382 + [SchemaFiled.DATE_RANGE]: [],
  383 + [SchemaFiled.INTERVAL]: null,
  384 + [SchemaFiled.LIMIT]: 7,
  385 + [SchemaFiled.AGG]: AggregateDataEnum.NONE,
  386 + });
  387 + getInstance()?.clear();
  388 + };
373 389
374   - onMounted(() => {
375   - initMap();
376   - (window as any).openDeviceInfoDrawer = openDeviceInfoDrawer;
377   - (window as any).openHistoryModal = openHistoryModal;
378   - });
  390 + onMounted(() => {
  391 + initMap();
  392 + (window as any).openDeviceInfoDrawer = openDeviceInfoDrawer;
  393 + (window as any).openHistoryModal = openHistoryModal;
  394 + });
379 395
380   - onUnmounted(() => {
381   - (window as any).openDeviceInfoDrawer = null;
382   - (window as any).openHistoryModal = null;
383   - });
  396 + onUnmounted(() => {
  397 + (window as any).openDeviceInfoDrawer = null;
  398 + (window as any).openHistoryModal = null;
  399 + });
384 400
385   - return {
386   - wrapRef,
387   - registerTable,
388   - deviceRowClick,
389   - DeviceState,
390   - registerModal,
391   - chartRef,
392   - isNull,
393   - registerDetailDrawer,
394   - loading,
395   - timePeriodRegister,
396   - handleCancelModal,
397   - };
398   - },
399   -});
  401 + return {
  402 + wrapRef,
  403 + registerTable,
  404 + deviceRowClick,
  405 + DeviceState,
  406 + registerModal,
  407 + chartRef,
  408 + isNull,
  409 + registerDetailDrawer,
  410 + loading,
  411 + timePeriodRegister,
  412 + handleCancelModal,
  413 + };
  414 + },
  415 + });
400 416 </script>
401 417 <style scoped lang="less">
402   -.wrapper {
403   - position: relative;
  418 + .wrapper {
  419 + position: relative;
404 420
405   - :deep(.BMap_shadow) {
406   - display: none;
  421 + :deep(.BMap_shadow) {
  422 + display: none;
  423 + }
407 424 }
408   -}
409 425
410   -.right-wrap {
411   - padding-top: 10px;
412   - width: 28%;
413   - height: 95%;
414   - position: absolute;
415   - right: 5%;
416   - top: 3%;
417   - background-color: #fff;
418   -}
  426 + .right-wrap {
  427 + padding-top: 10px;
  428 + width: 28%;
  429 + height: 95%;
  430 + position: absolute;
  431 + right: 5%;
  432 + top: 3%;
  433 + background-color: #fff;
  434 + }
419 435 </style>
... ...