Commit 2017fd1734fcc5c72bea58a0d5b94c6f2caf18bb

Authored by xp.Huang
1 parent 66737e54

v1.1.2_Release

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.

... ... @@ -8,7 +8,7 @@ VITE_GLOB_API_URL = /api
8 8 VITE_GLOB_API_URL_PREFIX = /yt
9 9
10 10 # 内容安全协议
11   -VITE_GLOB_CONTENT_SECURITY_POLICY = true
  11 +VITE_GLOB_CONTENT_SECURITY_POLICY = false
12 12
13 13 # 公共路径
14 14 VITE_GLOB_PUBLIC_PATH = /large-designer/
... ...
... ... @@ -30,6 +30,7 @@
30 30 "crypto-js": "^4.1.1",
31 31 "dayjs": "^1.11.7",
32 32 "dom-helpers": "^5.2.1",
  33 + "echarts-gl": "^2.0.9",
33 34 "echarts-liquidfill": "^3.1.0",
34 35 "echarts-stat": "^1.2.0",
35 36 "echarts-wordcloud": "^2.0.0",
... ...
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 + })
... ...
... ... @@ -16,3 +16,11 @@ export interface UploadResponse {
16 16 size: number
17 17 fileStaticUri: string
18 18 }
  19 +
  20 +
  21 +export interface OrganizationListItem {
  22 + id: string;
  23 + name: string;
  24 + parentId?: string;
  25 + remark: string;
  26 +}
\ No newline at end of file
... ...
... ... @@ -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
... ...
... ... @@ -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 })
... ...
... ... @@ -99,7 +99,7 @@ export const originUseChartDataFetch = (
99 99 fetchFn()
100 100 },
101 101 {
102   - immediate: false,
  102 + immediate: true,
103 103 deep: true
104 104 }
105 105 )
... ...
... ... @@ -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]
... ...
... ... @@ -33,6 +33,10 @@ export const option = {
33 33 width: 3,
34 34 color: {
35 35 type: 'linear',
  36 + x: 0,
  37 + y: 0,
  38 + x2: 0,
  39 + y2: 1,
36 40 colorStops: [
37 41 {
38 42 offset: 0,
... ...
... ... @@ -84,7 +84,10 @@ export const option = {
84 84 shadowColor: '#E1FFFF',
85 85 shadowBlur: 10
86 86 },
87   - data: []
  87 + data: [],
  88 + encode: {
  89 + value: 2
  90 + }
88 91 },
89 92 {
90 93 name: '区域',
... ...
... ... @@ -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>
... ...
... ... @@ -14,5 +14,6 @@ export enum ChatCategoryEnumName {
14 14 LINE = '折线图',
15 15 SCATTER = '散点图',
16 16 MAP = '地图',
  17 + COMBINATION = '组合图',
17 18 MORE = '更多'
18 19 }
... ...
  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 +]
... ...
1   -// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2 1 // @ts-nocheck
3 2 import {
4 3 ArcCurve,
... ...
... ... @@ -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]
... ...
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   -}
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>
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   -}
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]
... ...
  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 +}
... ...
  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>
... ...
  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 +}
... ...
  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 +}
... ...
  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
... ...
  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 +}
... ...
  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 ],
... ...