Showing
89 changed files
with
3863 additions
and
370 deletions
Too many changes to show.
To preserve performance only 89 of 517 files are displayed.
1 | 1 | import { defHttp } from '@/utils/external/http/axios' |
2 | -import { DictItem, UploadResponse } from './model' | |
2 | +import { DictItem, OrganizationListItem, UploadResponse } from './model' | |
3 | 3 | |
4 | 4 | enum Api { |
5 | 5 | GET_DICT = '/dict_item', |
... | ... | @@ -8,7 +8,15 @@ enum Api { |
8 | 8 | AREALIST = '/area/list', |
9 | 9 | PLATFORM = '/platform/get', |
10 | 10 | CONFIGURATION = '/configuration/center', |
11 | - CONFIGURATION_SHARE = '/configuration/center/share/' | |
11 | + CONFIGURATION_SHARE = '/configuration/center/share/', | |
12 | + BASEORIGINATION = '/organization/me/list/', | |
13 | + VIDEO = '/video/list', | |
14 | + DEVICE_PROFILE = '/device_profile/me/list', | |
15 | + DEVICE = '/device', | |
16 | + VIDEOURL = '/video/url/', | |
17 | + GEOJSONURL = '/map/geo_json/', | |
18 | + DEVICE_URL = '/device', | |
19 | + GET_ATTRBUTELIST = '/device/attributes/' | |
12 | 20 | } |
13 | 21 | |
14 | 22 | export const getDictItemByCode = (value: string) => { |
... | ... | @@ -46,10 +54,76 @@ export const getPlatformInfo = () => defHttp.get({ url: Api.PLATFORM }) |
46 | 54 | export const getConfigurationList = (params: object) => { |
47 | 55 | return defHttp.get({ url: `${Api.CONFIGURATION}`, params }) |
48 | 56 | } |
57 | + | |
49 | 58 | //组态设置是否公开或私有 |
50 | -export const setConfigurationIsShare = (params: string,isShare:boolean,data:object) => { | |
59 | +export const setConfigurationIsShare = (params: string, isShare: boolean, data: object) => { | |
51 | 60 | return defHttp.post({ |
52 | 61 | url: `${Api.CONFIGURATION_SHARE}${params}?isShare=${isShare}`, |
53 | 62 | data |
54 | 63 | }) |
55 | 64 | } |
65 | + | |
66 | +// 获取设备状态,在线 or 离线时间 | |
67 | +export const getDeviceActiveTime = (entityId: string) => { | |
68 | + return defHttp.get( | |
69 | + { | |
70 | + url: `/plugins/telemetry/DEVICE/${entityId}/values/attributes?keys=active` | |
71 | + }, | |
72 | + { | |
73 | + joinPrefix: false | |
74 | + } | |
75 | + ) | |
76 | +} | |
77 | + | |
78 | +//获取组织列表 | |
79 | +export const getOrganizationList = (params?: OrganizationListItem) => | |
80 | + defHttp.get({ | |
81 | + url: Api.BASEORIGINATION, | |
82 | + params | |
83 | + }) | |
84 | + | |
85 | +//获取视频列表 | |
86 | +export const getVideoList = (params?: object) => | |
87 | + defHttp.get({ | |
88 | + url: Api.VIDEO, | |
89 | + params | |
90 | + }) | |
91 | + | |
92 | +//获取产品列表 | |
93 | +export const getProfileList = (params?: object) => | |
94 | + defHttp.get({ | |
95 | + url: Api.DEVICE_PROFILE, | |
96 | + params | |
97 | + }) | |
98 | + | |
99 | +//获取设备列表 | |
100 | +export const getDeviceList = (params: any, data?: object) => | |
101 | + defHttp.post({ | |
102 | + url: Api.DEVICE, | |
103 | + params, | |
104 | + data | |
105 | + }) | |
106 | + | |
107 | +//获取平台视频流播放地址 | |
108 | +export const getVideoUrl = (id: string) => | |
109 | + defHttp.get({ | |
110 | + url: `${Api.VIDEOURL}${id}` | |
111 | + }) | |
112 | + | |
113 | +//获取行政区域 | |
114 | +export const getGeoJsonMap = (code: number, level: string) => | |
115 | + defHttp.get({ | |
116 | + url: `${Api.GEOJSONURL}${code}/${level}` | |
117 | + }) | |
118 | + | |
119 | +// 获取设备详情 | |
120 | +export const getDeviceDetail = (id: string) => | |
121 | + defHttp.get({ | |
122 | + url: Api.DEVICE_URL + `/${id}` | |
123 | + }) | |
124 | + | |
125 | +// 获取产品属性 | |
126 | +export const getAttribute = (deviceProfileId: string) => | |
127 | + defHttp.get({ | |
128 | + url: `${Api.GET_ATTRBUTELIST}${deviceProfileId}` | |
129 | + }) | ... | ... |
... | ... | @@ -91,7 +91,7 @@ export const saveDataViewList = (data: object) => { |
91 | 91 | */ |
92 | 92 | export const uploadFile = async (file: FormData, mode: ErrorMessageMode = 'modal') => { |
93 | 93 | return defHttp.post( |
94 | - { url: Api.FILE_UPLOAD, params: file }, | |
94 | + { url: Api.FILE_UPLOAD, params: file}, | |
95 | 95 | { |
96 | 96 | errorMessageMode: mode |
97 | 97 | } | ... | ... |
... | ... | @@ -16,7 +16,7 @@ import { PaginationResult } from '/#/external/axios'; |
16 | 16 | DEVICE_ATTR_LIST = '/device/attributes', |
17 | 17 | GET_PUBLIC_INTERFACE_ALL = '/data_view_interface/find/can_use_interfaces', |
18 | 18 | //ft |
19 | - GET_PUBLIC_INTERFACE_DETAIL = '/data_view_interface/get_interface_details' | |
19 | + GET_PUBLIC_INTERFACE_DETAIL = '/data_view_interface/get_interface_details' | |
20 | 20 | } |
21 | 21 | |
22 | 22 | export const getPublicInterface = async (params: Record<'page' | 'pageSize', number>) => { |
... | ... | @@ -26,9 +26,10 @@ export const getPublicInterface = async (params: Record<'page' | 'pageSize', num |
26 | 26 | }) |
27 | 27 | } |
28 | 28 | |
29 | -export const getOrgList = async () => { | |
29 | +export const getOrgList = async (params:object) => { | |
30 | 30 | return defHttp.get({ |
31 | - url: Api.ORG_LISt | |
31 | + url: Api.ORG_LISt, | |
32 | + params | |
32 | 33 | }) |
33 | 34 | } |
34 | 35 | ... | ... |
src/assets/images/chart/charts/bar_line.png
0 → 100644
25.9 KB
src/assets/images/chart/charts/map3D.png
0 → 100644
100 KB
6.02 KB
13.7 KB
22.2 KB
... | ... | @@ -75,7 +75,7 @@ export const useChartDataFetch = ( |
75 | 75 | echartsUpdateHandle(value) |
76 | 76 | // 更新回调函数 |
77 | 77 | if (updateCallback) { |
78 | - updateCallback(value) | |
78 | + updateCallback(value, targetComponent.request)//为了处理设备最新数据轮播列表,这里传过去当前组件的信息 | |
79 | 79 | } |
80 | 80 | } catch (error) { |
81 | 81 | console.error(error) |
... | ... | @@ -117,8 +117,10 @@ export const useChartDataFetch = ( |
117 | 117 | * 如果是分组并且绑定了ws |
118 | 118 | */ |
119 | 119 | chartEditStore.componentList.forEach((item:CreateComponentType | CreateComponentGroupType)=>{ |
120 | - if(item.request.requestUrl?.includes('ws')){ | |
121 | - initial(item, useChartEditStore, updateCallback) | |
120 | + if(item.isGroup){ | |
121 | + if(item.request.requestUrl?.includes('ws')){ | |
122 | + initial(item, useChartEditStore, updateCallback) | |
123 | + } | |
122 | 124 | } |
123 | 125 | }) |
124 | 126 | // | ... | ... |
1 | 1 | import { RequestContentTypeEnum } from "@/enums/external/httpEnum"; |
2 | 2 | import { CreateComponentType } from "@/packages/index.d"; |
3 | -import { useSocketStore} from "@/store/external/modules/socketStore"; | |
4 | -import { SocketReceiveMessageType,SocketSendMessageType } from "@/store/external/modules/socketStore.d"; | |
3 | +import { useSocketStore } from "@/store/external/modules/socketStore"; | |
4 | +import { SocketReceiveMessageType, SocketSendMessageType } from "@/store/external/modules/socketStore.d"; | |
5 | 5 | import { useChartEditStore } from "@/store/modules/chartEditStore/chartEditStore"; |
6 | 6 | import { getJwtToken, getShareJwtToken } from "@/utils/external/auth"; |
7 | 7 | import { useWebSocket, WebSocketResult } from "@vueuse/core"; |
8 | -import {onMounted, ref, unref} from "vue"; | |
8 | +import { onMounted, ref, unref } from "vue"; | |
9 | 9 | import { ExtraRequestConfigType } from "@/store/external/modules/extraComponentInfo.d"; |
10 | 10 | import { isShareMode } from "@/views/share/hook"; |
11 | 11 | |
... | ... | @@ -16,14 +16,14 @@ interface SocketConnectionPoolType { |
16 | 16 | url: string |
17 | 17 | } |
18 | 18 | |
19 | -interface SaveHistoryWsMessage{ | |
19 | +interface SaveHistoryWsMessage { | |
20 | 20 | id: string |
21 | 21 | message: SocketSendMessageType |
22 | 22 | } |
23 | 23 | |
24 | 24 | const socketConnectionPool: SocketConnectionPoolType[] = [] |
25 | 25 | |
26 | -const saveHistoryWsMessage=ref([] as SaveHistoryWsMessage[]) | |
26 | +const saveHistoryWsMessage = ref([] as SaveHistoryWsMessage[]) | |
27 | 27 | |
28 | 28 | const parse = (value: string) => { |
29 | 29 | try { |
... | ... | @@ -48,7 +48,7 @@ const getSocketInstance = (request: ExtraRequestConfigType) => { |
48 | 48 | if (~index) { |
49 | 49 | return socketConnectionPool[index].ws |
50 | 50 | } |
51 | - const token = isShareMode() ? getShareJwtToken() : getJwtToken() | |
51 | + const token = isShareMode() ? getShareJwtToken() : getJwtToken() | |
52 | 52 | const socketUrl = `${getOriginUrl(requestOriginUrl || '')}${requestUrl}?token=${token}` |
53 | 53 | |
54 | 54 | const instance = useWebSocket(socketUrl, { |
... | ... | @@ -112,18 +112,18 @@ export const useChartDataSocket = () => { |
112 | 112 | * @param targetComponent |
113 | 113 | */ |
114 | 114 | //删除组件 发送断开消息 |
115 | - const disconnectWs=(targetComponent:CreateComponentType)=>{ | |
116 | - try{ | |
117 | - saveHistoryWsMessage.value.forEach((item)=>{ | |
118 | - if(item.id===targetComponent.id){ | |
115 | + const disconnectWs = (targetComponent: CreateComponentType) => { | |
116 | + try { | |
117 | + saveHistoryWsMessage.value.forEach((item) => { | |
118 | + if (item.id === targetComponent.id) { | |
119 | 119 | const { request } = unref(targetComponent) |
120 | - if((request.requestContentType as RequestContentTypeEnum) !== RequestContentTypeEnum.WEB_SOCKET)return | |
120 | + if ((request.requestContentType as RequestContentTypeEnum) !== RequestContentTypeEnum.WEB_SOCKET) return | |
121 | 121 | const { send } = getSocketInstance(request) |
122 | - item.message.tsSubCmds[0].unsubscribe=true | |
122 | + item.message.tsSubCmds[0].unsubscribe = true | |
123 | 123 | send(JSON.stringify(item.message)) |
124 | - } | |
125 | - }) | |
126 | - }catch (e) { | |
124 | + } | |
125 | + }) | |
126 | + } catch (e) { | |
127 | 127 | console.log(` |
128 | 128 | 错误位置:src/hooks/external/useChartDataSocket.ts |
129 | 129 | 错误原因:${e} | ... | ... |
1 | +/** | |
2 | + * 特殊处理单设备-属性-历史数据查询组件联动 | |
3 | + */ | |
4 | +import { toRefs } from 'vue' | |
5 | +import { CreateComponentType } from '@/packages/index.d' | |
6 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
7 | + | |
8 | +// 获取类型 | |
9 | +type ChartEditStoreType = typeof useChartEditStore | |
10 | + | |
11 | +// Params 参数修改触发 api 更新图表请求 | |
12 | +export const useChartInteract = ( | |
13 | + chartConfig: CreateComponentType, | |
14 | + useChartEditStore: ChartEditStoreType, | |
15 | + param: { [T: string]: any } | |
16 | +) => { | |
17 | + const chartEditStore = useChartEditStore() | |
18 | + const { id } = chartConfig | |
19 | + const index = chartEditStore.fetchTargetIndex(id) | |
20 | + const { data } = param | |
21 | + if (index === -1) return | |
22 | + chartEditStore.getComponentList.forEach(targetItem => { | |
23 | + if (targetItem.id === id) { | |
24 | + const { Params } = toRefs(targetItem.request.requestParams) | |
25 | + if (Reflect.get(param.data, 'agg') !== 'NONE') { | |
26 | + Reflect.deleteProperty(Params.value, 'limit') | |
27 | + } | |
28 | + Object.keys(data).forEach((item: string) => { | |
29 | + Params.value[item] = data[item] | |
30 | + }) | |
31 | + } | |
32 | + }) | |
33 | +} | ... | ... |
... | ... | @@ -45,16 +45,25 @@ export const useChartInteract = ( |
45 | 45 | }) |
46 | 46 | } else { |
47 | 47 | if (targetItem.id === item.interactComponentId) { |
48 | - const { Params, Header } = toRefs(targetItem.request.requestParams) | |
49 | - Object.keys(item.interactFn).forEach(key => { | |
50 | - if (Params.value[key]) { | |
51 | - Params.value[key] = param[item.interactFn[key]] | |
48 | + const { Params, Header } = toRefs(targetItem.request.requestParams) | |
49 | + //特殊处理 只针对两个下拉选择器,一个是产品下拉,一个是设备下拉,选择了产品,设备列表选择值清空 | |
50 | + if (targetItem.chartConfig.title.includes('设备列表下拉选择器')) { | |
51 | + if (window.location.href.includes('preview')) { | |
52 | + if (window.sessionStorage.getItem('deviceProfileSelectStatus') === 'selected') { | |
53 | + targetItem.option.selectValue = "'" | |
52 | 54 | } |
53 | - if (Header.value[key]) { | |
54 | - Header.value[key] = param[item.interactFn[key]] | |
55 | - } | |
56 | - }) | |
55 | + } | |
57 | 56 | } |
57 | + // | |
58 | + Object.keys(item.interactFn).forEach(key => { | |
59 | + if (Params.value[key]) { | |
60 | + Params.value[key] = param[item.interactFn[key]] | |
61 | + } | |
62 | + if (Header.value[key]) { | |
63 | + Header.value[key] = param[item.interactFn[key]] | |
64 | + } | |
65 | + }) | |
66 | + } | |
58 | 67 | } |
59 | 68 | // |
60 | 69 | }) | ... | ... |
... | ... | @@ -48,10 +48,10 @@ export const useLifeHandler = (chartConfig: CreateComponentType | CreateComponen |
48 | 48 | try { |
49 | 49 | return new Function(` |
50 | 50 | return ( |
51 | - async function(mouseEvent){ | |
51 | + async function(components,mouseEvent){ | |
52 | 52 | ${fnStr} |
53 | 53 | } |
54 | - )`)() | |
54 | + )`)().bind(undefined,components) | |
55 | 55 | } catch (error) { |
56 | 56 | console.error(error) |
57 | 57 | } | ... | ... |
... | ... | @@ -19,8 +19,9 @@ |
19 | 19 | import { LayoutHeader } from '@/layout/components/LayoutHeader' |
20 | 20 | /** |
21 | 21 | * THINGS_KIT 升级版本这里有冲突 |
22 | - * 源文件 '@/components/GoUserInfo' | |
23 | - * 修改后 '@/components/external/GoUserInfo' | |
22 | + * 修改引入路径 | |
23 | + * 源文件路径 import { GoUserInfo } from '@/components/GoUserInfo' | |
24 | + * 修改后路径 import { GoUserInfo } from '@/components/external/GoUserInfo' | |
24 | 25 | */ |
25 | 26 | import { GoUserInfo } from '@/components/external/GoUserInfo' |
26 | 27 | </script> | ... | ... |
1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
2 | +import { BarLineConfig } from './index' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | +import dataJson from './data.json' | |
6 | + | |
7 | +export const includes = ['legend', 'xAxis', 'yAxis', 'grid'] | |
8 | +// 柱状折线组合图 分别定义series | |
9 | +// 写死name可以定义legend显示的名称 | |
10 | +export const barSeriesItem = { | |
11 | + type: 'bar', | |
12 | + barWidth: 15, | |
13 | + label: { | |
14 | + show: true, | |
15 | + position: 'top', | |
16 | + color: '#fff', | |
17 | + fontSize: 12 | |
18 | + }, | |
19 | + itemStyle: { | |
20 | + color: null, | |
21 | + borderRadius: 2 | |
22 | + } | |
23 | +} | |
24 | + | |
25 | +export const lineSeriesItem = { | |
26 | + type: 'line', | |
27 | + symbol: 'circle', | |
28 | + label: { | |
29 | + show: true, | |
30 | + position: 'top', | |
31 | + color: '#fff', | |
32 | + fontSize: 12 | |
33 | + }, | |
34 | + symbolSize: 5, //设定实心点的大小 | |
35 | + itemStyle: { | |
36 | + color: '#FFE47A', | |
37 | + borderWidth: 1 | |
38 | + }, | |
39 | + lineStyle: { | |
40 | + type: 'solid', | |
41 | + width: 3, | |
42 | + color: null | |
43 | + } | |
44 | +} | |
45 | + | |
46 | +export const option = { | |
47 | + tooltip: { | |
48 | + show: true, | |
49 | + trigger: 'axis', | |
50 | + axisPointer: { | |
51 | + show: true, | |
52 | + type: 'shadow' | |
53 | + } | |
54 | + }, | |
55 | + legend: { | |
56 | + data: null | |
57 | + }, | |
58 | + xAxis: { | |
59 | + show: true, | |
60 | + type: 'category' | |
61 | + }, | |
62 | + yAxis: { | |
63 | + show: true, | |
64 | + type: 'value' | |
65 | + }, | |
66 | + dataset: { ...dataJson }, | |
67 | + series: [barSeriesItem, lineSeriesItem] | |
68 | +} | |
69 | + | |
70 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
71 | + public key = BarLineConfig.key | |
72 | + public chartConfig = cloneDeep(BarLineConfig) | |
73 | + // 图表配置项 | |
74 | + public option = echartOptionProfixHandle(option, includes) | |
75 | +} | ... | ... |
1 | +<template> | |
2 | + <!-- Echarts 全局设置 --> | |
3 | + <global-setting :optionData="optionData"></global-setting> | |
4 | + <CollapseItem | |
5 | + v-for="(item, index) in seriesList" | |
6 | + :key="index" | |
7 | + :name="`${item.type == 'bar' ? '柱状图' : '折线图'}`" | |
8 | + :expanded="true" | |
9 | + > | |
10 | + <SettingItemBox name="图形" v-if="item.type == 'bar'"> | |
11 | + <SettingItem name="宽度"> | |
12 | + <n-input-number | |
13 | + v-model:value="item.barWidth" | |
14 | + :min="1" | |
15 | + :max="100" | |
16 | + size="small" | |
17 | + placeholder="自动计算" | |
18 | + ></n-input-number> | |
19 | + </SettingItem> | |
20 | + <SettingItem name="圆角"> | |
21 | + <n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number> | |
22 | + </SettingItem> | |
23 | + </SettingItemBox> | |
24 | + <SettingItemBox name="线条" v-if="item.type == 'line'"> | |
25 | + <SettingItem name="宽度"> | |
26 | + <n-input-number | |
27 | + v-model:value="item.lineStyle.width" | |
28 | + :min="1" | |
29 | + :max="100" | |
30 | + size="small" | |
31 | + placeholder="自动计算" | |
32 | + ></n-input-number> | |
33 | + </SettingItem> | |
34 | + <SettingItem name="类型"> | |
35 | + <n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select> | |
36 | + </SettingItem> | |
37 | + </SettingItemBox> | |
38 | + <SettingItemBox name="实心点" v-if="item.type == 'line'"> | |
39 | + <SettingItem name="大小"> | |
40 | + <n-input-number | |
41 | + v-model:value="item.symbolSize" | |
42 | + :min="1" | |
43 | + :max="100" | |
44 | + size="small" | |
45 | + placeholder="自动计算" | |
46 | + ></n-input-number> | |
47 | + </SettingItem> | |
48 | + </SettingItemBox> | |
49 | + <setting-item-box name="标签"> | |
50 | + <setting-item> | |
51 | + <n-space> | |
52 | + <n-switch v-model:value="item.label.show" size="small" /> | |
53 | + <n-text>展示标签</n-text> | |
54 | + </n-space> | |
55 | + </setting-item> | |
56 | + <setting-item name="大小"> | |
57 | + <n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number> | |
58 | + </setting-item> | |
59 | + <setting-item name="tip颜色"> | |
60 | + <n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker> | |
61 | + </setting-item> | |
62 | + <setting-item name="位置"> | |
63 | + <n-select | |
64 | + v-model:value="item.label.position" | |
65 | + :options="[ | |
66 | + { label: 'top', value: 'top' }, | |
67 | + { label: 'left', value: 'left' }, | |
68 | + { label: 'right', value: 'right' }, | |
69 | + { label: 'bottom', value: 'bottom' } | |
70 | + ]" | |
71 | + /> | |
72 | + </setting-item> | |
73 | + </setting-item-box> | |
74 | + </CollapseItem> | |
75 | +</template> | |
76 | + | |
77 | +<script setup lang="ts"> | |
78 | +import { PropType, computed } from 'vue' | |
79 | +import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
80 | +import { lineConf } from '@/packages/chartConfiguration/echarts' | |
81 | +import { GlobalThemeJsonType } from '@/settings/chartThemes' | |
82 | + | |
83 | +const props = defineProps({ | |
84 | + optionData: { | |
85 | + type: Object as PropType<GlobalThemeJsonType>, | |
86 | + required: true | |
87 | + } | |
88 | +}) | |
89 | + | |
90 | +const seriesList = computed(() => { | |
91 | + return props.optionData.series | |
92 | +}) | |
93 | +</script> | ... | ... |
1 | +{ | |
2 | + "dimensions": ["product", "data1", "data2"], | |
3 | + "source": [ | |
4 | + { | |
5 | + "product": "1月", | |
6 | + "data1": 104, | |
7 | + "data2": 30 | |
8 | + }, | |
9 | + { | |
10 | + "product": "2月", | |
11 | + "data1": 56, | |
12 | + "data2": 56 | |
13 | + }, | |
14 | + { | |
15 | + "product": "3月", | |
16 | + "data1": 136, | |
17 | + "data2": 36 | |
18 | + }, | |
19 | + { | |
20 | + "product": "4月", | |
21 | + "data1": 86, | |
22 | + "data2": 6 | |
23 | + }, | |
24 | + { | |
25 | + "product": "5月", | |
26 | + "data1": 98, | |
27 | + "data2": 10 | |
28 | + }, | |
29 | + { | |
30 | + "product": "6月", | |
31 | + "data1": 86, | |
32 | + "data2": 70 | |
33 | + }, | |
34 | + { | |
35 | + "product": "7月", | |
36 | + "data1": 77, | |
37 | + "data2": 57 | |
38 | + } | |
39 | + ] | |
40 | +} | ... | ... |
1 | +// 公共类型声明 | |
2 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
3 | +// 当前[信息模块]分类声明 | |
4 | +import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d' | |
5 | + | |
6 | +export const BarLineConfig: ConfigType = { | |
7 | + key: 'BarLine', | |
8 | + chartKey: 'VBarLine', | |
9 | + conKey: 'VCBarLine', | |
10 | + title: '柱状图 & 折线图', | |
11 | + category: ChatCategoryEnum.BAR, | |
12 | + categoryName: ChatCategoryEnumName.BAR, | |
13 | + package: PackagesCategoryEnum.CHARTS, | |
14 | + chartFrame: ChartFrameEnum.ECHARTS, | |
15 | + image: 'bar_line.png' | |
16 | +} | |
\ No newline at end of file | ... | ... |
1 | +<template> | |
2 | + <v-chart | |
3 | + ref="vChartRef" | |
4 | + :init-options="initOptions" | |
5 | + :theme="themeColor" | |
6 | + :option="option" | |
7 | + :manual-update="isPreview()" | |
8 | + autoresize | |
9 | + ></v-chart> | |
10 | +</template> | |
11 | + | |
12 | +<script setup lang="ts"> | |
13 | +import { ref, computed, watch, PropType, nextTick } from 'vue' | |
14 | +import VChart from 'vue-echarts' | |
15 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | |
16 | +import { use } from 'echarts/core' | |
17 | +import { CanvasRenderer } from 'echarts/renderers' | |
18 | +//引入柱状图 折线图 | |
19 | +import { BarChart, LineChart } from 'echarts/charts' | |
20 | +import config, { includes, barSeriesItem, lineSeriesItem } from './config' | |
21 | +import { mergeTheme } from '@/packages/public/chart' | |
22 | +import { useChartDataFetch } from '@/hooks' | |
23 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
24 | +import { isPreview } from '@/utils' | |
25 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | |
26 | + | |
27 | +const props = defineProps({ | |
28 | + themeSetting: { | |
29 | + type: Object, | |
30 | + required: true | |
31 | + }, | |
32 | + themeColor: { | |
33 | + type: Object, | |
34 | + required: true | |
35 | + }, | |
36 | + chartConfig: { | |
37 | + type: Object as PropType<config>, | |
38 | + required: true | |
39 | + } | |
40 | +}) | |
41 | + | |
42 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | |
43 | + | |
44 | +use([DatasetComponent, CanvasRenderer, BarChart, LineChart, GridComponent, TooltipComponent, LegendComponent]) | |
45 | + | |
46 | +const replaceMergeArr = ref<string[]>() | |
47 | + | |
48 | +const option = computed(() => { | |
49 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
50 | +}) | |
51 | + | |
52 | +watch( | |
53 | + () => props.chartConfig.option.dataset, | |
54 | + (newData, oldData) => { | |
55 | + if (newData.dimensions.length !== oldData.dimensions.length) { | |
56 | + const seriesArr = [] | |
57 | + for (let i = 0; i < newData.dimensions.length - 1; i++) { | |
58 | + seriesArr.push(barSeriesItem, lineSeriesItem) | |
59 | + } | |
60 | + replaceMergeArr.value = ['series'] | |
61 | + props.chartConfig.option.series = seriesArr | |
62 | + nextTick(() => { | |
63 | + replaceMergeArr.value = [] | |
64 | + }) | |
65 | + } | |
66 | + }, | |
67 | + { | |
68 | + deep: false | |
69 | + } | |
70 | +) | |
71 | + | |
72 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore) | |
73 | +</script> | ... | ... |
1 | 1 | import { BarCommonConfig } from './BarCommon/index' |
2 | 2 | import { BarCrossrangeConfig } from './BarCrossrange/index' |
3 | 3 | import { CapsuleChartConfig } from './CapsuleChart/index' |
4 | +import { BarLineConfig } from './BarLine/index' | |
4 | 5 | |
5 | -export default [BarCommonConfig, BarCrossrangeConfig, CapsuleChartConfig] | |
6 | +export default [BarCommonConfig, BarCrossrangeConfig, BarLineConfig, CapsuleChartConfig] | ... | ... |
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | :type="type" |
4 | 4 | :height="h" |
5 | 5 | :processing="processing" |
6 | - :percentage="option.dataset" | |
6 | + :percentage="dataset" | |
7 | 7 | :indicator-placement="indicatorPlacement" |
8 | 8 | :color="color" |
9 | 9 | :rail-color="railColor" |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | fontSize: `${indicatorTextSize}px` |
16 | 16 | }" |
17 | 17 | > |
18 | - {{ option.dataset }} {{ unit }} | |
18 | + {{ dataset }} {{ unit }} | |
19 | 19 | </n-text> |
20 | 20 | </n-progress> |
21 | 21 | </template> | ... | ... |
1 | +import { PublicConfigClass } from '@/packages/public' | |
2 | +import { CreateComponentType } from '@/packages/index.d' | |
3 | +import { chartInitConfig } from '@/settings/designSetting' | |
4 | +import { FullScreenConfig } from './index' | |
5 | +import cloneDeep from 'lodash/cloneDeep' | |
6 | + | |
7 | +export const option = { | |
8 | + border: 6, | |
9 | + bgColor: '#84a5e9', | |
10 | + borderColor: '#84a5e9' | |
11 | +} | |
12 | + | |
13 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
14 | + public key = FullScreenConfig.key | |
15 | + public attr = { ...chartInitConfig, w: 150, h: 150, zIndex: -1 } | |
16 | + public chartConfig = cloneDeep(FullScreenConfig) | |
17 | + public option = cloneDeep(option) | |
18 | +} | ... | ... |
1 | +<template> | |
2 | + <CollapseItem name="全屏按钮" expanded> | |
3 | + <SettingItemBox name="按钮"> | |
4 | + <SettingItem name="背景色"> | |
5 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.bgColor"></n-color-picker> | |
6 | + </SettingItem> | |
7 | + <SettingItem name="边框色"> | |
8 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.borderColor"></n-color-picker> | |
9 | + </SettingItem> | |
10 | + <SettingItem name="边框大小"> | |
11 | + <n-input-number v-model:value="optionData.border" size="small" :step="0.5" :min="0"></n-input-number> | |
12 | + </SettingItem> | |
13 | + </SettingItemBox> | |
14 | + </CollapseItem> | |
15 | +</template> | |
16 | + | |
17 | +<script setup lang="ts"> | |
18 | +import { PropType } from 'vue' | |
19 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
20 | +import { option } from './config' | |
21 | + | |
22 | +const props = defineProps({ | |
23 | + optionData: { | |
24 | + type: Object as PropType<typeof option>, | |
25 | + required: true | |
26 | + } | |
27 | +}) | |
28 | +</script> | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
3 | + | |
4 | +export const FullScreenConfig: ConfigType = { | |
5 | + key: 'FullScreen', | |
6 | + chartKey: 'VFullScreen', | |
7 | + conKey: 'VCFullScreen', | |
8 | + title: '全屏按钮', | |
9 | + category: ChatCategoryEnum.MORE, | |
10 | + categoryName: ChatCategoryEnumName.MORE, | |
11 | + package: PackagesCategoryEnum.DECORATES, | |
12 | + chartFrame: ChartFrameEnum.STATIC, | |
13 | + image: 'fullScreen.png' | |
14 | +} | ... | ... |
1 | +<template> | |
2 | + <svg @click="toggleFullscreen" v-if="!isFullscreen" viewBox="0 0 1024 1024"> | |
3 | + <path | |
4 | + d="M665.6 1017.6c-19.2 0-38.4-19.2-38.4-38.4s19.2-38.4 38.4-38.4h268.8l6.4-268.8c0-19.2 19.2-38.4 38.4-38.4s38.4 19.2 38.4 38.4v294.4c0 32-25.6 51.2-51.2 51.2h-300.8zM51.2 396.8c-19.2 0-38.4-19.2-38.4-38.4V64C12.8 32 38.4 12.8 64 12.8h294.4c19.2 0 38.4 19.2 38.4 38.4s-19.2 38.4-38.4 38.4H89.6v268.8c0 19.2-19.2 38.4-38.4 38.4zM64 1017.6c-32 0-51.2-25.6-51.2-51.2v-294.4c0-19.2 19.2-38.4 38.4-38.4s38.4 19.2 38.4 38.4v217.6l198.4-198.4c6.4-6.4 19.2-12.8 25.6-12.8s19.2 6.4 25.6 12.8c6.4 6.4 12.8 19.2 12.8 25.6 0 12.8-6.4 19.2-12.8 25.6l-198.4 198.4h217.6c19.2 0 38.4 19.2 38.4 38.4s-19.2 38.4-38.4 38.4H64z m915.2-620.8c-19.2 0-38.4-19.2-38.4-38.4V140.8l-198.4 198.4c-6.4 6.4-19.2 12.8-25.6 12.8-12.8 0-19.2-6.4-25.6-12.8-12.8-12.8-12.8-38.4 0-51.2l198.4-198.4h-217.6c-19.2 0-38.4-19.2-38.4-38.4s19.2-38.4 38.4-38.4h294.4c32 0 51.2 25.6 51.2 51.2v294.4c0 19.2-19.2 38.4-38.4 38.4z" | |
5 | + class="fullScreen-border" | |
6 | + ></path> | |
7 | + </svg> | |
8 | + <svg @click="toggleFullscreen" v-else viewBox="0 0 1024 1024"> | |
9 | + <path | |
10 | + d="M379.336 697.237L153.362 921.55c-14.11 14.007-36.905 13.922-50.912-0.188-14.007-14.11-13.922-36.905 0.188-50.912l227.6-225.927H138.645c-18.99 0-34.385-15.446-34.385-34.5 0-19.053 15.395-34.5 34.385-34.5H413.72c18.99 0 34.384 15.447 34.384 34.5v276c0 9.15-3.622 17.926-10.07 24.396a34.326 34.326 0 0 1-24.314 10.104 34.326 34.326 0 0 1-24.314-10.104 34.559 34.559 0 0 1-10.071-24.396V697.237z m263.395-366.88l227.813-227.813c14.059-14.059 36.853-14.059 50.912 0 14.059 14.059 14.059 36.853 0 50.912l-225.18 225.18h187.147c18.99 0 34.385 15.445 34.385 34.5 0 19.053-15.395 34.5-34.385 34.5H608.346c-18.99 0-34.384-15.447-34.384-34.5v-276c0-9.15 3.622-17.926 10.07-24.396a34.326 34.326 0 0 1 24.314-10.105c9.12 0 17.865 3.635 24.314 10.105a34.559 34.559 0 0 1 10.07 24.395v193.223zM99.385 410a34.326 34.326 0 0 1-24.314-10.105A34.559 34.559 0 0 1 65 375.5v-276C65 80.446 80.395 65 99.385 65h275.077c18.99 0 34.384 15.446 34.384 34.5 0 19.054-15.394 34.5-34.384 34.5H133.769v241.5c0 9.15-3.622 17.925-10.07 24.395A34.326 34.326 0 0 1 99.384 410z m825.23 552H649.538c-18.99 0-34.384-15.446-34.384-34.5 0-19.054 15.394-34.5 34.384-34.5h240.693V651.5c0-19.054 15.394-34.5 34.384-34.5 18.99 0 34.385 15.446 34.385 34.5v276c0 19.054-15.395 34.5-34.385 34.5z" | |
11 | + class="fullScreen-border" | |
12 | + ></path> | |
13 | + </svg> | |
14 | +</template> | |
15 | + | |
16 | +<script setup lang="ts"> | |
17 | +import { PropType, toRefs, ref, onMounted, onUnmounted } from 'vue' | |
18 | +import { CreateComponentType } from '@/packages/index.d' | |
19 | +import { option } from './config' | |
20 | + | |
21 | +const props = defineProps({ | |
22 | + chartConfig: { | |
23 | + type: Object as PropType<CreateComponentType & typeof option>, | |
24 | + required: true | |
25 | + } | |
26 | +}) | |
27 | + | |
28 | +let { border, bgColor, borderColor } = toRefs(props.chartConfig.option) | |
29 | +const isFullscreen = ref(false) | |
30 | +const checkFullscreen = () => { | |
31 | + isFullscreen.value = !!( | |
32 | + document.fullscreenElement || | |
33 | + (document as any).webkitFullscreenElement || | |
34 | + (document as any).mozFullScreenElement || | |
35 | + (document as any).msFullscreenElement | |
36 | + ) | |
37 | +} | |
38 | +checkFullscreen() | |
39 | + | |
40 | +const requestFullscreen = (element: Element) => { | |
41 | + if (element.requestFullscreen) { | |
42 | + element.requestFullscreen() | |
43 | + } else if ((document as any).mozRequestFullScreen) { | |
44 | + /* Firefox */ | |
45 | + (document as any).mozRequestFullScreen() | |
46 | + } else if ((document as any).webkitRequestFullscreen) { | |
47 | + /* Chrome, Safari and Opera */ | |
48 | + (document as any).webkitRequestFullscreen() | |
49 | + } else if ((document as any).msRequestFullscreen) { | |
50 | + /* IE/Edge */ | |
51 | + (document as any).msRequestFullscreen() | |
52 | + } | |
53 | +} | |
54 | + | |
55 | +const exitFullscreen = () => { | |
56 | + if (document.fullscreenElement && document.exitFullscreen) { | |
57 | + document.exitFullscreen() | |
58 | + } else if ((document as any).mozFullScreenElement && (document as any).mozCancelFullScreen) { | |
59 | + /* Firefox */ | |
60 | + (document as any).mozCancelFullScreen() | |
61 | + } else if ((document as any).webkitFullscreenElement && (document as any).webkitExitFullscreen) { | |
62 | + /* Chrome, Safari and Opera */ | |
63 | + (document as any).webkitExitFullscreen() | |
64 | + } else if ((document as any).msFullscreenElement && (document as any).msExitFullscreen) { | |
65 | + /* IE/Edge */ | |
66 | + (document as any).msExitFullscreen() | |
67 | + } | |
68 | +} | |
69 | + | |
70 | +const toggleFullscreen = () => { | |
71 | + if (!isFullscreen.value) { | |
72 | + requestFullscreen(document.documentElement) | |
73 | + } else { | |
74 | + exitFullscreen() | |
75 | + } | |
76 | + isFullscreen.value = !isFullscreen.value | |
77 | + // 由于全屏状态的改变不会立即生效,所以需要延迟一段时间再去获取全屏状态 | |
78 | + setTimeout(() => { | |
79 | + checkFullscreen() | |
80 | + }, 1000) | |
81 | +} | |
82 | + | |
83 | +// 监听全屏状态的改变,保证多个全屏组件的状态一致 | |
84 | +onMounted(() => { | |
85 | + document.addEventListener('fullscreenchange', checkFullscreen) | |
86 | + document.addEventListener('webkitfullscreenchange', checkFullscreen) | |
87 | + document.addEventListener('mozfullscreenchange', checkFullscreen) | |
88 | + document.addEventListener('MSFullscreenChange', checkFullscreen) | |
89 | +}) | |
90 | + | |
91 | +onUnmounted(() => { | |
92 | + document.removeEventListener('fullscreenchange', checkFullscreen) | |
93 | + document.removeEventListener('webkitfullscreenchange', checkFullscreen) | |
94 | + document.removeEventListener('mozfullscreenchange', checkFullscreen) | |
95 | + document.removeEventListener('MSFullscreenChange', checkFullscreen) | |
96 | +}) | |
97 | +</script> | |
98 | + | |
99 | +<style lang="scss" scoped> | |
100 | +svg { | |
101 | + display: block; | |
102 | + width: 100%; | |
103 | + height: 100%; | |
104 | + cursor: pointer; | |
105 | +} | |
106 | +.fullScreen-border { | |
107 | + stroke: v-bind('borderColor'); | |
108 | + stroke-width: v-bind('border+"px"'); | |
109 | + fill: v-bind('bgColor'); | |
110 | +} | |
111 | +</style> | ... | ... |
1 | 1 | import { NumberConfig } from './Number/index' |
2 | 2 | import { TimeCommonConfig } from './TimeCommon/index' |
3 | 3 | import { ClockConfig } from './Clock/index' |
4 | +import { FullScreenConfig } from './FullScreen/index' | |
4 | 5 | import { CountDownConfig } from './CountDown/index' |
5 | 6 | import { FlipperNumberConfig } from './FlipperNumber' |
6 | 7 | import { PipelineHConfig } from './PipelineH/index' |
7 | 8 | import { PipelineVConfig } from './PipelineV/index' |
8 | 9 | |
9 | -export default [NumberConfig, FlipperNumberConfig, TimeCommonConfig, CountDownConfig, ClockConfig, PipelineHConfig, PipelineVConfig] | |
10 | +export default [ | |
11 | + NumberConfig, | |
12 | + FlipperNumberConfig, | |
13 | + TimeCommonConfig, | |
14 | + CountDownConfig, | |
15 | + ClockConfig, | |
16 | + FullScreenConfig, | |
17 | + PipelineHConfig, | |
18 | + PipelineVConfig | |
19 | +] | ... | ... |
... | ... | @@ -27,21 +27,12 @@ export class Basic { |
27 | 27 | |
28 | 28 | this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000) |
29 | 29 | this.camera.position.set(0, 30, -250) |
30 | - /** | |
31 | - * 这里升级版本有冲突,升级版本可以使用这里的代码,官方还未修复 | |
32 | - * 修复官方Threejs生成缩略图无效问题,少加一个Threejs配置 | |
33 | - * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | |
34 | - * 源代码 属于新增代码,源代码无 | |
35 | - * 修改后代码 preserveDrawingBuffer: true //缩略图生效需开启 | |
36 | - * | |
37 | - */ | |
38 | 30 | this.renderer = new THREE.WebGLRenderer({ |
39 | 31 | // canvas: this.dom, |
40 | 32 | alpha: true, // 透明 |
41 | 33 | antialias: true, // 抗锯齿 |
42 | - preserveDrawingBuffer: true//缩略图生效需开启 | |
34 | + preserveDrawingBuffer: true | |
43 | 35 | }) |
44 | - //ft | |
45 | 36 | this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比 |
46 | 37 | this.renderer.setSize(window.innerWidth, window.innerHeight) // 设置渲染器宽高 |
47 | 38 | this.dom.appendChild(this.renderer.domElement) // 添加到dom中 | ... | ... |
1 | +import cloneDeep from 'lodash/cloneDeep' | |
2 | +import { PublicConfigClass } from '@/packages/public' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import { chartInitConfig } from '@/settings/designSetting' | |
5 | +import { COMPONENT_INTERACT_EVENT_KET } from '@/enums/eventEnum' | |
6 | +import { interactActions, ComponentInteractEventEnum } from './interact' | |
7 | +import {InputsInputConfig} from "./index"; | |
8 | + | |
9 | +export const option = { | |
10 | + // 时间组件展示类型,必须和 interactActions 中定义的数据一致 | |
11 | + [COMPONENT_INTERACT_EVENT_KET]: ComponentInteractEventEnum.DATA, | |
12 | + // 默认值 | |
13 | + inputValue: "0", | |
14 | + // 暴露配置内容给用户 | |
15 | + dataset: "" | |
16 | +} | |
17 | + | |
18 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
19 | + public key = InputsInputConfig.key | |
20 | + public attr = { ...chartInitConfig, w: 260, h: 32, zIndex: -1 } | |
21 | + public chartConfig = cloneDeep(InputsInputConfig) | |
22 | + public interactActions = interactActions | |
23 | + public option = cloneDeep(option) | |
24 | +} | |
\ No newline at end of file | ... | ... |
1 | +<template> | |
2 | + <collapse-item name="输入框配置" :expanded="true"> | |
3 | + <setting-item-box name="默认值" :alone="true"> | |
4 | + <n-input v-model:value="optionData.dataset" placeholder="若未输入,则默认值为0"/> | |
5 | + </setting-item-box> | |
6 | + </collapse-item> | |
7 | +</template> | |
8 | +<script setup lang="ts"> | |
9 | +import { PropType } from 'vue' | |
10 | +import { CollapseItem, SettingItemBox } from '@/components/Pages/ChartItemSetting' | |
11 | +import { option } from './config' | |
12 | +defineProps({ | |
13 | + optionData: { | |
14 | + type: Object as PropType<typeof option>, | |
15 | + required: true | |
16 | + } | |
17 | +}) | |
18 | +</script> | |
\ No newline at end of file | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
3 | + | |
4 | +export const InputsInputConfig: ConfigType = { | |
5 | + key: 'InputsInput', | |
6 | + chartKey: 'VInputsInput', | |
7 | + conKey: 'VCInputsInput', | |
8 | + title: '输入框', | |
9 | + category: ChatCategoryEnum.INPUTS, | |
10 | + categoryName: ChatCategoryEnumName.INPUTS, | |
11 | + package: PackagesCategoryEnum.INFORMATIONS, | |
12 | + chartFrame: ChartFrameEnum.STATIC, | |
13 | + image: 'inputs_select.png' | |
14 | +} | |
\ No newline at end of file | ... | ... |
1 | +<template> | |
2 | + <div> | |
3 | + <n-input :style="`width:${w}px;`" type="text" | |
4 | + v-model:value="option.value.dataset" | |
5 | + placeholder="请输入" | |
6 | + @change="onChange"> | |
7 | + | |
8 | + </n-input> | |
9 | + </div> | |
10 | +</template> | |
11 | + | |
12 | +<script lang="ts" setup> | |
13 | +import { PropType, toRefs, shallowReactive, watch } from 'vue' | |
14 | +import { CreateComponentType } from '@/packages/index.d' | |
15 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
16 | +import { useChartInteract } from '@/hooks' | |
17 | +import { InteractEventOn } from '@/enums/eventEnum' | |
18 | +import { ComponentInteractParamsEnum } from './interact' | |
19 | + | |
20 | +const props = defineProps({ | |
21 | + chartConfig: { | |
22 | + type: Object as PropType<CreateComponentType>, | |
23 | + required: true | |
24 | + } | |
25 | +}) | |
26 | + | |
27 | +const { w, h } = toRefs(props.chartConfig.attr) | |
28 | +const option = shallowReactive({ | |
29 | + value: { | |
30 | + inputValue: props.chartConfig.option.inputValue, | |
31 | + dataset: props.chartConfig.option.dataset | |
32 | + } | |
33 | +}) | |
34 | + | |
35 | +const onChange = (v: string) => { | |
36 | + if(v == undefined) return; | |
37 | + // 存储到联动数据 | |
38 | + useChartInteract( | |
39 | + props.chartConfig, | |
40 | + useChartEditStore, | |
41 | + { [ComponentInteractParamsEnum.DATA]: v }, | |
42 | + InteractEventOn.CHANGE | |
43 | + ) | |
44 | +} | |
45 | + | |
46 | +// 手动更新 | |
47 | +watch( | |
48 | + () => props.chartConfig.option, | |
49 | + (newData: any) => { | |
50 | + option.value = newData | |
51 | + onChange(newData.inputValue) | |
52 | + }, | |
53 | + { | |
54 | + immediate: true, | |
55 | + deep: true | |
56 | + } | |
57 | +) | |
58 | + | |
59 | +</script> | |
60 | + | |
61 | + | |
62 | + | |
63 | + | |
64 | + | ... | ... |
1 | +import { InteractEventOn, InteractActionsType } from '@/enums/eventEnum' | |
2 | + | |
3 | +// 时间组件类型 | |
4 | +export enum ComponentInteractEventEnum { | |
5 | + DATA = 'data' | |
6 | +} | |
7 | + | |
8 | +// 联动参数 | |
9 | +export enum ComponentInteractParamsEnum { | |
10 | + DATA = 'data' | |
11 | +} | |
12 | + | |
13 | +// 定义组件触发回调事件 | |
14 | +export const interactActions: InteractActionsType[] = [ | |
15 | + { | |
16 | + interactType: InteractEventOn.CHANGE, | |
17 | + interactName: '选择完成', | |
18 | + componentEmitEvents: { | |
19 | + [ComponentInteractEventEnum.DATA]: [ | |
20 | + { | |
21 | + value: ComponentInteractParamsEnum.DATA, | |
22 | + label: '选择项' | |
23 | + } | |
24 | + ] | |
25 | + } | |
26 | + } | |
27 | +] | ... | ... |
1 | +import cloneDeep from 'lodash/cloneDeep' | |
2 | +import { PublicConfigClass } from '@/packages/public' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import { chartInitConfig } from '@/settings/designSetting' | |
5 | +import { COMPONENT_INTERACT_EVENT_KET } from '@/enums/eventEnum' | |
6 | +import { interactActions, ComponentInteractEventEnum } from './interact' | |
7 | +import {InputsPaginationConfig} from "./index"; | |
8 | + | |
9 | +export const option = { | |
10 | + // 时间组件展示类型,必须和 interactActions 中定义的数据一致 | |
11 | + [COMPONENT_INTERACT_EVENT_KET]: ComponentInteractEventEnum.DATA, | |
12 | + // 默认值 | |
13 | + pageValue:1, | |
14 | + sizeValue:[2,4,8,10,20], | |
15 | + pageSize:4, | |
16 | + // 暴露配置内容给用户 | |
17 | + dataset: 10 | |
18 | +} | |
19 | + | |
20 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
21 | + public key = InputsPaginationConfig.key | |
22 | + public attr = { ...chartInitConfig, w: 395, h: 32, zIndex: -1 } | |
23 | + public chartConfig = cloneDeep(InputsPaginationConfig) | |
24 | + public interactActions = interactActions | |
25 | + public option = cloneDeep(option) | |
26 | +} | |
\ No newline at end of file | ... | ... |
1 | +<template> | |
2 | + <collapse-item name="分页配置" :expanded="true"> | |
3 | + <setting-item-box :alone="false" name="分页设置"> | |
4 | + <setting-item name="默认页码" :alone="true"> | |
5 | + <n-input-number v-model:value="optionData.pageValue" size="small" placeholder="字体大小"></n-input-number> | |
6 | + </setting-item> | |
7 | + <setting-item name="分页" :alone="true"> | |
8 | + <n-select v-model:value="optionData.pageSize" size="small" | |
9 | + :options="page" /> | |
10 | + </setting-item> | |
11 | + <setting-item name="页数" :alone="true"> | |
12 | + <n-input-number v-model:value="optionData.dataset" size="small" placeholder="字体大小"></n-input-number> | |
13 | + </setting-item> | |
14 | + </setting-item-box> | |
15 | + </collapse-item> | |
16 | +</template> | |
17 | +<script setup lang="ts"> | |
18 | +import { PropType } from 'vue' | |
19 | +import {CollapseItem, SettingItem, SettingItemBox} from '@/components/Pages/ChartItemSetting' | |
20 | +import { option } from './config' | |
21 | + | |
22 | +const page = [ | |
23 | + {label:'2',value:2}, | |
24 | + {label:'4',value:4}, | |
25 | + {label:'8',value:8}, | |
26 | + {label:'10',value:10}, | |
27 | + {label:'20',value:20} | |
28 | +] | |
29 | +defineProps({ | |
30 | + optionData: { | |
31 | + type: Object as PropType<typeof option>, | |
32 | + required: true | |
33 | + } | |
34 | +}) | |
35 | +</script> | |
\ No newline at end of file | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
3 | + | |
4 | +export const InputsPaginationConfig: ConfigType = { | |
5 | + key: 'InputsPagination', | |
6 | + chartKey: 'VInputsPagination', | |
7 | + conKey: 'VCInputsPagination', | |
8 | + title: '分页', | |
9 | + category: ChatCategoryEnum.INPUTS, | |
10 | + categoryName: ChatCategoryEnumName.INPUTS, | |
11 | + package: PackagesCategoryEnum.INFORMATIONS, | |
12 | + chartFrame: ChartFrameEnum.STATIC, | |
13 | + image: 'inputs_pagination.png' | |
14 | +} | |
\ No newline at end of file | ... | ... |
1 | +<template> | |
2 | + <div> | |
3 | + <n-pagination | |
4 | + @on-update:page="onChange" :style="`width:${w}px;`" | |
5 | + v-model:page="option.value.pageValue" | |
6 | + :page-count="option.value.dataset" | |
7 | + :page-slot="7" | |
8 | + show-size-picker | |
9 | + :page-sizes="option.value.sizeValue" | |
10 | + v-model:page-size="option.value.pageSize" | |
11 | + /> | |
12 | + </div> | |
13 | +</template> | |
14 | + | |
15 | +<script lang="ts" setup> | |
16 | +import { PropType, toRefs, shallowReactive, watch } from 'vue' | |
17 | +import { CreateComponentType } from '@/packages/index.d' | |
18 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
19 | +import { useChartInteract } from '@/hooks' | |
20 | +import { InteractEventOn } from '@/enums/eventEnum' | |
21 | +import { ComponentInteractParamsEnum } from './interact' | |
22 | + | |
23 | +const props = defineProps({ | |
24 | + chartConfig: { | |
25 | + type: Object as PropType<CreateComponentType>, | |
26 | + required: true | |
27 | + } | |
28 | +}) | |
29 | + | |
30 | +const { w, h } = toRefs(props.chartConfig.attr) | |
31 | +const option = shallowReactive({ | |
32 | + value: { | |
33 | + pageValue: props.chartConfig.option.pageValue, | |
34 | + dataset:props.chartConfig.option.dataset, | |
35 | + sizeValue:props.chartConfig.option.sizeValue, | |
36 | + pageSize:props.chartConfig.option.pageSize | |
37 | + } | |
38 | +}) | |
39 | + | |
40 | +const onChange = (v: number,v2:number) => { | |
41 | + if(v == undefined) return; | |
42 | + // 存储到联动数据 | |
43 | + useChartInteract( | |
44 | + props.chartConfig, | |
45 | + useChartEditStore, | |
46 | + { | |
47 | + [ComponentInteractParamsEnum.DATA]: v , | |
48 | + [ComponentInteractParamsEnum.DATA2]:v2 | |
49 | + }, | |
50 | + InteractEventOn.CHANGE | |
51 | + ) | |
52 | +} | |
53 | + | |
54 | +// 手动更新 | |
55 | +watch( | |
56 | + () => props.chartConfig.option, | |
57 | + (newData: any) => { | |
58 | + option.value = newData | |
59 | + onChange(newData.pageValue,newData.pageSize) | |
60 | + }, | |
61 | + { | |
62 | + immediate: true, | |
63 | + deep: true | |
64 | + } | |
65 | +) | |
66 | +</script> | |
\ No newline at end of file | ... | ... |
1 | +import { InteractEventOn, InteractActionsType } from '@/enums/eventEnum' | |
2 | + | |
3 | +// 时间组件类型 | |
4 | +export enum ComponentInteractEventEnum { | |
5 | + DATA = 'data' | |
6 | +} | |
7 | + | |
8 | +// 联动参数 | |
9 | +export enum ComponentInteractParamsEnum { | |
10 | + DATA = 'data', | |
11 | + DATA2 = 'data2' | |
12 | +} | |
13 | + | |
14 | +// 定义组件触发回调事件 | |
15 | +export const interactActions: InteractActionsType[] = [ | |
16 | + { | |
17 | + interactType: InteractEventOn.CHANGE, | |
18 | + interactName: '选择完成', | |
19 | + componentEmitEvents: { | |
20 | + [ComponentInteractEventEnum.DATA]: [ | |
21 | + { | |
22 | + value: ComponentInteractParamsEnum.DATA, | |
23 | + label: '页数' | |
24 | + }, | |
25 | + { | |
26 | + value: ComponentInteractParamsEnum.DATA2, | |
27 | + label: '每页条数' | |
28 | + } | |
29 | + ] | |
30 | + } | |
31 | + } | |
32 | +] | |
\ No newline at end of file | ... | ... |
1 | 1 | import { InputsDateConfig } from './InputsDate/index' |
2 | 2 | import { InputsSelectConfig } from './InputsSelect/index' |
3 | 3 | import { InputsTabConfig } from './InputsTab/index' |
4 | +import { InputsPaginationConfig } from "./InputsPagination/index"; | |
5 | +import { InputsInputConfig} from "./InputsInput/index"; | |
4 | 6 | |
5 | -export default [InputsDateConfig, InputsSelectConfig, InputsTabConfig] | |
7 | +export default [InputsDateConfig, InputsSelectConfig, InputsTabConfig,InputsPaginationConfig,InputsInputConfig] | ... | ... |
src/packages/components/Informations/Mores/Carousel/config.ts
deleted
100644 → 0
1 | -import { PublicConfigClass } from '@/packages/public' | |
2 | -import { CreateComponentType } from '@/packages/index.d' | |
3 | -import { CarouselConfig } from './index' | |
4 | -import cloneDeep from 'lodash/cloneDeep' | |
5 | -import logo from '@/assets/logo.png' | |
6 | - | |
7 | -// 示例图片资源 | |
8 | -const modules: Record<string, { default: string }> = import.meta.glob("./images/*", {eager: true}); | |
9 | -const dataset = [logo] | |
10 | -for (const item in modules) { | |
11 | - dataset.push(modules[item].default) | |
12 | -} | |
13 | - | |
14 | -export const option = { | |
15 | - // 图片资源列表 | |
16 | - dataset: dataset, | |
17 | - // 自动播放 | |
18 | - autoplay: true, | |
19 | - // 自动播放的间隔(ms) | |
20 | - interval: 5000, | |
21 | - // 每页显示的图片数量 | |
22 | - slidesPerview: 1, | |
23 | - // 轮播方向 | |
24 | - direction: "horizontal", | |
25 | - // 拖曳切换 | |
26 | - draggable: true, | |
27 | - // 居中显示 | |
28 | - centeredSlides: false, | |
29 | - // 过渡效果 | |
30 | - effect: "slide", | |
31 | - // 是否显示指示点 | |
32 | - showDots: true, | |
33 | - // 指示器样式 | |
34 | - dotType: "dot", | |
35 | - // 指示器位置 | |
36 | - dotPlacement: "bottom", | |
37 | - // 显示箭头 | |
38 | - showArrow: false, | |
39 | - // 图片样式 | |
40 | - fit: "contain", | |
41 | -} | |
42 | - | |
43 | -export default class Config extends PublicConfigClass implements CreateComponentType { | |
44 | - public key = CarouselConfig.key | |
45 | - public chartConfig = cloneDeep(CarouselConfig) | |
46 | - public option = cloneDeep(option) | |
47 | -} |
src/packages/components/Informations/Mores/Carousel/config.vue
deleted
100644 → 0
1 | -<template> | |
2 | - <collapse-item name="属性" :expanded="true"> | |
3 | - <setting-item-box name="路径" :alone="true"> | |
4 | - <setting-item v-for="item, index in optionData.dataset" :key="index"> | |
5 | - <n-input-group> | |
6 | - <n-input v-model:value="optionData.dataset[index]" size="small" placeholder="请输入图片地址"></n-input> | |
7 | - <n-button ghost @click="optionData.dataset.splice(index, 1)"> | |
8 | - - | |
9 | - </n-button> | |
10 | - </n-input-group> | |
11 | - </setting-item> | |
12 | - <setting-item> | |
13 | - <n-button size="small" @click="optionData.dataset.push('')"> | |
14 | - + | |
15 | - </n-button> | |
16 | - </setting-item> | |
17 | - </setting-item-box> | |
18 | - <setting-item-box name="播放器"> | |
19 | - <setting-item> | |
20 | - <n-space> | |
21 | - <n-switch v-model:value="optionData.autoplay" size="small" /> | |
22 | - <n-text>自动播放</n-text> | |
23 | - </n-space> | |
24 | - </setting-item> | |
25 | - <!-- 开启自动播放时,设置间隔时间 --> | |
26 | - <setting-item name="间隔时间"> | |
27 | - <n-input-number v-model:value="optionData.interval" size="small" placeholder=""></n-input-number> | |
28 | - </setting-item> | |
29 | - <setting-item name="轮播方向"> | |
30 | - <n-select v-model:value="optionData.direction" :options="directions" placeholder="选择方向" /> | |
31 | - </setting-item> | |
32 | - <setting-item name="过渡效果"> | |
33 | - <n-select v-model:value="optionData.effect" :options="effects" placeholder="效果" /> | |
34 | - </setting-item> | |
35 | - <setting-item name="每页数量"> | |
36 | - <n-input-number v-model:value="optionData.slidesPerview" size="small" placeholder=""></n-input-number> | |
37 | - </setting-item> | |
38 | - <setting-item> | |
39 | - <n-space> | |
40 | - <n-switch v-model:value="optionData.centeredSlides" size="small" /> | |
41 | - <n-text>居中显示</n-text> | |
42 | - </n-space> | |
43 | - </setting-item> | |
44 | - <setting-item name="图片样式"> | |
45 | - <n-select v-model:value="optionData.fit" :options="fitList" placeholder="样式" /> | |
46 | - </setting-item> | |
47 | - </setting-item-box> | |
48 | - | |
49 | - <setting-item-box name="指示器"> | |
50 | - <setting-item name="样式"> | |
51 | - <n-select v-model:value="optionData.dotType" :options="dotTypes" placeholder="选择样式" /> | |
52 | - </setting-item> | |
53 | - <setting-item name="位置"> | |
54 | - <n-select v-model:value="optionData.dotPlacement" :options="dotPlacements" placeholder="选择位置" /> | |
55 | - </setting-item> | |
56 | - <setting-item> | |
57 | - <n-space> | |
58 | - <n-switch v-model:value="optionData.showDots" size="small" /> | |
59 | - <n-text>显示</n-text> | |
60 | - </n-space> | |
61 | - </setting-item> | |
62 | - <setting-item> | |
63 | - <n-space> | |
64 | - <n-switch v-model:value="optionData.showArrow" size="small" /> | |
65 | - <n-text>箭头</n-text> | |
66 | - </n-space> | |
67 | - </setting-item> | |
68 | - <setting-item> | |
69 | - <n-space> | |
70 | - <n-switch v-model:value="optionData.draggable" size="small" /> | |
71 | - <n-text>拖曳切换</n-text> | |
72 | - </n-space> | |
73 | - </setting-item> | |
74 | - </setting-item-box> | |
75 | - | |
76 | - </collapse-item> | |
77 | -</template> | |
78 | - | |
79 | -<script setup lang="ts"> | |
80 | -import { PropType } from 'vue' | |
81 | -import { option } from './config' | |
82 | -import { | |
83 | - CollapseItem, | |
84 | - SettingItemBox, | |
85 | - SettingItem | |
86 | -} from '@/components/Pages/ChartItemSetting' | |
87 | - | |
88 | -const props = defineProps({ | |
89 | - optionData: { | |
90 | - type: Object as PropType<typeof option>, | |
91 | - required: true | |
92 | - } | |
93 | -}) | |
94 | - | |
95 | -// 字典 | |
96 | -const dotTypes = [ | |
97 | - { | |
98 | - label: "点", | |
99 | - value: "dot" | |
100 | - }, | |
101 | - { | |
102 | - label: "线", | |
103 | - value: "line" | |
104 | - } | |
105 | -] | |
106 | -const directions = [ | |
107 | - { | |
108 | - label: "水平方向", | |
109 | - value: "horizontal" | |
110 | - }, | |
111 | - { | |
112 | - label: "垂直方向", | |
113 | - value: "vertical" | |
114 | - } | |
115 | -] | |
116 | -const effects = [ | |
117 | - { | |
118 | - label: "slide", | |
119 | - value: "slide" | |
120 | - }, | |
121 | - { | |
122 | - label: "fade", | |
123 | - value: "fade" | |
124 | - }, | |
125 | - { | |
126 | - label: "card", | |
127 | - value: "card" | |
128 | - }, | |
129 | - { | |
130 | - label: "custom", | |
131 | - value: "custom" | |
132 | - } | |
133 | -] | |
134 | -const dotPlacements = [ | |
135 | - { | |
136 | - label: "上边", | |
137 | - value: "top" | |
138 | - }, | |
139 | - { | |
140 | - label: "下边", | |
141 | - value: "bottom" | |
142 | - }, | |
143 | - { | |
144 | - label: "左边", | |
145 | - value: "left" | |
146 | - }, | |
147 | - { | |
148 | - label: "右边", | |
149 | - value: "right" | |
150 | - } | |
151 | -] | |
152 | - | |
153 | -// 适应类型 | |
154 | -const fitList = [ | |
155 | - { | |
156 | - value: 'fill', | |
157 | - label: 'fill' | |
158 | - }, | |
159 | - { | |
160 | - value: 'contain', | |
161 | - label: 'contain' | |
162 | - }, | |
163 | - { | |
164 | - value: 'cover', | |
165 | - label: 'cover' | |
166 | - }, | |
167 | - { | |
168 | - value: 'scale-down', | |
169 | - label: 'scale-down' | |
170 | - }, | |
171 | - { | |
172 | - value: 'none', | |
173 | - label: 'none' | |
174 | - }, | |
175 | -] | |
176 | -</script> |
src/packages/components/Informations/Mores/Carousel/images/carousel.png
deleted
100644 → 0
63 KB
src/packages/components/Informations/Mores/Carousel/images/carousel2.png
deleted
100644 → 0
12.1 KB
src/packages/components/Informations/Mores/Carousel/index.ts
deleted
100644 → 0
1 | -import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | -import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d' | |
3 | - | |
4 | -export const CarouselConfig: ConfigType = { | |
5 | - key: 'Carousel', | |
6 | - chartKey: 'VCarousel', | |
7 | - conKey: 'VCCarousel', | |
8 | - title: '轮播图', | |
9 | - category: ChatCategoryEnum.MORE, | |
10 | - categoryName: ChatCategoryEnumName.MORE, | |
11 | - package: PackagesCategoryEnum.INFORMATIONS, | |
12 | - chartFrame: ChartFrameEnum.NAIVE_UI, | |
13 | - image: 'photo.png' | |
14 | -} |
src/packages/components/Informations/Mores/Carousel/index.vue
deleted
100644 → 0
1 | -<template> | |
2 | - <div> | |
3 | - <n-carousel :autoplay="autoplay" :interval="interval" :centered-slides="centeredSlides" :direction="direction" | |
4 | - :dot-placement="dotPlacement" :dot-type="dotType" :draggable="draggable" :effect="effect" | |
5 | - :slides-per-view="slidesPerview" :show-arrow="showArrow" :show-dots="showDots"> | |
6 | - <n-image v-for="url in option.dataset" :object-fit="fit" preview-disabled :src="url" | |
7 | - :fallback-src="requireErrorImg()" :width="w" :height="h"></n-image> | |
8 | - </n-carousel> | |
9 | - </div> | |
10 | -</template> | |
11 | -<script setup lang="ts"> | |
12 | -import { PropType, toRefs, shallowReactive, watch } from 'vue' | |
13 | -import { CreateComponentType } from '@/packages/index.d' | |
14 | -import { requireErrorImg } from '@/utils' | |
15 | -import { useChartDataFetch } from '@/hooks' | |
16 | -import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
17 | -import { option as configOption } from './config' | |
18 | - | |
19 | -const props = defineProps({ | |
20 | - chartConfig: { | |
21 | - type: Object as PropType<CreateComponentType>, | |
22 | - required: true | |
23 | - } | |
24 | -}) | |
25 | - | |
26 | -const option = shallowReactive({ | |
27 | - dataset: configOption.dataset | |
28 | -}) | |
29 | - | |
30 | -const { w, h } = toRefs(props.chartConfig.attr) | |
31 | -const { autoplay, interval, slidesPerview, direction, draggable, centeredSlides, effect, dotType, dotPlacement, showArrow, showDots, fit } = toRefs(props.chartConfig.option) | |
32 | - | |
33 | -watch( | |
34 | - () => props.chartConfig.option.dataset, | |
35 | - (newData: any) => { | |
36 | - option.dataset = newData | |
37 | - }, | |
38 | - { | |
39 | - immediate: true, | |
40 | - deep: false | |
41 | - } | |
42 | -) | |
43 | - | |
44 | -useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | |
45 | - option.dataset = newData | |
46 | -}) | |
47 | -</script> |
... | ... | @@ -33,6 +33,8 @@ const option = shallowReactive({ |
33 | 33 | dataset: '' |
34 | 34 | }) |
35 | 35 | |
36 | + | |
37 | + | |
36 | 38 | const getStyle = (radius: number) => { |
37 | 39 | return { |
38 | 40 | borderRadius: `${radius}px`, |
... | ... | @@ -40,12 +42,44 @@ const getStyle = (radius: number) => { |
40 | 42 | } |
41 | 43 | } |
42 | 44 | |
45 | +async function getBase64(imgUrl: string): Promise<string> { | |
46 | + return new Promise(resolve => { | |
47 | + let xhr = new XMLHttpRequest(); | |
48 | + xhr.open('get', imgUrl, true); | |
49 | + xhr.responseType = 'blob'; | |
50 | + xhr.onload = function() { | |
51 | + if (this.status == 200) { | |
52 | + //得到一个blob对象 | |
53 | + let blob = this.response; | |
54 | + // 重点2 | |
55 | + let oFileReader = new FileReader(); | |
56 | + oFileReader.onloadend = function(e) { | |
57 | + // 结果 | |
58 | + const base64 = e.target?.result as string | |
59 | + resolve(base64) | |
60 | + }; | |
61 | + oFileReader.readAsDataURL(blob); | |
62 | + } | |
63 | + }; | |
64 | + xhr.send(); | |
65 | + }) | |
66 | +} | |
67 | + | |
43 | 68 | // 编辑更新 |
44 | 69 | watch( |
45 | 70 | () => props.chartConfig.option.dataset, |
46 | - (newData: any) => { | |
71 | + async (newData: any) => { | |
72 | + // const base64 = await getBase64(newData) | |
73 | + // option.dataset = base64 | |
74 | + const reg = /^https?/ig | |
75 | + if(reg.test(newData)){ | |
76 | + const base64 = await getBase64(newData) | |
77 | + option.dataset = base64 | |
78 | + return | |
79 | + } | |
47 | 80 | option.dataset = newData |
48 | - }, | |
81 | + | |
82 | + }, | |
49 | 83 | { |
50 | 84 | immediate: true |
51 | 85 | } | ... | ... |
... | ... | @@ -3,6 +3,5 @@ import { ImageCarouselConfig } from './ImageCarousel/index' |
3 | 3 | import { IframeConfig } from './Iframe/index' |
4 | 4 | import { VideoConfig } from './Video/index' |
5 | 5 | import { WordCloudConfig } from './WordCloud/index' |
6 | -import { CarouselConfig } from './Carousel/index' | |
7 | 6 | |
8 | 7 | export default [ImageConfig, ImageCarouselConfig, VideoConfig, IframeConfig, WordCloudConfig] | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <collapse-item name="信息" :expanded="true"> |
3 | 3 | <setting-item-box name="文字" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
5 | + <n-input v-model:value="optionData.dataset" type="textarea" size="small"></n-input> | |
6 | 6 | </setting-item> |
7 | 7 | </setting-item-box> |
8 | 8 | </collapse-item> | ... | ... |
1 | 1 | <template> |
2 | 2 | <div class="go-text-box"> |
3 | 3 | <div class="content"> |
4 | - <span style="cursor: pointer; white-space: pre-wrap" v-if="link" @click="click"></span> | |
4 | + <span style="cursor: pointer; white-space: pre-wrap" v-if="link" @click="click">{{ option.dataset }}</span> | |
5 | 5 | <span style="white-space: pre-wrap" v-else>{{ option.dataset }}</span> |
6 | 6 | </div> |
7 | 7 | </div> | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <collapse-item name="信息" :expanded="true"> |
3 | 3 | <setting-item-box name="文字" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
5 | + <n-input v-model:value="optionData.dataset" type="textarea" size="small"></n-input> | |
6 | 6 | </setting-item> |
7 | 7 | </setting-item-box> |
8 | 8 | </collapse-item> | ... | ... |
1 | +import cloneDeep from 'lodash/cloneDeep' | |
2 | +import { PublicConfigClass } from '@/packages/public' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import { chartInitConfig } from '@/settings/designSetting' | |
5 | +import { TablesBasicConfig } from './index' | |
6 | +import dataJson from './data.json' | |
7 | + | |
8 | +const { dimensions, source } = dataJson | |
9 | +export const option = { | |
10 | + dataset: { dimensions, source }, | |
11 | + pagination: { | |
12 | + page: 1, | |
13 | + pageSize: 5 | |
14 | + }, | |
15 | + align: 'center', | |
16 | + style: { | |
17 | + border: 'on', | |
18 | + singleColumn: 'off', | |
19 | + singleLine: 'off', | |
20 | + bottomBordered: 'on', | |
21 | + striped: 'on', | |
22 | + fontSize: 16, | |
23 | + borderWidth: 0, | |
24 | + borderColor: 'black', | |
25 | + borderStyle: 'solid' | |
26 | + }, | |
27 | + inputShow: 'none' | |
28 | +} | |
29 | + | |
30 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
31 | + public key = TablesBasicConfig.key | |
32 | + public attr = { ...chartInitConfig, w: 600, h: 300, zIndex: -1 } | |
33 | + public chartConfig = cloneDeep(TablesBasicConfig) | |
34 | + public option = cloneDeep(option) | |
35 | +} | ... | ... |
1 | +<template> | |
2 | + <collapse-item name="表格设置" :expanded="true"> | |
3 | + <n-tag type="primary">若配置无响应,请在预览页面查看效果</n-tag> | |
4 | + <setting-item-box :alone="true" name="对齐方式"> | |
5 | + <setting-item :alone="true"> | |
6 | + <n-select | |
7 | + v-model:value="optionData.align" | |
8 | + size="small" | |
9 | + :options="[ | |
10 | + { label: '靠左', value: 'left' }, | |
11 | + { label: '居中', value: 'center' }, | |
12 | + { label: '靠右', value: 'right' } | |
13 | + ]" | |
14 | + /> | |
15 | + </setting-item> | |
16 | + </setting-item-box> | |
17 | + <setting-item-box :alone="false" name="分页设置"> | |
18 | + <setting-item name="默认页码" :alone="true"> | |
19 | + <n-input-number v-model:value="optionData.pagination.page" size="small" placeholder="字体大小"></n-input-number> | |
20 | + </setting-item> | |
21 | + <setting-item name="分页" :alone="true"> | |
22 | + <n-select v-model:value="optionData.pagination.pageSize" size="small" :options="page" /> | |
23 | + </setting-item> | |
24 | + </setting-item-box> | |
25 | + <setting-item-box :alone="false" name="表格数据"> | |
26 | + <SettingItem name="表头名称" class="form_name"> | |
27 | + <div style="width: 260px"> | |
28 | + <n-input v-model:value="header" size="small" placeholder="表头数据(英文','分割)"></n-input> | |
29 | + </div> | |
30 | + </SettingItem> | |
31 | + </setting-item-box> | |
32 | + <setting-item-box :alone="false" name="表格样式"> | |
33 | + <SettingItem name="显示边框" :alone="true"> | |
34 | + <n-select v-model:value="(optionData as any).style.border" size="small" :options="borderFlag" /> | |
35 | + </SettingItem> | |
36 | + <SettingItem name="底部边框" :alone="true"> | |
37 | + <n-select | |
38 | + v-model:value="(optionData as any).style.bottomBordered" | |
39 | + size="small" | |
40 | + :options="bottom_borderedFlag" | |
41 | + /> | |
42 | + </SettingItem> | |
43 | + <SettingItem name="列分割线" :alone="true"> | |
44 | + <n-select v-model:value="(optionData as any).style.singleLine" size="small" :options="columnFlag" /> | |
45 | + </SettingItem> | |
46 | + <SettingItem name="行分割线" :alone="true"> | |
47 | + <n-select v-model:value="(optionData as any).style.singleColumn" size="small" :options="lineFlag" /> | |
48 | + </SettingItem> | |
49 | + <SettingItem name="斑马条纹" :alone="true"> | |
50 | + <n-select v-model:value="(optionData as any).style.striped" size="small" :options="stripedFlag" /> | |
51 | + </SettingItem> | |
52 | + <setting-item name="字体大小" :alone="true"> | |
53 | + <n-input-number | |
54 | + v-model:value="optionData.style.fontSize" | |
55 | + :min="12" | |
56 | + size="small" | |
57 | + placeholder="字体大小" | |
58 | + ></n-input-number> | |
59 | + </setting-item> | |
60 | + <setting-item name="边框宽度" :alone="true"> | |
61 | + <n-input-number | |
62 | + v-model:value="optionData.style.borderWidth" | |
63 | + :min="0" | |
64 | + size="small" | |
65 | + placeholder="字体大小" | |
66 | + ></n-input-number> | |
67 | + </setting-item> | |
68 | + <setting-item name="边框颜色" :alone="true"> | |
69 | + <n-color-picker size="small" :modes="['rgb']" v-model:value="optionData.style.borderColor"></n-color-picker> | |
70 | + </setting-item> | |
71 | + <setting-item name="边框样式" :alone="true"> | |
72 | + <n-select v-model:value="optionData.style.borderStyle" size="small" :options="borderStyleFlag" /> | |
73 | + </setting-item> | |
74 | + <SettingItem name="表格搜索(前端静态搜索)" :alone="true"> | |
75 | + <n-select v-model:value="optionData.inputShow" size="small" :options="inputSelect" /> | |
76 | + </SettingItem> | |
77 | + </setting-item-box> | |
78 | + </collapse-item> | |
79 | +</template> | |
80 | + | |
81 | +<script setup lang="ts"> | |
82 | +import { PropType, watch, ref } from 'vue' | |
83 | +import { option } from './config' | |
84 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
85 | + | |
86 | +const page = [ | |
87 | + { label: '2', value: 2 }, | |
88 | + { label: '5', value: 5 }, | |
89 | + { label: '10', value: 10 }, | |
90 | + { label: '15', value: 15 }, | |
91 | + { label: '30', value: 30 } | |
92 | +] | |
93 | +const borderFlag = [ | |
94 | + { label: '显示', value: 'on' }, | |
95 | + { label: '不显示', value: 'off' } | |
96 | +] | |
97 | +const columnFlag = [ | |
98 | + { label: '显示', value: 'off' }, | |
99 | + { label: '不显示', value: 'on' } | |
100 | +] | |
101 | +const lineFlag = [ | |
102 | + { label: '显示', value: 'off' }, | |
103 | + { label: '不显示', value: 'on' } | |
104 | +] | |
105 | +const bottom_borderedFlag = [ | |
106 | + { label: '显示', value: 'on' }, | |
107 | + { label: '不显示', value: 'off' } | |
108 | +] | |
109 | +const stripedFlag = [ | |
110 | + { label: '显示', value: 'on' }, | |
111 | + { label: '不显示', value: 'off' } | |
112 | +] | |
113 | +const borderStyleFlag = [ | |
114 | + { label: '实线边框', value: 'solid' }, | |
115 | + { label: '虚线边框', value: 'dashed' }, | |
116 | + { label: '点状边框', value: 'dotted' }, | |
117 | + { label: '双线边框', value: 'double' } | |
118 | +] | |
119 | +const inputSelect = [ | |
120 | + { label: '停用', value: 'none' }, | |
121 | + { label: '启用', value: 'flex' } | |
122 | +] | |
123 | +const props = defineProps({ | |
124 | + optionData: { | |
125 | + type: Object as PropType<typeof option>, | |
126 | + required: true | |
127 | + } | |
128 | +}) | |
129 | + | |
130 | +const header = ref() | |
131 | +const median = ref<string[]>([]) | |
132 | +props.optionData.dataset.dimensions.forEach(item => { | |
133 | + median.value.push(item.title) | |
134 | +}) | |
135 | + | |
136 | +//转string | |
137 | +watch( | |
138 | + () => props.optionData, | |
139 | + () => { | |
140 | + median.value = [] | |
141 | + props.optionData.dataset.dimensions.forEach(item => { | |
142 | + median.value.push(item.title) | |
143 | + }) | |
144 | + header.value = median.value.toString() | |
145 | + }, | |
146 | + { | |
147 | + deep: false, | |
148 | + immediate: true | |
149 | + } | |
150 | +) | |
151 | + | |
152 | +//更新columns | |
153 | +watch([header], ([headerNew], [headerOld]) => { | |
154 | + if (headerNew !== headerOld) { | |
155 | + headerNew.split(',').forEach((item: string, index: number) => { | |
156 | + if (index + 1 <= props.optionData.dataset.dimensions.length) { | |
157 | + props.optionData.dataset.dimensions[index].title = headerNew.split(',')[index] | |
158 | + } | |
159 | + }) | |
160 | + } | |
161 | +}) | |
162 | +</script> | ... | ... |
1 | +{ | |
2 | + "dimensions": [ | |
3 | + { | |
4 | + "title": "产品名称", | |
5 | + "key": "productName" | |
6 | + }, | |
7 | + { | |
8 | + "title": "产品销量(万)", | |
9 | + "key": "totalSum" | |
10 | + }, | |
11 | + { | |
12 | + "title": "销售额(万)", | |
13 | + "key": "totalAmount" | |
14 | + } | |
15 | + ], | |
16 | + "source": [ | |
17 | + { | |
18 | + "key": 0, | |
19 | + "productName": "产品A1", | |
20 | + "totalSum": 10, | |
21 | + "totalAmount": 10 | |
22 | + }, | |
23 | + { | |
24 | + "key": 1, | |
25 | + "productName": "产品B1", | |
26 | + "totalSum": 10, | |
27 | + "totalAmount": 10 | |
28 | + }, | |
29 | + { | |
30 | + "key": 2, | |
31 | + "productName": "产品C1", | |
32 | + "totalSum": 10, | |
33 | + "totalAmount": 10 | |
34 | + }, | |
35 | + { | |
36 | + "key": 3, | |
37 | + "productName": "产品D1", | |
38 | + "totalSum": 10, | |
39 | + "totalAmount": 10 | |
40 | + }, | |
41 | + { | |
42 | + "key": 4, | |
43 | + "productName": "产品A2", | |
44 | + "totalSum": 10, | |
45 | + "totalAmount": 10 | |
46 | + }, | |
47 | + { | |
48 | + "key": 5, | |
49 | + "productName": "产品D2", | |
50 | + "totalSum": 10, | |
51 | + "totalAmount": 10 | |
52 | + }, | |
53 | + { | |
54 | + "key": 6, | |
55 | + "productName": "产品A3", | |
56 | + "totalSum": 10, | |
57 | + "totalAmount": 10 | |
58 | + } | |
59 | + ] | |
60 | +} | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
3 | + | |
4 | +export const TablesBasicConfig: ConfigType = { | |
5 | + key: 'TablesBasic', | |
6 | + chartKey: 'VTablesBasic', | |
7 | + conKey: 'VCTablesBasic', | |
8 | + title: '基础分页表格', | |
9 | + category: ChatCategoryEnum.TABLE, | |
10 | + categoryName: ChatCategoryEnumName.TABLE, | |
11 | + package: PackagesCategoryEnum.TABLES, | |
12 | + chartFrame: ChartFrameEnum.COMMON, | |
13 | + image: 'tables_basic.png' | |
14 | +} | ... | ... |
1 | +<template> | |
2 | + <div class="go-tables-basic"> | |
3 | + <n-input | |
4 | + v-model:value="inputData" | |
5 | + placeholder="请输入信息" | |
6 | + :style="`display: ${inputShow}`" | |
7 | + style="margin-bottom: 5px; float: right; width: 240px" | |
8 | + > | |
9 | + <template #prefix> | |
10 | + <n-icon :component="SearchIcon" /> | |
11 | + </template> | |
12 | + </n-input> | |
13 | + <n-data-table | |
14 | + :style="` | |
15 | + width: ${w}px; | |
16 | + height: ${h}px; | |
17 | + font-size: ${option.style.fontSize}px; | |
18 | + border-width: ${option.style.border === 'on' ? option.style.borderWidth : 0}px; | |
19 | + border-color: ${option.style.borderColor}; | |
20 | + border-style: ${option.style.borderStyle}`" | |
21 | + :bordered="option.style.border === 'on'" | |
22 | + :single-column="option.style.singleColumn === 'on'" | |
23 | + :single-line="option.style.singleLine === 'on'" | |
24 | + :bottom-bordered="option.style.bottomBordered === 'on'" | |
25 | + :striped="option.style.striped === 'on'" | |
26 | + :max-height="h" | |
27 | + size="small" | |
28 | + :columns="option.dataset.dimensions" | |
29 | + :data="filterData" | |
30 | + :pagination="pagination" | |
31 | + /> | |
32 | + </div> | |
33 | +</template> | |
34 | + | |
35 | +<script setup lang="ts"> | |
36 | +import { computed, PropType, toRefs, watch, reactive, ref } from 'vue' | |
37 | +import { CreateComponentType } from '@/packages/index.d' | |
38 | +import { icon } from '@/plugins' | |
39 | + | |
40 | +const props = defineProps({ | |
41 | + chartConfig: { | |
42 | + type: Object as PropType<CreateComponentType>, | |
43 | + required: true | |
44 | + } | |
45 | +}) | |
46 | + | |
47 | +const { SearchIcon } = icon.ionicons5 | |
48 | + | |
49 | +//查询字段 | |
50 | +const inputData = ref('') | |
51 | +//前台过滤 | |
52 | +const filterData = computed(() => { | |
53 | + return option?.dataset?.source?.filter((item: any) => { | |
54 | + return Object.values(item).some(val => { | |
55 | + return String(val).toLowerCase().includes(inputData.value.toLowerCase()) | |
56 | + }) | |
57 | + }) | |
58 | +}) | |
59 | + | |
60 | +const { align, pagination, inputShow } = toRefs(props.chartConfig.option) | |
61 | + | |
62 | +pagination.value.onChange = (page: number) => { | |
63 | + pagination.value.page = page | |
64 | +} | |
65 | + | |
66 | +const { w, h } = toRefs(props.chartConfig.attr) | |
67 | + | |
68 | +const option = reactive({ | |
69 | + dataset: props.chartConfig.option.dataset, | |
70 | + style: props.chartConfig.option.style | |
71 | +}) | |
72 | + | |
73 | +watch( | |
74 | + () => props.chartConfig.option.dataset, | |
75 | + (newData: any) => { | |
76 | + option.dataset = newData | |
77 | + option?.dataset?.dimensions?.forEach((header: any) => { | |
78 | + header.align = align.value | |
79 | + }) | |
80 | + }, | |
81 | + { | |
82 | + immediate: true, | |
83 | + deep: true | |
84 | + } | |
85 | +) | |
86 | +</script> | |
87 | + | |
88 | +<style lang="scss" scoped> | |
89 | +@include go('tables-basic') { | |
90 | + display: flex; | |
91 | + flex-direction: column; | |
92 | + gap: 15px; | |
93 | + align-items: flex-end; | |
94 | +} | |
95 | +</style> | ... | ... |
1 | 1 | import { TableListConfig } from './TableList' |
2 | 2 | import { TableScrollBoardConfig } from './TableScrollBoard' |
3 | +import { TablesBasicConfig } from "./TablesBasic/index"; | |
3 | 4 | |
4 | -export default [TableListConfig, TableScrollBoardConfig] | |
5 | +export default [TableListConfig, TableScrollBoardConfig,TablesBasicConfig] | ... | ... |
src/packages/components/external/Charts/Lines/OverrideLineForDeviceHistoryQuery/config.ts
0 → 100644
1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
2 | +import { OverrideLineForDeviceHistoryQueryConfig } from './index' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | +import dataJson from './data.json' | |
6 | +import { chartInitConfig } from '@/settings/designSetting' | |
7 | + | |
8 | +export const includes = ['legend', 'xAxis', 'yAxis', 'grid'] | |
9 | +export const seriesItem = { | |
10 | + type: 'line', | |
11 | + label: { | |
12 | + show: true, | |
13 | + position: 'top', | |
14 | + color: '#fff', | |
15 | + fontSize: 12 | |
16 | + }, | |
17 | + symbolSize: 5, //设定实心点的大小 | |
18 | + itemStyle: { | |
19 | + color: null, | |
20 | + borderRadius: 0 | |
21 | + }, | |
22 | + lineStyle: { | |
23 | + type: 'solid', | |
24 | + width: 3, | |
25 | + color: null | |
26 | + } | |
27 | +} | |
28 | +// 其它配置项比如新增(动画) | |
29 | +const otherConfig = { | |
30 | + // 轮播动画 | |
31 | + isCarousel: false | |
32 | +} | |
33 | +export const option = { | |
34 | + queryCondition: { | |
35 | + timeRange: [new Date().getTime() - 86400000, new Date().getTime()], //默认最近一天 | |
36 | + limit: 7, | |
37 | + agg: 'NONE', | |
38 | + interval: null | |
39 | + }, | |
40 | + ...otherConfig, | |
41 | + tooltip: { | |
42 | + show: true, | |
43 | + trigger: 'axis', | |
44 | + axisPointer: { | |
45 | + type: 'line' | |
46 | + } | |
47 | + }, | |
48 | + xAxis: { | |
49 | + show: true, | |
50 | + type: 'category' | |
51 | + }, | |
52 | + yAxis: { | |
53 | + show: true, | |
54 | + type: 'value' | |
55 | + }, | |
56 | + dataset: { ...dataJson }, | |
57 | + series: [seriesItem, seriesItem] | |
58 | +} | |
59 | + | |
60 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
61 | + public key: string = OverrideLineForDeviceHistoryQueryConfig.key | |
62 | + public chartConfig = cloneDeep(OverrideLineForDeviceHistoryQueryConfig) | |
63 | + public attr = { ...chartInitConfig, w: 735, h: 435, zIndex: -1 } | |
64 | + // 图表配置项 | |
65 | + public option = echartOptionProfixHandle(option, includes) | |
66 | +} | ... | ... |
src/packages/components/external/Charts/Lines/OverrideLineForDeviceHistoryQuery/config.vue
0 → 100644
1 | +<template> | |
2 | + <!-- Echarts 全局设置 --> | |
3 | + <global-setting :optionData="optionData"></global-setting> | |
4 | + <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true"> | |
5 | + <SettingItemBox name="线条"> | |
6 | + <SettingItem name="宽度"> | |
7 | + <n-input-number | |
8 | + v-model:value="item.lineStyle.width" | |
9 | + :min="1" | |
10 | + :max="100" | |
11 | + size="small" | |
12 | + placeholder="自动计算" | |
13 | + ></n-input-number> | |
14 | + </SettingItem> | |
15 | + <SettingItem name="类型"> | |
16 | + <n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select> | |
17 | + </SettingItem> | |
18 | + </SettingItemBox> | |
19 | + <SettingItemBox name="动画" :alone="true"> | |
20 | + <SettingItem> | |
21 | + <n-space> | |
22 | + <n-switch v-model:value="optionData.isCarousel" size="small"></n-switch> | |
23 | + <n-text>开启<n-text :depth="3">(将自动隐藏图例)</n-text></n-text> | |
24 | + </n-space> | |
25 | + </SettingItem> | |
26 | + <SettingItem> | |
27 | + <n-text :depth="3">无鼠标点击图例场景时,可强行打开图例</n-text> | |
28 | + </SettingItem> | |
29 | + </SettingItemBox> | |
30 | + <SettingItemBox name="实心点"> | |
31 | + <SettingItem name="大小"> | |
32 | + <n-input-number | |
33 | + v-model:value="item.symbolSize" | |
34 | + :min="1" | |
35 | + :max="100" | |
36 | + size="small" | |
37 | + placeholder="自动计算" | |
38 | + ></n-input-number> | |
39 | + </SettingItem> | |
40 | + </SettingItemBox> | |
41 | + <setting-item-box name="标签"> | |
42 | + <setting-item> | |
43 | + <n-space> | |
44 | + <n-switch v-model:value="item.label.show" size="small" /> | |
45 | + <n-text>展示标签</n-text> | |
46 | + </n-space> | |
47 | + </setting-item> | |
48 | + <setting-item name="大小"> | |
49 | + <n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number> | |
50 | + </setting-item> | |
51 | + <setting-item name="颜色"> | |
52 | + <n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker> | |
53 | + </setting-item> | |
54 | + <setting-item name="位置"> | |
55 | + <n-select | |
56 | + v-model:value="item.label.position" | |
57 | + :options="[ | |
58 | + { label: 'top', value: 'top' }, | |
59 | + { label: 'left', value: 'left' }, | |
60 | + { label: 'right', value: 'right' }, | |
61 | + { label: 'bottom', value: 'bottom' } | |
62 | + ]" | |
63 | + /> | |
64 | + </setting-item> | |
65 | + </setting-item-box> | |
66 | + </CollapseItem> | |
67 | +</template> | |
68 | + | |
69 | +<script setup lang="ts"> | |
70 | +import { PropType, computed } from 'vue' | |
71 | +import { lineConf } from '@/packages/chartConfiguration/echarts/index' | |
72 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | |
73 | +import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
74 | + | |
75 | +const props = defineProps({ | |
76 | + optionData: { | |
77 | + type: Object as PropType<GlobalThemeJsonType>, | |
78 | + required: true | |
79 | + } | |
80 | +}) | |
81 | + | |
82 | +const seriesList = computed(() => { | |
83 | + return props.optionData.series | |
84 | +}) | |
85 | +</script> | ... | ... |
src/packages/components/external/Charts/Lines/OverrideLineForDeviceHistoryQuery/data.json
0 → 100644
1 | +{ | |
2 | + "dimensions": ["product", "data1", "data2"], | |
3 | + "source": [ | |
4 | + { | |
5 | + "product": "Mon", | |
6 | + "data1": 120, | |
7 | + "data2": 130 | |
8 | + }, | |
9 | + { | |
10 | + "product": "Tue", | |
11 | + "data1": 200, | |
12 | + "data2": 130 | |
13 | + }, | |
14 | + { | |
15 | + "product": "Wed", | |
16 | + "data1": 150, | |
17 | + "data2": 312 | |
18 | + }, | |
19 | + { | |
20 | + "product": "Thu", | |
21 | + "data1": 80, | |
22 | + "data2": 268 | |
23 | + }, | |
24 | + { | |
25 | + "product": "Fri", | |
26 | + "data1": 70, | |
27 | + "data2": 155 | |
28 | + }, | |
29 | + { | |
30 | + "product": "Sat", | |
31 | + "data1": 110, | |
32 | + "data2": 117 | |
33 | + }, | |
34 | + { | |
35 | + "product": "Sun", | |
36 | + "data1": 130, | |
37 | + "data2": 160 | |
38 | + } | |
39 | + ] | |
40 | +} | ... | ... |
src/packages/components/external/Charts/Lines/OverrideLineForDeviceHistoryQuery/helper.ts
0 → 100644
1 | +import dayjs, { Dayjs } from 'dayjs'; | |
2 | + | |
3 | +enum TimeUnit { | |
4 | + SECOND = 'second', | |
5 | + MINUTE = 'MINUTE', | |
6 | + HOUR = 'HOUR', | |
7 | + DAY = 'DAY', | |
8 | +} | |
9 | + | |
10 | +const unitMapping = { | |
11 | + [TimeUnit.SECOND]: '秒', | |
12 | + [TimeUnit.MINUTE]: '分', | |
13 | + [TimeUnit.HOUR]: '小时', | |
14 | + [TimeUnit.DAY]: '天', | |
15 | +}; | |
16 | + | |
17 | +const unitConversion = { | |
18 | + [TimeUnit.SECOND]: 1 * 1000, | |
19 | + [TimeUnit.MINUTE]: 1 * 60 * 1000, | |
20 | + [TimeUnit.HOUR]: 1 * 60 * 60 * 1000, | |
21 | + [TimeUnit.DAY]: 1 * 60 * 60 * 24 * 1000, | |
22 | +}; | |
23 | + | |
24 | +export const intervalOption = [ | |
25 | + { | |
26 | + id: 1, | |
27 | + unit: TimeUnit.SECOND, | |
28 | + linkage: [{ id: 1, unit: TimeUnit.SECOND }], | |
29 | + }, | |
30 | + { | |
31 | + id: 5, | |
32 | + unit: TimeUnit.SECOND, | |
33 | + linkage: [{ id: 1, unit: TimeUnit.SECOND }], | |
34 | + }, | |
35 | + { | |
36 | + id: 10, | |
37 | + unit: TimeUnit.SECOND, | |
38 | + linkage: [{ id: 1, unit: TimeUnit.SECOND }], | |
39 | + }, | |
40 | + { | |
41 | + id: 15, | |
42 | + unit: TimeUnit.SECOND, | |
43 | + linkage: [{ id: 1, unit: TimeUnit.SECOND }], | |
44 | + }, | |
45 | + { | |
46 | + id: 30, | |
47 | + unit: TimeUnit.SECOND, | |
48 | + linkage: [{ id: 1, unit: TimeUnit.SECOND }], | |
49 | + }, | |
50 | + { | |
51 | + id: 1, | |
52 | + unit: TimeUnit.MINUTE, | |
53 | + linkage: [ | |
54 | + { id: 1, unit: TimeUnit.SECOND }, | |
55 | + { id: 5, unit: TimeUnit.SECOND }, | |
56 | + ], | |
57 | + }, | |
58 | + { | |
59 | + id: 2, | |
60 | + unit: TimeUnit.MINUTE, | |
61 | + linkage: [ | |
62 | + { id: 1, unit: TimeUnit.SECOND }, | |
63 | + { id: 5, unit: TimeUnit.SECOND }, | |
64 | + { id: 10, unit: TimeUnit.SECOND }, | |
65 | + { id: 15, unit: TimeUnit.SECOND }, | |
66 | + ], | |
67 | + }, | |
68 | + { | |
69 | + id: 5, | |
70 | + unit: TimeUnit.MINUTE, | |
71 | + linkage: [ | |
72 | + { id: 1, unit: TimeUnit.SECOND }, | |
73 | + { id: 5, unit: TimeUnit.SECOND }, | |
74 | + { id: 10, unit: TimeUnit.SECOND }, | |
75 | + { id: 15, unit: TimeUnit.SECOND }, | |
76 | + { id: 30, unit: TimeUnit.SECOND }, | |
77 | + ], | |
78 | + }, | |
79 | + { | |
80 | + id: 10, | |
81 | + unit: TimeUnit.MINUTE, | |
82 | + linkage: [ | |
83 | + { id: 5, unit: TimeUnit.SECOND }, | |
84 | + { id: 10, unit: TimeUnit.SECOND }, | |
85 | + { id: 15, unit: TimeUnit.SECOND }, | |
86 | + { id: 30, unit: TimeUnit.SECOND }, | |
87 | + { id: 1, unit: TimeUnit.MINUTE }, | |
88 | + ], | |
89 | + }, | |
90 | + { | |
91 | + id: 15, | |
92 | + unit: TimeUnit.MINUTE, | |
93 | + linkage: [ | |
94 | + { id: 5, unit: TimeUnit.SECOND }, | |
95 | + { id: 10, unit: TimeUnit.SECOND }, | |
96 | + { id: 15, unit: TimeUnit.SECOND }, | |
97 | + { id: 30, unit: TimeUnit.SECOND }, | |
98 | + { id: 1, unit: TimeUnit.MINUTE }, | |
99 | + { id: 2, unit: TimeUnit.MINUTE }, | |
100 | + ], | |
101 | + }, | |
102 | + { | |
103 | + id: 30, | |
104 | + unit: TimeUnit.MINUTE, | |
105 | + linkage: [ | |
106 | + { id: 5, unit: TimeUnit.SECOND }, | |
107 | + { id: 10, unit: TimeUnit.SECOND }, | |
108 | + { id: 15, unit: TimeUnit.SECOND }, | |
109 | + { id: 30, unit: TimeUnit.SECOND }, | |
110 | + { id: 1, unit: TimeUnit.MINUTE }, | |
111 | + { id: 2, unit: TimeUnit.MINUTE }, | |
112 | + ], | |
113 | + }, | |
114 | + { | |
115 | + id: 1, | |
116 | + unit: TimeUnit.HOUR, | |
117 | + linkage: [ | |
118 | + { id: 10, unit: TimeUnit.SECOND }, | |
119 | + { id: 15, unit: TimeUnit.SECOND }, | |
120 | + { id: 30, unit: TimeUnit.SECOND }, | |
121 | + { id: 1, unit: TimeUnit.MINUTE }, | |
122 | + { id: 2, unit: TimeUnit.MINUTE }, | |
123 | + { id: 5, unit: TimeUnit.MINUTE }, | |
124 | + ], | |
125 | + }, | |
126 | + { | |
127 | + id: 2, | |
128 | + unit: TimeUnit.HOUR, | |
129 | + linkage: [ | |
130 | + { id: 15, unit: TimeUnit.SECOND }, | |
131 | + { id: 30, unit: TimeUnit.SECOND }, | |
132 | + { id: 1, unit: TimeUnit.MINUTE }, | |
133 | + { id: 2, unit: TimeUnit.MINUTE }, | |
134 | + { id: 5, unit: TimeUnit.MINUTE }, | |
135 | + { id: 10, unit: TimeUnit.MINUTE }, | |
136 | + { id: 15, unit: TimeUnit.MINUTE }, | |
137 | + ], | |
138 | + }, | |
139 | + { | |
140 | + id: 5, | |
141 | + unit: TimeUnit.HOUR, | |
142 | + linkage: [ | |
143 | + { id: 1, unit: TimeUnit.MINUTE }, | |
144 | + { id: 2, unit: TimeUnit.MINUTE }, | |
145 | + { id: 5, unit: TimeUnit.MINUTE }, | |
146 | + { id: 10, unit: TimeUnit.MINUTE }, | |
147 | + { id: 15, unit: TimeUnit.MINUTE }, | |
148 | + { id: 30, unit: TimeUnit.MINUTE }, | |
149 | + ], | |
150 | + }, | |
151 | + { | |
152 | + id: 10, | |
153 | + unit: TimeUnit.HOUR, | |
154 | + linkage: [ | |
155 | + { id: 2, unit: TimeUnit.MINUTE }, | |
156 | + { id: 5, unit: TimeUnit.MINUTE }, | |
157 | + { id: 10, unit: TimeUnit.MINUTE }, | |
158 | + { id: 15, unit: TimeUnit.MINUTE }, | |
159 | + { id: 30, unit: TimeUnit.MINUTE }, | |
160 | + { id: 1, unit: TimeUnit.HOUR }, | |
161 | + ], | |
162 | + }, | |
163 | + { | |
164 | + id: 12, | |
165 | + unit: TimeUnit.HOUR, | |
166 | + linkage: [ | |
167 | + { id: 2, unit: TimeUnit.MINUTE }, | |
168 | + { id: 5, unit: TimeUnit.MINUTE }, | |
169 | + { id: 10, unit: TimeUnit.MINUTE }, | |
170 | + { id: 15, unit: TimeUnit.MINUTE }, | |
171 | + { id: 30, unit: TimeUnit.MINUTE }, | |
172 | + { id: 1, unit: TimeUnit.HOUR }, | |
173 | + ], | |
174 | + }, | |
175 | + { | |
176 | + id: 1, | |
177 | + unit: TimeUnit.DAY, | |
178 | + linkage: [ | |
179 | + { id: 5, unit: TimeUnit.MINUTE }, | |
180 | + { id: 10, unit: TimeUnit.MINUTE }, | |
181 | + { id: 15, unit: TimeUnit.MINUTE }, | |
182 | + { id: 30, unit: TimeUnit.MINUTE }, | |
183 | + { id: 1, unit: TimeUnit.HOUR }, | |
184 | + { id: 2, unit: TimeUnit.HOUR }, | |
185 | + ], | |
186 | + }, | |
187 | + { | |
188 | + id: 7, | |
189 | + unit: TimeUnit.DAY, | |
190 | + linkage: [ | |
191 | + { id: 30, unit: TimeUnit.MINUTE }, | |
192 | + { id: 1, unit: TimeUnit.HOUR }, | |
193 | + { id: 2, unit: TimeUnit.HOUR }, | |
194 | + { id: 5, unit: TimeUnit.HOUR }, | |
195 | + { id: 10, unit: TimeUnit.HOUR }, | |
196 | + { id: 12, unit: TimeUnit.HOUR }, | |
197 | + { id: 1, unit: TimeUnit.DAY }, | |
198 | + ], | |
199 | + }, | |
200 | + { | |
201 | + id: 30, | |
202 | + unit: TimeUnit.DAY, | |
203 | + linkage: [ | |
204 | + { id: 2, unit: TimeUnit.HOUR }, | |
205 | + { id: 5, unit: TimeUnit.HOUR }, | |
206 | + { id: 10, unit: TimeUnit.HOUR }, | |
207 | + { id: 12, unit: TimeUnit.HOUR }, | |
208 | + { id: 1, unit: TimeUnit.DAY }, | |
209 | + ], | |
210 | + }, | |
211 | +].map((item) => { | |
212 | + return { | |
213 | + value: item.id * unitConversion[item.unit], | |
214 | + label: item.id + unitMapping[item.unit], | |
215 | + linkage: item.linkage.map((item) => { | |
216 | + return { | |
217 | + value: item.id * unitConversion[item.unit], | |
218 | + label: item.id + unitMapping[item.unit], | |
219 | + }; | |
220 | + }), | |
221 | + }; | |
222 | +}); | |
223 | + | |
224 | +const rangeIntervalOption = [ | |
225 | + { | |
226 | + id: 90, | |
227 | + unit: TimeUnit.DAY, | |
228 | + linkage: [ | |
229 | + { id: 5, unit: TimeUnit.HOUR }, | |
230 | + { id: 10, unit: TimeUnit.HOUR }, | |
231 | + { id: 12, unit: TimeUnit.HOUR }, | |
232 | + { id: 1, unit: TimeUnit.DAY }, | |
233 | + { id: 7, unit: TimeUnit.DAY }, | |
234 | + ], | |
235 | + }, | |
236 | + { | |
237 | + id: 180, | |
238 | + unit: TimeUnit.DAY, | |
239 | + linkage: [ | |
240 | + { id: 10, unit: TimeUnit.HOUR }, | |
241 | + { id: 12, unit: TimeUnit.HOUR }, | |
242 | + { id: 1, unit: TimeUnit.DAY }, | |
243 | + { id: 7, unit: TimeUnit.DAY }, | |
244 | + ], | |
245 | + }, | |
246 | + { | |
247 | + id: 360, | |
248 | + unit: TimeUnit.DAY, | |
249 | + linkage: [ | |
250 | + { id: 1, unit: TimeUnit.DAY }, | |
251 | + { id: 7, unit: TimeUnit.DAY }, | |
252 | + { id: 30, unit: TimeUnit.DAY }, | |
253 | + ], | |
254 | + }, | |
255 | +].map((item) => { | |
256 | + return { | |
257 | + value: item.id * unitConversion[item.unit], | |
258 | + label: item.id + unitMapping[item.unit], | |
259 | + linkage: item.linkage.map((item) => { | |
260 | + return { | |
261 | + value: item.id * unitConversion[item.unit], | |
262 | + label: item.id + unitMapping[item.unit], | |
263 | + }; | |
264 | + }), | |
265 | + }; | |
266 | +}); | |
267 | + | |
268 | +/** | |
269 | + * @description | |
270 | + * @param {number} value | |
271 | + * @returns | |
272 | + */ | |
273 | +export function getPacketIntervalByValue(value: number) { | |
274 | + return intervalOption.find((item) => item.value === value)?.linkage || []; | |
275 | +} | |
276 | + | |
277 | +export function getPacketIntervalByRange( | |
278 | + [start, end] = [null, null] as [Nullable<Dayjs>, Nullable<Dayjs>] | |
279 | +) { | |
280 | + start = typeof start === 'number' ? dayjs(start) : start | |
281 | + end = typeof end === 'number' ? dayjs(end) : end | |
282 | + | |
283 | + if (start && end) { | |
284 | + const value = end.diff(start, 'ms'); | |
285 | + let options: { value: number; label: string }[] = []; | |
286 | + for (const item of [...intervalOption, ...rangeIntervalOption]) { | |
287 | + if (item.value <= value) continue; | |
288 | + if (item.value >= value) { | |
289 | + options = item.linkage; | |
290 | + break; | |
291 | + } | |
292 | + } | |
293 | + return options; | |
294 | + } | |
295 | + return []; | |
296 | +} | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideLineForDeviceHistoryQuery', true) | |
6 | + | |
7 | +export const OverrideLineForDeviceHistoryQueryConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '单设备-属性-历史数据查询', | |
12 | + category: ChatCategoryEnum.LINE, | |
13 | + categoryName: ChatCategoryEnumName.LINE, | |
14 | + package: PackagesCategoryEnum.CHARTS, | |
15 | + chartFrame: ChartFrameEnum.ECHARTS, | |
16 | + image: 'line.png' | |
17 | +} | ... | ... |
src/packages/components/external/Charts/Lines/OverrideLineForDeviceHistoryQuery/index.vue
0 → 100644
1 | +<template> | |
2 | + <div> | |
3 | + <n-space vertical> | |
4 | + <div class="form"> | |
5 | + <n-date-picker size="small" :to="true" clearable v-model:value="queryCondition.timeRange" type="datetimerange" | |
6 | + :shortcuts="rangeShortcuts" format="yyyy-MM-dd" @change="queryCondition.interval = null" /> | |
7 | + <n-select v-model:value="queryCondition.agg" size="small" :options="aggOptions" clearable | |
8 | + @change="handleAggChange" /> | |
9 | + <n-input-number :min="7" :max="50000" size="small" v-if="queryCondition.agg === 'NONE'" | |
10 | + v-model:value="queryCondition.limit" clearable /> | |
11 | + <n-select v-if="queryCondition.agg !== 'NONE'" size="small" v-model:value="queryCondition.interval" | |
12 | + :options="getPacketIntervalByRange(queryCondition.timeRange)" /> | |
13 | + </div> | |
14 | + </n-space> | |
15 | + <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()" | |
16 | + :update-options="{ | |
17 | + replaceMerge: replaceMergeArr | |
18 | + }" autoresize @mouseover="handleHighlight" @mouseout="handleDownplay"> | |
19 | + </v-chart> | |
20 | + </div> | |
21 | +</template> | |
22 | + | |
23 | +<script setup lang="ts"> | |
24 | +import { PropType, computed, watch, ref, nextTick, onMounted, toRefs } from 'vue' | |
25 | +import VChart from 'vue-echarts' | |
26 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | |
27 | +import { use } from 'echarts/core' | |
28 | +import { CanvasRenderer } from 'echarts/renderers' | |
29 | +import { LineChart } from 'echarts/charts' | |
30 | +import config, { includes, seriesItem } from './config' | |
31 | +import { mergeTheme } from '@/packages/public/chart' | |
32 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
33 | +import { useChartDataFetch } from '@/hooks' | |
34 | +import { isPreview } from '@/utils' | |
35 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | |
36 | +import isObject from 'lodash/isObject' | |
37 | +import cloneDeep from 'lodash/cloneDeep' | |
38 | +import dataJson from './data.json' | |
39 | +import { useFullScreen } from '../../utls/fullScreen' | |
40 | +import { useAssembleDataHooks } from '@/hooks/external/useAssembleData.hook' | |
41 | +import { SocketReceiveMessageType } from '@/store/external/modules/socketStore.d' | |
42 | +import { useChartInteract } from '@/hooks/external/useChartSelectDeviceInteract.hook' | |
43 | +import { getPacketIntervalByRange } from './helper' | |
44 | + | |
45 | +const props = defineProps({ | |
46 | + themeSetting: { | |
47 | + type: Object, | |
48 | + required: true | |
49 | + }, | |
50 | + themeColor: { | |
51 | + type: Object, | |
52 | + required: true | |
53 | + }, | |
54 | + chartConfig: { | |
55 | + type: Object as PropType<config>, | |
56 | + required: true | |
57 | + } | |
58 | +}) | |
59 | + | |
60 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | |
61 | + | |
62 | +const { queryCondition } = toRefs(props.chartConfig.option) | |
63 | + | |
64 | +use([DatasetComponent, CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent]) | |
65 | + | |
66 | +const chartEditStore = useChartEditStore() | |
67 | + | |
68 | +const replaceMergeArr = ref<string[]>() | |
69 | + | |
70 | +const aggOptions = [ | |
71 | + { label: '最小值', value: 'MIN' }, | |
72 | + { label: '最大值', value: 'MAX' }, | |
73 | + { label: '平均值', value: 'AVG' }, | |
74 | + { label: '求和', value: 'SUM' }, | |
75 | + { label: '计数', value: 'COUNT' }, | |
76 | + { label: '空', value: 'NONE' } | |
77 | +] | |
78 | + | |
79 | +const rangeShortcuts = { | |
80 | + 昨天: () => { | |
81 | + const cur = new Date().getTime() | |
82 | + return [cur - 86400000, cur] as const | |
83 | + }, | |
84 | + 最近7天: () => { | |
85 | + const cur = new Date().getTime() | |
86 | + return [cur - 604800000, cur] as const | |
87 | + }, | |
88 | + 最近30天: () => { | |
89 | + const cur = new Date().getTime() | |
90 | + return [cur - 2592000000, cur] as const | |
91 | + } | |
92 | +} | |
93 | + | |
94 | +const option = computed(() => { | |
95 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
96 | +}) | |
97 | + | |
98 | +// dataset 无法变更条数的补丁 | |
99 | +watch( | |
100 | + () => props.chartConfig.option.dataset, | |
101 | + (newData: { dimensions: any }, oldData) => { | |
102 | + try { | |
103 | + if (!isObject(newData) || !('dimensions' in newData)) return | |
104 | + if (Array.isArray(newData?.dimensions)) { | |
105 | + const seriesArr = [] | |
106 | + // 对oldData进行判断,防止传入错误数据之后对旧维度判断产生干扰 | |
107 | + // 此处计算的是dimensions的Y轴维度,若是dimensions.length为0或1,则默认为1,排除X轴维度干扰 | |
108 | + const oldDimensions = | |
109 | + Array.isArray(oldData?.dimensions) && oldData.dimensions.length >= 1 ? oldData.dimensions.length : 1 | |
110 | + const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1 | |
111 | + const dimensionsGap = newDimensions - oldDimensions | |
112 | + if (dimensionsGap < 0) { | |
113 | + props.chartConfig.option.series.splice(newDimensions - 1) | |
114 | + } else if (dimensionsGap > 0) { | |
115 | + if (!oldData || !oldData?.dimensions || !Array.isArray(oldData?.dimensions) || !oldData?.dimensions.length) { | |
116 | + props.chartConfig.option.series = [] | |
117 | + } | |
118 | + for (let i = 0; i < dimensionsGap; i++) { | |
119 | + seriesArr.push(cloneDeep(seriesItem)) | |
120 | + } | |
121 | + props.chartConfig.option.series.push(...seriesArr) | |
122 | + } | |
123 | + replaceMergeArr.value = ['series'] | |
124 | + nextTick(() => { | |
125 | + replaceMergeArr.value = [] | |
126 | + }) | |
127 | + } | |
128 | + } catch (error) { | |
129 | + console.log(error) | |
130 | + } | |
131 | + }, | |
132 | + { | |
133 | + deep: false | |
134 | + } | |
135 | +) | |
136 | + | |
137 | +let seriesDataNum = -1 | |
138 | +let seriesDataMaxLength = 0 | |
139 | +let intervalInstance: any = null | |
140 | +const duration = 1500 | |
141 | + | |
142 | +// 会重新选择需要选中和展示的数据 | |
143 | +const handleSeriesData = () => { | |
144 | + if (seriesDataNum > -1) { | |
145 | + vChartRef.value?.dispatchAction({ | |
146 | + type: 'downplay', | |
147 | + dataIndex: seriesDataNum | |
148 | + }) | |
149 | + } | |
150 | + seriesDataNum = seriesDataNum >= seriesDataMaxLength - 1 ? 0 : seriesDataNum + 1 | |
151 | + vChartRef.value?.dispatchAction({ | |
152 | + type: 'showTip', | |
153 | + seriesIndex: 0, | |
154 | + dataIndex: seriesDataNum | |
155 | + }) | |
156 | +} | |
157 | + | |
158 | +// 新增轮播 | |
159 | +const addPieInterval = (newData?: typeof dataJson, skipPre = false) => { | |
160 | + if (!skipPre && !Array.isArray(newData?.source)) return | |
161 | + if (!skipPre) seriesDataMaxLength = newData?.source.length || 0 | |
162 | + clearInterval(intervalInstance) | |
163 | + intervalInstance = setInterval(() => { | |
164 | + handleSeriesData() | |
165 | + }, duration) | |
166 | +} | |
167 | + | |
168 | +// 取消轮播 | |
169 | +const clearPieInterval = () => { | |
170 | + vChartRef.value?.dispatchAction({ | |
171 | + type: 'hideTip', | |
172 | + seriesIndex: 0, | |
173 | + dataIndex: seriesDataNum | |
174 | + }) | |
175 | + vChartRef.value?.dispatchAction({ | |
176 | + type: 'downplay', | |
177 | + dataIndex: seriesDataNum | |
178 | + }) | |
179 | + clearInterval(intervalInstance) | |
180 | + intervalInstance = null | |
181 | +} | |
182 | + | |
183 | +// 处理鼠标聚焦高亮内容 | |
184 | +const handleHighlight = () => { | |
185 | + clearPieInterval() | |
186 | +} | |
187 | + | |
188 | +// 处理鼠标取消悬浮 | |
189 | +const handleDownplay = () => { | |
190 | + if (props.chartConfig.option.isCarousel && !intervalInstance) { | |
191 | + // 恢复轮播 | |
192 | + addPieInterval(undefined, true) | |
193 | + } | |
194 | +} | |
195 | + | |
196 | +watch( | |
197 | + () => props.chartConfig.option.isCarousel, | |
198 | + newData => { | |
199 | + if (newData) { | |
200 | + addPieInterval(undefined, true) | |
201 | + props.chartConfig.option.legend.show = false | |
202 | + } else { | |
203 | + props.chartConfig.option.legend.show = true | |
204 | + clearPieInterval() | |
205 | + } | |
206 | + } | |
207 | +) | |
208 | + | |
209 | +//fix 修复v-chart图表绑定联动组件视图不更新问题 | |
210 | +const updateVChart = async (newData: SocketReceiveMessageType) => { | |
211 | + //区分是普通请求还是ws请求 | |
212 | + if (!isObject(newData) || !('dimensions' in newData)) { | |
213 | + const { data } = newData | |
214 | + const { keys, record } = useAssembleDataHooks(data) | |
215 | + vChartRef.value?.setOption({ | |
216 | + dataset: { | |
217 | + dimensions: ['ts', ...keys], | |
218 | + source: [record] | |
219 | + } | |
220 | + }) | |
221 | + } else { | |
222 | + //异步更新,同步更新会造成联动组件控制,图表不及时更新 | |
223 | + await nextTick().then(() => { | |
224 | + vChartRef.value?.setOption( | |
225 | + { | |
226 | + ...option.value, | |
227 | + dataset: newData | |
228 | + }, | |
229 | + { | |
230 | + notMerge: true | |
231 | + } | |
232 | + ) | |
233 | + }) | |
234 | + } | |
235 | +} | |
236 | + | |
237 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any, targetComponent: any) => { | |
238 | + //联动支持分组 | |
239 | + /** | |
240 | + * 修复多个分组,然后下拉框联动,会影响另一个组件 | |
241 | + */ | |
242 | + chartEditStore.getComponentList.forEach(targetItem => { | |
243 | + if (targetItem.isGroup) { | |
244 | + targetItem.groupList?.forEach(groupItem => { | |
245 | + if (groupItem.id === props.chartConfig.id) { | |
246 | + groupItem.option.dataset = newData | |
247 | + } | |
248 | + }) | |
249 | + } else { | |
250 | + if (targetItem.id === props.chartConfig.id) { | |
251 | + targetItem.option.dataset = newData | |
252 | + } | |
253 | + } | |
254 | + }) | |
255 | + // | |
256 | + updateVChart(newData) | |
257 | +}) | |
258 | + | |
259 | +const handleAggChange = (value: string) => { | |
260 | + if (value === 'NONE') queryCondition.value.interval = null | |
261 | +} | |
262 | + | |
263 | +onMounted(() => { | |
264 | + seriesDataMaxLength = dataJson.source.length | |
265 | + if (props.chartConfig.option.isCarousel) { | |
266 | + addPieInterval(undefined, true) | |
267 | + } | |
268 | +}) | |
269 | + | |
270 | +watch( | |
271 | + () => queryCondition.value, | |
272 | + newValue => { | |
273 | + const obj = { | |
274 | + startTs: newValue.timeRange.at(-2), | |
275 | + endTs: newValue.timeRange.at(-1), | |
276 | + limit: newValue.limit, | |
277 | + agg: newValue.agg, | |
278 | + interval: newValue.interval | |
279 | + } | |
280 | + if (newValue.agg !== 'NONE') { | |
281 | + Reflect.deleteProperty(obj, 'limit') | |
282 | + } | |
283 | + onChange(obj) | |
284 | + }, | |
285 | + { | |
286 | + deep: true | |
287 | + } | |
288 | +) | |
289 | + | |
290 | +// 监听事件改变 | |
291 | +const onChange = (v: object) => { | |
292 | + // 存储到联动数据 | |
293 | + useChartInteract(props.chartConfig, useChartEditStore, { data: v }) | |
294 | +} | |
295 | + | |
296 | +</script> | |
297 | + | |
298 | +<style lang="scss" scoped> | |
299 | +.form { | |
300 | + display: grid; | |
301 | + grid-template-columns: 2fr 1fr 1fr; | |
302 | + gap: 8px; | |
303 | +} | |
304 | +</style> | ... | ... |
... | ... | @@ -16,13 +16,13 @@ |
16 | 16 | </template> |
17 | 17 | |
18 | 18 | <script setup lang="ts"> |
19 | -import { PropType, computed, watch, ref, onMounted, unref, toRaw, toRefs } from 'vue' | |
19 | +import { PropType, computed, watch, ref, onMounted, unref, toRaw, toRefs,nextTick } from 'vue' | |
20 | 20 | import VChart from 'vue-echarts' |
21 | 21 | import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' |
22 | 22 | import { use } from 'echarts/core' |
23 | 23 | import { CanvasRenderer } from 'echarts/renderers' |
24 | 24 | import { LineChart } from 'echarts/charts' |
25 | -import config, { includes } from './config' | |
25 | +import config, { includes,seriesItem } from './config' | |
26 | 26 | import { mergeTheme } from '@/packages/public/chart' |
27 | 27 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
28 | 28 | import { useChartDataFetch } from '@/hooks' |
... | ... | @@ -32,6 +32,8 @@ import dataJson from './data.json' |
32 | 32 | import { useFullScreen } from '../../utls/fullScreen' |
33 | 33 | import { SocketReceiveMessageType } from '@/store/external/modules/socketStore.d' |
34 | 34 | import { useAssembleDataHooks } from '@/hooks/external/useAssembleData.hook' |
35 | +import isObject from 'lodash/isObject' | |
36 | +import cloneDeep from 'lodash/cloneDeep' | |
35 | 37 | |
36 | 38 | const props = defineProps({ |
37 | 39 | themeSetting: { |
... | ... | @@ -161,6 +163,45 @@ watch( |
161 | 163 | } |
162 | 164 | ) |
163 | 165 | |
166 | +// dataset 无法变更条数的补丁 | |
167 | +watch( | |
168 | + () => props.chartConfig.option.dataset, | |
169 | + (newData: { dimensions: any }, oldData) => { | |
170 | + try { | |
171 | + if (!isObject(newData) || !('dimensions' in newData)) return | |
172 | + if (Array.isArray(newData?.dimensions)) { | |
173 | + const seriesArr = [] | |
174 | + // 对oldData进行判断,防止传入错误数据之后对旧维度判断产生干扰 | |
175 | + // 此处计算的是dimensions的Y轴维度,若是dimensions.length为0或1,则默认为1,排除X轴维度干扰 | |
176 | + const oldDimensions = | |
177 | + Array.isArray(oldData?.dimensions) && oldData.dimensions.length >= 1 ? oldData.dimensions.length : 1 | |
178 | + const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1 | |
179 | + const dimensionsGap = newDimensions - oldDimensions | |
180 | + if (dimensionsGap < 0) { | |
181 | + props.chartConfig.option.series.splice(newDimensions - 1) | |
182 | + } else if (dimensionsGap > 0) { | |
183 | + if (!oldData || !oldData?.dimensions || !Array.isArray(oldData?.dimensions) || !oldData?.dimensions.length) { | |
184 | + props.chartConfig.option.series = [] | |
185 | + } | |
186 | + for (let i = 0; i < dimensionsGap; i++) { | |
187 | + seriesArr.push(cloneDeep(seriesItem)) | |
188 | + } | |
189 | + props.chartConfig.option.series.push(...seriesArr) | |
190 | + } | |
191 | + replaceMergeArr.value = ['series'] | |
192 | + nextTick(() => { | |
193 | + replaceMergeArr.value = [] | |
194 | + }) | |
195 | + } | |
196 | + } catch (error) { | |
197 | + console.log(error) | |
198 | + } | |
199 | + }, | |
200 | + { | |
201 | + deep: false | |
202 | + } | |
203 | +) | |
204 | + | |
164 | 205 | //fix 修复v-chart图表绑定联动组件视图不更新问题 |
165 | 206 | const updateVChart = (newData: SocketReceiveMessageType) => { |
166 | 207 | if (!newData) return | ... | ... |
src/packages/components/external/Charts/Maps/AddThreeDimensionalMap/components/SelectCity.vue
0 → 100644
1 | +<script lang="ts" setup name="SelectCity"> | |
2 | +import { onMounted, reactive } from 'vue' | |
3 | +import { getAreaList } from '@/api/external/common/index' | |
4 | +import { areaEnum } from '../config' | |
5 | + | |
6 | +const props = defineProps({ | |
7 | + drillingIn: { | |
8 | + type: Boolean, | |
9 | + default: false | |
10 | + }, | |
11 | + optionData: { | |
12 | + type: Object | |
13 | + } | |
14 | +}) | |
15 | + | |
16 | +const emits = defineEmits(['submit']) | |
17 | + | |
18 | +const selectOptions = reactive({ | |
19 | + provinceOptions: [], | |
20 | + cityOptions: [], | |
21 | + countryOptions: [] | |
22 | +}) | |
23 | + | |
24 | +const selectValues = reactive({ | |
25 | + provinceValue: 'china', | |
26 | + cityValue: null, | |
27 | + countyValue: null, | |
28 | + levelStr: areaEnum.COUNTRY | |
29 | +}) | |
30 | + | |
31 | +const getAreaLists = async (level = areaEnum.PROVINCE, parentId = 1) => { | |
32 | + const resp = await getAreaList({ | |
33 | + level, | |
34 | + parentId | |
35 | + }) | |
36 | + if (!resp) return [] | |
37 | + return resp.map((item: any) => ({ label: item.name, value: item.code })) | |
38 | +} | |
39 | + | |
40 | +onMounted(async () => { | |
41 | + selectOptions.provinceOptions = await getAreaLists() | |
42 | + ;(selectOptions.provinceOptions as never as any).unshift({ | |
43 | + label: '中国', | |
44 | + value: 'china' | |
45 | + }) | |
46 | + onHandleSelectProvince(props.optionData?.mapRegion.saveSelect['provinceValue']) | |
47 | + for (let i in selectValues) Reflect.set(selectValues, i, props.optionData?.mapRegion.saveSelect[i]) | |
48 | +}) | |
49 | + | |
50 | +const onHandleSelectProvince = async (value: number | string) => { | |
51 | + selectValues.cityValue = null | |
52 | + selectValues.countyValue = null | |
53 | + if (value === 'china') return (selectValues.levelStr = areaEnum.COUNTRY) | |
54 | + selectOptions.cityOptions = await getAreaLists(areaEnum.CITY, value as any) | |
55 | + selectValues.levelStr = areaEnum.PROVINCE | |
56 | +} | |
57 | + | |
58 | +const onHandleSelectCity = async (value: number) => { | |
59 | + selectValues.countyValue = null | |
60 | + selectOptions.countryOptions = await getAreaLists(areaEnum.COUNTY, value) | |
61 | + selectValues.levelStr = areaEnum.CITY | |
62 | +} | |
63 | + | |
64 | +const onHandleSubmit = () => { | |
65 | + emits('submit', selectValues) | |
66 | +} | |
67 | +const resetValue = () => { | |
68 | + selectValues.provinceValue = 'china' | |
69 | + selectValues.cityValue = null | |
70 | + selectValues.countyValue = null | |
71 | + selectValues.levelStr = areaEnum.COUNTRY | |
72 | + selectOptions.cityOptions = [] | |
73 | + selectOptions.countryOptions = [] | |
74 | +} | |
75 | +defineExpose({ | |
76 | + resetValue | |
77 | +}) | |
78 | +</script> | |
79 | + | |
80 | +<template> | |
81 | + <div class="select-city-content"> | |
82 | + <n-select | |
83 | + @change="onHandleSelectProvince" | |
84 | + placeholder="请选择省份" | |
85 | + v-model:value="selectValues.provinceValue" | |
86 | + :options="selectOptions.provinceOptions" | |
87 | + /> | |
88 | + <n-select | |
89 | + v-if="!props.drillingIn" | |
90 | + @change="onHandleSelectCity" | |
91 | + placeholder="请选择城市" | |
92 | + v-model:value="selectValues.cityValue" | |
93 | + :options="selectOptions.cityOptions" | |
94 | + /> | |
95 | + <!-- 保留待用(下钻到区以下) --> | |
96 | + <!-- <n-select | |
97 | + v-if="!drillingIn" | |
98 | + placeholder="请选择区域" | |
99 | + v-model:value="selectValues.countyValue" | |
100 | + :options="selectOptions.countryOptions" | |
101 | + /> --> | |
102 | + <n-button type="primary" @click="onHandleSubmit">确定</n-button> | |
103 | + </div> | |
104 | +</template> | |
105 | + | |
106 | +<style lang="scss" scoped> | |
107 | +.select-city-content { | |
108 | + display: flex; | |
109 | + flex-direction: column; | |
110 | + justify-content: space-between; | |
111 | + align-items: center; | |
112 | + gap: 30px; | |
113 | +} | |
114 | +</style> | ... | ... |
1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
2 | +import { AddThreeDimensionalMapConfig } from './index' | |
3 | +import { chartInitConfig } from '@/settings/designSetting' | |
4 | +import { CreateComponentType } from '@/packages/index.d' | |
5 | +import cloneDeep from 'lodash/cloneDeep' | |
6 | +import dataMaps from './data.json' | |
7 | + | |
8 | +//省市区枚举 | |
9 | +export const enum areaEnum { | |
10 | + PROVINCE = 'PROVINCE', //省份 | |
11 | + CITY = 'CITY', //城市 | |
12 | + COUNTY = 'COUNTY', //县 | |
13 | + COUNTRY = 'COUNTRY', //国家 | |
14 | + TOWN = 'TOWN' //镇 | |
15 | +} | |
16 | + | |
17 | +export const includes = [] | |
18 | + | |
19 | +export const option = { | |
20 | + iconColor: 'black', | |
21 | + showIcon: false, | |
22 | + iconDistanceRight: 20, | |
23 | + iconDistanceTop: 20, | |
24 | + drillingIn: false, | |
25 | + dataset: dataMaps, | |
26 | + mapRegion: { | |
27 | + adcode: 'china', | |
28 | + showHainanIsLands: true, | |
29 | + saveSelect: { | |
30 | + levelStr: areaEnum.COUNTRY | |
31 | + } | |
32 | + }, | |
33 | + tooltip: { | |
34 | + show: true | |
35 | + }, | |
36 | + geo3D: { | |
37 | + show: false, // 隐藏该层,为true时会导致出现两个地图 | |
38 | + map: 'centerMap', | |
39 | + roam: true, | |
40 | + regionHeight: 0, | |
41 | + emphasis: { | |
42 | + label: { | |
43 | + show: true, | |
44 | + textStyle: { | |
45 | + color: '#000', | |
46 | + fontSize: 14 | |
47 | + } | |
48 | + }, | |
49 | + itemStyle: { | |
50 | + color: '#ff0' | |
51 | + } | |
52 | + } | |
53 | + }, | |
54 | + series: [ | |
55 | + { | |
56 | + type: 'map3D', | |
57 | + map: 'centerMap', | |
58 | + name: 'centerMap', | |
59 | + regionHeight: 3, | |
60 | + label: { | |
61 | + show: true, | |
62 | + textStyle: { | |
63 | + color: '#fff', | |
64 | + fontSize: 14 | |
65 | + } | |
66 | + }, | |
67 | + itemStyle: { | |
68 | + color: 'green', | |
69 | + borderWidth: 0.8, | |
70 | + borderColor: 'blue' | |
71 | + }, | |
72 | + data: [] | |
73 | + }, | |
74 | + { | |
75 | + name: 'scatter3D', | |
76 | + type: 'scatter3D', | |
77 | + coordinateSystem: 'geo3D', | |
78 | + symbol: 'circle', | |
79 | + symbolSize: 20, | |
80 | + animation: true, | |
81 | + data: dataMaps | |
82 | + } | |
83 | + ] | |
84 | +} | |
85 | +export const MapDefaultConfig = { ...option } | |
86 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
87 | + public key: string = AddThreeDimensionalMapConfig.key | |
88 | + public attr = { ...chartInitConfig, w: 750, h: 800, zIndex: -1 } | |
89 | + public chartConfig = cloneDeep(AddThreeDimensionalMapConfig) | |
90 | + public option = echartOptionProfixHandle(option, includes) | |
91 | +} | ... | ... |
1 | +<template> | |
2 | + <!-- Echarts 全局设置 --> | |
3 | + <global-setting :optionData="optionData"></global-setting> | |
4 | + <CollapseItem name="地图" :expanded="true"> | |
5 | + <SettingItemBox name="开启下钻"> | |
6 | + <SettingItem name=""> | |
7 | + <n-switch @change="handleChangeDrillingIn" v-model:value="optionData.drillingIn" size="small"></n-switch> | |
8 | + </SettingItem> | |
9 | + </SettingItemBox> | |
10 | + <SettingItemBox name="返回图标"> | |
11 | + <SettingItem name=""> | |
12 | + <n-switch v-model:value="optionData.drillingIn" size="small"></n-switch> | |
13 | + </SettingItem> | |
14 | + </SettingItemBox> | |
15 | + <SettingItemBox name="图标颜色"> | |
16 | + <SettingItem name=""> | |
17 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.iconColor"></n-color-picker> | |
18 | + </SettingItem> | |
19 | + </SettingItemBox> | |
20 | + <SettingItemBox name="图标距离"> | |
21 | + <SettingItem name="距右"> | |
22 | + <n-input-number | |
23 | + v-model:value="optionData.iconDistanceRight" | |
24 | + :min="1" | |
25 | + size="small" | |
26 | + placeholder="请输入" | |
27 | + ></n-input-number> | |
28 | + </SettingItem> | |
29 | + <SettingItem name="距上"> | |
30 | + <n-input-number | |
31 | + v-model:value="optionData.iconDistanceTop" | |
32 | + :min="1" | |
33 | + size="small" | |
34 | + placeholder="请输入" | |
35 | + ></n-input-number> | |
36 | + </SettingItem> | |
37 | + </SettingItemBox> | |
38 | + <SelectCity | |
39 | + ref="SelectCityRef" | |
40 | + :optionData="optionData" | |
41 | + :drillingIn="optionData.drillingIn" | |
42 | + @submit="onHandleSelectValues" | |
43 | + /> | |
44 | + <SettingItemBox name="颜色"> | |
45 | + <SettingItem name="区域颜色"> | |
46 | + <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].itemStyle.color"></n-color-picker> | |
47 | + </SettingItem> | |
48 | + <SettingItem name="边框颜色"> | |
49 | + <n-color-picker | |
50 | + size="small" | |
51 | + :modes="['hex']" | |
52 | + v-model:value="seriesList[0].itemStyle.borderColor" | |
53 | + ></n-color-picker> | |
54 | + </SettingItem> | |
55 | + <SettingItem name="边框大小"> | |
56 | + <n-input-number | |
57 | + v-model:value="seriesList[0].itemStyle.borderWidth" | |
58 | + :min="0" | |
59 | + size="small" | |
60 | + placeholder="请输入" | |
61 | + ></n-input-number> | |
62 | + </SettingItem> | |
63 | + <SettingItem name="厚度"> | |
64 | + <n-input-number | |
65 | + v-model:value="seriesList[0].regionHeight" | |
66 | + :min="0" | |
67 | + size="small" | |
68 | + placeholder="请输入" | |
69 | + ></n-input-number> | |
70 | + </SettingItem> | |
71 | + </SettingItemBox> | |
72 | + <SettingItemBox name="标题"> | |
73 | + <SettingItem name="是否显示"> | |
74 | + <n-switch v-model:value="seriesList[0].label.show" size="small"></n-switch> | |
75 | + </SettingItem> | |
76 | + <SettingItem name="颜色"> | |
77 | + <n-color-picker | |
78 | + size="small" | |
79 | + :modes="['hex']" | |
80 | + v-model:value="seriesList[0].label.textStyle.color" | |
81 | + ></n-color-picker> | |
82 | + </SettingItem> | |
83 | + <SettingItem name="大小"> | |
84 | + <n-input-number | |
85 | + v-model:value="seriesList[0].label.textStyle.fontSize" | |
86 | + :min="0" | |
87 | + size="small" | |
88 | + placeholder="请输入" | |
89 | + ></n-input-number> | |
90 | + </SettingItem> | |
91 | + </SettingItemBox> | |
92 | + <SettingItemBox name="高亮"> | |
93 | + <SettingItem name="标题显示"> | |
94 | + <n-switch v-model:value="optionData.geo3D.emphasis.label.show" size="small"></n-switch> | |
95 | + </SettingItem> | |
96 | + <SettingItem name="颜色"> | |
97 | + <n-color-picker | |
98 | + size="small" | |
99 | + :modes="['hex']" | |
100 | + v-model:value="optionData.geo3D.emphasis.label.textStyle.color" | |
101 | + ></n-color-picker> | |
102 | + </SettingItem> | |
103 | + <SettingItem name="大小"> | |
104 | + <n-input-number | |
105 | + v-model:value="optionData.geo3D.emphasis.label.textStyle.fontSize" | |
106 | + :min="0" | |
107 | + size="small" | |
108 | + placeholder="请输入" | |
109 | + ></n-input-number> | |
110 | + </SettingItem> | |
111 | + <SettingItem name="区块颜色"> | |
112 | + <n-color-picker | |
113 | + size="small" | |
114 | + :modes="['hex']" | |
115 | + v-model:value="optionData.geo3D.emphasis.itemStyle.color" | |
116 | + ></n-color-picker> | |
117 | + </SettingItem> | |
118 | + </SettingItemBox> | |
119 | + <SettingItemBox name="标记"> | |
120 | + <SettingItem name="大小"> | |
121 | + <n-input-number | |
122 | + v-model:value="seriesList[1].symbolSize" | |
123 | + :min="0" | |
124 | + :step="10" | |
125 | + size="small" | |
126 | + placeholder="请输入" | |
127 | + ></n-input-number> | |
128 | + </SettingItem> | |
129 | + <SettingItem name="形状"> | |
130 | + <n-select :options="symbolOption" v-model:value="seriesList[1].symbol"></n-select> | |
131 | + </SettingItem> | |
132 | + </SettingItemBox> | |
133 | + </CollapseItem> | |
134 | +</template> | |
135 | + | |
136 | +<script setup lang="ts"> | |
137 | +import { PropType, computed, ref } from 'vue' | |
138 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
139 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | |
140 | +import { GlobalSetting } from '@/components/Pages/ChartItemSetting' | |
141 | +import SelectCity from './components/SelectCity.vue' | |
142 | + | |
143 | +const props = defineProps({ | |
144 | + optionData: { | |
145 | + type: Object as PropType<GlobalThemeJsonType>, | |
146 | + required: true | |
147 | + } | |
148 | +}) | |
149 | + | |
150 | +const seriesList = computed(() => { | |
151 | + return props.optionData.series | |
152 | +}) | |
153 | + | |
154 | +const symbolOption = ref([ | |
155 | + { | |
156 | + label: 'circle', | |
157 | + value: 'circle' | |
158 | + }, | |
159 | + { | |
160 | + label: 'rect', | |
161 | + value: 'rect' | |
162 | + }, | |
163 | + { | |
164 | + label: 'roundRect', | |
165 | + value: 'roundRect' | |
166 | + }, | |
167 | + { | |
168 | + label: 'triangle', | |
169 | + value: 'triangle' | |
170 | + }, | |
171 | + { | |
172 | + label: 'diamond', | |
173 | + value: 'diamond' | |
174 | + }, | |
175 | + { | |
176 | + label: 'pin', | |
177 | + value: 'pin' | |
178 | + }, | |
179 | + { | |
180 | + label: 'arrow', | |
181 | + value: 'arrow' | |
182 | + }, | |
183 | + { | |
184 | + label: 'none', | |
185 | + value: 'none' | |
186 | + } | |
187 | +]) | |
188 | + | |
189 | +const SelectCityRef = ref<typeof SelectCity>() | |
190 | + | |
191 | +const onHandleSelectValues = (values: any) => { | |
192 | + const { cityValue, countyValue, provinceValue } = values | |
193 | + props.optionData.mapRegion.saveSelect = values | |
194 | + props.optionData.mapRegion.adcode = countyValue | |
195 | + ? countyValue | |
196 | + : cityValue | |
197 | + ? cityValue | |
198 | + : provinceValue === 'china' | |
199 | + ? 'china' | |
200 | + : provinceValue | |
201 | +} | |
202 | + | |
203 | +const handleChangeDrillingIn = () => { | |
204 | + SelectCityRef.value?.resetValue() | |
205 | +} | |
206 | +</script> | ... | ... |
1 | +[ | |
2 | + { | |
3 | + "name": "四川省", | |
4 | + "value": [104.10068024609373, 30.580525329665175, 20000], | |
5 | + "adcode": 510000, | |
6 | + "height": 5, | |
7 | + "itemStyle": { | |
8 | + "color": "pink", | |
9 | + "opacity": 1, | |
10 | + "borderWidth": 0.4, | |
11 | + "borderColor": "#5F9EA0" | |
12 | + } | |
13 | + }, | |
14 | + { | |
15 | + "name": "湖南省", | |
16 | + "value": [111.73068512890623, 27.86754509366569, 20000], | |
17 | + "adcode": 430000, | |
18 | + "height": 4, | |
19 | + "itemStyle": { | |
20 | + "color": "blue", | |
21 | + "opacity": 1, | |
22 | + "borderWidth": 0.4, | |
23 | + "borderColor": "#5F9EA0" | |
24 | + } | |
25 | + }, | |
26 | + { | |
27 | + "name": "吉林省", | |
28 | + "value": [126.45236481640623, 43.7943407914815, 20000], | |
29 | + "adcode": 220000, | |
30 | + "height": 5, | |
31 | + "itemStyle": { | |
32 | + "color": "yellow", | |
33 | + "opacity": 1, | |
34 | + "borderWidth": 0.4, | |
35 | + "borderColor": "#5F9EA0" | |
36 | + } | |
37 | + }, | |
38 | + { | |
39 | + "name": "山东省", | |
40 | + "value": [118.67404450390623, 36.16387465872037, 20000], | |
41 | + "adcode": 370000, | |
42 | + "height": 6, | |
43 | + "itemStyle": { | |
44 | + "color": "orange", | |
45 | + "opacity": 1, | |
46 | + "borderWidth": 0.4, | |
47 | + "borderColor": "#5F9EA0" | |
48 | + } | |
49 | + } | |
50 | +] | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('AddThreeDimensionalMap', true) | |
6 | + | |
7 | +export const AddThreeDimensionalMapConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '3d地图', | |
12 | + category: ChatCategoryEnum.MAP, | |
13 | + categoryName: ChatCategoryEnumName.MAP, | |
14 | + package: PackagesCategoryEnum.CHARTS, | |
15 | + chartFrame: ChartFrameEnum.COMMON, | |
16 | + image: 'map3D.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <!-- 原生方式,没有使用vue-echarts --> | |
3 | + <n-space vertical> | |
4 | + <n-spin :show="show"> | |
5 | + <div :style="`width:${w}px;height:${h}px;`" ref="map3DRef"></div> | |
6 | + </n-spin> | |
7 | + </n-space> | |
8 | +</template> | |
9 | + | |
10 | +<script setup lang="ts"> | |
11 | +import { onMounted, ref, nextTick, PropType, toRefs, watch, reactive } from 'vue' | |
12 | +import * as echarts from 'echarts' | |
13 | +import { registerMap } from 'echarts/core' | |
14 | +import 'echarts-gl' | |
15 | +import config, { areaEnum } from './config' | |
16 | +import { getGeoJsonMap } from '@/api/external/common' | |
17 | +import dataMaps from './data.json' | |
18 | + | |
19 | +const props = defineProps({ | |
20 | + chartConfig: { | |
21 | + type: Object as PropType<config>, | |
22 | + required: true | |
23 | + } | |
24 | +}) | |
25 | + | |
26 | +const iconStr = ref( | |
27 | + 'path://M853.333333 245.333333H245.333333l93.866667-93.866666c12.8-12.8 12.8-34.133333 0-46.933334-12.8-12.8-34.133333-12.8-46.933333 0l-145.066667 145.066667c-12.8 12.8-12.8 34.133333 0 46.933333l145.066667 145.066667c6.4 6.4 14.933333 10.666667 23.466666 10.666667s17.066667-4.266667 23.466667-10.666667c12.8-12.8 12.8-34.133333 0-46.933333L256 311.466667h597.333333c6.4 0 10.666667 4.266667 10.666667 10.666666v426.666667c0 6.4-4.266667 10.666667-10.666667 10.666667H170.666667c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h682.666666c40.533333 0 74.666667-34.133333 74.666667-74.666667V320c0-40.533333-34.133333-74.666667-74.666667-74.666667z' | |
28 | +) | |
29 | + | |
30 | +const { w, h } = toRefs(props.chartConfig.attr) | |
31 | + | |
32 | +const map3DRef = ref() | |
33 | + | |
34 | +const show = ref(true) | |
35 | + | |
36 | +const chartInstance = ref() | |
37 | + | |
38 | +const toolBoxOption = ref({ | |
39 | + show: true, | |
40 | + right: 110, | |
41 | + top: 20, | |
42 | + feature: { | |
43 | + myFullButton: { | |
44 | + show: true, | |
45 | + title: '返回', | |
46 | + icon: iconStr.value, | |
47 | + iconStyle: { | |
48 | + color: '' | |
49 | + }, | |
50 | + onclick: () => watchAdcode() | |
51 | + } | |
52 | + } | |
53 | +}) | |
54 | + | |
55 | +watch( | |
56 | + () => props.chartConfig.option, | |
57 | + newData => { | |
58 | + const { iconColor, drillingIn, iconDistanceRight, iconDistanceTop } = newData | |
59 | + toolBoxOption.value.feature.myFullButton.show = drillingIn | |
60 | + toolBoxOption.value.feature.myFullButton.iconStyle.color = iconColor | |
61 | + toolBoxOption.value.right = iconDistanceRight | |
62 | + toolBoxOption.value.top = iconDistanceTop | |
63 | + }, | |
64 | + { | |
65 | + deep: true | |
66 | + } | |
67 | +) | |
68 | + | |
69 | +props.chartConfig.option = { | |
70 | + ...props.chartConfig.option, | |
71 | + ...{ toolbox: toolBoxOption.value } | |
72 | +} | |
73 | + | |
74 | +//地图点击返回 | |
75 | +const watchAdcode = async () => { | |
76 | + if (props.chartConfig.option.drillingIn) { | |
77 | + //如果是从右边配置里设置的,比如点击四川省,然后点击返回 | |
78 | + const savePopParent = saveHistoryParent.value.pop() | |
79 | + let saveAdcode: any = savePopParent?.adcode | |
80 | + saveLevelStr.level = savePopParent?.level as string | |
81 | + if (!savePopParent) { | |
82 | + saveAdcode = getParentAdcode(props.chartConfig.option.mapRegion.adcode) | |
83 | + saveLevelStr.level = (regionMapParentArea as any)[props.chartConfig.option.mapRegion.saveSelect.levelStr] | |
84 | + } | |
85 | + if (saveAdcode === 0) { | |
86 | + saveAdcode = 'china' | |
87 | + saveLevelStr.level = 'COUNTRY' | |
88 | + } | |
89 | + await getGeojson(saveAdcode) | |
90 | + const adcode = saveAdcode === 100000 ? 'china' : saveAdcode | |
91 | + props.chartConfig.option.geo3D.map = adcode | |
92 | + props.chartConfig.option.series.forEach((item: any) => { | |
93 | + if (item.type === 'map3D') item.map = adcode | |
94 | + item.data = props.chartConfig.option.dataset | |
95 | + }) | |
96 | + handleSetOption(chartInstance.value, props.chartConfig.option) | |
97 | + handleDataPoint(adcode) | |
98 | + } | |
99 | +} | |
100 | + | |
101 | +const regionMapParentArea = { | |
102 | + [areaEnum.PROVINCE]: areaEnum.COUNTRY, //省份的上一级 中国 | |
103 | + [areaEnum.CITY]: areaEnum.PROVINCE, //城市的上一级 省份 | |
104 | + [areaEnum.COUNTY]: areaEnum.CITY, //县或者区的上一级 城市 | |
105 | + [areaEnum.TOWN]: areaEnum.COUNTY //镇的上一级 县或者区 | |
106 | +} | |
107 | + | |
108 | +//地图点击 | |
109 | +const handleMap3DClick = async (params: any) => { | |
110 | + if (props.chartConfig.option.drillingIn) { | |
111 | + const { name } = params | |
112 | + saveGeojson.value?.features.forEach((item: any) => { | |
113 | + if (item.properties.name === name) { | |
114 | + const level = item.properties.level.toUpperCase() | |
115 | + const adcode = item.properties.adcode | |
116 | + if (level === 'DISTRICT') return | |
117 | + if(String(adcode).startsWith('15') && level===areaEnum.CITY) return | |
118 | + props.chartConfig.option.mapRegion.adcode = adcode | |
119 | + saveLevelStr.level = level | |
120 | + handleDataPoint(adcode) | |
121 | + saveHistoryParent.value.push({ | |
122 | + adcode: item.properties.parent.adcode, | |
123 | + level: (regionMapParentArea as any)[level] | |
124 | + }) | |
125 | + } | |
126 | + }) | |
127 | + } | |
128 | +} | |
129 | + | |
130 | +const saveGeojson: any = ref({}) // 保存geojson | |
131 | + | |
132 | +const chinaDefaultRegionId = ref(100000) //如果是china则adcode为100000 | |
133 | + | |
134 | +const saveLevelStr = reactive({ | |
135 | + // 地区级别 | |
136 | + level: '' | |
137 | +}) | |
138 | + | |
139 | +//父级地区编码和级别接口 | |
140 | +interface HistoryParentType { | |
141 | + adcode: number | |
142 | + level: string | |
143 | +} | |
144 | + | |
145 | +const saveHistoryParent = ref<HistoryParentType[]>([]) | |
146 | + | |
147 | +//动态注册地图 | |
148 | +const getGeojson = (regionId: any) => { | |
149 | + try { | |
150 | + return new Promise<boolean>(resolve => { | |
151 | + const { levelStr } = props.chartConfig.option.mapRegion.saveSelect //右侧配置项获取的行政级别 | |
152 | + getGeoJsonMap( | |
153 | + regionId === 'china' ? chinaDefaultRegionId.value : regionId, | |
154 | + !saveLevelStr.level ? levelStr : saveLevelStr.level | |
155 | + ).then(res => { | |
156 | + const { geoJson, name, code, level } = res.data | |
157 | + const geoJsonFile = JSON.parse(geoJson) | |
158 | + if (!geoJsonFile) return | |
159 | + saveGeojson.value = geoJsonFile | |
160 | + const nameChina = name === '中国' ? 'china' : name | |
161 | + registerMap(level === areaEnum.COUNTRY ? nameChina : code, { geoJSON: geoJsonFile as any, specialAreas: {} }) | |
162 | + show.value = false | |
163 | + resolve(true) | |
164 | + }) | |
165 | + }) | |
166 | + } catch (error) { | |
167 | + show.value = false | |
168 | + console.error('注册地图出错', error) | |
169 | + //注册出错则注册空的,不然在选择正确的adcode,则视图无法更新 | |
170 | + registerMap(props.chartConfig.option.mapRegion.adcode, { geoJSON: {} as any, specialAreas: {} }) | |
171 | + } | |
172 | +} | |
173 | + | |
174 | + | |
175 | +//传adcode 获取上级 | |
176 | +const getParentAdcode = (adcode: number) => { | |
177 | + let adcodeNum = 100000 | |
178 | + saveGeojson.value?.features.forEach((item: any) => { | |
179 | + if (item.properties.adcode === adcode) { | |
180 | + adcodeNum = item.properties.parent.adcode | |
181 | + } | |
182 | + }) | |
183 | + return adcodeNum | |
184 | +} | |
185 | + | |
186 | +watch( | |
187 | + () => w.value, | |
188 | + (value: number) => { | |
189 | + chartInstance.value.resize({ | |
190 | + width: value + 'px', | |
191 | + height: h.value + 'px' | |
192 | + }) | |
193 | + } | |
194 | +) | |
195 | + | |
196 | +const initMap = async () => { | |
197 | + chartInstance.value = echarts.init(map3DRef.value) | |
198 | + await nextTick() | |
199 | + await getGeojson(props.chartConfig.option.mapRegion.adcode) | |
200 | + await nextTick().then(() => { | |
201 | + handleSetOption(chartInstance.value, props.chartConfig.option) | |
202 | + }) | |
203 | + chartInstance.value.on('click', (e: any) => { | |
204 | + handleMap3DClick(e) | |
205 | + }) | |
206 | +} | |
207 | + | |
208 | +// 手动触发渲染 | |
209 | +const handleSetOption = (instance: any, option: any) => { | |
210 | + if (!instance) return | |
211 | + try { | |
212 | + instance.clear() | |
213 | + instance.setOption(option) | |
214 | + } catch (error) { | |
215 | + console.error('触发渲染出错', error) | |
216 | + } | |
217 | +} | |
218 | + | |
219 | +onMounted(() => { | |
220 | + initMap() | |
221 | +}) | |
222 | + | |
223 | +//处理数据标点 | |
224 | +const handleDataPoint = (newData: any) => { | |
225 | + if (newData === 'china') { | |
226 | + props.chartConfig.option.dataset = dataMaps | |
227 | + } else { | |
228 | + props.chartConfig.option.dataset = dataMaps.filter((item: any) => item.adcode === newData) | |
229 | + } | |
230 | +} | |
231 | + | |
232 | +//监听地图展示区域发生变化 | |
233 | +watch( | |
234 | + () => `${props.chartConfig.option.mapRegion.adcode}`, | |
235 | + async (newData: any) => { | |
236 | + try { | |
237 | + await getGeojson(newData) | |
238 | + props.chartConfig.option.geo3D.map = newData | |
239 | + props.chartConfig.option.series.forEach((item: any) => { | |
240 | + if (item.type === 'map3D') { | |
241 | + item.map = newData | |
242 | + item.data = props.chartConfig.option.dataset | |
243 | + } | |
244 | + }) | |
245 | + handleSetOption(chartInstance.value, props.chartConfig.option) | |
246 | + handleDataPoint(newData) | |
247 | + } catch (error) { | |
248 | + console.log('展示区域发生变化出错', error) | |
249 | + } | |
250 | + }, | |
251 | + { | |
252 | + immediate: true | |
253 | + } | |
254 | +) | |
255 | + | |
256 | +// 监听地图右侧配置项变化 | |
257 | +watch( | |
258 | + props.chartConfig.option, | |
259 | + async newData => { | |
260 | + try { | |
261 | + handleSetOption(chartInstance.value, newData) | |
262 | + } catch (error) { | |
263 | + console.log(error) | |
264 | + } | |
265 | + }, | |
266 | + { | |
267 | + deep: true | |
268 | + } | |
269 | +) | |
270 | + | |
271 | +// 监听地图dataset配置项变化 | |
272 | +watch( | |
273 | + () => props.chartConfig.option.dataset, | |
274 | + newData => { | |
275 | + try { | |
276 | + props.chartConfig.option.series.forEach((item: any) => { | |
277 | + if (item.type === 'map3D') { | |
278 | + item.data = newData | |
279 | + } | |
280 | + }) | |
281 | + handleSetOption(chartInstance.value, props.chartConfig.option) | |
282 | + } catch (error) { | |
283 | + console.log(error) | |
284 | + } | |
285 | + }, | |
286 | + { | |
287 | + deep: true | |
288 | + } | |
289 | +) | |
290 | +</script> | ... | ... |
1 | +<template> | |
2 | + <n-drawer display-directive="if" :show="modelShow" :width="502" :placement="placement"> | |
3 | + <n-drawer-content title="设备筛选"> | |
4 | + <n-space vertical> | |
5 | + <span>组织</span> | |
6 | + <n-tree-select | |
7 | + placement="top-start" | |
8 | + label-field="name" | |
9 | + v-model:value="searchParams.organizationId" | |
10 | + key-field="id" | |
11 | + children-field="children" | |
12 | + :options="originationOption" | |
13 | + /> | |
14 | + <span>产品</span> | |
15 | + <n-select v-model:value="searchParams.deviceProfileIds" :options="deviceProfileOption" /> | |
16 | + <span>设备</span> | |
17 | + <n-input v-model:value="searchParams.name" type="text" placeholder="请输入设备名称" /> | |
18 | + <span>设备状态</span> | |
19 | + <n-radio-group v-model:value="searchParams.deviceState" name="radiogroup"> | |
20 | + <n-space> | |
21 | + <n-radio v-for="(item, index) in deviceStateGroup" :key="index" :value="item.value"> | |
22 | + {{ item.label }} | |
23 | + </n-radio> | |
24 | + </n-space> | |
25 | + </n-radio-group> | |
26 | + <span>是否告警</span> | |
27 | + <n-radio-group v-model:value="searchParams.alarmStatus" name="radiogroup"> | |
28 | + <n-space> | |
29 | + <n-radio v-for="(item, index) in alarmStatusGroup" :key="index" :value="item.value"> | |
30 | + {{ item.label }} | |
31 | + </n-radio> | |
32 | + </n-space> | |
33 | + </n-radio-group> | |
34 | + <span>配置查询分页</span> | |
35 | + <n-space justify="space-between"> | |
36 | + <n-input-number :min="1" v-model:value="searchPage.page" clearable /> | |
37 | + <n-input-number :step="10" :min="10" v-model:value="searchPage.pageSize" clearable /> | |
38 | + </n-space> | |
39 | + </n-space> | |
40 | + <template #footer> | |
41 | + <n-button @click="handleCancel" type="tertiary">取消</n-button> | |
42 | + <div style="visibility: hidden; width: 10px">占位</div> | |
43 | + <n-button @click="handleSubmit" type="success">确定</n-button> | |
44 | + </template> | |
45 | + </n-drawer-content> | |
46 | + </n-drawer> | |
47 | +</template> | |
48 | + | |
49 | +<script lang="ts" setup> | |
50 | +import { ref, onMounted, reactive } from 'vue' | |
51 | +import { NTreeSelect } from 'naive-ui' | |
52 | +import { getOrganizationList, getProfileList } from '@/api/external/common/index' | |
53 | + | |
54 | +interface searchParamsInterface { | |
55 | + organizationId: string | null | |
56 | + deviceProfileIds: null | |
57 | + name: string | |
58 | + deviceState: string | null | |
59 | + alarmStatus: number | |
60 | +} | |
61 | + | |
62 | +defineProps({ | |
63 | + modelShow: Boolean | |
64 | +}) | |
65 | + | |
66 | +const emit = defineEmits(['searchParams', 'closeDrawer']) | |
67 | + | |
68 | +const placement = ref('right') | |
69 | + | |
70 | +//设备状态 | |
71 | +const deviceStateGroup = [ | |
72 | + { | |
73 | + label: '全部', | |
74 | + value: null | |
75 | + }, | |
76 | + { | |
77 | + label: '待激活', | |
78 | + value: 'INACTIVE' | |
79 | + }, | |
80 | + { | |
81 | + label: '在线', | |
82 | + value: 'ONLINE' | |
83 | + }, | |
84 | + { | |
85 | + label: '离线', | |
86 | + value: 'OFFLINE' | |
87 | + } | |
88 | +] | |
89 | + | |
90 | +//告警状态 | |
91 | +const alarmStatusGroup = [ | |
92 | + { | |
93 | + label: '是', | |
94 | + value: 1 | |
95 | + }, | |
96 | + { | |
97 | + label: '否', | |
98 | + value: 0 | |
99 | + } | |
100 | +] | |
101 | + | |
102 | +const searchPage = reactive<{ page: number; pageSize: number }>({ | |
103 | + page: 1, | |
104 | + pageSize: 10 | |
105 | +}) | |
106 | + | |
107 | +const searchParams = reactive<searchParamsInterface>({ | |
108 | + organizationId: null, | |
109 | + deviceProfileIds: null, | |
110 | + name: '', | |
111 | + deviceState: null, | |
112 | + alarmStatus: 0 | |
113 | +}) | |
114 | + | |
115 | +const originationOption = ref([]) | |
116 | + | |
117 | +const deviceProfileOption = ref([]) | |
118 | + | |
119 | +const loadList = async () => { | |
120 | + const resOrganization = await getOrganizationList() | |
121 | + const resProfileList = await getProfileList() | |
122 | + Promise.all([resOrganization, resProfileList]).then(res => { | |
123 | + originationOption.value = res[0] | |
124 | + deviceProfileOption.value = res[1].map((item: { name: string; tbProfileId: string }) => ({ | |
125 | + label: item.name, | |
126 | + value: item.tbProfileId | |
127 | + })) | |
128 | + }) | |
129 | +} | |
130 | + | |
131 | +const handleSubmit = () => { | |
132 | + searchParams.deviceProfileIds = [searchParams.deviceProfileIds] as any | |
133 | + emit('searchParams', searchPage, searchParams) | |
134 | + handleCancel() | |
135 | +} | |
136 | + | |
137 | +const handleCancel = () => { | |
138 | + for (let i in searchParams) Reflect.set(searchParams, i, null) | |
139 | + searchParams.name = '' | |
140 | + searchParams.alarmStatus = 0 | |
141 | + searchPage.page = 1 | |
142 | + searchPage.pageSize = 10 | |
143 | + emit('closeDrawer') | |
144 | +} | |
145 | + | |
146 | +onMounted(() => { | |
147 | + loadList() | |
148 | +}) | |
149 | +</script> | |
150 | + | |
151 | +<style lang="scss" scoped></style> | ... | ... |
1 | +import { PublicConfigClass } from '@/packages/public' | |
2 | +import { CreateComponentType } from '@/packages/index.d' | |
3 | +import { OverrideMapAmapConfig } from './index' | |
4 | +import { chartInitConfig } from '@/settings/designSetting' | |
5 | +import cloneDeep from 'lodash/cloneDeep' | |
6 | +import dataJson from './data.json' | |
7 | + | |
8 | +export type dataExtraInfoType = typeof dataJson.markers[number]['extraInfo'] //data.json下的extraInfo类型 | |
9 | + | |
10 | +export type dataJsonType = typeof dataJson //data.json类型 | |
11 | + | |
12 | +export type dataJsonMarkersType = typeof dataJson.markers[number] //data.json markers类型 | |
13 | + | |
14 | +//标注数据格式 | |
15 | +export const fileterDevice = (items: any) => { | |
16 | + const values = items.reduce((acc: any, curr: any) => { | |
17 | + acc.push({ | |
18 | + name: curr.alias, | |
19 | + value: 10, | |
20 | + position: [curr.deviceInfo.longitude, curr.deviceInfo.latitude], | |
21 | + extraInfo: { | |
22 | + tbDeviceId: curr.tbDeviceId, | |
23 | + name: curr.name, | |
24 | + alias: curr.alias, | |
25 | + organizationDTO: curr.organizationDTO, | |
26 | + deviceState: curr.deviceState, | |
27 | + deviceProfile: curr.deviceProfile, | |
28 | + deviceInfo: curr.deviceInfo | |
29 | + } | |
30 | + }) | |
31 | + return [...acc] | |
32 | + }, []) | |
33 | + return { | |
34 | + markers: values | |
35 | + } | |
36 | +} | |
37 | + | |
38 | +export enum ThemeEnum { | |
39 | + NORMAL = 'normal', | |
40 | + DARK = 'dark', | |
41 | + LIGHT = 'light', | |
42 | + WHITES_MOKE = 'whitesmoke', | |
43 | + FRESH = 'fresh', | |
44 | + GREY = 'grey', | |
45 | + GRAFFITI = 'graffiti', | |
46 | + MACARON = 'macaron', | |
47 | + BLUE = 'blue', | |
48 | + DARKBLUE = 'darkblue', | |
49 | + WINE = 'wine', | |
50 | + WEIXIN = 'tileLayer' | |
51 | +} | |
52 | + | |
53 | +export enum LangEnum { | |
54 | + ZH_CN = 'zh_cn', | |
55 | + EN = 'en', | |
56 | + ZH_EN = 'zh_en' | |
57 | +} | |
58 | + | |
59 | +export enum ViewModeEnum { | |
60 | + PLANE = '2D', | |
61 | + STEREOSCOPIC = '3D' | |
62 | +} | |
63 | + | |
64 | +export enum FeaturesEnum { | |
65 | + BG = 'bg', | |
66 | + POINT = 'point', | |
67 | + ROAD = 'road', | |
68 | + BUILDING = 'building' | |
69 | +} | |
70 | + | |
71 | +export enum MarkerEnum { | |
72 | + // 圆圈 | |
73 | + CIRCLE_MARKER = 'CircleMarker', | |
74 | + // 定位标点 | |
75 | + MARKER = 'Marker', | |
76 | + // 暂无 | |
77 | + NONE = 'none' | |
78 | +} | |
79 | + | |
80 | +export const option = { | |
81 | + dataset: dataJson, | |
82 | + mapOptions: { | |
83 | + pitch: 60, | |
84 | + skyColor: '#53A9DE', | |
85 | + amapKey: 'd5f3e16589dbecae64d05fe90e2ba4f2', | |
86 | + amapStyleKey: ThemeEnum.DARK, | |
87 | + amapStyleKeyCustom: '', | |
88 | + amapLon: 104.108689, | |
89 | + amapLat: 30.66176, | |
90 | + amapZindex: 11, | |
91 | + marker: { | |
92 | + fillColor: '#E98984FF', | |
93 | + fillOpacity: 0.5, | |
94 | + strokeColor: 'white', | |
95 | + strokeWeight: 2, | |
96 | + strokeOpacity: 0.5, | |
97 | + zIndex: 10, | |
98 | + bubble: true, | |
99 | + cursor: 'pointer', | |
100 | + clickable: true | |
101 | + }, | |
102 | + mapMarkerType: MarkerEnum.MARKER, | |
103 | + viewMode: ViewModeEnum.PLANE, | |
104 | + lang: LangEnum.ZH_CN, | |
105 | + features: [FeaturesEnum.BG, FeaturesEnum.POINT, FeaturesEnum.ROAD, FeaturesEnum.BUILDING] | |
106 | + } | |
107 | +} | |
108 | + | |
109 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
110 | + public key = OverrideMapAmapConfig.key | |
111 | + public attr = { ...chartInitConfig, w: 1000, h: 800, zIndex: -1 } | |
112 | + public chartConfig = cloneDeep(OverrideMapAmapConfig) | |
113 | + public option = cloneDeep(option) | |
114 | +} | ... | ... |
1 | +<template> | |
2 | + <collapse-item name="基础" :expanded="true"> | |
3 | + <setting-item-box name="语言类型" :alone="true"> | |
4 | + <setting-item> | |
5 | + <n-select size="small" v-model:value="optionData.mapOptions.lang" :options="langOptions" /> | |
6 | + </setting-item> | |
7 | + </setting-item-box> | |
8 | + <setting-item-box name="Key" :alone="true"> | |
9 | + <setting-item name="请务必使用自己的高德应用 key"> | |
10 | + <n-input v-model:value="optionData.mapOptions.amapKey" size="small"></n-input> | |
11 | + </setting-item> | |
12 | + </setting-item-box> | |
13 | + <setting-item-box name="自定义地图样式ID" :alone="true"> | |
14 | + <setting-item> | |
15 | + <n-input size="small" v-model:value="optionData.mapOptions.amapStyleKeyCustom" /> | |
16 | + </setting-item> | |
17 | + </setting-item-box> | |
18 | + </collapse-item> | |
19 | + <collapse-item name="地图" :expanded="true"> | |
20 | + <setting-item-box name="主题"> | |
21 | + <setting-item> | |
22 | + <n-select size="small" v-model:value="optionData.mapOptions.amapStyleKey" :options="themeOptions" /> | |
23 | + </setting-item> | |
24 | + </setting-item-box> | |
25 | + <setting-item-box name="内容" :alone="true"> | |
26 | + <n-checkbox-group v-model:value="optionData.mapOptions.features"> | |
27 | + <n-space item-style="display: flex;"> | |
28 | + <n-checkbox :value="item.value" :label="item.label" v-for="(item, index) in featuresOptions" :key="index" /> | |
29 | + </n-space> | |
30 | + </n-checkbox-group> | |
31 | + </setting-item-box> | |
32 | + <setting-item-box name="位置"> | |
33 | + <setting-item name="经度"> | |
34 | + <n-input-number v-model:value="optionData.mapOptions.amapLon" :show-button="false" size="small"> | |
35 | + <template #suffix>°</template> | |
36 | + </n-input-number> | |
37 | + </setting-item> | |
38 | + <setting-item name="纬度"> | |
39 | + <n-input-number v-model:value="optionData.mapOptions.amapLat" :show-button="false" size="small"> | |
40 | + <template #suffix>°</template> | |
41 | + </n-input-number> | |
42 | + </setting-item> | |
43 | + <setting-item name="初始缩放"> | |
44 | + <n-input-number v-model:value="optionData.mapOptions.amapZindex" :min="0" size="small"></n-input-number> | |
45 | + </setting-item> | |
46 | + </setting-item-box> | |
47 | + <setting-item-box name="模式" :alone="true"> | |
48 | + <setting-item> | |
49 | + <n-radio-group v-model:value="optionData.mapOptions.viewMode" name="radiogroup"> | |
50 | + <n-space> | |
51 | + <n-radio v-for="song in viewModeOptions" :key="song.value" :value="song.value"> | |
52 | + {{ song.label }} | |
53 | + </n-radio> | |
54 | + </n-space> | |
55 | + </n-radio-group> | |
56 | + </setting-item> | |
57 | + </setting-item-box> | |
58 | + <template v-if="optionData.mapOptions.viewMode === '3D'"> | |
59 | + <setting-item-box> | |
60 | + <setting-item name="天空色"> | |
61 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.mapOptions.skyColor"></n-color-picker> | |
62 | + </setting-item> | |
63 | + <setting-item name="俯仰角"> | |
64 | + <n-input-number v-model:value="optionData.mapOptions.pitch" :min="0" :max="83" size="small"></n-input-number> | |
65 | + </setting-item> | |
66 | + </setting-item-box> | |
67 | + </template> | |
68 | + </collapse-item> | |
69 | + <collapse-item name="标记" :expanded="true"> | |
70 | + <setting-item-box name="样式"> | |
71 | + <setting-item name="类型"> | |
72 | + <n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" /> | |
73 | + </setting-item> | |
74 | + <setting-item name="颜色"> | |
75 | + <n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker> | |
76 | + </setting-item> | |
77 | + </setting-item-box> | |
78 | + </collapse-item> | |
79 | +</template> | |
80 | + | |
81 | +<script setup lang="ts"> | |
82 | +import { PropType } from 'vue' | |
83 | +import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config' | |
84 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
85 | + | |
86 | +defineProps({ | |
87 | + optionData: { | |
88 | + type: Object as PropType<typeof option>, | |
89 | + required: true | |
90 | + } | |
91 | +}) | |
92 | + | |
93 | +const themeOptions = [ | |
94 | + { | |
95 | + value: ThemeEnum.NORMAL, | |
96 | + label: '标准' | |
97 | + }, | |
98 | + { | |
99 | + value: ThemeEnum.DARK, | |
100 | + label: '幻影黑' | |
101 | + }, | |
102 | + { | |
103 | + value: ThemeEnum.LIGHT, | |
104 | + label: '月光银' | |
105 | + }, | |
106 | + { | |
107 | + value: ThemeEnum.WHITES_MOKE, | |
108 | + label: '远山黛' | |
109 | + }, | |
110 | + { | |
111 | + value: ThemeEnum.FRESH, | |
112 | + label: '草色青' | |
113 | + }, | |
114 | + { | |
115 | + value: ThemeEnum.GREY, | |
116 | + label: '雅士灰' | |
117 | + }, | |
118 | + { | |
119 | + value: ThemeEnum.GRAFFITI, | |
120 | + label: '涂鸦' | |
121 | + }, | |
122 | + { | |
123 | + value: ThemeEnum.MACARON, | |
124 | + label: '马卡龙' | |
125 | + }, | |
126 | + { | |
127 | + value: ThemeEnum.BLUE, | |
128 | + label: '靛青蓝' | |
129 | + }, | |
130 | + { | |
131 | + value: ThemeEnum.DARKBLUE, | |
132 | + label: '极夜蓝' | |
133 | + }, | |
134 | + { | |
135 | + value: ThemeEnum.WINE, | |
136 | + label: '酱籽' | |
137 | + }, | |
138 | + { | |
139 | + value: ThemeEnum.WEIXIN, | |
140 | + label: '卫星' | |
141 | + } | |
142 | +] | |
143 | + | |
144 | +const langOptions = [ | |
145 | + { | |
146 | + value: LangEnum.ZH_CN, | |
147 | + label: '中文简体' | |
148 | + }, | |
149 | + { | |
150 | + value: LangEnum.EN, | |
151 | + label: '英文' | |
152 | + }, | |
153 | + { | |
154 | + value: LangEnum.ZH_EN, | |
155 | + label: '中英文对照' | |
156 | + } | |
157 | +] | |
158 | + | |
159 | +const viewModeOptions = [ | |
160 | + { | |
161 | + value: ViewModeEnum.PLANE, | |
162 | + label: '2D' | |
163 | + }, | |
164 | + { | |
165 | + value: ViewModeEnum.STEREOSCOPIC, | |
166 | + label: '3D' | |
167 | + } | |
168 | +] | |
169 | + | |
170 | +const featuresOptions = [ | |
171 | + { | |
172 | + value: FeaturesEnum.BG, | |
173 | + label: '显示地图背景' | |
174 | + }, | |
175 | + { | |
176 | + value: FeaturesEnum.POINT, | |
177 | + label: '显示标识' | |
178 | + }, | |
179 | + { | |
180 | + value: FeaturesEnum.ROAD, | |
181 | + label: '显示道路' | |
182 | + }, | |
183 | + { | |
184 | + value: FeaturesEnum.BUILDING, | |
185 | + label: '显示建筑' | |
186 | + } | |
187 | +] | |
188 | + | |
189 | +const MarkerOptions = [ | |
190 | + { | |
191 | + value: MarkerEnum.CIRCLE_MARKER, | |
192 | + label: '圆形标点' | |
193 | + }, | |
194 | + { | |
195 | + value: MarkerEnum.MARKER, | |
196 | + label: '定位标点' | |
197 | + }, | |
198 | + { | |
199 | + value: MarkerEnum.NONE, | |
200 | + label: '隐藏标点' | |
201 | + } | |
202 | +] | |
203 | +</script> | ... | ... |
1 | +{ | |
2 | + "markers": [ | |
3 | + { | |
4 | + "name": "模拟11111111111", | |
5 | + "value": 20, | |
6 | + "position": [103.856504, 30.687278], | |
7 | + "extraInfo": { | |
8 | + "tbDeviceId": "@xxxxxxxxxxx", | |
9 | + "name": "模拟11111111111", | |
10 | + "alias": "模拟11111111111", | |
11 | + "organizationDTO": { | |
12 | + "name": "模拟11111111111" | |
13 | + }, | |
14 | + "deviceState": "INACTIVE", | |
15 | + "deviceProfile": { | |
16 | + "transportType": "MQTT" | |
17 | + }, | |
18 | + "deviceInfo": { | |
19 | + "address": "四川省", | |
20 | + "longitude": 103.856504, | |
21 | + "latitude": 30.687278 | |
22 | + } | |
23 | + } | |
24 | + }, | |
25 | + { | |
26 | + "name": "模拟22222222222", | |
27 | + "value": 30, | |
28 | + "position": [104.095368, 30.716787], | |
29 | + "extraInfo": { | |
30 | + "tbDeviceId": "@xxxxxxxxxxxxxxx", | |
31 | + "name": "模拟22222222222", | |
32 | + "alias": "模拟22222222222", | |
33 | + "organizationDTO": { | |
34 | + "name": "模拟22222222222" | |
35 | + }, | |
36 | + "deviceState": "INACTIVE", | |
37 | + "deviceProfile": { | |
38 | + "transportType": "TCP" | |
39 | + }, | |
40 | + "deviceInfo": { | |
41 | + "address": "四川省", | |
42 | + "longitude": 104.095368, | |
43 | + "latitude": 30.716787 | |
44 | + } | |
45 | + } | |
46 | + } | |
47 | + ] | |
48 | +} | ... | ... |
851 Bytes
582 Bytes
514 Bytes
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideMapAmap', true) | |
6 | + | |
7 | +export const OverrideMapAmapConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '高德地图', | |
12 | + category: ChatCategoryEnum.MAP, | |
13 | + categoryName: ChatCategoryEnumName.MAP, | |
14 | + package: PackagesCategoryEnum.CHARTS, | |
15 | + chartFrame: ChartFrameEnum.COMMON, | |
16 | + image: 'map_amap.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <div @mouseenter="handleMouseenter" @mouseleave="handleMouseleave" class="chart-amap" ref="vChartRef"> | |
3 | + <div v-show="showSearchBox" @click.stop="handleOpenSearchBox" class="search-box"></div> | |
4 | + <search-box :modelShow="modelShow" @searchParams="handleSearchParams" @closeDrawer="handleCloseDrawer"></search-box> | |
5 | + </div> | |
6 | +</template> | |
7 | + | |
8 | +<script setup lang="ts"> | |
9 | +import { ref, PropType, toRefs, watch } from 'vue' | |
10 | +import AMapLoader from '@amap/amap-jsapi-loader' | |
11 | +import { CreateComponentType } from '@/packages/index.d' | |
12 | +import { useChartDataFetch } from '@/hooks' | |
13 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
14 | +import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType, fileterDevice } from './config' | |
15 | +import { isArray } from '@/utils' | |
16 | +import djh from './images/djh.png' | |
17 | +import online from './images/online.png' | |
18 | +import lx1 from './images/lx1.png' | |
19 | +import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index' | |
20 | +import dayjs from 'dayjs' | |
21 | +import SearchBox from './components/SearchBox.vue' | |
22 | + | |
23 | +const props = defineProps({ | |
24 | + chartConfig: { | |
25 | + type: Object as PropType<CreateComponentType>, | |
26 | + required: true | |
27 | + } | |
28 | +}) | |
29 | + | |
30 | +const modelShow = ref(false) | |
31 | + | |
32 | +const showSearchBox = ref(false) | |
33 | + | |
34 | +let { | |
35 | + amapKey, | |
36 | + amapStyleKey, | |
37 | + amapLon, | |
38 | + amapLat, | |
39 | + amapZindex, | |
40 | + mapMarkerType, | |
41 | + lang, | |
42 | + amapStyleKeyCustom, | |
43 | + features, | |
44 | + viewMode, | |
45 | + pitch, | |
46 | + skyColor, | |
47 | + marker | |
48 | +} = toRefs(props.chartConfig.option.mapOptions) | |
49 | + | |
50 | +//官方没有高德地图api的ts,所以类型全用的any | |
51 | +let mapIns: any = null | |
52 | +let markers: any = [] | |
53 | +let AMapIns: any = null | |
54 | +const vChartRef = ref<HTMLElement>() | |
55 | + | |
56 | +const initMap = (newData: any) => { | |
57 | + // 初始化 | |
58 | + AMapLoader.load({ | |
59 | + key: amapKey.value, //api服务key--另外需要在public中使用安全密钥!!! | |
60 | + version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 | |
61 | + plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete'] // 需要使用的的插件列表 | |
62 | + }) | |
63 | + .then(AMap => { | |
64 | + AMapIns = AMap | |
65 | + mapIns = new AMap.Map(vChartRef.value, { | |
66 | + resizeEnable: true, | |
67 | + zoom: amapZindex.value, // 地图显示的缩放级别 | |
68 | + center: [amapLon.value, amapLat.value], | |
69 | + lang: lang.value, | |
70 | + features: features.value, | |
71 | + pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度 | |
72 | + skyColor: skyColor.value, | |
73 | + viewMode: viewMode.value, // 地图模式 | |
74 | + willReadFrequently: true | |
75 | + }) | |
76 | + dataHandle(props.chartConfig.option.dataset) //处理地图标点 | |
77 | + let satellite = new AMap.TileLayer.Satellite() | |
78 | + let roadNet = new AMap.TileLayer.RoadNet() | |
79 | + if (newData.amapStyleKey === ThemeEnum.WEIXIN) { | |
80 | + mapIns.add([satellite, roadNet]) | |
81 | + } else { | |
82 | + mapIns.remove([satellite, roadNet]) | |
83 | + mapIns.setMapStyle( | |
84 | + `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}` | |
85 | + ) | |
86 | + } | |
87 | + //点击地图任意地方关闭infoWindow窗体 | |
88 | + mapIns.on('click', () => { | |
89 | + mapIns.clearInfoWindow() | |
90 | + }) | |
91 | + }) | |
92 | + .catch(e => { | |
93 | + console.error(e) | |
94 | + }) | |
95 | +} | |
96 | + | |
97 | +//创建信息弹窗 | |
98 | +const createInfoWindow = async (extraInfo: dataExtraInfoType) => { | |
99 | + try { | |
100 | + const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId } = extraInfo | |
101 | + if (tbDeviceId.startsWith('@')) return //假的模拟数据则终止 | |
102 | + const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 | |
103 | + let { lastUpdateTs } = res[0] | |
104 | + const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss') | |
105 | + return ` | |
106 | + <div style="width:15vw;height:18vh;background-color:white;"> | |
107 | + <div style="margin:0px 10px"> | |
108 | + <div style="display:flex;justify-content:space-between; margin:20px 0px;"> | |
109 | + <div style="font-size:16px;font-weight:bold">${alias || name}</div> | |
110 | + ${ | |
111 | + deviceState === 'INACTIVE' | |
112 | + ? `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${djh}" class="mr-1">待激活</div>` | |
113 | + : deviceState === 'ONLINE' | |
114 | + ? `<div style="display:flex;align-items:center; "> | |
115 | + <img style="width:15px;height:15px" src="${online}" class="mr-1">在线</div>` | |
116 | + : `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${lx1}" class="mr-1">离线</div>` | |
117 | + } | |
118 | + </div> | |
119 | + <div>所属组织:${organizationDTO.name}</div> | |
120 | + <div style="margin-top:6px;">接入协议:${deviceProfile.transportType}</div> | |
121 | + <div style="margin-top:6px;"> | |
122 | + 设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address} | |
123 | + </div> | |
124 | + <div style="margin-top:6px;"> | |
125 | + ${ | |
126 | + deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线' | |
127 | + }时间:${lastUpdateFormatTs} | |
128 | + </div> | |
129 | + </div> | |
130 | + </div> | |
131 | + ` | |
132 | + } catch (e) { | |
133 | + console.error(e) | |
134 | + } | |
135 | +} | |
136 | + | |
137 | +const handleMouseenter = () => (showSearchBox.value = true) | |
138 | + | |
139 | +const handleMouseleave = () => (showSearchBox.value = false) | |
140 | + | |
141 | +const handleOpenSearchBox = () => (modelShow.value = true) | |
142 | + | |
143 | +const handleCloseDrawer = () => (modelShow.value = false) | |
144 | + | |
145 | +const handleSearchParams = async (searchPage: any, params: any) => { | |
146 | + try { | |
147 | + const { items } = await getDeviceList(searchPage, params) | |
148 | + const values = fileterDevice(items) | |
149 | + if (!values) return | |
150 | + dataHandle(values) | |
151 | + } finally { | |
152 | + handleCloseDrawer() | |
153 | + } | |
154 | +} | |
155 | + | |
156 | +//地图点击 | |
157 | +const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { | |
158 | + markerInstance.setExtData({ | |
159 | + extraInfo: markerItem.extraInfo | |
160 | + }) | |
161 | + markerInstance.setLabel({ | |
162 | + content: markerItem.extraInfo.alias || markerItem.extraInfo.name | |
163 | + }) | |
164 | + markerInstance.on('click', async (e: any) => { | |
165 | + const { extraInfo } = e.target.getExtData() | |
166 | + let infoWindow = new AMapIns.InfoWindow({ | |
167 | + content: await createInfoWindow(extraInfo), | |
168 | + offset: new AMapIns.Pixel(0, -50) | |
169 | + }) | |
170 | + infoWindow.open(mapIns, e.target.getPosition()) | |
171 | + }) | |
172 | +} | |
173 | + | |
174 | +const dataHandle = (newData: dataJsonType) => { | |
175 | + if (!mapIns && !AMapIns) { | |
176 | + initMap(props.chartConfig.option) | |
177 | + return | |
178 | + } | |
179 | + if (isArray(newData.markers)) { | |
180 | + // 先清除旧标记 | |
181 | + mapIns.remove(markers) | |
182 | + markers = [] | |
183 | + // 记录新标记 | |
184 | + if (mapMarkerType.value === MarkerEnum.MARKER) { | |
185 | + newData.markers.forEach((markerItem: dataJsonMarkersType) => { | |
186 | + const markerInstance = new AMapIns.Marker({ | |
187 | + position: [markerItem.position[0], markerItem.position[1]], | |
188 | + offset: new AMapIns.Pixel(-13, -30) | |
189 | + }) | |
190 | + // markers.push(markerInstance) 原作者这种方式添加,属于JS API 1.4.8版本的 | |
191 | + // markerInstance.setMap(mapIns) | |
192 | + mapIns.add(markerInstance) | |
193 | + mapClick(markerInstance, markerItem) | |
194 | + }) | |
195 | + } else if (mapMarkerType.value === MarkerEnum.CIRCLE_MARKER) { | |
196 | + newData.markers.forEach((markerItem: dataJsonMarkersType) => { | |
197 | + const markerInstance = new AMapIns.CircleMarker({ | |
198 | + center: [ | |
199 | + !markerItem.position[0] ? 0 : markerItem.position[0], | |
200 | + !markerItem.position[1] ? 0 : markerItem.position[1] | |
201 | + ], | |
202 | + radius: markerItem.value, //圆圈半径大小 | |
203 | + ...marker.value | |
204 | + }) | |
205 | + // markers.push(markerInstance) //原作者这种方式添加,属于JS API 1.4.8版本的 | |
206 | + // markerInstance.setMap(mapIns) | |
207 | + mapIns.add(markerInstance) | |
208 | + mapClick(markerInstance, markerItem) // circle点击无效 | |
209 | + }) | |
210 | + } | |
211 | + } | |
212 | +} | |
213 | + | |
214 | +const stopWatch = watch( | |
215 | + () => props.chartConfig.option.mapOptions, | |
216 | + option => { | |
217 | + initMap(option) | |
218 | + }, | |
219 | + { | |
220 | + immediate: true, | |
221 | + deep: true | |
222 | + } | |
223 | +) | |
224 | + | |
225 | +watch( | |
226 | + () => props.chartConfig.option.dataset, | |
227 | + newData => { | |
228 | + try { | |
229 | + dataHandle(newData) | |
230 | + } catch (error) { | |
231 | + console.log(error) | |
232 | + } | |
233 | + }, | |
234 | + { | |
235 | + deep: false | |
236 | + } | |
237 | +) | |
238 | + | |
239 | +// 预览 | |
240 | +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | |
241 | + stopWatch() | |
242 | + dataHandle(newData) | |
243 | +}) | |
244 | +</script> | |
245 | + | |
246 | +<style lang="scss" scoped> | |
247 | +.chart-amap { | |
248 | + position: relative; | |
249 | + .search-box { | |
250 | + cursor: pointer; | |
251 | + position: absolute; | |
252 | + right: -25px; | |
253 | + top: 15px; | |
254 | + z-index: 9999; | |
255 | + width: 50px; | |
256 | + height: 50px; | |
257 | + border-radius: 50%; | |
258 | + background-color: rgba(255, 255, 255, 0.2); | |
259 | + } | |
260 | +} | |
261 | +</style> | ... | ... |
... | ... | @@ -4,9 +4,12 @@ import { getAreaList } from '@/api/external/common/index' |
4 | 4 | import { areaEnum } from '../config' |
5 | 5 | |
6 | 6 | const props = defineProps({ |
7 | - drillingIn:{ | |
8 | - type:Boolean, | |
9 | - default:false | |
7 | + drillingIn: { | |
8 | + type: Boolean, | |
9 | + default: false | |
10 | + }, | |
11 | + optionData: { | |
12 | + type: Object | |
10 | 13 | } |
11 | 14 | }) |
12 | 15 | |
... | ... | @@ -21,7 +24,8 @@ const selectOptions = reactive({ |
21 | 24 | const selectValues = reactive({ |
22 | 25 | provinceValue: 'china', |
23 | 26 | cityValue: null, |
24 | - countyValue: null | |
27 | + countyValue: null, | |
28 | + levelStr: areaEnum.COUNTRY | |
25 | 29 | }) |
26 | 30 | |
27 | 31 | const getAreaLists = async (level = areaEnum.PROVINCE, parentId = 1) => { |
... | ... | @@ -39,23 +43,39 @@ onMounted(async () => { |
39 | 43 | label: '中国', |
40 | 44 | value: 'china' |
41 | 45 | }) |
46 | + onHandleSelectProvince(props.optionData?.mapRegion.saveSelect['provinceValue']) | |
47 | + for (let i in selectValues) Reflect.set(selectValues, i, props.optionData?.mapRegion.saveSelect[i]) | |
42 | 48 | }) |
43 | 49 | |
44 | 50 | const onHandleSelectProvince = async (value: number | string) => { |
45 | 51 | selectValues.cityValue = null |
46 | 52 | selectValues.countyValue = null |
47 | - if (value === 'china') return | |
53 | + if (value === 'china') return (selectValues.levelStr = areaEnum.COUNTRY) | |
48 | 54 | selectOptions.cityOptions = await getAreaLists(areaEnum.CITY, value as any) |
55 | + selectValues.levelStr = areaEnum.PROVINCE | |
49 | 56 | } |
50 | 57 | |
51 | 58 | const onHandleSelectCity = async (value: number) => { |
52 | 59 | selectValues.countyValue = null |
53 | 60 | selectOptions.countryOptions = await getAreaLists(areaEnum.COUNTY, value) |
61 | + selectValues.levelStr = areaEnum.CITY | |
54 | 62 | } |
55 | 63 | |
56 | 64 | const onHandleSubmit = () => { |
57 | 65 | emits('submit', selectValues) |
58 | 66 | } |
67 | + | |
68 | +const resetValue = () => { | |
69 | + selectValues.provinceValue = 'china' | |
70 | + selectValues.cityValue = null | |
71 | + selectValues.countyValue = null | |
72 | + selectValues.levelStr = areaEnum.COUNTRY | |
73 | + selectOptions.cityOptions = [] | |
74 | + selectOptions.countryOptions = [] | |
75 | +} | |
76 | +defineExpose({ | |
77 | + resetValue | |
78 | +}) | |
59 | 79 | </script> |
60 | 80 | |
61 | 81 | <template> | ... | ... |
... | ... | @@ -7,22 +7,27 @@ import dataJson from './data.json' |
7 | 7 | |
8 | 8 | //省市区枚举 |
9 | 9 | export const enum areaEnum { |
10 | - PROVINCE = 'PROVINCE', | |
11 | - CITY = 'CITY', | |
12 | - COUNTY = 'COUNTY' | |
10 | + PROVINCE = 'PROVINCE',//省份 | |
11 | + CITY = 'CITY',//城市 | |
12 | + COUNTY = 'COUNTY',//县 | |
13 | + COUNTRY = 'COUNTRY',//国家 | |
14 | + TOWN = 'TOWN'//镇 | |
13 | 15 | } |
14 | 16 | export const includes = [] |
15 | 17 | |
16 | 18 | export const option = { |
17 | - iconColor:'black', | |
18 | - showIcon:false, | |
19 | - iconDistanceRight:20, | |
20 | - iconDistanceTop:20, | |
19 | + iconColor: 'black', | |
20 | + showIcon: false, | |
21 | + iconDistanceRight: 20, | |
22 | + iconDistanceTop: 20, | |
21 | 23 | drillingIn: false, |
22 | 24 | dataset: dataJson, |
23 | 25 | mapRegion: { |
24 | 26 | adcode: 'china', |
25 | - showHainanIsLands: true | |
27 | + showHainanIsLands: true, | |
28 | + saveSelect: { | |
29 | + levelStr: areaEnum.COUNTRY | |
30 | + } | |
26 | 31 | }, |
27 | 32 | tooltip: { |
28 | 33 | show: true, |
... | ... | @@ -156,7 +161,7 @@ export const option = { |
156 | 161 | shadowOffsetY: 2, |
157 | 162 | shadowBlur: 10 |
158 | 163 | } |
159 | - }, | |
164 | + } | |
160 | 165 | ] |
161 | 166 | } |
162 | 167 | export const MapDefaultConfig = { ...option } | ... | ... |
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | <CollapseItem name="地图" :expanded="true"> |
5 | 5 | <SettingItemBox name="开启下钻"> |
6 | 6 | <SettingItem name=""> |
7 | - <n-switch v-model:value="optionData.drillingIn" size="small"></n-switch> | |
7 | + <n-switch @change="handleChangeDrillingIn" v-model:value="optionData.drillingIn" size="small"></n-switch> | |
8 | 8 | </SettingItem> |
9 | 9 | </SettingItemBox> |
10 | 10 | <SettingItemBox name="返回图标"> |
... | ... | @@ -14,11 +14,7 @@ |
14 | 14 | </SettingItemBox> |
15 | 15 | <SettingItemBox name="图标颜色"> |
16 | 16 | <SettingItem name=""> |
17 | - <n-color-picker | |
18 | - size="small" | |
19 | - :modes="['hex']" | |
20 | - v-model:value="optionData.iconColor" | |
21 | - ></n-color-picker> | |
17 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.iconColor"></n-color-picker> | |
22 | 18 | </SettingItem> |
23 | 19 | </SettingItemBox> |
24 | 20 | <SettingItemBox name="图标距离"> |
... | ... | @@ -39,7 +35,12 @@ |
39 | 35 | ></n-input-number> |
40 | 36 | </SettingItem> |
41 | 37 | </SettingItemBox> |
42 | - <SelectCity :drillingIn="optionData.drillingIn" @submit="onHandleSelectValues" /> | |
38 | + <SelectCity | |
39 | + ref="SelectCityRef" | |
40 | + :optionData="optionData" | |
41 | + :drillingIn="optionData.drillingIn" | |
42 | + @submit="onHandleSelectValues" | |
43 | + /> | |
43 | 44 | <SettingItemBox name="区域颜色"> |
44 | 45 | <SettingItem name="0%处颜色"> |
45 | 46 | <n-color-picker |
... | ... | @@ -281,6 +282,8 @@ const props = defineProps({ |
281 | 282 | } |
282 | 283 | }) |
283 | 284 | |
285 | +const SelectCityRef = ref<typeof SelectCity>() | |
286 | + | |
284 | 287 | const initMapRegionOptions = () => { |
285 | 288 | mapChinaJson.features.forEach((element: any) => { |
286 | 289 | if (element.properties.name) { |
... | ... | @@ -300,6 +303,7 @@ const mapRegion = computed(() => { |
300 | 303 | |
301 | 304 | const onHandleSelectValues = (values: any) => { |
302 | 305 | const { cityValue, countyValue, provinceValue } = values |
306 | + props.optionData.mapRegion.saveSelect = values | |
303 | 307 | props.optionData.mapRegion.adcode = countyValue |
304 | 308 | ? countyValue |
305 | 309 | : cityValue |
... | ... | @@ -308,4 +312,8 @@ const onHandleSelectValues = (values: any) => { |
308 | 312 | ? 'china' |
309 | 313 | : provinceValue |
310 | 314 | } |
315 | + | |
316 | +const handleChangeDrillingIn = () => { | |
317 | + SelectCityRef.value?.resetValue() | |
318 | +} | |
311 | 319 | </script> | ... | ... |
... | ... | @@ -2,22 +2,27 @@ |
2 | 2 | "point": [ |
3 | 3 | { |
4 | 4 | "name": "北京", |
5 | + "adcode": 110000, | |
5 | 6 | "value": [116.405285, 39.904989, 200] |
6 | 7 | }, |
7 | 8 | { |
8 | 9 | "name": "郑州", |
10 | + "adcode": 410000, | |
9 | 11 | "value": [113.665412, 34.757975, 888] |
10 | 12 | }, |
11 | 13 | { |
12 | 14 | "name": "青海", |
15 | + "adcode": 630000, | |
13 | 16 | "value": [101.778916, 36.623178, 666] |
14 | 17 | }, |
15 | 18 | { |
16 | 19 | "name": "宁夏回族自治区", |
20 | + "adcode": 640000, | |
17 | 21 | "value": [106.278179, 38.46637, 66] |
18 | 22 | }, |
19 | 23 | { |
20 | 24 | "name": "哈尔滨市", |
25 | + "adcode": 230000, | |
21 | 26 | "value": [126.642464, 45.756967, 101] |
22 | 27 | } |
23 | 28 | ], | ... | ... |