Commit 32b3b0d0b3485ec9475b075205d88be4a0985cc3
Merge branch 'main_dev'
# Conflicts: # src/api/external/common/index.ts # src/assets/images/chart/photos/carousel1.jpeg # src/assets/images/chart/photos/carousel2.jpeg # src/packages/components/Informations/Mores/Image/index.vue # src/packages/components/external/Charts/Lines/OverrideLineForDeviceHistoryQuery/index.vue # src/packages/components/external/Charts/Maps/AddThreeDimensionalMap/config.ts # src/packages/components/external/Charts/Maps/AddThreeDimensionalMap/index.vue # src/packages/components/external/Charts/Maps/OverrideMapAmap/components/SearchBox.vue # src/packages/components/external/Charts/Maps/OverrideMapAmap/config.ts # src/packages/components/external/Charts/Maps/OverrideMapAmap/config.vue # src/packages/components/external/Charts/Maps/OverrideMapAmap/data.json # src/packages/components/external/Charts/Maps/OverrideMapAmap/images/djh.png # src/packages/components/external/Charts/Maps/OverrideMapAmap/images/lx1.png # src/packages/components/external/Charts/Maps/OverrideMapAmap/images/online.png # src/packages/components/external/Charts/Maps/OverrideMapAmap/index.ts # src/packages/components/external/Charts/Maps/OverrideMapAmap/index.vue # src/packages/components/external/Charts/Maps/OverrideMapBase/config.ts # src/packages/components/external/Charts/Maps/OverrideMapBase/index.vue # src/packages/components/external/Informations/Mores/SingleCamera/components/VideoPlay.vue # src/packages/components/external/Informations/Mores/SingleCamera/config.vue # src/packages/components/external/Tables/Tables/OverrideTableScrollBoard/index.vue # src/packages/index.ts # src/views/chart/ContentEdit/components/EditTools/external/index.ts
Showing
88 changed files
with
1875 additions
and
592 deletions
| 1 | # Proxy | 1 | # Proxy |
| 2 | -VITE_GLOB_PROXY = [["/api", "http://222.180.200.114:48080/api"]] | ||
| 3 | -# VITE_GLOB_PROXY = [["/api", "http://192.168.10.118:8080/api"]] | 2 | +VITE_GLOB_PROXY = [["/api", "http://locahost:8080/api"]] |
| 4 | 3 | ||
| 5 | # Api prefix | 4 | # Api prefix |
| 6 | VITE_GLOB_API_URL = /api | 5 | VITE_GLOB_API_URL = /api |
| 7 | 6 | ||
| 8 | -# | 7 | +# |
| 9 | VITE_GLOB_API_URL_PREFIX = /yt | 8 | VITE_GLOB_API_URL_PREFIX = /yt |
| 10 | 9 | ||
| 11 | # 内容安全协议 | 10 | # 内容安全协议 |
| @@ -32,6 +32,6 @@ module.exports = { | @@ -32,6 +32,6 @@ module.exports = { | ||
| 32 | 'vue/no-unused-vars': 'off', | 32 | 'vue/no-unused-vars': 'off', |
| 33 | 'vue/multi-word-component-names': 'off', | 33 | 'vue/multi-word-component-names': 'off', |
| 34 | 'vue/valid-template-root': 'off', | 34 | 'vue/valid-template-root': 'off', |
| 35 | - 'vue/no-mutating-props': 'off', | 35 | + 'vue/no-mutating-props': 'off' |
| 36 | } | 36 | } |
| 37 | } | 37 | } |
| @@ -12,6 +12,7 @@ | @@ -12,6 +12,7 @@ | ||
| 12 | "preview": "vite preview", | 12 | "preview": "vite preview", |
| 13 | "preview:alone": "vite preview --mode alone.prod", | 13 | "preview:alone": "vite preview --mode alone.prod", |
| 14 | "new": "plop --plopfile ./plop/plopfile.js", | 14 | "new": "plop --plopfile ./plop/plopfile.js", |
| 15 | + "newCom": "plop --plopfile ./plop/external/plopfile.js", | ||
| 15 | "postinstall": "husky install", | 16 | "postinstall": "husky install", |
| 16 | "lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src", | 17 | "lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src", |
| 17 | "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix" | 18 | "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix" |
| 1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | ||
| 2 | +import { {{name}}Config } 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 | + | ||
| 9 | +export const seriesItem = { | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +export const option = { | ||
| 13 | + dataset: { ...dataJson }, | ||
| 14 | + series: [seriesItem, seriesItem] | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +export default class Config extends PublicConfigClass implements CreateComponentType { | ||
| 18 | + public key = {{name}}Config.key | ||
| 19 | + public chartConfig = cloneDeep({{name}}Config) | ||
| 20 | + // 图表配置项 | ||
| 21 | + public option = echartOptionProfixHandle(option, includes) | ||
| 22 | +} |
| 1 | +<template> | ||
| 2 | + <!-- Echarts 全局设置 --> | ||
| 3 | + <global-setting :optionData="optionData"></global-setting> | ||
| 4 | + <CollapseItem name="xxx" :expanded="true"> | ||
| 5 | + <SettingItemBox name="xx"> | ||
| 6 | + <SettingItem name="xx"> | ||
| 7 | + </SettingItem> | ||
| 8 | + </SettingItemBox> | ||
| 9 | + </CollapseItem> | ||
| 10 | +</template> | ||
| 11 | + | ||
| 12 | +<script setup lang="ts"> | ||
| 13 | +import { PropType, computed } from 'vue' | ||
| 14 | +import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | ||
| 15 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | ||
| 16 | + | ||
| 17 | +const props = defineProps({ | ||
| 18 | + optionData: { | ||
| 19 | + type: Object as PropType<GlobalThemeJsonType>, | ||
| 20 | + required: true | ||
| 21 | + } | ||
| 22 | +}) | ||
| 23 | + | ||
| 24 | +const seriesList = computed(() => { | ||
| 25 | + return props.optionData.series | ||
| 26 | +}) | ||
| 27 | +</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 { 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('{{name}}', true) | ||
| 6 | + | ||
| 7 | +export const {{name}}Config: ConfigType = { | ||
| 8 | + key, | ||
| 9 | + chartKey, | ||
| 10 | + conKey, | ||
| 11 | + title: 'xxx', | ||
| 12 | + category: ChatCategoryEnum.BAR, | ||
| 13 | + categoryName: ChatCategoryEnumName.BAR, | ||
| 14 | + package: PackagesCategoryEnum.CHARTS, | ||
| 15 | + chartFrame: ChartFrameEnum.ECHARTS, | ||
| 16 | + image: 'bar_x.png' | ||
| 17 | +} |
| 1 | +<template> | ||
| 2 | + <v-chart | ||
| 3 | + ref="vChartRef" | ||
| 4 | + :init-options="initOptions" | ||
| 5 | + :theme="themeColor" | ||
| 6 | + :option="option" | ||
| 7 | + :manual-update="isPreview()" | ||
| 8 | + :update-options="{ | ||
| 9 | + replaceMerge: replaceMergeArr | ||
| 10 | + }" | ||
| 11 | + autoresize | ||
| 12 | + @mouseover="handleHighlight" | ||
| 13 | + @mouseout="handleDownplay" | ||
| 14 | + > | ||
| 15 | + </v-chart> | ||
| 16 | +</template> | ||
| 17 | + | ||
| 18 | +<script setup lang="ts"> | ||
| 19 | +import { ref, nextTick, computed, watch, PropType, onMounted } from 'vue' | ||
| 20 | +import VChart from 'vue-echarts' | ||
| 21 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | ||
| 22 | +import { use } from 'echarts/core' | ||
| 23 | +import { CanvasRenderer } from 'echarts/renderers' | ||
| 24 | +import { BarChart } from 'echarts/charts' | ||
| 25 | +import config, { includes, seriesItem } from './config' | ||
| 26 | +import { mergeTheme } from '@/packages/public/chart' | ||
| 27 | +import { useChartDataFetch } from '@/hooks' | ||
| 28 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | ||
| 29 | +import { isPreview } from '@/utils' | ||
| 30 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | ||
| 31 | +import isObject from 'lodash/isObject' | ||
| 32 | +import dataJson from './data.json' | ||
| 33 | +import cloneDeep from 'lodash/cloneDeep' | ||
| 34 | +import { useAssembleDataHooks } from '@/hooks/external/useAssembleData.hook' | ||
| 35 | +import { SocketReceiveMessageType } from '@/store/external/modules/socketStore.d' | ||
| 36 | + | ||
| 37 | +const props = defineProps({ | ||
| 38 | + themeSetting: { | ||
| 39 | + type: Object, | ||
| 40 | + required: true | ||
| 41 | + }, | ||
| 42 | + themeColor: { | ||
| 43 | + type: Object, | ||
| 44 | + required: true | ||
| 45 | + }, | ||
| 46 | + chartConfig: { | ||
| 47 | + type: Object as PropType<config>, | ||
| 48 | + required: true | ||
| 49 | + } | ||
| 50 | +}) | ||
| 51 | + | ||
| 52 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | ||
| 53 | + | ||
| 54 | +use([DatasetComponent, CanvasRenderer, BarChart, GridComponent, TooltipComponent, LegendComponent]) | ||
| 55 | + | ||
| 56 | +const chartEditStore = useChartEditStore() | ||
| 57 | + | ||
| 58 | +const replaceMergeArr = ref<string[]>() | ||
| 59 | + | ||
| 60 | +const option = computed(() => { | ||
| 61 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | ||
| 62 | +}) | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +// dataset 无法变更条数的补丁 | ||
| 66 | +watch( | ||
| 67 | + () => props.chartConfig.option.dataset, | ||
| 68 | + (newData: { dimensions: any }, oldData) => { | ||
| 69 | + try { | ||
| 70 | + if (!isObject(newData) || !('dimensions' in newData)) return | ||
| 71 | + if (Array.isArray(newData?.dimensions)) { | ||
| 72 | + const seriesArr = [] | ||
| 73 | + // 对oldData进行判断,防止传入错误数据之后对旧维度判断产生干扰 | ||
| 74 | + // 此处计算的是dimensions的Y轴维度,若是dimensions.length为0或1,则默认为1,排除X轴维度干扰 | ||
| 75 | + const oldDimensions = | ||
| 76 | + Array.isArray(oldData?.dimensions) && oldData.dimensions.length >= 1 ? oldData.dimensions.length : 1 | ||
| 77 | + const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1 | ||
| 78 | + const dimensionsGap = newDimensions - oldDimensions | ||
| 79 | + if (dimensionsGap < 0) { | ||
| 80 | + props.chartConfig.option.series.splice(newDimensions - 1) | ||
| 81 | + } else if (dimensionsGap > 0) { | ||
| 82 | + if (!oldData || !oldData?.dimensions || !Array.isArray(oldData?.dimensions) || !oldData?.dimensions.length) { | ||
| 83 | + props.chartConfig.option.series = [] | ||
| 84 | + } | ||
| 85 | + for (let i = 0; i < dimensionsGap; i++) { | ||
| 86 | + seriesArr.push(cloneDeep(seriesItem)) | ||
| 87 | + } | ||
| 88 | + props.chartConfig.option.series.push(...seriesArr) | ||
| 89 | + } | ||
| 90 | + replaceMergeArr.value = ['series'] | ||
| 91 | + nextTick(() => { | ||
| 92 | + replaceMergeArr.value = [] | ||
| 93 | + }) | ||
| 94 | + } | ||
| 95 | + } catch (error) { | ||
| 96 | + console.log(error) | ||
| 97 | + } | ||
| 98 | + }, | ||
| 99 | + { | ||
| 100 | + deep: false | ||
| 101 | + } | ||
| 102 | +) | ||
| 103 | + | ||
| 104 | +//fix 修复v-chart图表绑定联动组件视图不更新问题 | ||
| 105 | +const updateVChart = async (newData: SocketReceiveMessageType) => { | ||
| 106 | + //区分是普通请求还是ws请求 | ||
| 107 | + if (!isObject(newData) || !('dimensions' in newData)) { | ||
| 108 | + const { data } = newData | ||
| 109 | + const { keys, record } = useAssembleDataHooks(data) | ||
| 110 | + vChartRef.value?.setOption({ | ||
| 111 | + dataset: { | ||
| 112 | + dimensions: ['ts', ...keys], | ||
| 113 | + source: [record] | ||
| 114 | + } | ||
| 115 | + }) | ||
| 116 | + } else { | ||
| 117 | + //异步更新,同步更新会造成联动组件控制,图表不及时更新 | ||
| 118 | + await nextTick().then(() => { | ||
| 119 | + vChartRef.value?.setOption( | ||
| 120 | + { | ||
| 121 | + ...option.value, | ||
| 122 | + dataset: newData | ||
| 123 | + }, | ||
| 124 | + { | ||
| 125 | + notMerge: true | ||
| 126 | + } | ||
| 127 | + ) | ||
| 128 | + }) | ||
| 129 | + } | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, newData => { | ||
| 133 | + //联动支持分组 | ||
| 134 | + /** | ||
| 135 | + * 修复多个分组,然后下拉框联动,会影响另一个组件 | ||
| 136 | + */ | ||
| 137 | + chartEditStore.getComponentList.forEach(targetItem => { | ||
| 138 | + if (targetItem.isGroup) { | ||
| 139 | + targetItem.groupList?.forEach(groupItem => { | ||
| 140 | + if (groupItem.id === props.chartConfig.id) { | ||
| 141 | + groupItem.option.dataset = newData | ||
| 142 | + } | ||
| 143 | + }) | ||
| 144 | + } else { | ||
| 145 | + if (targetItem.id === props.chartConfig.id) { | ||
| 146 | + targetItem.option.dataset = newData | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + }) | ||
| 150 | + // | ||
| 151 | + updateVChart(newData) | ||
| 152 | +}) | ||
| 153 | +</script> |
plop/external/component-template/prompt.js
0 → 100644
| 1 | +module.exports = { | ||
| 2 | + description: | ||
| 3 | + 'create a component(在packages/components/external,一般是需要重写的组件,这里写的示例生成在src/packages/components/external/Charts/Bars目录下)', | ||
| 4 | + prompts: [ | ||
| 5 | + { | ||
| 6 | + type: 'input', | ||
| 7 | + name: 'name', | ||
| 8 | + message: 'Please enter component name,such as "OverrideComponentName":', | ||
| 9 | + validate(value) { | ||
| 10 | + if (!value || value.trim === '') { | ||
| 11 | + return 'component name is required' | ||
| 12 | + } | ||
| 13 | + return true | ||
| 14 | + } | ||
| 15 | + } | ||
| 16 | + ], | ||
| 17 | + actions: data => { | ||
| 18 | + const dataName = data.name | ||
| 19 | + | ||
| 20 | + const actions = [ | ||
| 21 | + { | ||
| 22 | + type: 'add', | ||
| 23 | + path: `${process.cwd()}/src/packages/components/external/Charts/Bars${dataName}/config.ts`, | ||
| 24 | + templateFile: './component-template/config.ts.hbs', | ||
| 25 | + data: { | ||
| 26 | + name: data.name | ||
| 27 | + } | ||
| 28 | + }, | ||
| 29 | + { | ||
| 30 | + type: 'add', | ||
| 31 | + path: `${process.cwd()}/src/packages/components/external/Charts/Bars${dataName}/config.vue`, | ||
| 32 | + templateFile: './component-template/config.vue.hbs', | ||
| 33 | + data: { | ||
| 34 | + name: data.name | ||
| 35 | + } | ||
| 36 | + }, | ||
| 37 | + { | ||
| 38 | + type: 'add', | ||
| 39 | + path: `${process.cwd()}/src/packages/components/external/Charts/Bars${dataName}/data.json`, | ||
| 40 | + templateFile: './component-template/data.json.hbs', | ||
| 41 | + data: { | ||
| 42 | + name: data.name | ||
| 43 | + } | ||
| 44 | + }, | ||
| 45 | + { | ||
| 46 | + type: 'add', | ||
| 47 | + path: `${process.cwd()}/src/packages/components/external/Charts/Bars${dataName}/index.ts`, | ||
| 48 | + templateFile: './component-template/index.ts.hbs', | ||
| 49 | + data: { | ||
| 50 | + name: data.name | ||
| 51 | + } | ||
| 52 | + }, | ||
| 53 | + { | ||
| 54 | + type: 'add', | ||
| 55 | + path: `${process.cwd()}/src/packages/components/external/Charts/Bars${dataName}/index.vue`, | ||
| 56 | + templateFile: './component-template/index.vue.hbs', | ||
| 57 | + data: { | ||
| 58 | + name: data.name | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + ] | ||
| 62 | + | ||
| 63 | + return actions | ||
| 64 | + } | ||
| 65 | +} |
plop/external/plopfile.js
0 → 100644
readme/sponsors/dandian-banner.png
0 → 100644
14.9 KB
readme/sponsors/mdy-banner.png
0 → 100644
53.7 KB
| @@ -16,7 +16,9 @@ enum Api { | @@ -16,7 +16,9 @@ enum Api { | ||
| 16 | VIDEOURL = '/video/url/', | 16 | VIDEOURL = '/video/url/', |
| 17 | GEOJSONURL = '/map/geo_json/', | 17 | GEOJSONURL = '/map/geo_json/', |
| 18 | DEVICE_URL = '/device', | 18 | DEVICE_URL = '/device', |
| 19 | - GET_ATTRBUTELIST = '/device/attributes/' | 19 | + GET_ATTRBUTELIST = '/device/attributes/', |
| 20 | + GET_DEVICE_LATEST = '/plugins/telemetry/DEVICE/', | ||
| 21 | + DEVICE_ATTR = '/device/attributes', | ||
| 20 | } | 22 | } |
| 21 | 23 | ||
| 22 | export const getDictItemByCode = (value: string) => { | 24 | export const getDictItemByCode = (value: string) => { |
| @@ -111,7 +113,7 @@ export const getVideoUrl = (id: string) => | @@ -111,7 +113,7 @@ export const getVideoUrl = (id: string) => | ||
| 111 | }) | 113 | }) |
| 112 | 114 | ||
| 113 | //获取行政区域 | 115 | //获取行政区域 |
| 114 | -export const getGeoJsonMap = (code: number, level: string) => | 116 | +export const getGeoJsonMap = (code: number | string, level: string) => |
| 115 | defHttp.get({ | 117 | defHttp.get({ |
| 116 | url: `${Api.GEOJSONURL}${code}/${level}` | 118 | url: `${Api.GEOJSONURL}${code}/${level}` |
| 117 | }) | 119 | }) |
| @@ -127,3 +129,20 @@ export const getAttribute = (deviceProfileId: string) => | @@ -127,3 +129,20 @@ export const getAttribute = (deviceProfileId: string) => | ||
| 127 | defHttp.get({ | 129 | defHttp.get({ |
| 128 | url: `${Api.GET_ATTRBUTELIST}${deviceProfileId}` | 130 | url: `${Api.GET_ATTRBUTELIST}${deviceProfileId}` |
| 129 | }) | 131 | }) |
| 132 | + | ||
| 133 | +// 获取设备最新数据 | ||
| 134 | +export const getDeviceLatest = (tbDeviceId: string) => | ||
| 135 | +defHttp.get({ | ||
| 136 | + url: `${Api.GET_DEVICE_LATEST}${tbDeviceId}/values/timeseries` | ||
| 137 | +}, { | ||
| 138 | + joinPrefix: false | ||
| 139 | +}) | ||
| 140 | + | ||
| 141 | +//获取产品属性 | ||
| 142 | +export const getProfileAttrs = (params: { deviceProfileId: string; dataType?: string }) => { | ||
| 143 | + const { deviceProfileId, dataType } = params; | ||
| 144 | + return defHttp.get({ | ||
| 145 | + url: `${Api.DEVICE_ATTR}/${deviceProfileId}`, | ||
| 146 | + params: { dataType }, | ||
| 147 | + }); | ||
| 148 | +}; |
| 1 | -import { RequestBodyEnum, RequestParams } from "@/enums/httpEnum" | 1 | +import { RequestBodyEnum, RequestHttpEnum, RequestParams } from "@/enums/httpEnum" |
| 2 | import { ExtraRequestConfigType } from "@/store/external/modules/extraComponentInfo.d" | 2 | import { ExtraRequestConfigType } from "@/store/external/modules/extraComponentInfo.d" |
| 3 | import { RequestConfigType } from "@/store/modules/chartEditStore/chartEditStore.d" | 3 | import { RequestConfigType } from "@/store/modules/chartEditStore/chartEditStore.d" |
| 4 | import { isShareMode } from "@/views/share/hook" | 4 | import { isShareMode } from "@/views/share/hook" |
| @@ -115,6 +115,22 @@ const handleParams = (Params: Recordable) => { | @@ -115,6 +115,22 @@ const handleParams = (Params: Recordable) => { | ||
| 115 | return Object.keys(Params).filter(Boolean).reduce((prev, next) => ({ ...prev, [next]: Params[next] }), {}) | 115 | return Object.keys(Params).filter(Boolean).reduce((prev, next) => ({ ...prev, [next]: Params[next] }), {}) |
| 116 | } | 116 | } |
| 117 | 117 | ||
| 118 | +//post请求动态追加query参数 | ||
| 119 | +const objConvertQuery= (data:Recordable)=> { | ||
| 120 | + const _result = []; | ||
| 121 | + for (const key in data) { | ||
| 122 | + const value = data[key]; | ||
| 123 | + if (value.constructor == Array) { | ||
| 124 | + value.forEach(function(_value) { | ||
| 125 | + _result.push(key + "=" + _value); | ||
| 126 | + }); | ||
| 127 | + } else { | ||
| 128 | + _result.push(key + '=' + value); | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + return _result.join('&'); | ||
| 132 | +} | ||
| 133 | + | ||
| 118 | export const customRequest = async (request: RequestConfigType) => { | 134 | export const customRequest = async (request: RequestConfigType) => { |
| 119 | const { requestHttpType, requestParams, requestParamsBodyType, requestOriginUrl } = request as ExtraRequestConfigType | 135 | const { requestHttpType, requestParams, requestParamsBodyType, requestOriginUrl } = request as ExtraRequestConfigType |
| 120 | let { requestUrl } = request as ExtraRequestConfigType | 136 | let { requestUrl } = request as ExtraRequestConfigType |
| @@ -130,18 +146,18 @@ export const customRequest = async (request: RequestConfigType) => { | @@ -130,18 +146,18 @@ export const customRequest = async (request: RequestConfigType) => { | ||
| 130 | const body = transformBodyValue(Body, requestParamsBodyType) | 146 | const body = transformBodyValue(Body, requestParamsBodyType) |
| 131 | 147 | ||
| 132 | /** | 148 | /** |
| 133 | - * ft 修改post请求针对这种接口(/api/yt/device,设备列表分页),是post请求,body体一定要携带内容,空内容也可以 | 149 | + * ft 修改post请求针对这种接口(/api/yt/device,设备列表分页),是post请求,page和pageSize是query参数,body可以不传内容 |
| 134 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | 150 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 |
| 135 | - * 源代码 data: body, | ||
| 136 | - * 修改后代码 data: !body? {"":null} :body, | 151 | + * 源代码 url: requestUrl |
| 152 | + * 修改后代码 requestHttpType === RequestHttpEnum.GET.toUpperCase() ? requestUrl: `${requestUrl}?${objConvertQuery(Params)} | ||
| 137 | */ | 153 | */ |
| 138 | Params = handleParams(Params) | 154 | Params = handleParams(Params) |
| 139 | return customHttp.request<any>({ | 155 | return customHttp.request<any>({ |
| 140 | - url: requestUrl, | 156 | + url: requestHttpType === RequestHttpEnum.GET.toUpperCase() ? requestUrl: `${requestUrl}?${objConvertQuery(Params)}`, |
| 141 | baseURL: getOriginUrl(requestOriginUrl!), | 157 | baseURL: getOriginUrl(requestOriginUrl!), |
| 142 | method: requestHttpType, | 158 | method: requestHttpType, |
| 143 | - params: Params, | ||
| 144 | - data: !body? {"":null} :body, | 159 | + params: requestHttpType === RequestHttpEnum.GET.toUpperCase() ? Params: null, |
| 160 | + data: body, | ||
| 145 | headers: extraValue(Header) | 161 | headers: extraValue(Header) |
| 146 | }, { | 162 | }, { |
| 147 | joinPrefix: false, | 163 | joinPrefix: false, |
src/assets/external/marker/1.png
0 → 100644
1.24 KB
src/assets/external/marker/2.png
0 → 100644
1.4 KB
src/assets/external/marker/3.png
0 → 100644
904 Bytes
src/assets/external/marker/4.png
0 → 100644
1.02 KB
src/assets/external/marker/5.png
0 → 100644
970 Bytes
63 KB
12.1 KB
| 1 | +<template> | ||
| 2 | + <n-upload | ||
| 3 | + v-model:file-list="fileList" | ||
| 4 | + :show-file-list="true" | ||
| 5 | + :customRequest="customRequest" | ||
| 6 | + :onBeforeUpload="beforeUploadHandle" | ||
| 7 | + :onRemove="remove" | ||
| 8 | + > | ||
| 9 | + <n-upload-dragger> | ||
| 10 | + <img v-if="uploadImageUrl" class="upload-show" :src="uploadImageUrl" alt="上传的图片" /> | ||
| 11 | + <div class="upload-img" v-show="!uploadImageUrl"> | ||
| 12 | + <img src="@/assets/images/canvas/noImage.png" /> | ||
| 13 | + <n-text class="upload-desc" depth="3"> | ||
| 14 | + 上传文件需小于 {{ uploadSizeFormat.size }}M ,格式为 {{ uploadSizeFormat.format }} 的文件 | ||
| 15 | + </n-text> | ||
| 16 | + </div> | ||
| 17 | + </n-upload-dragger> | ||
| 18 | + </n-upload> | ||
| 19 | +</template> | ||
| 20 | + | ||
| 21 | +<script lang="ts" setup name="TKUpload"> | ||
| 22 | +import { ref, PropType, nextTick } from 'vue' | ||
| 23 | +import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui' | ||
| 24 | +import { fetchRouteParamsLocation } from '@/utils' | ||
| 25 | +import { uploadFile } from '@/api/external/contentSave/content' | ||
| 26 | +import { FileTypeEnum } from '@/enums/external/fileTypeEnum' | ||
| 27 | + | ||
| 28 | +interface uploadSizeFormatIF { | ||
| 29 | + size: number | ||
| 30 | + format: string | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +const props = defineProps({ | ||
| 34 | + uploadImageUrl: { | ||
| 35 | + type: String as PropType<string>, | ||
| 36 | + default: '' | ||
| 37 | + }, | ||
| 38 | + uploadSizeFormat: { | ||
| 39 | + type: Object as PropType<uploadSizeFormatIF>, | ||
| 40 | + default: () => ({ | ||
| 41 | + size: 5, | ||
| 42 | + format: 'png/jpg/jpeg/gif' | ||
| 43 | + }) | ||
| 44 | + } | ||
| 45 | +}) | ||
| 46 | + | ||
| 47 | +const emit = defineEmits(['sendFile', 'removeFile']) | ||
| 48 | + | ||
| 49 | +const fileList = ref<UploadFileInfo[]>() | ||
| 50 | + | ||
| 51 | +// 自定义上传操作 | ||
| 52 | +const customRequest = (options: UploadCustomRequestOptions) => { | ||
| 53 | + const { file } = options | ||
| 54 | + nextTick(async () => { | ||
| 55 | + if (file.file) { | ||
| 56 | + const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_upload.png`, { | ||
| 57 | + type: file.file.type | ||
| 58 | + }) | ||
| 59 | + let uploadParams = new FormData() | ||
| 60 | + uploadParams.append('file', newNameFile) | ||
| 61 | + const uploadRes = await uploadFile(uploadParams) | ||
| 62 | + if (!uploadRes) return | ||
| 63 | + emit('sendFile', uploadRes?.fileStaticUri) | ||
| 64 | + window['$message'].success('上传文件成功!') | ||
| 65 | + } else { | ||
| 66 | + window['$message'].error('上传文件失败,请稍后重试!') | ||
| 67 | + } | ||
| 68 | + }) | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +// 文件上传前置处理 | ||
| 72 | +const beforeUploadHandle = (file: UploadFileInfo) => { | ||
| 73 | + fileList.value = [] | ||
| 74 | + const type = file.file?.type | ||
| 75 | + const size = file.file?.size as number | ||
| 76 | + const typeSuffix = type?.split('/')?.at(-1)?.toUpperCase() as keyof typeof FileTypeEnum | ||
| 77 | + if (size > 1024 * 1024 * props.uploadSizeFormat.size) { | ||
| 78 | + window['$message'].warning(`文件超出 ${props.uploadSizeFormat.size}M限制,请重新上传!`) | ||
| 79 | + return false | ||
| 80 | + } | ||
| 81 | + if (!FileTypeEnum[typeSuffix]) { | ||
| 82 | + window['$message'].warning('文件格式不符合,请重新上传!') | ||
| 83 | + return false | ||
| 84 | + } | ||
| 85 | + return true | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +//单个点击删除 | ||
| 89 | +const remove = () => { | ||
| 90 | + fileList.value = [] | ||
| 91 | + emit('removeFile', true) | ||
| 92 | +} | ||
| 93 | +</script> | ||
| 94 | + | ||
| 95 | +<style lang="scss" scoped> | ||
| 96 | +$uploadHeight: 193px; | ||
| 97 | +@include deep() { | ||
| 98 | + .n-card__content { | ||
| 99 | + padding: 0; | ||
| 100 | + overflow: hidden; | ||
| 101 | + } | ||
| 102 | + .n-upload-dragger { | ||
| 103 | + padding: 5px; | ||
| 104 | + } | ||
| 105 | +} | ||
| 106 | +.upload-show { | ||
| 107 | + width: -webkit-fill-available; | ||
| 108 | + height: $uploadHeight; | ||
| 109 | + border-radius: 5px; | ||
| 110 | +} | ||
| 111 | +.upload-img { | ||
| 112 | + display: flex; | ||
| 113 | + flex-direction: column; | ||
| 114 | + align-items: center; | ||
| 115 | + img { | ||
| 116 | + height: 150px; | ||
| 117 | + } | ||
| 118 | + .upload-desc { | ||
| 119 | + padding: 10px 0; | ||
| 120 | + } | ||
| 121 | +} | ||
| 122 | +</style> |
src/enums/external/fileTypeEnum.ts
0 → 100644
| @@ -56,7 +56,10 @@ const initMap = (newData: any) => { | @@ -56,7 +56,10 @@ const initMap = (newData: any) => { | ||
| 56 | pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度 | 56 | pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度 |
| 57 | skyColor: skyColor.value, | 57 | skyColor: skyColor.value, |
| 58 | viewMode: viewMode.value, // 地图模式 | 58 | viewMode: viewMode.value, // 地图模式 |
| 59 | - willReadFrequently: true | 59 | + willReadFrequently: true, |
| 60 | + WebGLParams: { | ||
| 61 | + preserveDrawingBuffer: true, | ||
| 62 | + } | ||
| 60 | }) | 63 | }) |
| 61 | dataHandle(props.chartConfig.option.dataset) | 64 | dataHandle(props.chartConfig.option.dataset) |
| 62 | let satellite = new AMap.TileLayer.Satellite() | 65 | let satellite = new AMap.TileLayer.Satellite() |
| @@ -68,7 +71,7 @@ const initMap = (newData: any) => { | @@ -68,7 +71,7 @@ const initMap = (newData: any) => { | ||
| 68 | mapIns.setMapStyle(`amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}`) | 71 | mapIns.setMapStyle(`amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}`) |
| 69 | } | 72 | } |
| 70 | }) | 73 | }) |
| 71 | - .catch(e => {}) | 74 | + .catch(e => { }) |
| 72 | } | 75 | } |
| 73 | 76 | ||
| 74 | const dataHandle = (newData: any) => { | 77 | const dataHandle = (newData: any) => { |
| @@ -11,7 +11,10 @@ export const option = { | @@ -11,7 +11,10 @@ export const option = { | ||
| 11 | dataset: dataJson, | 11 | dataset: dataJson, |
| 12 | mapRegion: { | 12 | mapRegion: { |
| 13 | adcode: 'china', | 13 | adcode: 'china', |
| 14 | - showHainanIsLands: true | 14 | + showHainanIsLands: true, |
| 15 | + enter: false, | ||
| 16 | + backSize: 20, | ||
| 17 | + backColor: '#ffffff' | ||
| 15 | }, | 18 | }, |
| 16 | tooltip: { | 19 | tooltip: { |
| 17 | show: true, | 20 | show: true, |
| @@ -103,19 +106,19 @@ export const option = { | @@ -103,19 +106,19 @@ export const option = { | ||
| 103 | borderColor: 'rgba(147, 235, 248, 0.8)', | 106 | borderColor: 'rgba(147, 235, 248, 0.8)', |
| 104 | textStyle: { | 107 | textStyle: { |
| 105 | color: '#FFFFFF', | 108 | color: '#FFFFFF', |
| 106 | - fontSize: 12, | 109 | + fontSize: 12 |
| 107 | } | 110 | } |
| 108 | }, | 111 | }, |
| 109 | label: { | 112 | label: { |
| 110 | show: false, | 113 | show: false, |
| 111 | color: '#FFFFFF', | 114 | color: '#FFFFFF', |
| 112 | - fontSize: 12, | 115 | + fontSize: 12 |
| 113 | }, | 116 | }, |
| 114 | emphasis: { | 117 | emphasis: { |
| 115 | disabled: false, | 118 | disabled: false, |
| 116 | label: { | 119 | label: { |
| 117 | color: '#FFFFFF', | 120 | color: '#FFFFFF', |
| 118 | - fontSize: 12, | 121 | + fontSize: 12 |
| 119 | }, | 122 | }, |
| 120 | itemStyle: { | 123 | itemStyle: { |
| 121 | areaColor: '#389BB7', | 124 | areaColor: '#389BB7', |
| @@ -148,6 +151,26 @@ export const option = { | @@ -148,6 +151,26 @@ export const option = { | ||
| 148 | shadowOffsetY: 2, | 151 | shadowOffsetY: 2, |
| 149 | shadowBlur: 10 | 152 | shadowBlur: 10 |
| 150 | } | 153 | } |
| 154 | + }, | ||
| 155 | + { | ||
| 156 | + type: 'lines', | ||
| 157 | + zlevel: 2, | ||
| 158 | + effect: { | ||
| 159 | + show: true, | ||
| 160 | + period: 4, //箭头指向速度,值越小速度越快 | ||
| 161 | + trailLength: 0.4, //特效尾迹长度[0,1]值越大,尾迹越长重 | ||
| 162 | + symbol: 'arrow', //箭头图标 | ||
| 163 | + symbolSize: 7 //图标大小 | ||
| 164 | + }, | ||
| 165 | + lineStyle: { | ||
| 166 | + normal: { | ||
| 167 | + color: '#4fb6d2', | ||
| 168 | + width: 1, //线条宽度 | ||
| 169 | + opacity: 0.1, //尾迹线条透明度 | ||
| 170 | + curveness: 0.3 //尾迹线条曲直度 | ||
| 171 | + } | ||
| 172 | + }, | ||
| 173 | + data: [] | ||
| 151 | } | 174 | } |
| 152 | ] | 175 | ] |
| 153 | } | 176 | } |
| @@ -69,11 +69,7 @@ | @@ -69,11 +69,7 @@ | ||
| 69 | </n-space> | 69 | </n-space> |
| 70 | </SettingItem> | 70 | </SettingItem> |
| 71 | <SettingItem name="字体颜色"> | 71 | <SettingItem name="字体颜色"> |
| 72 | - <n-color-picker | ||
| 73 | - size="small" | ||
| 74 | - :modes="['hex']" | ||
| 75 | - v-model:value="seriesList[1].label.color" | ||
| 76 | - ></n-color-picker> | 72 | + <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[1].label.color"></n-color-picker> |
| 77 | </SettingItem> | 73 | </SettingItem> |
| 78 | <SettingItem name="字体大小"> | 74 | <SettingItem name="字体大小"> |
| 79 | <n-input-number | 75 | <n-input-number |
| @@ -129,7 +125,7 @@ | @@ -129,7 +125,7 @@ | ||
| 129 | ></n-color-picker> | 125 | ></n-color-picker> |
| 130 | </SettingItem> | 126 | </SettingItem> |
| 131 | </SettingItemBox> | 127 | </SettingItemBox> |
| 132 | - | 128 | + |
| 133 | <SettingItemBox name="悬浮弹窗"> | 129 | <SettingItemBox name="悬浮弹窗"> |
| 134 | <SettingItem name="显示"> | 130 | <SettingItem name="显示"> |
| 135 | <n-space> | 131 | <n-space> |
| @@ -180,6 +176,22 @@ | @@ -180,6 +176,22 @@ | ||
| 180 | <SettingItem> | 176 | <SettingItem> |
| 181 | <n-checkbox v-model:checked="mapRegion.showHainanIsLands" size="small">显示南海群岛</n-checkbox> | 177 | <n-checkbox v-model:checked="mapRegion.showHainanIsLands" size="small">显示南海群岛</n-checkbox> |
| 182 | </SettingItem> | 178 | </SettingItem> |
| 179 | + <SettingItem v-if="seriesList[2]"> | ||
| 180 | + <n-checkbox v-model:checked="mapRegion.enter" size="small">点击进入下级</n-checkbox> | ||
| 181 | + </SettingItem> | ||
| 182 | + </SettingItemBox> | ||
| 183 | + <SettingItemBox name="返回图标" v-if="mapRegion.enter"> | ||
| 184 | + <SettingItem name="颜色"> | ||
| 185 | + <n-color-picker size="small" :modes="['hex']" v-model:value="mapRegion.backColor"></n-color-picker> | ||
| 186 | + </SettingItem> | ||
| 187 | + <SettingItem name="大小"> | ||
| 188 | + <n-input-number | ||
| 189 | + v-model:value="mapRegion.backSize" | ||
| 190 | + :min="1" | ||
| 191 | + size="small" | ||
| 192 | + placeholder="请输入字体大小" | ||
| 193 | + ></n-input-number> | ||
| 194 | + </SettingItem> | ||
| 183 | </SettingItemBox> | 195 | </SettingItemBox> |
| 184 | </CollapseItem> | 196 | </CollapseItem> |
| 185 | <CollapseItem name="标记" :expanded="true"> | 197 | <CollapseItem name="标记" :expanded="true"> |
| @@ -191,7 +203,7 @@ | @@ -191,7 +203,7 @@ | ||
| 191 | <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].itemStyle.color"></n-color-picker> | 203 | <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].itemStyle.color"></n-color-picker> |
| 192 | </SettingItem> | 204 | </SettingItem> |
| 193 | </SettingItemBox> | 205 | </SettingItemBox> |
| 194 | - | 206 | + |
| 195 | <SettingItemBox name="文本"> | 207 | <SettingItemBox name="文本"> |
| 196 | <SettingItem name="显示"> | 208 | <SettingItem name="显示"> |
| 197 | <n-space> | 209 | <n-space> |
| @@ -223,6 +235,47 @@ | @@ -223,6 +235,47 @@ | ||
| 223 | </SettingItem> | 235 | </SettingItem> |
| 224 | </SettingItemBox> | 236 | </SettingItemBox> |
| 225 | </CollapseItem> | 237 | </CollapseItem> |
| 238 | + | ||
| 239 | + <CollapseItem v-if="seriesList[2]" name="飞线" :expanded="true"> | ||
| 240 | + <SettingItemBox name="箭头"> | ||
| 241 | + <SettingItem name="速度"> | ||
| 242 | + <n-tooltip trigger="hover"> | ||
| 243 | + <template #trigger> | ||
| 244 | + <n-input-number v-model:value="seriesList[2].effect.period" size="small" :min="0"></n-input-number> | ||
| 245 | + </template> | ||
| 246 | + 值越小速度越快 | ||
| 247 | + </n-tooltip> | ||
| 248 | + </SettingItem> | ||
| 249 | + <SettingItem name="尾迹"> | ||
| 250 | + <n-tooltip trigger="hover"> | ||
| 251 | + <template #trigger> | ||
| 252 | + <n-input-number | ||
| 253 | + v-model:value="seriesList[2].effect.trailLength" | ||
| 254 | + size="small" | ||
| 255 | + :min="0" | ||
| 256 | + :max="1" | ||
| 257 | + ></n-input-number> | ||
| 258 | + </template> | ||
| 259 | + 特效尾迹长度[0,1]值越大,尾迹越长重 | ||
| 260 | + </n-tooltip> | ||
| 261 | + </SettingItem> | ||
| 262 | + <SettingItem name="大小"> | ||
| 263 | + <n-input-number v-model:value="seriesList[2].effect.symbolSize" size="small" :min="0"></n-input-number> | ||
| 264 | + </SettingItem> | ||
| 265 | + </SettingItemBox> | ||
| 266 | + <SettingItemBox name="配置"> | ||
| 267 | + <SettingItem name="颜色"> | ||
| 268 | + <n-color-picker | ||
| 269 | + size="small" | ||
| 270 | + :modes="['hex']" | ||
| 271 | + v-model:value="seriesList[2].lineStyle.normal.color" | ||
| 272 | + ></n-color-picker> | ||
| 273 | + </SettingItem> | ||
| 274 | + <SettingItem name="宽度"> | ||
| 275 | + <n-input-number v-model:value="seriesList[2].lineStyle.normal.width" size="small" :min="1"></n-input-number> | ||
| 276 | + </SettingItem> | ||
| 277 | + </SettingItemBox> | ||
| 278 | + </CollapseItem> | ||
| 226 | </template> | 279 | </template> |
| 227 | 280 | ||
| 228 | <script setup lang="ts"> | 281 | <script setup lang="ts"> |
| @@ -21,6 +21,32 @@ | @@ -21,6 +21,32 @@ | ||
| 21 | "value": [126.642464, 45.756967, 101] | 21 | "value": [126.642464, 45.756967, 101] |
| 22 | } | 22 | } |
| 23 | ], | 23 | ], |
| 24 | + "line": [ | ||
| 25 | + { | ||
| 26 | + "coords": [ | ||
| 27 | + [113.665412, 34.757975], | ||
| 28 | + [116.405285, 39.904989] | ||
| 29 | + ] | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + "coords": [ | ||
| 33 | + [101.778916, 36.623178], | ||
| 34 | + [116.405285, 39.904989] | ||
| 35 | + ] | ||
| 36 | + }, | ||
| 37 | + { | ||
| 38 | + "coords": [ | ||
| 39 | + [106.278179, 38.46637], | ||
| 40 | + [116.405285, 39.904989] | ||
| 41 | + ] | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + "coords": [ | ||
| 45 | + [126.642464, 45.756967], | ||
| 46 | + [116.405285, 39.904989] | ||
| 47 | + ] | ||
| 48 | + } | ||
| 49 | + ], | ||
| 24 | "map": [ | 50 | "map": [ |
| 25 | { | 51 | { |
| 26 | "name": "北京市", | 52 | "name": "北京市", |
| 1 | <template> | 1 | <template> |
| 2 | - <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option.value" :manual-update="isPreview()" autoresize> | ||
| 3 | - </v-chart> | 2 | + <div> |
| 3 | + <div class="back-icon" v-if="(enter && levelHistory.length !== 0) || (enter && !isPreview())" @click="backLevel"> | ||
| 4 | + <n-icon :color="backColor" :size="backSize * 1.1"> | ||
| 5 | + <ArrowBackIcon /> | ||
| 6 | + </n-icon> | ||
| 7 | + <span | ||
| 8 | + :style="{ | ||
| 9 | + 'font-weight': 200, | ||
| 10 | + color: backColor, | ||
| 11 | + 'font-size': `${backSize}px` | ||
| 12 | + }" | ||
| 13 | + > | ||
| 14 | + 返回上级 | ||
| 15 | + </span> | ||
| 16 | + </div> | ||
| 17 | + <v-chart | ||
| 18 | + ref="vChartRef" | ||
| 19 | + :init-options="initOptions" | ||
| 20 | + :theme="themeColor" | ||
| 21 | + :option="option.value" | ||
| 22 | + :manual-update="isPreview()" | ||
| 23 | + autoresize | ||
| 24 | + @click="chartPEvents" | ||
| 25 | + > | ||
| 26 | + </v-chart> | ||
| 27 | + </div> | ||
| 4 | </template> | 28 | </template> |
| 5 | 29 | ||
| 6 | <script setup lang="ts"> | 30 | <script setup lang="ts"> |
| 7 | -import { PropType, reactive, watch, ref, nextTick } from 'vue' | 31 | +import { PropType, reactive, watch, ref, nextTick, toRefs } from 'vue' |
| 8 | import config, { includes } from './config' | 32 | import config, { includes } from './config' |
| 9 | import VChart from 'vue-echarts' | 33 | import VChart from 'vue-echarts' |
| 34 | +import { icon } from '@/plugins' | ||
| 10 | import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | 35 | import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' |
| 11 | import { use, registerMap } from 'echarts/core' | 36 | import { use, registerMap } from 'echarts/core' |
| 12 | import { EffectScatterChart, MapChart } from 'echarts/charts' | 37 | import { EffectScatterChart, MapChart } from 'echarts/charts' |
| @@ -16,6 +41,7 @@ import { mergeTheme, setOption } from '@/packages/public/chart' | @@ -16,6 +41,7 @@ import { mergeTheme, setOption } from '@/packages/public/chart' | ||
| 16 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | 41 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 17 | import { isPreview } from '@/utils' | 42 | import { isPreview } from '@/utils' |
| 18 | import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json' | 43 | import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json' |
| 44 | +import mapChinaJson from './mapGeojson/china.json' | ||
| 19 | import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components' | 45 | import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components' |
| 20 | 46 | ||
| 21 | const props = defineProps({ | 47 | const props = defineProps({ |
| @@ -33,6 +59,10 @@ const props = defineProps({ | @@ -33,6 +59,10 @@ const props = defineProps({ | ||
| 33 | } | 59 | } |
| 34 | }) | 60 | }) |
| 35 | 61 | ||
| 62 | +const { ArrowBackIcon } = icon.ionicons5 | ||
| 63 | +let levelHistory: any = ref([]) | ||
| 64 | + | ||
| 65 | +const { backColor, backSize, enter } = toRefs(props.chartConfig.option.mapRegion) | ||
| 36 | const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | 66 | const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) |
| 37 | 67 | ||
| 38 | use([ | 68 | use([ |
| @@ -67,7 +97,7 @@ registerMap(`${props.chartConfig.option.mapRegion.adcode}`, { geoJSON: {} as any | @@ -67,7 +97,7 @@ registerMap(`${props.chartConfig.option.mapRegion.adcode}`, { geoJSON: {} as any | ||
| 67 | // 进行更换初始化地图 如果为china 单独处理 | 97 | // 进行更换初始化地图 如果为china 单独处理 |
| 68 | const registerMapInitAsync = async () => { | 98 | const registerMapInitAsync = async () => { |
| 69 | await nextTick() | 99 | await nextTick() |
| 70 | - const adCode = `${props.chartConfig.option.mapRegion.adcode}`; | 100 | + const adCode = `${props.chartConfig.option.mapRegion.adcode}` |
| 71 | if (adCode !== 'china') { | 101 | if (adCode !== 'china') { |
| 72 | await getGeojson(adCode) | 102 | await getGeojson(adCode) |
| 73 | } else { | 103 | } else { |
| @@ -87,7 +117,16 @@ const vEchartsSetOption = () => { | @@ -87,7 +117,16 @@ const vEchartsSetOption = () => { | ||
| 87 | const dataSetHandle = async (dataset: any) => { | 117 | const dataSetHandle = async (dataset: any) => { |
| 88 | props.chartConfig.option.series.forEach((item: any) => { | 118 | props.chartConfig.option.series.forEach((item: any) => { |
| 89 | if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point | 119 | if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point |
| 90 | - else if (item.type === 'map' && dataset.map) item.data = dataset.map | 120 | + else if (item.type === 'lines' && dataset.line) { |
| 121 | + item.data = dataset.line.map((it: any) => { | ||
| 122 | + return { | ||
| 123 | + ...it, | ||
| 124 | + lineStyle: { | ||
| 125 | + color: props.chartConfig.option.series[2].lineStyle.normal.color | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + }) | ||
| 129 | + } else if (item.type === 'map' && dataset.map) item.data = dataset.map | ||
| 91 | }) | 130 | }) |
| 92 | if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces | 131 | if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces |
| 93 | 132 | ||
| @@ -101,6 +140,45 @@ const hainanLandsHandle = async (newData: boolean) => { | @@ -101,6 +140,45 @@ const hainanLandsHandle = async (newData: boolean) => { | ||
| 101 | registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} }) | 140 | registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} }) |
| 102 | } | 141 | } |
| 103 | } | 142 | } |
| 143 | + | ||
| 144 | +// 点击区域 | ||
| 145 | +const chartPEvents = (e: any) => { | ||
| 146 | + if (e.seriesType !== 'map') return | ||
| 147 | + if (!props.chartConfig.option.mapRegion.enter) { | ||
| 148 | + return | ||
| 149 | + } | ||
| 150 | + mapChinaJson.features.forEach(item => { | ||
| 151 | + var pattern = new RegExp(e.name) | ||
| 152 | + if (pattern.test(item.properties.name)) { | ||
| 153 | + let code = String(item.properties.adcode) | ||
| 154 | + levelHistory.value.push(code) | ||
| 155 | + checkOrMap(code) | ||
| 156 | + } | ||
| 157 | + }) | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +// 返回上一级 | ||
| 161 | +const backLevel = () => { | ||
| 162 | + levelHistory.value = [] | ||
| 163 | + if (levelHistory.value.length > 1) { | ||
| 164 | + levelHistory.value.pop() | ||
| 165 | + const code = levelHistory[levelHistory.value.length - 1] | ||
| 166 | + checkOrMap(code) | ||
| 167 | + } else { | ||
| 168 | + checkOrMap('china') | ||
| 169 | + } | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +// 切换地图 | ||
| 173 | +const checkOrMap = async (newData: string) => { | ||
| 174 | + await getGeojson(newData) | ||
| 175 | + props.chartConfig.option.geo.map = newData | ||
| 176 | + props.chartConfig.option.series.forEach((item: any) => { | ||
| 177 | + if (item.type === 'map') item.map = newData | ||
| 178 | + }) | ||
| 179 | + vEchartsSetOption() | ||
| 180 | +} | ||
| 181 | + | ||
| 104 | //监听 dataset 数据发生变化 | 182 | //监听 dataset 数据发生变化 |
| 105 | watch( | 183 | watch( |
| 106 | () => props.chartConfig.option.dataset, | 184 | () => props.chartConfig.option.dataset, |
| @@ -113,33 +191,42 @@ watch( | @@ -113,33 +191,42 @@ watch( | ||
| 113 | } | 191 | } |
| 114 | ) | 192 | ) |
| 115 | 193 | ||
| 116 | -//监听是否显示南海群岛 | ||
| 117 | -watch( | ||
| 118 | - () => props.chartConfig.option.mapRegion.showHainanIsLands, | ||
| 119 | - async newData => { | ||
| 120 | - try { | ||
| 121 | - await hainanLandsHandle(newData) | ||
| 122 | - vEchartsSetOption() | ||
| 123 | - } catch (error) { | ||
| 124 | - console.log(error) | 194 | +// 监听线的颜色 |
| 195 | +if (props.chartConfig.option.series[2] && !isPreview()) { | ||
| 196 | + watch( | ||
| 197 | + () => props.chartConfig.option.series[2].lineStyle.normal.color, | ||
| 198 | + () => { | ||
| 199 | + dataSetHandle(props.chartConfig.option.dataset) | ||
| 200 | + }, | ||
| 201 | + { | ||
| 202 | + deep: false | ||
| 125 | } | 203 | } |
| 126 | - }, | ||
| 127 | - { | ||
| 128 | - deep: false | ||
| 129 | - } | ||
| 130 | -) | 204 | + ) |
| 205 | +} | ||
| 131 | 206 | ||
| 207 | +//监听是否显示南海群岛 | ||
| 208 | +if (!isPreview()) { | ||
| 209 | + watch( | ||
| 210 | + () => props.chartConfig.option.mapRegion.showHainanIsLands, | ||
| 211 | + async newData => { | ||
| 212 | + try { | ||
| 213 | + await hainanLandsHandle(newData) | ||
| 214 | + vEchartsSetOption() | ||
| 215 | + } catch (error) { | ||
| 216 | + console.log(error) | ||
| 217 | + } | ||
| 218 | + }, | ||
| 219 | + { | ||
| 220 | + deep: false | ||
| 221 | + } | ||
| 222 | + ) | ||
| 223 | +} | ||
| 132 | //监听地图展示区域发生变化 | 224 | //监听地图展示区域发生变化 |
| 133 | watch( | 225 | watch( |
| 134 | () => `${props.chartConfig.option.mapRegion.adcode}`, | 226 | () => `${props.chartConfig.option.mapRegion.adcode}`, |
| 135 | - async newData => { | 227 | + newData => { |
| 136 | try { | 228 | try { |
| 137 | - await getGeojson(newData) | ||
| 138 | - props.chartConfig.option.geo.map = newData | ||
| 139 | - props.chartConfig.option.series.forEach((item: any) => { | ||
| 140 | - if (item.type === 'map') item.map = newData | ||
| 141 | - }) | ||
| 142 | - vEchartsSetOption() | 229 | + checkOrMap(newData) |
| 143 | } catch (error) { | 230 | } catch (error) { |
| 144 | console.log(error) | 231 | console.log(error) |
| 145 | } | 232 | } |
| @@ -154,3 +241,16 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | @@ -154,3 +241,16 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | ||
| 154 | dataSetHandle(newData) | 241 | dataSetHandle(newData) |
| 155 | }) | 242 | }) |
| 156 | </script> | 243 | </script> |
| 244 | + | ||
| 245 | +<style scope lang="scss"> | ||
| 246 | +.back-icon { | ||
| 247 | + z-index: 50; | ||
| 248 | + cursor: pointer; | ||
| 249 | + position: absolute; | ||
| 250 | + display: flex; | ||
| 251 | + align-items: center; | ||
| 252 | + top: 0; | ||
| 253 | + left: 0; | ||
| 254 | + gap: 2px; | ||
| 255 | +} | ||
| 256 | +</style> |
| @@ -9,7 +9,8 @@ export const includes = [] | @@ -9,7 +9,8 @@ export const includes = [] | ||
| 9 | // 关系图布局 | 9 | // 关系图布局 |
| 10 | export const GraphLayout = [ | 10 | export const GraphLayout = [ |
| 11 | { label: '无', value: 'none' }, | 11 | { label: '无', value: 'none' }, |
| 12 | - { label: '环形', value: 'circular' } | 12 | + { label: '环形', value: 'circular' }, |
| 13 | + { label: '力引导', value: 'force' } | ||
| 13 | ] | 14 | ] |
| 14 | 15 | ||
| 15 | // 标签开关 | 16 | // 标签开关 |
| @@ -24,7 +25,13 @@ export const LabelPosition = [ | @@ -24,7 +25,13 @@ export const LabelPosition = [ | ||
| 24 | { label: '右侧', value: 'right' }, | 25 | { label: '右侧', value: 'right' }, |
| 25 | { label: '顶部', value: 'top' }, | 26 | { label: '顶部', value: 'top' }, |
| 26 | { label: '底部', value: 'bottom' }, | 27 | { label: '底部', value: 'bottom' }, |
| 27 | - { label: '内部', value: 'inside' }, | 28 | + { label: '内部', value: 'inside' } |
| 29 | +] | ||
| 30 | + | ||
| 31 | +// 图-迭代动画 | ||
| 32 | +export const LayoutAnimation = [ | ||
| 33 | + { label: '开启', value: 1 }, | ||
| 34 | + { label: '关闭', value: 0 } | ||
| 28 | ] | 35 | ] |
| 29 | 36 | ||
| 30 | export const option = { | 37 | export const option = { |
| @@ -33,11 +40,11 @@ export const option = { | @@ -33,11 +40,11 @@ export const option = { | ||
| 33 | legend:{ | 40 | legend:{ |
| 34 | show:true, | 41 | show:true, |
| 35 | textStyle:{ | 42 | textStyle:{ |
| 36 | - color:"#eee", | ||
| 37 | - fontSize: 14 , | 43 | + color: '#eee', |
| 44 | + fontSize: 14 | ||
| 38 | }, | 45 | }, |
| 39 | data: dataJson.categories.map(function (a) { | 46 | data: dataJson.categories.map(function (a) { |
| 40 | - return a.name; | 47 | + return a.name |
| 41 | }) | 48 | }) |
| 42 | }, | 49 | }, |
| 43 | series: [ | 50 | series: [ |
| @@ -47,7 +54,7 @@ export const option = { | @@ -47,7 +54,7 @@ export const option = { | ||
| 47 | data: dataJson.nodes, | 54 | data: dataJson.nodes, |
| 48 | links: dataJson.links, | 55 | links: dataJson.links, |
| 49 | categories: dataJson.categories, | 56 | categories: dataJson.categories, |
| 50 | - label: { // 标签 | 57 | + label: { |
| 51 | show: 1, | 58 | show: 1, |
| 52 | position: 'right', | 59 | position: 'right', |
| 53 | formatter: '{b}' | 60 | formatter: '{b}' |
| @@ -58,10 +65,17 @@ export const option = { | @@ -58,10 +65,17 @@ export const option = { | ||
| 58 | lineStyle: { | 65 | lineStyle: { |
| 59 | color: 'source', // 线条颜色 | 66 | color: 'source', // 线条颜色 |
| 60 | curveness: 0.2 // 线条卷曲程度 | 67 | curveness: 0.2 // 线条卷曲程度 |
| 68 | + }, | ||
| 69 | + force: { | ||
| 70 | + repulsion: 100, | ||
| 71 | + gravity: 0.1, | ||
| 72 | + edgeLength: 30, | ||
| 73 | + layoutAnimation: 1, | ||
| 74 | + friction: 0.6 | ||
| 61 | } | 75 | } |
| 62 | } | 76 | } |
| 63 | ] | 77 | ] |
| 64 | - }; | 78 | +} |
| 65 | 79 | ||
| 66 | export default class Config extends PublicConfigClass implements CreateComponentType { | 80 | export default class Config extends PublicConfigClass implements CreateComponentType { |
| 67 | public key = GraphConfig.key | 81 | public key = GraphConfig.key |
| @@ -35,7 +35,58 @@ | @@ -35,7 +35,58 @@ | ||
| 35 | ></n-color-picker> | 35 | ></n-color-picker> |
| 36 | </SettingItem> | 36 | </SettingItem> |
| 37 | <SettingItem name="文本"> | 37 | <SettingItem name="文本"> |
| 38 | - <n-input-number v-model:value="optionData.legend.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="文字大小"> | 38 | + <n-input-number |
| 39 | + v-model:value="optionData.legend.textStyle.fontSize" | ||
| 40 | + :min="0" | ||
| 41 | + :step="1" | ||
| 42 | + size="small" | ||
| 43 | + placeholder="文字大小" | ||
| 44 | + > | ||
| 45 | + </n-input-number> | ||
| 46 | + </SettingItem> | ||
| 47 | + </SettingItemBox> | ||
| 48 | + <SettingItemBox name="力引导" v-if="optionData.series[0].force && graphConfig.layout == 'force'"> | ||
| 49 | + <SettingItem name="斥力因子" v-if="optionData.series[0].force.repulsion"> | ||
| 50 | + <n-input-number | ||
| 51 | + v-model:value="optionData.series[0].force.repulsion" | ||
| 52 | + :min="0" | ||
| 53 | + :step="1" | ||
| 54 | + size="small" | ||
| 55 | + placeholder="斥力因子大小" | ||
| 56 | + > | ||
| 57 | + </n-input-number> | ||
| 58 | + </SettingItem> | ||
| 59 | + <SettingItem name="引力因子" v-if="optionData.series[0].force.gravity"> | ||
| 60 | + <n-input-number | ||
| 61 | + v-model:value="optionData.series[0].force.gravity" | ||
| 62 | + :min="0" | ||
| 63 | + :step="0.1" | ||
| 64 | + size="small" | ||
| 65 | + placeholder="引力因子" | ||
| 66 | + > | ||
| 67 | + </n-input-number> | ||
| 68 | + </SettingItem> | ||
| 69 | + <SettingItem name="节点距离"> | ||
| 70 | + <n-input-number | ||
| 71 | + v-model:value="optionData.series[0].force.edgeLength" | ||
| 72 | + :min="0" | ||
| 73 | + :step="1" | ||
| 74 | + size="small" | ||
| 75 | + placeholder="节点距离" | ||
| 76 | + > | ||
| 77 | + </n-input-number> | ||
| 78 | + </SettingItem> | ||
| 79 | + <SettingItem name="迭代动画"> | ||
| 80 | + <n-select v-model:value="graphConfig.force.layoutAnimation" :options="LayoutAnimation" size="small" /> | ||
| 81 | + </SettingItem> | ||
| 82 | + <SettingItem name="节点速度"> | ||
| 83 | + <n-input-number | ||
| 84 | + v-model:value="optionData.series[0].force.friction" | ||
| 85 | + :min="0" | ||
| 86 | + :step="0.1" | ||
| 87 | + size="small" | ||
| 88 | + placeholder="节点速度" | ||
| 89 | + > | ||
| 39 | </n-input-number> | 90 | </n-input-number> |
| 40 | </SettingItem> | 91 | </SettingItem> |
| 41 | </SettingItemBox> | 92 | </SettingItemBox> |
| @@ -46,7 +97,7 @@ | @@ -46,7 +97,7 @@ | ||
| 46 | <script setup lang="ts"> | 97 | <script setup lang="ts"> |
| 47 | import { PropType, computed } from 'vue' | 98 | import { PropType, computed } from 'vue' |
| 48 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | 99 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 49 | -import { option, GraphLayout, LabelSwitch, LabelPosition } from './config' | 100 | +import { option, GraphLayout, LabelSwitch, LabelPosition, LayoutAnimation } from './config' |
| 50 | import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | 101 | import { GlobalThemeJsonType } from '@/settings/chartThemes/index' |
| 51 | 102 | ||
| 52 | const props = defineProps({ | 103 | const props = defineProps({ |
| @@ -56,7 +107,7 @@ const props = defineProps({ | @@ -56,7 +107,7 @@ const props = defineProps({ | ||
| 56 | } | 107 | } |
| 57 | }) | 108 | }) |
| 58 | 109 | ||
| 59 | -const graphConfig = computed<typeof option.series[0]>(() => { | 110 | +const graphConfig = computed<(typeof option.series)[0]>(() => { |
| 60 | return props.optionData.series[0] | 111 | return props.optionData.series[0] |
| 61 | }) | 112 | }) |
| 62 | </script> | 113 | </script> |
| 1 | <template> | 1 | <template> |
| 2 | - <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()" autoresize></v-chart> | 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> | ||
| 3 | </template> | 10 | </template> |
| 4 | 11 | ||
| 5 | <script setup lang="ts"> | 12 | <script setup lang="ts"> |
| @@ -8,4 +8,14 @@ import { DialConfig } from './Dial/index' | @@ -8,4 +8,14 @@ import { DialConfig } from './Dial/index' | ||
| 8 | import { SankeyConfig } from './Sankey/index' | 8 | import { SankeyConfig } from './Sankey/index' |
| 9 | import { GraphConfig } from './Graph/index' | 9 | import { GraphConfig } from './Graph/index' |
| 10 | 10 | ||
| 11 | -export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, WaterPoloConfig, TreeMapConfig, GraphConfig, SankeyConfig, DialConfig] | 11 | +export default [ |
| 12 | + ProcessConfig, | ||
| 13 | + RadarConfig, | ||
| 14 | + FunnelConfig, | ||
| 15 | + HeatmapConfig, | ||
| 16 | + WaterPoloConfig, | ||
| 17 | + TreeMapConfig, | ||
| 18 | + GraphConfig, | ||
| 19 | + SankeyConfig, | ||
| 20 | + DialConfig | ||
| 21 | +] |
| @@ -33,8 +33,6 @@ const option = shallowReactive({ | @@ -33,8 +33,6 @@ const option = shallowReactive({ | ||
| 33 | dataset: '' | 33 | dataset: '' |
| 34 | }) | 34 | }) |
| 35 | 35 | ||
| 36 | - | ||
| 37 | - | ||
| 38 | const getStyle = (radius: number) => { | 36 | const getStyle = (radius: number) => { |
| 39 | return { | 37 | return { |
| 40 | borderRadius: `${radius}px`, | 38 | borderRadius: `${radius}px`, |
| @@ -78,7 +76,6 @@ watch( | @@ -78,7 +76,6 @@ watch( | ||
| 78 | return | 76 | return |
| 79 | } | 77 | } |
| 80 | option.dataset = newData | 78 | option.dataset = newData |
| 81 | - | ||
| 82 | }, | 79 | }, |
| 83 | { | 80 | { |
| 84 | immediate: true | 81 | immediate: true |
| 1 | import { ChartFrameEnum, PackagesCategoryEnum } from '@/packages/index.d' | 1 | import { ChartFrameEnum, PackagesCategoryEnum } from '@/packages/index.d' |
| 2 | import { ImageConfig } from '@/packages/components/Informations/Mores/Image/index' | 2 | import { ImageConfig } from '@/packages/components/Informations/Mores/Image/index' |
| 3 | import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d' | 3 | import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d' |
| 4 | +import carousel1 from '@/assets/images/chart/photos/carousel1.jpeg' | ||
| 5 | +import carousel2 from '@/assets/images/chart/photos/carousel2.jpeg' | ||
| 4 | 6 | ||
| 5 | // 远程共享库(调接口获取图像列表) | 7 | // 远程共享库(调接口获取图像列表) |
| 6 | const imageList = [ | 8 | const imageList = [ |
| 7 | - { imageName: 'carousel1', imageUrl: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, | ||
| 8 | - { imageName: 'carousel2', imageUrl: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg' } | 9 | + { imageName: 'carousel1', imageUrl: 'carousel1.jpeg' }, |
| 10 | + { imageName: 'carousel2', imageUrl: 'carousel2.jpeg' } | ||
| 9 | ] | 11 | ] |
| 10 | 12 | ||
| 11 | const photoConfigList = imageList.map(i => ({ | 13 | const photoConfigList = imageList.map(i => ({ |
| @@ -15,7 +17,7 @@ const photoConfigList = imageList.map(i => ({ | @@ -15,7 +17,7 @@ const photoConfigList = imageList.map(i => ({ | ||
| 15 | package: PackagesCategoryEnum.PHOTOS, | 17 | package: PackagesCategoryEnum.PHOTOS, |
| 16 | chartFrame: ChartFrameEnum.STATIC, | 18 | chartFrame: ChartFrameEnum.STATIC, |
| 17 | image: i.imageUrl, | 19 | image: i.imageUrl, |
| 18 | - dataset: i.imageUrl, | 20 | + dataset: i.imageName==='carousel1' ? carousel1 : carousel2, |
| 19 | title: i.imageName, | 21 | title: i.imageName, |
| 20 | redirectComponent: `${ImageConfig.package}/${ImageConfig.category}/${ImageConfig.key}` // 跳转组件路径规则:packageName/categoryName/componentKey | 22 | redirectComponent: `${ImageConfig.package}/${ImageConfig.category}/${ImageConfig.key}` // 跳转组件路径规则:packageName/categoryName/componentKey |
| 21 | })) | 23 | })) |
| @@ -235,6 +235,7 @@ const updateVChart = async (newData: SocketReceiveMessageType) => { | @@ -235,6 +235,7 @@ const updateVChart = async (newData: SocketReceiveMessageType) => { | ||
| 235 | } | 235 | } |
| 236 | 236 | ||
| 237 | const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any, targetComponent: any) => { | 237 | const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any, targetComponent: any) => { |
| 238 | + props.chartConfig.option.queryCondition.timeRange=[targetComponent.requestParams.Params.startTs,targetComponent.requestParams.Params.endTs] | ||
| 238 | //联动支持分组 | 239 | //联动支持分组 |
| 239 | /** | 240 | /** |
| 240 | * 修复多个分组,然后下拉框联动,会影响另一个组件 | 241 | * 修复多个分组,然后下拉框联动,会影响另一个组件 |
| @@ -14,6 +14,26 @@ export const enum areaEnum { | @@ -14,6 +14,26 @@ export const enum areaEnum { | ||
| 14 | TOWN = 'TOWN' //镇 | 14 | TOWN = 'TOWN' //镇 |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | +//父级地区编码和级别接口 | ||
| 18 | +export interface HistoryParentType { | ||
| 19 | + adcode: string | number | ||
| 20 | + level: string | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +//数据源接口 | ||
| 24 | +export interface dataPointI { | ||
| 25 | + name: string | ||
| 26 | + value: number[] | ||
| 27 | + adcode: number | ||
| 28 | + height: number | ||
| 29 | + itemStyle: { | ||
| 30 | + color: string | ||
| 31 | + opacity: number | ||
| 32 | + borderWidth: number | ||
| 33 | + borderColor: string | ||
| 34 | + } | ||
| 35 | +} | ||
| 36 | + | ||
| 17 | export const includes = [] | 37 | export const includes = [] |
| 18 | 38 | ||
| 19 | export const option = { | 39 | export const option = { |
| @@ -23,6 +43,9 @@ export const option = { | @@ -23,6 +43,9 @@ export const option = { | ||
| 23 | iconDistanceTop: 20, | 43 | iconDistanceTop: 20, |
| 24 | drillingIn: false, | 44 | drillingIn: false, |
| 25 | dataset: dataMaps, | 45 | dataset: dataMaps, |
| 46 | + saveClickRegion: { | ||
| 47 | + level: '' | ||
| 48 | + }, | ||
| 26 | mapRegion: { | 49 | mapRegion: { |
| 27 | adcode: 'china', | 50 | adcode: 'china', |
| 28 | showHainanIsLands: true, | 51 | showHainanIsLands: true, |
| @@ -41,6 +64,9 @@ export const option = { | @@ -41,6 +64,9 @@ export const option = { | ||
| 41 | emphasis: { | 64 | emphasis: { |
| 42 | label: { | 65 | label: { |
| 43 | show: true, | 66 | show: true, |
| 67 | + formatter: function (params: Recordable) { | ||
| 68 | + return params.data.name ? params.data.name : ' ' | ||
| 69 | + }, | ||
| 44 | textStyle: { | 70 | textStyle: { |
| 45 | color: '#000', | 71 | color: '#000', |
| 46 | fontSize: 14 | 72 | fontSize: 14 |
| @@ -59,6 +85,9 @@ export const option = { | @@ -59,6 +85,9 @@ export const option = { | ||
| 59 | regionHeight: 3, | 85 | regionHeight: 3, |
| 60 | label: { | 86 | label: { |
| 61 | show: true, | 87 | show: true, |
| 88 | + formatter: function (params: Recordable) { | ||
| 89 | + return params.data.name ? params.data.name : ' ' | ||
| 90 | + }, | ||
| 62 | textStyle: { | 91 | textStyle: { |
| 63 | color: '#fff', | 92 | color: '#fff', |
| 64 | fontSize: 14 | 93 | fontSize: 14 |
| @@ -12,7 +12,7 @@ import { onMounted, ref, nextTick, PropType, toRefs, watch, reactive } from 'vue | @@ -12,7 +12,7 @@ import { onMounted, ref, nextTick, PropType, toRefs, watch, reactive } from 'vue | ||
| 12 | import * as echarts from 'echarts' | 12 | import * as echarts from 'echarts' |
| 13 | import { registerMap } from 'echarts/core' | 13 | import { registerMap } from 'echarts/core' |
| 14 | import 'echarts-gl' | 14 | import 'echarts-gl' |
| 15 | -import config, { areaEnum } from './config' | 15 | +import config, { areaEnum, dataPointI, HistoryParentType } from './config' |
| 16 | import { getGeoJsonMap } from '@/api/external/common' | 16 | import { getGeoJsonMap } from '@/api/external/common' |
| 17 | import dataMaps from './data.json' | 17 | import dataMaps from './data.json' |
| 18 | 18 | ||
| @@ -41,7 +41,7 @@ const toolBoxOption = ref({ | @@ -41,7 +41,7 @@ const toolBoxOption = ref({ | ||
| 41 | top: 20, | 41 | top: 20, |
| 42 | feature: { | 42 | feature: { |
| 43 | myFullButton: { | 43 | myFullButton: { |
| 44 | - show: true, | 44 | + show: false, |
| 45 | title: '返回', | 45 | title: '返回', |
| 46 | icon: iconStr.value, | 46 | icon: iconStr.value, |
| 47 | iconStyle: { | 47 | iconStyle: { |
| @@ -55,8 +55,34 @@ const toolBoxOption = ref({ | @@ -55,8 +55,34 @@ const toolBoxOption = ref({ | ||
| 55 | watch( | 55 | watch( |
| 56 | () => props.chartConfig.option, | 56 | () => props.chartConfig.option, |
| 57 | newData => { | 57 | newData => { |
| 58 | - const { iconColor, drillingIn, iconDistanceRight, iconDistanceTop } = newData | ||
| 59 | - toolBoxOption.value.feature.myFullButton.show = drillingIn | 58 | + const { iconColor, drillingIn, iconDistanceRight, iconDistanceTop, mapRegion } = newData |
| 59 | + if (drillingIn && !newData.saveClickRegion.level) { | ||
| 60 | + toolBoxOption.value.feature.myFullButton.show = !drillingIn | ||
| 61 | + } else if ( | ||
| 62 | + drillingIn && | ||
| 63 | + newData.saveClickRegion.level === areaEnum.PROVINCE && | ||
| 64 | + mapRegion.saveSelect.levelStr === areaEnum.COUNTRY | ||
| 65 | + ) { | ||
| 66 | + toolBoxOption.value.feature.myFullButton.show = drillingIn | ||
| 67 | + } else if ( | ||
| 68 | + drillingIn && | ||
| 69 | + newData.saveClickRegion.level === areaEnum.COUNTRY && | ||
| 70 | + mapRegion.saveSelect.levelStr === areaEnum.COUNTRY | ||
| 71 | + ) { | ||
| 72 | + toolBoxOption.value.feature.myFullButton.show = !drillingIn | ||
| 73 | + } else if ( | ||
| 74 | + drillingIn && | ||
| 75 | + newData.saveClickRegion.level === areaEnum.CITY && | ||
| 76 | + mapRegion.saveSelect.levelStr === areaEnum.PROVINCE | ||
| 77 | + ) { | ||
| 78 | + toolBoxOption.value.feature.myFullButton.show = drillingIn | ||
| 79 | + } else if ( | ||
| 80 | + drillingIn && | ||
| 81 | + newData.saveClickRegion.level === areaEnum.PROVINCE && | ||
| 82 | + mapRegion.saveSelect.levelStr === areaEnum.PROVINCE | ||
| 83 | + ) { | ||
| 84 | + toolBoxOption.value.feature.myFullButton.show = !drillingIn | ||
| 85 | + } | ||
| 60 | toolBoxOption.value.feature.myFullButton.iconStyle.color = iconColor | 86 | toolBoxOption.value.feature.myFullButton.iconStyle.color = iconColor |
| 61 | toolBoxOption.value.right = iconDistanceRight | 87 | toolBoxOption.value.right = iconDistanceRight |
| 62 | toolBoxOption.value.top = iconDistanceTop | 88 | toolBoxOption.value.top = iconDistanceTop |
| @@ -73,28 +99,27 @@ props.chartConfig.option = { | @@ -73,28 +99,27 @@ props.chartConfig.option = { | ||
| 73 | 99 | ||
| 74 | //地图点击返回 | 100 | //地图点击返回 |
| 75 | const watchAdcode = async () => { | 101 | const watchAdcode = async () => { |
| 102 | + stopWatch() | ||
| 76 | if (props.chartConfig.option.drillingIn) { | 103 | if (props.chartConfig.option.drillingIn) { |
| 77 | //如果是从右边配置里设置的,比如点击四川省,然后点击返回 | 104 | //如果是从右边配置里设置的,比如点击四川省,然后点击返回 |
| 78 | const savePopParent = saveHistoryParent.value.pop() | 105 | const savePopParent = saveHistoryParent.value.pop() |
| 79 | - let saveAdcode: any = savePopParent?.adcode | ||
| 80 | - saveLevelStr.level = savePopParent?.level as string | 106 | + let saveAdcode = savePopParent?.adcode as string | number |
| 107 | + saveLevelStr.level = savePopParent?.level | ||
| 81 | if (!savePopParent) { | 108 | if (!savePopParent) { |
| 82 | saveAdcode = getParentAdcode(props.chartConfig.option.mapRegion.adcode) | 109 | saveAdcode = getParentAdcode(props.chartConfig.option.mapRegion.adcode) |
| 83 | - saveLevelStr.level = (regionMapParentArea as any)[props.chartConfig.option.mapRegion.saveSelect.levelStr] | 110 | + saveLevelStr.level = (regionMapParentArea as Recordable)[props.chartConfig.option.mapRegion.saveSelect.levelStr] |
| 84 | } | 111 | } |
| 85 | if (saveAdcode === 0) { | 112 | if (saveAdcode === 0) { |
| 86 | saveAdcode = 'china' | 113 | saveAdcode = 'china' |
| 87 | saveLevelStr.level = 'COUNTRY' | 114 | saveLevelStr.level = 'COUNTRY' |
| 88 | } | 115 | } |
| 89 | - await getGeojson(saveAdcode) | 116 | + const exist = await getGeojson(saveAdcode) |
| 90 | const adcode = saveAdcode === 100000 ? 'china' : saveAdcode | 117 | 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) | 118 | + props.chartConfig.option.saveClickRegion.level = saveLevelStr.level |
| 119 | + if (exist) { | ||
| 120 | + //fix解决点击下钻返回后页面为空问题 | ||
| 121 | + props.chartConfig.option.mapRegion.adcode = adcode | ||
| 122 | + } | ||
| 98 | } | 123 | } |
| 99 | } | 124 | } |
| 100 | 125 | ||
| @@ -106,46 +131,41 @@ const regionMapParentArea = { | @@ -106,46 +131,41 @@ const regionMapParentArea = { | ||
| 106 | } | 131 | } |
| 107 | 132 | ||
| 108 | //地图点击 | 133 | //地图点击 |
| 109 | -const handleMap3DClick = async (params: any) => { | 134 | +const handleMap3DClick = async (params: Recordable) => { |
| 110 | if (props.chartConfig.option.drillingIn) { | 135 | if (props.chartConfig.option.drillingIn) { |
| 111 | const { name } = params | 136 | const { name } = params |
| 112 | - saveGeojson.value?.features.forEach((item: any) => { | 137 | + saveGeojson.value?.features.forEach((item: Recordable) => { |
| 113 | if (item.properties.name === name) { | 138 | if (item.properties.name === name) { |
| 114 | const level = item.properties.level.toUpperCase() | 139 | const level = item.properties.level.toUpperCase() |
| 115 | const adcode = item.properties.adcode | 140 | const adcode = item.properties.adcode |
| 116 | if (level === 'DISTRICT') return | 141 | if (level === 'DISTRICT') return |
| 117 | - if(String(adcode).startsWith('15') && level===areaEnum.CITY) return | 142 | + if (String(adcode).startsWith('15') && level === areaEnum.CITY) return |
| 118 | props.chartConfig.option.mapRegion.adcode = adcode | 143 | props.chartConfig.option.mapRegion.adcode = adcode |
| 144 | + props.chartConfig.option.saveClickRegion.level = level | ||
| 119 | saveLevelStr.level = level | 145 | saveLevelStr.level = level |
| 120 | handleDataPoint(adcode) | 146 | handleDataPoint(adcode) |
| 121 | saveHistoryParent.value.push({ | 147 | saveHistoryParent.value.push({ |
| 122 | adcode: item.properties.parent.adcode, | 148 | adcode: item.properties.parent.adcode, |
| 123 | - level: (regionMapParentArea as any)[level] | 149 | + level: (regionMapParentArea as Recordable)[level] |
| 124 | }) | 150 | }) |
| 125 | } | 151 | } |
| 126 | }) | 152 | }) |
| 127 | } | 153 | } |
| 128 | } | 154 | } |
| 129 | 155 | ||
| 130 | -const saveGeojson: any = ref({}) // 保存geojson | 156 | +const saveGeojson: Recordable = ref({}) // 保存geojson |
| 131 | 157 | ||
| 132 | const chinaDefaultRegionId = ref(100000) //如果是china则adcode为100000 | 158 | const chinaDefaultRegionId = ref(100000) //如果是china则adcode为100000 |
| 133 | 159 | ||
| 134 | -const saveLevelStr = reactive({ | ||
| 135 | - // 地区级别 | ||
| 136 | - level: '' | 160 | +// 保存地区级别 |
| 161 | +const saveLevelStr = reactive<{ level: string | undefined }>({ | ||
| 162 | + level: '' | ||
| 137 | }) | 163 | }) |
| 138 | 164 | ||
| 139 | -//父级地区编码和级别接口 | ||
| 140 | -interface HistoryParentType { | ||
| 141 | - adcode: number | ||
| 142 | - level: string | ||
| 143 | -} | ||
| 144 | - | ||
| 145 | const saveHistoryParent = ref<HistoryParentType[]>([]) | 165 | const saveHistoryParent = ref<HistoryParentType[]>([]) |
| 146 | 166 | ||
| 147 | //动态注册地图 | 167 | //动态注册地图 |
| 148 | -const getGeojson = (regionId: any) => { | 168 | +const getGeojson = (regionId: number | string) => { |
| 149 | try { | 169 | try { |
| 150 | return new Promise<boolean>(resolve => { | 170 | return new Promise<boolean>(resolve => { |
| 151 | const { levelStr } = props.chartConfig.option.mapRegion.saveSelect //右侧配置项获取的行政级别 | 171 | const { levelStr } = props.chartConfig.option.mapRegion.saveSelect //右侧配置项获取的行政级别 |
| @@ -171,11 +191,10 @@ const getGeojson = (regionId: any) => { | @@ -171,11 +191,10 @@ const getGeojson = (regionId: any) => { | ||
| 171 | } | 191 | } |
| 172 | } | 192 | } |
| 173 | 193 | ||
| 174 | - | ||
| 175 | //传adcode 获取上级 | 194 | //传adcode 获取上级 |
| 176 | const getParentAdcode = (adcode: number) => { | 195 | const getParentAdcode = (adcode: number) => { |
| 177 | let adcodeNum = 100000 | 196 | let adcodeNum = 100000 |
| 178 | - saveGeojson.value?.features.forEach((item: any) => { | 197 | + saveGeojson.value?.features.forEach((item: Recordable) => { |
| 179 | if (item.properties.adcode === adcode) { | 198 | if (item.properties.adcode === adcode) { |
| 180 | adcodeNum = item.properties.parent.adcode | 199 | adcodeNum = item.properties.parent.adcode |
| 181 | } | 200 | } |
| @@ -200,13 +219,13 @@ const initMap = async () => { | @@ -200,13 +219,13 @@ const initMap = async () => { | ||
| 200 | await nextTick().then(() => { | 219 | await nextTick().then(() => { |
| 201 | handleSetOption(chartInstance.value, props.chartConfig.option) | 220 | handleSetOption(chartInstance.value, props.chartConfig.option) |
| 202 | }) | 221 | }) |
| 203 | - chartInstance.value.on('click', (e: any) => { | 222 | + chartInstance.value.on('click', (e: Recordable) => { |
| 204 | handleMap3DClick(e) | 223 | handleMap3DClick(e) |
| 205 | }) | 224 | }) |
| 206 | } | 225 | } |
| 207 | 226 | ||
| 208 | // 手动触发渲染 | 227 | // 手动触发渲染 |
| 209 | -const handleSetOption = (instance: any, option: any) => { | 228 | +const handleSetOption = (instance: any, option: Recordable) => { |
| 210 | if (!instance) return | 229 | if (!instance) return |
| 211 | try { | 230 | try { |
| 212 | instance.clear() | 231 | instance.clear() |
| @@ -221,22 +240,32 @@ onMounted(() => { | @@ -221,22 +240,32 @@ onMounted(() => { | ||
| 221 | }) | 240 | }) |
| 222 | 241 | ||
| 223 | //处理数据标点 | 242 | //处理数据标点 |
| 224 | -const handleDataPoint = (newData: any) => { | 243 | +const handleDataPoint = (newData: string | number) => { |
| 225 | if (newData === 'china') { | 244 | if (newData === 'china') { |
| 226 | props.chartConfig.option.dataset = dataMaps | 245 | props.chartConfig.option.dataset = dataMaps |
| 246 | + props.chartConfig.option.series.forEach((item: Recordable) => { | ||
| 247 | + if (item.type === 'scatter3D') { | ||
| 248 | + item.data = dataMaps | ||
| 249 | + } | ||
| 250 | + }) | ||
| 227 | } else { | 251 | } else { |
| 228 | - props.chartConfig.option.dataset = dataMaps.filter((item: any) => item.adcode === newData) | 252 | + props.chartConfig.option.dataset = dataMaps.filter((item: dataPointI) => item.adcode === newData) |
| 253 | + props.chartConfig.option.series.forEach((item: Recordable) => { | ||
| 254 | + if (item.type === 'scatter3D') { | ||
| 255 | + item.data = dataMaps.filter((item: dataPointI) => item.adcode === newData) | ||
| 256 | + } | ||
| 257 | + }) | ||
| 229 | } | 258 | } |
| 230 | } | 259 | } |
| 231 | 260 | ||
| 232 | //监听地图展示区域发生变化 | 261 | //监听地图展示区域发生变化 |
| 233 | watch( | 262 | watch( |
| 234 | () => `${props.chartConfig.option.mapRegion.adcode}`, | 263 | () => `${props.chartConfig.option.mapRegion.adcode}`, |
| 235 | - async (newData: any) => { | 264 | + async (newData: string | number) => { |
| 236 | try { | 265 | try { |
| 237 | await getGeojson(newData) | 266 | await getGeojson(newData) |
| 238 | props.chartConfig.option.geo3D.map = newData | 267 | props.chartConfig.option.geo3D.map = newData |
| 239 | - props.chartConfig.option.series.forEach((item: any) => { | 268 | + props.chartConfig.option.series.forEach((item: Recordable) => { |
| 240 | if (item.type === 'map3D') { | 269 | if (item.type === 'map3D') { |
| 241 | item.map = newData | 270 | item.map = newData |
| 242 | item.data = props.chartConfig.option.dataset | 271 | item.data = props.chartConfig.option.dataset |
| @@ -254,7 +283,7 @@ watch( | @@ -254,7 +283,7 @@ watch( | ||
| 254 | ) | 283 | ) |
| 255 | 284 | ||
| 256 | // 监听地图右侧配置项变化 | 285 | // 监听地图右侧配置项变化 |
| 257 | -watch( | 286 | +const stopWatch = watch( |
| 258 | props.chartConfig.option, | 287 | props.chartConfig.option, |
| 259 | async newData => { | 288 | async newData => { |
| 260 | try { | 289 | try { |
| @@ -273,7 +302,7 @@ watch( | @@ -273,7 +302,7 @@ watch( | ||
| 273 | () => props.chartConfig.option.dataset, | 302 | () => props.chartConfig.option.dataset, |
| 274 | newData => { | 303 | newData => { |
| 275 | try { | 304 | try { |
| 276 | - props.chartConfig.option.series.forEach((item: any) => { | 305 | + props.chartConfig.option.series.forEach((item: Recordable) => { |
| 277 | if (item.type === 'map3D') { | 306 | if (item.type === 'map3D') { |
| 278 | item.data = newData | 307 | item.data = newData |
| 279 | } | 308 | } |
src/packages/components/external/Charts/Maps/OverrideMapAmap/components/DeviceLatestTable.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <n-drawer display-directive="if" v-model:show="modelShow" :width="502" :placement="placement"> | ||
| 3 | + <n-drawer-content :title="drawerTitle" closable> | ||
| 4 | + <n-space vertical> | ||
| 5 | + <n-data-table size="small" :columns="dimensions" :data="source" :pagination="paginationReactive" /> | ||
| 6 | + </n-space> | ||
| 7 | + </n-drawer-content> | ||
| 8 | + </n-drawer> | ||
| 9 | +</template> | ||
| 10 | + | ||
| 11 | +<script lang="ts" setup> | ||
| 12 | +import { ref, reactive } from 'vue' | ||
| 13 | +import { NDataTable,NDrawer, NDrawerContent, NSpace } from 'naive-ui' | ||
| 14 | +import { dimensions } from '../config' | ||
| 15 | +import type { sourceInterface } from '../config' | ||
| 16 | +import dayjs from 'dayjs' | ||
| 17 | +import { Placement } from 'naive-ui/es/drawer/src/DrawerBodyWrapper' | ||
| 18 | + | ||
| 19 | +const placement = ref<Placement>('right') | ||
| 20 | + | ||
| 21 | +const modelShow =ref(false) | ||
| 22 | + | ||
| 23 | +const drawerTitle = ref('') | ||
| 24 | + | ||
| 25 | +const source =ref<sourceInterface[]>([]) | ||
| 26 | + | ||
| 27 | +const paginationReactive = reactive({ | ||
| 28 | + page: 1, | ||
| 29 | + pageSize: 10, | ||
| 30 | + showSizePicker: true, | ||
| 31 | + pageSizes: [10, 20], | ||
| 32 | + onChange: (page: number) => { | ||
| 33 | + paginationReactive.page = page | ||
| 34 | + }, | ||
| 35 | + onUpdatePageSize: (pageSize: number) => { | ||
| 36 | + paginationReactive.pageSize = pageSize | ||
| 37 | + paginationReactive.page = 1 | ||
| 38 | + } | ||
| 39 | + }) | ||
| 40 | + | ||
| 41 | +const setValue = (value: Recordable, attrs: Recordable[]) => { | ||
| 42 | + source.value = [] | ||
| 43 | + try { | ||
| 44 | + if (!value) return | ||
| 45 | + const deviceLatestList = Object.keys(value).map((item:string) => { | ||
| 46 | + return { | ||
| 47 | + key: item, | ||
| 48 | + value: value[item][0]?.value, | ||
| 49 | + lastUpdateTime: dayjs( value[item][0]?.ts)?.format('YYYY-MM-DD HH:mm:ss') | ||
| 50 | + } | ||
| 51 | + }) | ||
| 52 | + const list = deviceLatestList?.reduce((acc:sourceInterface[],curr: sourceInterface)=>{ | ||
| 53 | + const byKeyFindName = attrs?.find(item => item.identifier === curr.key)?.name | ||
| 54 | + curr.key = byKeyFindName | ||
| 55 | + acc.push({...curr}) | ||
| 56 | + return [...acc] | ||
| 57 | + },[]) | ||
| 58 | + source.value = list.filter(item=>Boolean(item.key)) | ||
| 59 | + }catch(e){ | ||
| 60 | + console.log(e) | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +const setDrawerTitle = (title:string) => drawerTitle.value = title | ||
| 65 | + | ||
| 66 | +const openDrawer = () => modelShow.value = true | ||
| 67 | + | ||
| 68 | +defineExpose({ | ||
| 69 | + setValue, | ||
| 70 | + openDrawer, | ||
| 71 | + setDrawerTitle | ||
| 72 | +}) | ||
| 73 | +</script> | ||
| 74 | + | ||
| 75 | +<style lang="scss" scoped></style> |
| @@ -129,7 +129,7 @@ const loadList = async () => { | @@ -129,7 +129,7 @@ const loadList = async () => { | ||
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | const handleSubmit = () => { | 131 | const handleSubmit = () => { |
| 132 | - searchParams.deviceProfileIds = [searchParams.deviceProfileIds] as any | 132 | + // searchParams.deviceProfileIds = [searchParams.deviceProfileIds] as any |
| 133 | emit('searchParams', searchPage, searchParams) | 133 | emit('searchParams', searchPage, searchParams) |
| 134 | handleCancel() | 134 | handleCancel() |
| 135 | } | 135 | } |
| @@ -4,6 +4,7 @@ import { OverrideMapAmapConfig } from './index' | @@ -4,6 +4,7 @@ import { OverrideMapAmapConfig } from './index' | ||
| 4 | import { chartInitConfig } from '@/settings/designSetting' | 4 | import { chartInitConfig } from '@/settings/designSetting' |
| 5 | import cloneDeep from 'lodash/cloneDeep' | 5 | import cloneDeep from 'lodash/cloneDeep' |
| 6 | import dataJson from './data.json' | 6 | import dataJson from './data.json' |
| 7 | +import defaultImg from '../../../../../../assets/external/marker/1.png' | ||
| 7 | 8 | ||
| 8 | export type dataExtraInfoType = typeof dataJson.markers[number]['extraInfo'] //data.json下的extraInfo类型 | 9 | export type dataExtraInfoType = typeof dataJson.markers[number]['extraInfo'] //data.json下的extraInfo类型 |
| 9 | 10 | ||
| @@ -12,7 +13,7 @@ export type dataJsonType = typeof dataJson //data.json类型 | @@ -12,7 +13,7 @@ export type dataJsonType = typeof dataJson //data.json类型 | ||
| 12 | export type dataJsonMarkersType = typeof dataJson.markers[number] //data.json markers类型 | 13 | export type dataJsonMarkersType = typeof dataJson.markers[number] //data.json markers类型 |
| 13 | 14 | ||
| 14 | //标注数据格式 | 15 | //标注数据格式 |
| 15 | -export const fileterDevice = (items: any) => { | 16 | +export const filterDevice = (items: any) => { |
| 16 | const values = items.reduce((acc: any, curr: any) => { | 17 | const values = items.reduce((acc: any, curr: any) => { |
| 17 | acc.push({ | 18 | acc.push({ |
| 18 | name: curr.alias, | 19 | name: curr.alias, |
| @@ -25,6 +26,7 @@ export const fileterDevice = (items: any) => { | @@ -25,6 +26,7 @@ export const fileterDevice = (items: any) => { | ||
| 25 | organizationDTO: curr.organizationDTO, | 26 | organizationDTO: curr.organizationDTO, |
| 26 | deviceState: curr.deviceState, | 27 | deviceState: curr.deviceState, |
| 27 | deviceProfile: curr.deviceProfile, | 28 | deviceProfile: curr.deviceProfile, |
| 29 | + deviceProfileId: curr.deviceProfileId, | ||
| 28 | deviceInfo: curr.deviceInfo | 30 | deviceInfo: curr.deviceInfo |
| 29 | } | 31 | } |
| 30 | }) | 32 | }) |
| @@ -35,6 +37,34 @@ export const fileterDevice = (items: any) => { | @@ -35,6 +37,34 @@ export const fileterDevice = (items: any) => { | ||
| 35 | } | 37 | } |
| 36 | } | 38 | } |
| 37 | 39 | ||
| 40 | +export interface devicePartInfoInterface{ | ||
| 41 | + tbDeviceId:string | ||
| 42 | + name:string | ||
| 43 | + alias:string | ||
| 44 | + deviceProfileId:string | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +export const dimensions= [ | ||
| 48 | + { | ||
| 49 | + "title": "键", | ||
| 50 | + "key": "key" | ||
| 51 | + }, | ||
| 52 | + { | ||
| 53 | + "title": "值", | ||
| 54 | + "key": "value" | ||
| 55 | + }, | ||
| 56 | + { | ||
| 57 | + "title": "最后更新时间", | ||
| 58 | + "key": "lastUpdateTime" | ||
| 59 | + } | ||
| 60 | +] | ||
| 61 | + | ||
| 62 | +export interface sourceInterface { | ||
| 63 | + key: string | ||
| 64 | + value: string | ||
| 65 | + lastUpdateTime: string | ||
| 66 | +} | ||
| 67 | + | ||
| 38 | export enum ThemeEnum { | 68 | export enum ThemeEnum { |
| 39 | NORMAL = 'normal', | 69 | NORMAL = 'normal', |
| 40 | DARK = 'dark', | 70 | DARK = 'dark', |
| @@ -88,6 +118,11 @@ export const option = { | @@ -88,6 +118,11 @@ export const option = { | ||
| 88 | amapLon: 104.108689, | 118 | amapLon: 104.108689, |
| 89 | amapLat: 30.66176, | 119 | amapLat: 30.66176, |
| 90 | amapZindex: 11, | 120 | amapZindex: 11, |
| 121 | + iconMarker: '1.png', | ||
| 122 | + mpBorderConfig: { | ||
| 123 | + value: 'Border01' | ||
| 124 | + }, | ||
| 125 | + bgColor: 'rgba(255, 255, 255, 0.05)', | ||
| 91 | marker: { | 126 | marker: { |
| 92 | fillColor: '#E98984FF', | 127 | fillColor: '#E98984FF', |
| 93 | fillOpacity: 0.5, | 128 | fillOpacity: 0.5, |
| @@ -71,19 +71,60 @@ | @@ -71,19 +71,60 @@ | ||
| 71 | <setting-item name="类型"> | 71 | <setting-item name="类型"> |
| 72 | <n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" /> | 72 | <n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" /> |
| 73 | </setting-item> | 73 | </setting-item> |
| 74 | - <setting-item name="颜色"> | 74 | + <setting-item v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.CIRCLE_MARKER" name="颜色"> |
| 75 | <n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker> | 75 | <n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker> |
| 76 | </setting-item> | 76 | </setting-item> |
| 77 | </setting-item-box> | 77 | </setting-item-box> |
| 78 | + <setting-item-box name="图标选择" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> | ||
| 79 | + <setting-item> | ||
| 80 | + <NSelect | ||
| 81 | + size="small" | ||
| 82 | + placeholder="请选择您要使用的图标" | ||
| 83 | + style="width: 250px" | ||
| 84 | + :value="iconMarkerValue" | ||
| 85 | + :options="iconMarkerOptions" | ||
| 86 | + :render-label="renderOption" | ||
| 87 | + clearable | ||
| 88 | + filterable | ||
| 89 | + @update:value="selectHandle" | ||
| 90 | + /> | ||
| 91 | + </setting-item> | ||
| 92 | + </setting-item-box> | ||
| 93 | + <setting-item-box name="弹窗选择" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> | ||
| 94 | + <setting-item> | ||
| 95 | + <NSelect | ||
| 96 | + size="small" | ||
| 97 | + placeholder="请选择您要使用的弹窗" | ||
| 98 | + style="width: 250px" | ||
| 99 | + :value="mapSelectBorderValue" | ||
| 100 | + :options="mapBorderOptions" | ||
| 101 | + :render-label="renderMapBorderOption" | ||
| 102 | + clearable | ||
| 103 | + filterable | ||
| 104 | + @update:value="selectMapBorderHandle" | ||
| 105 | + /> | ||
| 106 | + </setting-item> | ||
| 107 | + </setting-item-box> | ||
| 108 | + <setting-item-box name="弹窗背景" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> | ||
| 109 | + <div style="width: 10vw"> | ||
| 110 | + <n-color-picker :modes="['rgb']" v-model:value="optionData.mapOptions.bgColor" size="small"></n-color-picker> | ||
| 111 | + </div> | ||
| 112 | + </setting-item-box> | ||
| 78 | </collapse-item> | 113 | </collapse-item> |
| 79 | </template> | 114 | </template> |
| 80 | 115 | ||
| 81 | <script setup lang="ts"> | 116 | <script setup lang="ts"> |
| 82 | -import { PropType } from 'vue' | 117 | +import { PropType, ref, computed, h, onMounted } from 'vue' |
| 83 | import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config' | 118 | import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config' |
| 84 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | 119 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 120 | +import { NEllipsis, NImage, NSelect, NSpace, SelectOption } from 'naive-ui' | ||
| 121 | +import { getUrlBase64 } from '@/utils/external/imageUrlToBase64' | ||
| 85 | 122 | ||
| 86 | -defineProps({ | 123 | +interface BaseSelectBorderIF extends SelectOption { |
| 124 | + config?: string | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +const props = defineProps({ | ||
| 87 | optionData: { | 128 | optionData: { |
| 88 | type: Object as PropType<typeof option>, | 129 | type: Object as PropType<typeof option>, |
| 89 | required: true | 130 | required: true |
| @@ -187,10 +228,10 @@ const featuresOptions = [ | @@ -187,10 +228,10 @@ const featuresOptions = [ | ||
| 187 | ] | 228 | ] |
| 188 | 229 | ||
| 189 | const MarkerOptions = [ | 230 | const MarkerOptions = [ |
| 190 | - { | ||
| 191 | - value: MarkerEnum.CIRCLE_MARKER, | ||
| 192 | - label: '圆形标点' | ||
| 193 | - }, | 231 | + // { |
| 232 | + // value: MarkerEnum.CIRCLE_MARKER, | ||
| 233 | + // label: '圆形标点' //在地图里点击无效,所以注释,有需要自行打开即可 | ||
| 234 | + // }, | ||
| 194 | { | 235 | { |
| 195 | value: MarkerEnum.MARKER, | 236 | value: MarkerEnum.MARKER, |
| 196 | label: '定位标点' | 237 | label: '定位标点' |
| @@ -200,4 +241,103 @@ const MarkerOptions = [ | @@ -200,4 +241,103 @@ const MarkerOptions = [ | ||
| 200 | label: '隐藏标点' | 241 | label: '隐藏标点' |
| 201 | } | 242 | } |
| 202 | ] | 243 | ] |
| 244 | + | ||
| 245 | +const iconMarkerValue = ref<string | null>('1.png') | ||
| 246 | + | ||
| 247 | +const isHref = (url: string) => { | ||
| 248 | + try { | ||
| 249 | + new URL(url) | ||
| 250 | + return true | ||
| 251 | + } catch (error) { | ||
| 252 | + return false | ||
| 253 | + } | ||
| 254 | +} | ||
| 255 | + | ||
| 256 | +// import.meta.glob 这个不能封装,必须是字符串,不能通过传值传进去 | ||
| 257 | +const iconMarkerOptions = computed(() => { | ||
| 258 | + const pathList = import.meta.glob('../../../../../../assets/external/marker/*') | ||
| 259 | + return Object.keys(pathList).map(item => { | ||
| 260 | + const imgName = item.split('/').at(-1) | ||
| 261 | + return { | ||
| 262 | + label: imgName, | ||
| 263 | + value: imgName | ||
| 264 | + } as SelectOption | ||
| 265 | + }) | ||
| 266 | +}) | ||
| 267 | +// | ||
| 268 | + | ||
| 269 | +const getMarkerImagePath = (name: string) => { | ||
| 270 | + return isHref(name) ? name : new URL(`../../../../../../assets/external/marker/${name}`, import.meta.url).href | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +const renderOption = (option: SelectOption) => { | ||
| 274 | + return h(NSpace, { justify: 'space-between', style: 'padding: 0 15px; height: 28px; line-height: 28px;' }, () => [ | ||
| 275 | + h(NImage, { | ||
| 276 | + width: 25, | ||
| 277 | + src: getMarkerImagePath(option.value as string), | ||
| 278 | + previewDisabled: true, | ||
| 279 | + style: { height: '25px' } | ||
| 280 | + } as Recordable), | ||
| 281 | + h(NEllipsis, null, () => option.label) | ||
| 282 | + ]) | ||
| 283 | +} | ||
| 284 | + | ||
| 285 | +const selectHandle = (value: string) => { | ||
| 286 | + iconMarkerValue.value = value | ||
| 287 | + getUrlBase64(getMarkerImagePath(value),'png',(baseEncodeText: string)=>{ | ||
| 288 | + props.optionData.mapOptions.iconMarker =baseEncodeText | ||
| 289 | + }) | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +const mapSelectBorderValue = ref<string | null>('border01.png') | ||
| 293 | + | ||
| 294 | +const needBorder = ['border01.png', 'border02.png', 'border03.png', 'border05.png', 'border07.png'] | ||
| 295 | + | ||
| 296 | +// import.meta.glob 这个不能封装,必须是字符串,不能通过传值传进去 | ||
| 297 | +const mapBorderOptions = computed(() => { | ||
| 298 | + const pathList = import.meta.glob('../../../../../../assets/images/chart/decorates/*') | ||
| 299 | + return Object.keys(pathList) | ||
| 300 | + .map(item => { | ||
| 301 | + const imgName = item.split('/').at(-1) as string | ||
| 302 | + if (needBorder.includes(imgName)) { | ||
| 303 | + return { | ||
| 304 | + label: imgName, | ||
| 305 | + value: imgName | ||
| 306 | + } as SelectOption | ||
| 307 | + } | ||
| 308 | + }) | ||
| 309 | + .filter(Boolean) as SelectOption[] | ||
| 310 | +}) | ||
| 311 | +// | ||
| 312 | + | ||
| 313 | +const getMapBorderImagePath = (name: string) => { | ||
| 314 | + return isHref(name) ? name : new URL(`../../../../../../assets/images/chart/decorates/${name}`, import.meta.url).href | ||
| 315 | +} | ||
| 316 | + | ||
| 317 | +const renderMapBorderOption = (option: SelectOption) => { | ||
| 318 | + return h(NSpace, { justify: 'space-between', style: 'padding: 0 15px; height: 28px; line-height: 28px;' }, () => [ | ||
| 319 | + h(NImage, { | ||
| 320 | + width: 25, | ||
| 321 | + src: getMapBorderImagePath(option.value as string), | ||
| 322 | + previewDisabled: true, | ||
| 323 | + style: { height: '25px' } | ||
| 324 | + } as Recordable), | ||
| 325 | + h(NEllipsis, null, () => option.label) | ||
| 326 | + ]) | ||
| 327 | +} | ||
| 328 | + | ||
| 329 | +const selectMapBorderHandle = (value: string) => { | ||
| 330 | + mapSelectBorderValue.value = value | ||
| 331 | + const toLowerValue = value.toLocaleLowerCase() | ||
| 332 | + ;(props.optionData.mapOptions.mpBorderConfig as BaseSelectBorderIF) = { | ||
| 333 | + value: toLowerValue[0]?.toUpperCase() + toLowerValue?.substr(1)?.split('.')[0] | ||
| 334 | + } | ||
| 335 | +} | ||
| 336 | + | ||
| 337 | +onMounted(() => { | ||
| 338 | + getUrlBase64(getMarkerImagePath(props.optionData.mapOptions.iconMarker),'png',(baseEncodeText: string)=>{ | ||
| 339 | + props.optionData.mapOptions.iconMarker = baseEncodeText | ||
| 340 | + }) | ||
| 341 | + mapSelectBorderValue.value = `${props.optionData.mapOptions.mpBorderConfig.value?.toLocaleLowerCase()}.png` | ||
| 342 | +}) | ||
| 203 | </script> | 343 | </script> |
| 1 | { | 1 | { |
| 2 | "markers": [ | 2 | "markers": [ |
| 3 | { | 3 | { |
| 4 | - "name": "模拟11111111111", | 4 | + "name": "模拟数据1", |
| 5 | "value": 20, | 5 | "value": 20, |
| 6 | "position": [103.856504, 30.687278], | 6 | "position": [103.856504, 30.687278], |
| 7 | "extraInfo": { | 7 | "extraInfo": { |
| 8 | "tbDeviceId": "@xxxxxxxxxxx", | 8 | "tbDeviceId": "@xxxxxxxxxxx", |
| 9 | - "name": "模拟11111111111", | ||
| 10 | - "alias": "模拟11111111111", | 9 | + "name": "模拟数据1", |
| 10 | + "alias": "模拟数据1", | ||
| 11 | "organizationDTO": { | 11 | "organizationDTO": { |
| 12 | - "name": "模拟11111111111" | 12 | + "name": "模拟数据1" |
| 13 | }, | 13 | }, |
| 14 | "deviceState": "INACTIVE", | 14 | "deviceState": "INACTIVE", |
| 15 | "deviceProfile": { | 15 | "deviceProfile": { |
| 16 | "transportType": "MQTT" | 16 | "transportType": "MQTT" |
| 17 | }, | 17 | }, |
| 18 | + "deviceProfileId":"", | ||
| 18 | "deviceInfo": { | 19 | "deviceInfo": { |
| 19 | "address": "四川省", | 20 | "address": "四川省", |
| 20 | "longitude": 103.856504, | 21 | "longitude": 103.856504, |
| @@ -23,20 +24,21 @@ | @@ -23,20 +24,21 @@ | ||
| 23 | } | 24 | } |
| 24 | }, | 25 | }, |
| 25 | { | 26 | { |
| 26 | - "name": "模拟22222222222", | 27 | + "name": "模拟数据2", |
| 27 | "value": 30, | 28 | "value": 30, |
| 28 | "position": [104.095368, 30.716787], | 29 | "position": [104.095368, 30.716787], |
| 29 | "extraInfo": { | 30 | "extraInfo": { |
| 30 | "tbDeviceId": "@xxxxxxxxxxxxxxx", | 31 | "tbDeviceId": "@xxxxxxxxxxxxxxx", |
| 31 | - "name": "模拟22222222222", | ||
| 32 | - "alias": "模拟22222222222", | 32 | + "name": "模拟数据2", |
| 33 | + "alias": "模拟数据2", | ||
| 33 | "organizationDTO": { | 34 | "organizationDTO": { |
| 34 | - "name": "模拟22222222222" | 35 | + "name": "模拟数据2" |
| 35 | }, | 36 | }, |
| 36 | "deviceState": "INACTIVE", | 37 | "deviceState": "INACTIVE", |
| 37 | "deviceProfile": { | 38 | "deviceProfile": { |
| 38 | "transportType": "TCP" | 39 | "transportType": "TCP" |
| 39 | }, | 40 | }, |
| 41 | + "deviceProfileId":"", | ||
| 40 | "deviceInfo": { | 42 | "deviceInfo": { |
| 41 | "address": "四川省", | 43 | "address": "四川省", |
| 42 | "longitude": 104.095368, | 44 | "longitude": 104.095368, |
937 Bytes
| @@ -8,7 +8,7 @@ export const OverrideMapAmapConfig: ConfigType = { | @@ -8,7 +8,7 @@ export const OverrideMapAmapConfig: ConfigType = { | ||
| 8 | key, | 8 | key, |
| 9 | chartKey, | 9 | chartKey, |
| 10 | conKey, | 10 | conKey, |
| 11 | - title: '高德地图', | 11 | + title: '设备分布', |
| 12 | category: ChatCategoryEnum.MAP, | 12 | category: ChatCategoryEnum.MAP, |
| 13 | categoryName: ChatCategoryEnumName.MAP, | 13 | categoryName: ChatCategoryEnumName.MAP, |
| 14 | package: PackagesCategoryEnum.CHARTS, | 14 | package: PackagesCategoryEnum.CHARTS, |
| 1 | <template> | 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> | 2 | + <div class="chart-amap" ref="vChartRef"> |
| 3 | + <device-latest-table ref="deviceLatestTableRef"></device-latest-table> | ||
| 5 | </div> | 4 | </div> |
| 6 | </template> | 5 | </template> |
| 7 | 6 | ||
| 8 | <script setup lang="ts"> | 7 | <script setup lang="ts"> |
| 9 | -import { ref, PropType, toRefs, watch } from 'vue' | 8 | +import { ref, PropType, toRefs, watch, render, h, onMounted, onUnmounted, reactive } from 'vue' |
| 10 | import AMapLoader from '@amap/amap-jsapi-loader' | 9 | import AMapLoader from '@amap/amap-jsapi-loader' |
| 11 | import { CreateComponentType } from '@/packages/index.d' | 10 | import { CreateComponentType } from '@/packages/index.d' |
| 12 | import { useChartDataFetch } from '@/hooks' | 11 | import { useChartDataFetch } from '@/hooks' |
| 13 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | 12 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 14 | -import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType, fileterDevice } from './config' | 13 | +import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType, devicePartInfoInterface } from './config' |
| 15 | import { isArray } from '@/utils' | 14 | import { isArray } from '@/utils' |
| 16 | -import djh from './images/djh.png' | 15 | +import inactive from './images/inactive.png' |
| 17 | import online from './images/online.png' | 16 | import online from './images/online.png' |
| 18 | -import lx1 from './images/lx1.png' | ||
| 19 | -import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index' | 17 | +import offline from './images/offline.png' |
| 18 | +import listView from './images/list.png' | ||
| 19 | +import { getDeviceActiveTime } from '@/api/external/common/index' | ||
| 20 | import dayjs from 'dayjs' | 20 | import dayjs from 'dayjs' |
| 21 | -import SearchBox from './components/SearchBox.vue' | 21 | +import DeviceLatestTable from './components/DeviceLatestTable.vue' |
| 22 | +import { getDeviceLatest, getProfileAttrs } from '@/api/external/common' | ||
| 23 | +import { NButton } from 'naive-ui' | ||
| 22 | 24 | ||
| 23 | const props = defineProps({ | 25 | const props = defineProps({ |
| 24 | chartConfig: { | 26 | chartConfig: { |
| @@ -27,11 +29,9 @@ const props = defineProps({ | @@ -27,11 +29,9 @@ const props = defineProps({ | ||
| 27 | } | 29 | } |
| 28 | }) | 30 | }) |
| 29 | 31 | ||
| 30 | -const modelShow = ref(false) | 32 | +const deviceLatestTableRef = ref<Nullable<InstanceType<typeof DeviceLatestTable>>>(null); |
| 31 | 33 | ||
| 32 | -const showSearchBox = ref(false) | ||
| 33 | - | ||
| 34 | -let { | 34 | +const { |
| 35 | amapKey, | 35 | amapKey, |
| 36 | amapStyleKey, | 36 | amapStyleKey, |
| 37 | amapLon, | 37 | amapLon, |
| @@ -44,7 +44,10 @@ let { | @@ -44,7 +44,10 @@ let { | ||
| 44 | viewMode, | 44 | viewMode, |
| 45 | pitch, | 45 | pitch, |
| 46 | skyColor, | 46 | skyColor, |
| 47 | - marker | 47 | + marker, |
| 48 | + iconMarker, | ||
| 49 | + mpBorderConfig, | ||
| 50 | + bgColor | ||
| 48 | } = toRefs(props.chartConfig.option.mapOptions) | 51 | } = toRefs(props.chartConfig.option.mapOptions) |
| 49 | 52 | ||
| 50 | //官方没有高德地图api的ts,所以类型全用的any | 53 | //官方没有高德地图api的ts,所以类型全用的any |
| @@ -71,7 +74,10 @@ const initMap = (newData: any) => { | @@ -71,7 +74,10 @@ const initMap = (newData: any) => { | ||
| 71 | pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度 | 74 | pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度 |
| 72 | skyColor: skyColor.value, | 75 | skyColor: skyColor.value, |
| 73 | viewMode: viewMode.value, // 地图模式 | 76 | viewMode: viewMode.value, // 地图模式 |
| 74 | - willReadFrequently: true | 77 | + willReadFrequently: true, |
| 78 | + WebGLParams: { | ||
| 79 | + preserveDrawingBuffer: true, | ||
| 80 | + } | ||
| 75 | }) | 81 | }) |
| 76 | dataHandle(props.chartConfig.option.dataset) //处理地图标点 | 82 | dataHandle(props.chartConfig.option.dataset) //处理地图标点 |
| 77 | let satellite = new AMap.TileLayer.Satellite() | 83 | let satellite = new AMap.TileLayer.Satellite() |
| @@ -84,7 +90,6 @@ const initMap = (newData: any) => { | @@ -84,7 +90,6 @@ const initMap = (newData: any) => { | ||
| 84 | `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}` | 90 | `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}` |
| 85 | ) | 91 | ) |
| 86 | } | 92 | } |
| 87 | - //点击地图任意地方关闭infoWindow窗体 | ||
| 88 | mapIns.on('click', () => { | 93 | mapIns.on('click', () => { |
| 89 | mapIns.clearInfoWindow() | 94 | mapIns.clearInfoWindow() |
| 90 | }) | 95 | }) |
| @@ -94,81 +99,115 @@ const initMap = (newData: any) => { | @@ -94,81 +99,115 @@ const initMap = (newData: any) => { | ||
| 94 | }) | 99 | }) |
| 95 | } | 100 | } |
| 96 | 101 | ||
| 102 | + | ||
| 103 | +//携带设备的tbDeviceId和别名或者名称 | ||
| 104 | +const devicePartInfo = reactive<devicePartInfoInterface>({ | ||
| 105 | + tbDeviceId: '', | ||
| 106 | + name: '', | ||
| 107 | + alias: '', | ||
| 108 | + deviceProfileId: '', | ||
| 109 | +}) | ||
| 110 | + | ||
| 97 | //创建信息弹窗 | 111 | //创建信息弹窗 |
| 98 | const createInfoWindow = async (extraInfo: dataExtraInfoType) => { | 112 | const createInfoWindow = async (extraInfo: dataExtraInfoType) => { |
| 99 | try { | 113 | try { |
| 100 | - const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId } = extraInfo | 114 | + const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId, deviceProfileId } = extraInfo |
| 115 | + devicePartInfo.tbDeviceId = tbDeviceId | ||
| 116 | + devicePartInfo.alias = alias | ||
| 117 | + devicePartInfo.name = name | ||
| 118 | + devicePartInfo.deviceProfileId = deviceProfileId | ||
| 101 | if (tbDeviceId.startsWith('@')) return //假的模拟数据则终止 | 119 | if (tbDeviceId.startsWith('@')) return //假的模拟数据则终止 |
| 102 | const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 | 120 | const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 |
| 103 | let { lastUpdateTs } = res[0] | 121 | let { lastUpdateTs } = res[0] |
| 104 | const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss') | 122 | const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss') |
| 123 | + //以render方式渲染小组件里的边框组件 | ||
| 124 | + const BorderInstance = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/index.vue`) | ||
| 125 | + const config = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/config.ts`) | ||
| 126 | + const BorderConfigInstance = new config.default() | ||
| 127 | + const id = Date.now().toString() | ||
| 128 | + setTimeout(() => { | ||
| 129 | + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| 130 | + render(h(BorderInstance.default, { chartConfig: BorderConfigInstance }), document.getElementById(id)!) | ||
| 131 | + }, 100) | ||
| 132 | + // | ||
| 133 | + const textOverflow = `width:16rem;text-overflow:ellipsis;overflow:hidden;word-break:break-all;white-space:nowrap;` | ||
| 134 | + const textOverflowFontBold = `width:12rem;text-overflow:ellipsis;overflow:hidden;word-break:break-all;white-space:nowrap;font-size:15px;font-weight:bold;` | ||
| 135 | + const deviceStateContainer = `display:flex;justify-content:space-between;align-items:center;` | ||
| 136 | + const deviceStateImg = `width:1.2rem;height:1.2rem;` | ||
| 137 | + const deviceStateText = `margin-left:0.6rem;font-weight:bold;` | ||
| 105 | return ` | 138 | 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 | - ` | 139 | + <div id="${id}" style="width:30rem;"> |
| 140 | + <div style="display:flex;flex-direction:column;margin:3.5rem 5.5rem 2rem 6.5rem;position:relative;"> | ||
| 141 | + <div style="display:flex;justify-content:space-between;align-items:center;color:white;"> | ||
| 142 | + <span style="${textOverflowFontBold}">${alias || name}</span> | ||
| 143 | + ${deviceState === 'INACTIVE' | ||
| 144 | + ? `<div style="${deviceStateContainer}"> | ||
| 145 | + <img onclick="handleOpenDrawer()" title="最新数据" style="${deviceStateImg};cursor:pointer;" src="${listView}"/> | ||
| 146 | + <img style="${deviceStateImg};margin-left:0.3rem" src="${inactive}"/> | ||
| 147 | + <span style="${deviceStateText}">待激活</span> | ||
| 148 | + </div>` | ||
| 149 | + : deviceState === 'ONLINE' | ||
| 150 | + ? `<div style="${deviceStateContainer}"> | ||
| 151 | + <img onclick="handleOpenDrawer()" title="最新数据" style="${deviceStateImg};cursor:pointer;" src="${listView}"/> | ||
| 152 | + <img style="${deviceStateImg};margin-left:0.3rem" src="${online}"/> | ||
| 153 | + <span style="${deviceStateText}">在线</span> | ||
| 154 | + </div>` | ||
| 155 | + : `<div style="${deviceStateContainer}"> | ||
| 156 | + <img onclick="handleOpenDrawer()" title="最新数据" style="${deviceStateImg};cursor:pointer;" src="${listView}"/> | ||
| 157 | + <img style="${deviceStateImg};margin-left:0.3rem" src="${offline}"/> | ||
| 158 | + <span style="${deviceStateText}">离线</span> | ||
| 159 | + </div>` | ||
| 160 | + } | ||
| 161 | + </div> | ||
| 162 | + <div style="display:flex;flex-direction:column;justify-content:space-between;color:white;margin-top:1rem;gap:0.95rem;"> | ||
| 163 | + <div style="${textOverflow}">所属组织:${organizationDTO.name}</div> | ||
| 164 | + <div style="${textOverflow}">接入协议:${deviceProfile.transportType}</div> | ||
| 165 | + <div style="${textOverflow}">设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address}</div> | ||
| 166 | + <div style="${textOverflow}">${deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线'}时间:${lastUpdateFormatTs}</div> | ||
| 167 | + </div> | ||
| 168 | + </div> | ||
| 169 | + </div> | ||
| 170 | + ` | ||
| 132 | } catch (e) { | 171 | } catch (e) { |
| 133 | console.error(e) | 172 | console.error(e) |
| 134 | } | 173 | } |
| 135 | } | 174 | } |
| 136 | 175 | ||
| 137 | -const handleMouseenter = () => (showSearchBox.value = true) | ||
| 138 | - | ||
| 139 | -const handleMouseleave = () => (showSearchBox.value = false) | ||
| 140 | - | ||
| 141 | -const handleOpenSearchBox = () => (modelShow.value = true) | 176 | +const handleOpenDrawer = async () => { |
| 177 | + deviceLatestTableRef.value?.openDrawer() | ||
| 178 | + deviceLatestTableRef.value?.setDrawerTitle(devicePartInfo.alias || devicePartInfo.name) | ||
| 179 | + if (!devicePartInfo.tbDeviceId) return | ||
| 180 | + const resp = await getDeviceLatest(devicePartInfo.tbDeviceId) | ||
| 181 | + const respGetAttrs = await getProfileAttrs({ deviceProfileId: devicePartInfo.deviceProfileId }) | ||
| 182 | + deviceLatestTableRef.value?.setValue(resp, respGetAttrs) | ||
| 183 | +} | ||
| 142 | 184 | ||
| 143 | -const handleCloseDrawer = () => (modelShow.value = false) | 185 | +onMounted(() => { |
| 186 | + (window as any).handleOpenDrawer = handleOpenDrawer; | ||
| 187 | +}); | ||
| 144 | 188 | ||
| 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 | -} | 189 | +onUnmounted(() => { |
| 190 | + (window as any).handleOpenDrawer = null; | ||
| 191 | +}); | ||
| 155 | 192 | ||
| 156 | -//地图点击 | 193 | +//地图鼠标hover |
| 157 | const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { | 194 | const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { |
| 158 | markerInstance.setExtData({ | 195 | markerInstance.setExtData({ |
| 159 | extraInfo: markerItem.extraInfo | 196 | extraInfo: markerItem.extraInfo |
| 160 | }) | 197 | }) |
| 161 | - markerInstance.setLabel({ | ||
| 162 | - content: markerItem.extraInfo.alias || markerItem.extraInfo.name | ||
| 163 | - }) | 198 | + // mouseover |
| 164 | markerInstance.on('click', async (e: any) => { | 199 | markerInstance.on('click', async (e: any) => { |
| 165 | const { extraInfo } = e.target.getExtData() | 200 | const { extraInfo } = e.target.getExtData() |
| 201 | + if (extraInfo.tbDeviceId.startsWith('@')) return //假的模拟数据则终止弹窗 | ||
| 166 | let infoWindow = new AMapIns.InfoWindow({ | 202 | let infoWindow = new AMapIns.InfoWindow({ |
| 167 | content: await createInfoWindow(extraInfo), | 203 | content: await createInfoWindow(extraInfo), |
| 168 | - offset: new AMapIns.Pixel(0, -50) | 204 | + offset: new AMapIns.Pixel(3, -30) |
| 169 | }) | 205 | }) |
| 170 | infoWindow.open(mapIns, e.target.getPosition()) | 206 | infoWindow.open(mapIns, e.target.getPosition()) |
| 171 | }) | 207 | }) |
| 208 | + // markerInstance.on('click', () => { | ||
| 209 | + // mapIns.clearInfoWindow() | ||
| 210 | + // }) | ||
| 172 | } | 211 | } |
| 173 | 212 | ||
| 174 | const dataHandle = (newData: dataJsonType) => { | 213 | const dataHandle = (newData: dataJsonType) => { |
| @@ -185,11 +224,13 @@ const dataHandle = (newData: dataJsonType) => { | @@ -185,11 +224,13 @@ const dataHandle = (newData: dataJsonType) => { | ||
| 185 | newData.markers.forEach((markerItem: dataJsonMarkersType) => { | 224 | newData.markers.forEach((markerItem: dataJsonMarkersType) => { |
| 186 | const markerInstance = new AMapIns.Marker({ | 225 | const markerInstance = new AMapIns.Marker({ |
| 187 | position: [markerItem.position[0], markerItem.position[1]], | 226 | position: [markerItem.position[0], markerItem.position[1]], |
| 188 | - offset: new AMapIns.Pixel(-13, -30) | 227 | + offset: new AMapIns.Pixel(-13, 5), |
| 228 | + icon: iconMarker.value | ||
| 189 | }) | 229 | }) |
| 190 | - // markers.push(markerInstance) 原作者这种方式添加,属于JS API 1.4.8版本的 | ||
| 191 | - // markerInstance.setMap(mapIns) | ||
| 192 | - mapIns.add(markerInstance) | 230 | + // 原作者这种方式添加,属于JS API 1.4.8版本的 |
| 231 | + markers.push(markerInstance) | ||
| 232 | + markerInstance.setMap(mapIns) | ||
| 233 | + // mapIns.add(markerInstance) | ||
| 193 | mapClick(markerInstance, markerItem) | 234 | mapClick(markerInstance, markerItem) |
| 194 | }) | 235 | }) |
| 195 | } else if (mapMarkerType.value === MarkerEnum.CIRCLE_MARKER) { | 236 | } else if (mapMarkerType.value === MarkerEnum.CIRCLE_MARKER) { |
| @@ -239,13 +280,47 @@ watch( | @@ -239,13 +280,47 @@ watch( | ||
| 239 | // 预览 | 280 | // 预览 |
| 240 | useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | 281 | useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { |
| 241 | stopWatch() | 282 | stopWatch() |
| 242 | - dataHandle(newData) | 283 | + setTimeout(() => { |
| 284 | + dataHandle(newData) | ||
| 285 | + }, 1000) | ||
| 243 | }) | 286 | }) |
| 244 | </script> | 287 | </script> |
| 245 | 288 | ||
| 289 | +<style> | ||
| 290 | +/**去除高德地图原生弹窗 */ | ||
| 291 | +.amap-info-outer, | ||
| 292 | +.amap-menu-outer { | ||
| 293 | + box-shadow: 0 1px 2px rgba(0, 0, 0, 0) !important; | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +.amap-info-outer { | ||
| 297 | + background-color: rgba(0, 0, 0, 0) !important; | ||
| 298 | +} | ||
| 299 | + | ||
| 300 | +.amap-info-close { | ||
| 301 | + display: none !important; | ||
| 302 | +} | ||
| 303 | + | ||
| 304 | +.amap-info-content { | ||
| 305 | + overflow: hidden !important; | ||
| 306 | +} | ||
| 307 | + | ||
| 308 | +/**去除高德地图原生弹窗*/ | ||
| 309 | +.amap-info-content .go-border-box { | ||
| 310 | + position: absolute; | ||
| 311 | + transform: scale(0.68); | ||
| 312 | + top: 0; | ||
| 313 | + z-index: -100; | ||
| 314 | +} | ||
| 315 | + | ||
| 316 | +.amap-info-content .go-border-box svg { | ||
| 317 | + background-color: v-bind(bgColor); | ||
| 318 | +} | ||
| 319 | +</style> | ||
| 246 | <style lang="scss" scoped> | 320 | <style lang="scss" scoped> |
| 247 | .chart-amap { | 321 | .chart-amap { |
| 248 | position: relative; | 322 | position: relative; |
| 323 | + | ||
| 249 | .search-box { | 324 | .search-box { |
| 250 | cursor: pointer; | 325 | cursor: pointer; |
| 251 | position: absolute; | 326 | position: absolute; |
| @@ -7,11 +7,11 @@ import dataJson from './data.json' | @@ -7,11 +7,11 @@ import dataJson from './data.json' | ||
| 7 | 7 | ||
| 8 | //省市区枚举 | 8 | //省市区枚举 |
| 9 | export const enum areaEnum { | 9 | export const enum areaEnum { |
| 10 | - PROVINCE = 'PROVINCE',//省份 | ||
| 11 | - CITY = 'CITY',//城市 | ||
| 12 | - COUNTY = 'COUNTY',//县 | ||
| 13 | - COUNTRY = 'COUNTRY',//国家 | ||
| 14 | - TOWN = 'TOWN'//镇 | 10 | + PROVINCE = 'PROVINCE', //省份 |
| 11 | + CITY = 'CITY', //城市 | ||
| 12 | + COUNTY = 'COUNTY', //县 | ||
| 13 | + COUNTRY = 'COUNTRY', //国家 | ||
| 14 | + TOWN = 'TOWN' //镇 | ||
| 15 | } | 15 | } |
| 16 | export const includes = [] | 16 | export const includes = [] |
| 17 | 17 | ||
| @@ -22,6 +22,9 @@ export const option = { | @@ -22,6 +22,9 @@ export const option = { | ||
| 22 | iconDistanceTop: 20, | 22 | iconDistanceTop: 20, |
| 23 | drillingIn: false, | 23 | drillingIn: false, |
| 24 | dataset: dataJson, | 24 | dataset: dataJson, |
| 25 | + saveClickRegion: { | ||
| 26 | + level: '' | ||
| 27 | + }, | ||
| 25 | mapRegion: { | 28 | mapRegion: { |
| 26 | adcode: 'china', | 29 | adcode: 'china', |
| 27 | showHainanIsLands: true, | 30 | showHainanIsLands: true, |
| @@ -161,6 +164,26 @@ export const option = { | @@ -161,6 +164,26 @@ export const option = { | ||
| 161 | shadowOffsetY: 2, | 164 | shadowOffsetY: 2, |
| 162 | shadowBlur: 10 | 165 | shadowBlur: 10 |
| 163 | } | 166 | } |
| 167 | + }, | ||
| 168 | + { | ||
| 169 | + type: 'lines', | ||
| 170 | + zlevel: 2, | ||
| 171 | + effect: { | ||
| 172 | + show: true, | ||
| 173 | + period: 4, //箭头指向速度,值越小速度越快 | ||
| 174 | + trailLength: 0.4, //特效尾迹长度[0,1]值越大,尾迹越长重 | ||
| 175 | + symbol: 'arrow', //箭头图标 | ||
| 176 | + symbolSize: 7 //图标大小 | ||
| 177 | + }, | ||
| 178 | + lineStyle: { | ||
| 179 | + normal: { | ||
| 180 | + color: '#4fb6d2', | ||
| 181 | + width: 1, //线条宽度 | ||
| 182 | + opacity: 0.1, //尾迹线条透明度 | ||
| 183 | + curveness: 0.3 //尾迹线条曲直度 | ||
| 184 | + } | ||
| 185 | + }, | ||
| 186 | + data: [] | ||
| 164 | } | 187 | } |
| 165 | ] | 188 | ] |
| 166 | } | 189 | } |
| @@ -246,6 +246,46 @@ | @@ -246,6 +246,46 @@ | ||
| 246 | </SettingItem> | 246 | </SettingItem> |
| 247 | </SettingItemBox> | 247 | </SettingItemBox> |
| 248 | </CollapseItem> | 248 | </CollapseItem> |
| 249 | + <CollapseItem v-if="seriesList[2]" name="飞线" :expanded="true"> | ||
| 250 | + <SettingItemBox name="箭头"> | ||
| 251 | + <SettingItem name="速度"> | ||
| 252 | + <n-tooltip trigger="hover"> | ||
| 253 | + <template #trigger> | ||
| 254 | + <n-input-number v-model:value="seriesList[2].effect.period" size="small" :min="0"></n-input-number> | ||
| 255 | + </template> | ||
| 256 | + 值越小速度越快 | ||
| 257 | + </n-tooltip> | ||
| 258 | + </SettingItem> | ||
| 259 | + <SettingItem name="尾迹"> | ||
| 260 | + <n-tooltip trigger="hover"> | ||
| 261 | + <template #trigger> | ||
| 262 | + <n-input-number | ||
| 263 | + v-model:value="seriesList[2].effect.trailLength" | ||
| 264 | + size="small" | ||
| 265 | + :min="0" | ||
| 266 | + :max="1" | ||
| 267 | + ></n-input-number> | ||
| 268 | + </template> | ||
| 269 | + 特效尾迹长度[0,1]值越大,尾迹越长重 | ||
| 270 | + </n-tooltip> | ||
| 271 | + </SettingItem> | ||
| 272 | + <SettingItem name="大小"> | ||
| 273 | + <n-input-number v-model:value="seriesList[2].effect.symbolSize" size="small" :min="0"></n-input-number> | ||
| 274 | + </SettingItem> | ||
| 275 | + </SettingItemBox> | ||
| 276 | + <SettingItemBox name="配置"> | ||
| 277 | + <SettingItem name="颜色"> | ||
| 278 | + <n-color-picker | ||
| 279 | + size="small" | ||
| 280 | + :modes="['hex']" | ||
| 281 | + v-model:value="seriesList[2].lineStyle.normal.color" | ||
| 282 | + ></n-color-picker> | ||
| 283 | + </SettingItem> | ||
| 284 | + <SettingItem name="宽度"> | ||
| 285 | + <n-input-number v-model:value="seriesList[2].lineStyle.normal.width" size="small" :min="1"></n-input-number> | ||
| 286 | + </SettingItem> | ||
| 287 | + </SettingItemBox> | ||
| 288 | + </CollapseItem> | ||
| 249 | </template> | 289 | </template> |
| 250 | 290 | ||
| 251 | <script setup lang="ts"> | 291 | <script setup lang="ts"> |
| @@ -26,6 +26,36 @@ | @@ -26,6 +26,36 @@ | ||
| 26 | "value": [126.642464, 45.756967, 101] | 26 | "value": [126.642464, 45.756967, 101] |
| 27 | } | 27 | } |
| 28 | ], | 28 | ], |
| 29 | + "line": [ | ||
| 30 | + { | ||
| 31 | + "adcode": 410000, | ||
| 32 | + "coords": [ | ||
| 33 | + [113.665412, 34.757975], | ||
| 34 | + [116.405285, 39.904989] | ||
| 35 | + ] | ||
| 36 | + }, | ||
| 37 | + { | ||
| 38 | + "adcode": 630000, | ||
| 39 | + "coords": [ | ||
| 40 | + [101.778916, 36.623178], | ||
| 41 | + [116.405285, 39.904989] | ||
| 42 | + ] | ||
| 43 | + }, | ||
| 44 | + { | ||
| 45 | + "adcode": 640000, | ||
| 46 | + "coords": [ | ||
| 47 | + [106.278179, 38.46637], | ||
| 48 | + [116.405285, 39.904989] | ||
| 49 | + ] | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + "adcode": 230000, | ||
| 53 | + "coords": [ | ||
| 54 | + [126.642464, 45.756967], | ||
| 55 | + [116.405285, 39.904989] | ||
| 56 | + ] | ||
| 57 | + } | ||
| 58 | + ], | ||
| 29 | "map": [ | 59 | "map": [ |
| 30 | { | 60 | { |
| 31 | "name": "北京市", | 61 | "name": "北京市", |
| @@ -74,7 +74,7 @@ const toolBoxOption = ref({ | @@ -74,7 +74,7 @@ const toolBoxOption = ref({ | ||
| 74 | top: 20, | 74 | top: 20, |
| 75 | feature: { | 75 | feature: { |
| 76 | myFullButton: { | 76 | myFullButton: { |
| 77 | - show: true, | 77 | + show: false, |
| 78 | title: '返回', | 78 | title: '返回', |
| 79 | icon: iconStr.value, | 79 | icon: iconStr.value, |
| 80 | iconStyle: { | 80 | iconStyle: { |
| @@ -88,22 +88,49 @@ const toolBoxOption = ref({ | @@ -88,22 +88,49 @@ const toolBoxOption = ref({ | ||
| 88 | watch( | 88 | watch( |
| 89 | () => props.chartConfig.option, | 89 | () => props.chartConfig.option, |
| 90 | newData => { | 90 | newData => { |
| 91 | - const { iconColor, drillingIn, iconDistanceRight, iconDistanceTop } = newData | ||
| 92 | - toolBoxOption.value.feature.myFullButton.show = drillingIn | 91 | + const { iconColor, drillingIn, iconDistanceRight, iconDistanceTop, mapRegion } = newData |
| 92 | + if (drillingIn && !newData.saveClickRegion.level) { | ||
| 93 | + toolBoxOption.value.feature.myFullButton.show = !drillingIn | ||
| 94 | + } else if ( | ||
| 95 | + drillingIn && | ||
| 96 | + newData.saveClickRegion.level === areaEnum.PROVINCE && | ||
| 97 | + mapRegion.saveSelect.levelStr === areaEnum.COUNTRY | ||
| 98 | + ) { | ||
| 99 | + toolBoxOption.value.feature.myFullButton.show = drillingIn | ||
| 100 | + } else if ( | ||
| 101 | + drillingIn && | ||
| 102 | + newData.saveClickRegion.level === areaEnum.COUNTRY && | ||
| 103 | + mapRegion.saveSelect.levelStr === areaEnum.COUNTRY | ||
| 104 | + ) { | ||
| 105 | + toolBoxOption.value.feature.myFullButton.show = !drillingIn | ||
| 106 | + } else if ( | ||
| 107 | + drillingIn && | ||
| 108 | + newData.saveClickRegion.level === areaEnum.CITY && | ||
| 109 | + mapRegion.saveSelect.levelStr === areaEnum.PROVINCE | ||
| 110 | + ) { | ||
| 111 | + toolBoxOption.value.feature.myFullButton.show = drillingIn | ||
| 112 | + } else if ( | ||
| 113 | + drillingIn && | ||
| 114 | + newData.saveClickRegion.level === areaEnum.PROVINCE && | ||
| 115 | + mapRegion.saveSelect.levelStr === areaEnum.PROVINCE | ||
| 116 | + ) { | ||
| 117 | + toolBoxOption.value.feature.myFullButton.show = !drillingIn | ||
| 118 | + } | ||
| 93 | toolBoxOption.value.feature.myFullButton.iconStyle.color = iconColor | 119 | toolBoxOption.value.feature.myFullButton.iconStyle.color = iconColor |
| 94 | toolBoxOption.value.right = iconDistanceRight | 120 | toolBoxOption.value.right = iconDistanceRight |
| 95 | toolBoxOption.value.top = iconDistanceTop | 121 | toolBoxOption.value.top = iconDistanceTop |
| 122 | + vEchartsSetOption() | ||
| 96 | }, | 123 | }, |
| 97 | { | 124 | { |
| 98 | - deep: true | 125 | + deep: true, |
| 99 | } | 126 | } |
| 100 | ) | 127 | ) |
| 101 | - | ||
| 102 | props.chartConfig.option = { | 128 | props.chartConfig.option = { |
| 103 | ...props.chartConfig.option, | 129 | ...props.chartConfig.option, |
| 104 | ...{ toolbox: toolBoxOption.value } | 130 | ...{ toolbox: toolBoxOption.value } |
| 105 | } | 131 | } |
| 106 | 132 | ||
| 133 | + | ||
| 107 | //地图点击返回 adcode=100000,名字必须是china | 134 | //地图点击返回 adcode=100000,名字必须是china |
| 108 | const watchAdcode = async () => { | 135 | const watchAdcode = async () => { |
| 109 | if (props.chartConfig.option.drillingIn) { | 136 | if (props.chartConfig.option.drillingIn) { |
| @@ -121,6 +148,7 @@ const watchAdcode = async () => { | @@ -121,6 +148,7 @@ const watchAdcode = async () => { | ||
| 121 | } | 148 | } |
| 122 | await getGeojson(saveAdcode) | 149 | await getGeojson(saveAdcode) |
| 123 | const adcode = saveAdcode === 100000 ? 'china' : saveAdcode | 150 | const adcode = saveAdcode === 100000 ? 'china' : saveAdcode |
| 151 | + props.chartConfig.option.saveClickRegion.level = saveLevelStr.level | ||
| 124 | props.chartConfig.option.geo.map = adcode | 152 | props.chartConfig.option.geo.map = adcode |
| 125 | props.chartConfig.option.series.forEach((item: any) => { | 153 | props.chartConfig.option.series.forEach((item: any) => { |
| 126 | if (item.type === 'map') item.map = adcode | 154 | if (item.type === 'map') item.map = adcode |
| @@ -202,9 +230,19 @@ const vEchartsSetOption = async () => { | @@ -202,9 +230,19 @@ const vEchartsSetOption = async () => { | ||
| 202 | const dataSetHandle = async (dataset: any) => { | 230 | const dataSetHandle = async (dataset: any) => { |
| 203 | props.chartConfig.option.series.forEach((item: any) => { | 231 | props.chartConfig.option.series.forEach((item: any) => { |
| 204 | if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point | 232 | if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point |
| 205 | - else if (item.type === 'map' && dataset.map) item.data = dataset.map | 233 | + else if (item.type === 'lines' && dataset.line) { |
| 234 | + item.data = dataset.line.map((it: any) => { | ||
| 235 | + return { | ||
| 236 | + ...it, | ||
| 237 | + lineStyle: { | ||
| 238 | + color: props.chartConfig.option.series[2].lineStyle.normal.color | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + }) | ||
| 242 | + } else if (item.type === 'map' && dataset.map) item.data = dataset.map | ||
| 206 | }) | 243 | }) |
| 207 | if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces | 244 | if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces |
| 245 | + | ||
| 208 | isPreview() && vEchartsSetOption() | 246 | isPreview() && vEchartsSetOption() |
| 209 | } | 247 | } |
| 210 | 248 | ||
| @@ -245,14 +283,16 @@ watch( | @@ -245,14 +283,16 @@ watch( | ||
| 245 | } | 283 | } |
| 246 | ) | 284 | ) |
| 247 | 285 | ||
| 248 | -//处理数据标点 | 286 | +//处理数据标点包括飞线 |
| 249 | const handleDataPoint = (newData: any) => { | 287 | const handleDataPoint = (newData: any) => { |
| 250 | if (newData === 'china') { | 288 | if (newData === 'china') { |
| 251 | dataSetHandle(dataJson) | 289 | dataSetHandle(dataJson) |
| 252 | } else { | 290 | } else { |
| 253 | const filterPoint = dataJson.point.filter((item: any) => item.adcode === newData) | 291 | const filterPoint = dataJson.point.filter((item: any) => item.adcode === newData) |
| 292 | + // const filterLine = dataJson.line.filter((item: any) => item.adcode === newData) | ||
| 254 | dataSetHandle({ | 293 | dataSetHandle({ |
| 255 | - point: filterPoint | 294 | + point: filterPoint, |
| 295 | + line: [] //由于点击单个地图模块,所以去除飞线 | ||
| 256 | }) | 296 | }) |
| 257 | } | 297 | } |
| 258 | } | 298 | } |
| @@ -274,7 +314,8 @@ watch( | @@ -274,7 +314,8 @@ watch( | ||
| 274 | } | 314 | } |
| 275 | }, | 315 | }, |
| 276 | { | 316 | { |
| 277 | - deep: false | 317 | + deep: false, |
| 318 | + immediate: true, | ||
| 278 | } | 319 | } |
| 279 | ) | 320 | ) |
| 280 | 321 | ||
| @@ -310,8 +351,9 @@ const handleVChartClick = async (params: any) => { | @@ -310,8 +351,9 @@ const handleVChartClick = async (params: any) => { | ||
| 310 | const level = item.properties.level.toUpperCase() | 351 | const level = item.properties.level.toUpperCase() |
| 311 | const adcode = item.properties.adcode | 352 | const adcode = item.properties.adcode |
| 312 | if (level === 'DISTRICT') return | 353 | if (level === 'DISTRICT') return |
| 313 | - if(String(adcode).startsWith('15') && level===areaEnum.CITY) return | 354 | + if (String(adcode).startsWith('15') && level === areaEnum.CITY) return |
| 314 | props.chartConfig.option.mapRegion.adcode = adcode | 355 | props.chartConfig.option.mapRegion.adcode = adcode |
| 356 | + props.chartConfig.option.saveClickRegion.level = level | ||
| 315 | saveLevelStr.level = level | 357 | saveLevelStr.level = level |
| 316 | handleDataPoint(adcode) | 358 | handleDataPoint(adcode) |
| 317 | saveHistoryParent.value.push({ | 359 | saveHistoryParent.value.push({ |
| @@ -5,7 +5,7 @@ import cloneDeep from 'lodash/cloneDeep' | @@ -5,7 +5,7 @@ import cloneDeep from 'lodash/cloneDeep' | ||
| 5 | import { chartInitConfig } from '@/settings/designSetting' | 5 | import { chartInitConfig } from '@/settings/designSetting' |
| 6 | 6 | ||
| 7 | export const option = { | 7 | export const option = { |
| 8 | - dataset: [new URL('/src/assets/external/three/test.obj', import.meta.url).href],//三维数据源 | 8 | + dataset: [new URL('/src/assets/external/three/test.obj', import.meta.url).href], //三维数据源 |
| 9 | backgroundColor: '', //场景背景色 | 9 | backgroundColor: '', //场景背景色 |
| 10 | backgroundAlpha: 0, //场景透明度 | 10 | backgroundAlpha: 0, //场景透明度 |
| 11 | enableDamping: false, //是否启用阻尼 | 11 | enableDamping: false, //是否启用阻尼 |
| @@ -18,7 +18,9 @@ export const option = { | @@ -18,7 +18,9 @@ export const option = { | ||
| 18 | */ | 18 | */ |
| 19 | outputEncoding: 'liner', | 19 | outputEncoding: 'liner', |
| 20 | clearScene: false, //是否清空场景内容 | 20 | clearScene: false, //是否清空场景内容 |
| 21 | - lights: [//灯光为数组,type 为 环境光(AmbientLight) | 方向光(DirectionalLight) | 点光(PointLight) | 半球光(HemisphereLight) | 21 | + lightInput: 1, |
| 22 | + lights: [ | ||
| 23 | + //灯光为数组,type 为 环境光(AmbientLight) | 方向光(DirectionalLight) | 点光(PointLight) | 半球光(HemisphereLight) | ||
| 22 | { | 24 | { |
| 23 | type: 'AmbientLight', | 25 | type: 'AmbientLight', |
| 24 | label: '环境光(只有颜色)', | 26 | label: '环境光(只有颜色)', |
| @@ -54,38 +56,41 @@ export const option = { | @@ -54,38 +56,41 @@ export const option = { | ||
| 54 | size: 1, | 56 | size: 1, |
| 55 | show: false | 57 | show: false |
| 56 | }, | 58 | }, |
| 57 | - position: [//模型位置 | 59 | + position: [ |
| 60 | + //模型位置 | ||
| 58 | { | 61 | { |
| 59 | x: 0, | 62 | x: 0, |
| 60 | y: 0, | 63 | y: 0, |
| 61 | z: 0 | 64 | z: 0 |
| 62 | } | 65 | } |
| 63 | ], | 66 | ], |
| 64 | - rotation: [//模型旋转 | 67 | + rotation: [ |
| 68 | + //模型旋转 | ||
| 65 | { | 69 | { |
| 66 | x: 0, | 70 | x: 0, |
| 67 | y: 0, | 71 | y: 0, |
| 68 | z: 0 | 72 | z: 0 |
| 69 | } | 73 | } |
| 70 | ], | 74 | ], |
| 71 | - showFps:false,//是否显示fps | ||
| 72 | - labels:[ //添加图片/文字标签,暂且支持文字 | 75 | + showFps: false, //是否显示fps |
| 76 | + labels: [ | ||
| 77 | + //添加图片/文字标签,暂且支持文字 | ||
| 73 | { | 78 | { |
| 74 | - image: "", | ||
| 75 | - text: "", | 79 | + image: '', |
| 80 | + text: '', | ||
| 76 | textStyle: { | 81 | textStyle: { |
| 77 | - fontFamily: "Arial", | 82 | + fontFamily: 'Arial', |
| 78 | fontSize: 18, | 83 | fontSize: 18, |
| 79 | - fontWeight: "normal", | 84 | + fontWeight: 'normal', |
| 80 | lineHeight: 1, | 85 | lineHeight: 1, |
| 81 | - color: "#ffffff", | 86 | + color: '#ffffff', |
| 82 | borderWidth: 8, | 87 | borderWidth: 8, |
| 83 | borderRadius: 4, | 88 | borderRadius: 4, |
| 84 | - borderColor: "rgba(0,0,0,1)", | ||
| 85 | - backgroundColor: "rgba(0, 0, 0, 1)" | 89 | + borderColor: 'rgba(0,0,0,1)', |
| 90 | + backgroundColor: 'rgba(0, 0, 0, 1)' | ||
| 86 | }, | 91 | }, |
| 87 | - position: {x:0, y:0, z:0}, | ||
| 88 | - scale:{x:1, y:1, z:0}, | 92 | + position: { x: 0, y: 0, z: 0 }, |
| 93 | + scale: { x: 1, y: 1, z: 0 }, | ||
| 89 | sid: null | 94 | sid: null |
| 90 | } | 95 | } |
| 91 | ] | 96 | ] |
| @@ -11,15 +11,15 @@ | @@ -11,15 +11,15 @@ | ||
| 11 | <n-input-number :min="0" v-model:value="optionData.borderConfig.size" size="small" /> | 11 | <n-input-number :min="0" v-model:value="optionData.borderConfig.size" size="small" /> |
| 12 | </setting-item> | 12 | </setting-item> |
| 13 | </setting-item-box> | 13 | </setting-item-box> |
| 14 | - <setting-item-box name="上传文件"> | 14 | + <setting-item-box name="上传文件"> |
| 15 | <setting-item> | 15 | <setting-item> |
| 16 | <FileUpload | 16 | <FileUpload |
| 17 | - :max="100" | ||
| 18 | - :fileList="optionData.dataset" | ||
| 19 | - :threeSupportFileFormat="threeSupportFileFormat" | ||
| 20 | - :singleFileType="singleFileTypeNotMtl" | ||
| 21 | - @fileStaticUri="handleFileStaticUri" | ||
| 22 | - /> | 17 | + :max="100" |
| 18 | + :fileList="optionData.dataset" | ||
| 19 | + :threeSupportFileFormat="threeSupportFileFormat" | ||
| 20 | + :singleFileType="singleFileTypeNotMtl" | ||
| 21 | + @fileStaticUri="handleFileStaticUri" | ||
| 22 | + /> | ||
| 23 | </setting-item> | 23 | </setting-item> |
| 24 | </setting-item-box> | 24 | </setting-item-box> |
| 25 | <setting-item-box :alone="true"> | 25 | <setting-item-box :alone="true"> |
| @@ -78,8 +78,13 @@ | @@ -78,8 +78,13 @@ | ||
| 78 | </template> | 78 | </template> |
| 79 | </setting-item> | 79 | </setting-item> |
| 80 | </setting-item-box> | 80 | </setting-item-box> |
| 81 | + <setting-item-box name="灯光选择"> | ||
| 82 | + <setting-item name="灯光选择((0,环境光),(1,方向光),(2,点光),(3,半球光))"> | ||
| 83 | + <n-input-number :min="0" :max="3" v-model:value="optionData.lightInput" size="small" /> | ||
| 84 | + </setting-item> | ||
| 85 | + </setting-item-box> | ||
| 81 | <setting-item-box name="灯光配置"> | 86 | <setting-item-box name="灯光配置"> |
| 82 | - <setting-item v-for="(item, index) in optionData.lights" :name="item.label" :key="index"> | 87 | + <setting-item v-for="(item, index) in [optionData.lights[optionData.lightInput]]" :name="item.label" :key="index"> |
| 83 | <n-color-picker | 88 | <n-color-picker |
| 84 | v-if="!includeHemisphereLight.includes(item.type)" | 89 | v-if="!includeHemisphereLight.includes(item.type)" |
| 85 | size="small" | 90 | size="small" |
| @@ -180,15 +185,9 @@ | @@ -180,15 +185,9 @@ | ||
| 180 | <SettingItem v-if="optionData.enableDamping" name="阻尼值"> | 185 | <SettingItem v-if="optionData.enableDamping" name="阻尼值"> |
| 181 | <n-input-number v-model:value="optionData.dampingFactor" :min="0" :max="1" size="small"></n-input-number> | 186 | <n-input-number v-model:value="optionData.dampingFactor" :min="0" :max="1" size="small"></n-input-number> |
| 182 | </SettingItem> | 187 | </SettingItem> |
| 183 | - <SettingItem name="启用动画"> | ||
| 184 | - <n-switch v-model:value="optionData.autoPlay" size="small" /> | ||
| 185 | - </SettingItem> | ||
| 186 | - <SettingItem name="输出编码"> | 188 | + <SettingItem name="输出编码,可取值为 liner 或 sRGB。linear 是 LinearEncoding 线性编码, sRGB 即 sRGBEncoding rgb 模式编码(sRGBEncoding 能更好的还原材质颜色)"> |
| 187 | <n-select v-model:value="optionData.outputEncoding" size="small" :options="encodinghList"></n-select> | 189 | <n-select v-model:value="optionData.outputEncoding" size="small" :options="encodinghList"></n-select> |
| 188 | </SettingItem> | 190 | </SettingItem> |
| 189 | - <SettingItem name="是否清空场景"> | ||
| 190 | - <n-switch @change="handleChange" v-model:value="optionData.clearScene" size="small" /> | ||
| 191 | - </SettingItem> | ||
| 192 | </setting-item-box> | 191 | </setting-item-box> |
| 193 | </collapse-item> | 192 | </collapse-item> |
| 194 | </template> | 193 | </template> |
| @@ -253,10 +252,6 @@ const encodinghList = [ | @@ -253,10 +252,6 @@ const encodinghList = [ | ||
| 253 | { label: 'sRGB ', value: 'sRGB ' } | 252 | { label: 'sRGB ', value: 'sRGB ' } |
| 254 | ] | 253 | ] |
| 255 | 254 | ||
| 256 | -const handleChange = (e: boolean) => { | ||
| 257 | - if (e) props.optionData.dataset = [''] | ||
| 258 | -} | ||
| 259 | - | ||
| 260 | const handleFileStaticUri = (value: UploadFileInfo[]) => { | 255 | const handleFileStaticUri = (value: UploadFileInfo[]) => { |
| 261 | props.optionData.dataset = value.map(item => item?.url)?.filter(Boolean) as any | 256 | props.optionData.dataset = value.map(item => item?.url)?.filter(Boolean) as any |
| 262 | if (Array.isArray(props.optionData.dataset) && props.optionData.dataset.length === 0) { | 257 | if (Array.isArray(props.optionData.dataset) && props.optionData.dataset.length === 0) { |
| 1 | <template> | 1 | <template> |
| 2 | - <div class="go-content-box" :style="{ border: !borderConfig.show ? 'none' : ''}"> | ||
| 3 | - <div v-if="useDetectWebGLContext()"> | 2 | + <div class="go-content-box" :style="{ border: !borderConfig.show ? 'none' : '' }"> |
| 3 | + <div> | ||
| 4 | <vue3dLoader | 4 | <vue3dLoader |
| 5 | ref="vue3dLoaderRef" | 5 | ref="vue3dLoaderRef" |
| 6 | :webGLRendererOptions="webGLRendererOptions" | 6 | :webGLRendererOptions="webGLRendererOptions" |
| @@ -20,7 +20,7 @@ | @@ -20,7 +20,7 @@ | ||
| 20 | @load="onLoad" | 20 | @load="onLoad" |
| 21 | :position="position" | 21 | :position="position" |
| 22 | :rotation="rotation" | 22 | :rotation="rotation" |
| 23 | - :lights="lights" | 23 | + :lights="[lights[lightInput]]" |
| 24 | :showFps="showFps" | 24 | :showFps="showFps" |
| 25 | :labels="labels" | 25 | :labels="labels" |
| 26 | /> | 26 | /> |
| @@ -29,15 +29,13 @@ | @@ -29,15 +29,13 @@ | ||
| 29 | <n-progress type="line" :color="themeColor" :percentage="process" :indicator-placement="'inside'" processing /> | 29 | <n-progress type="line" :color="themeColor" :percentage="process" :indicator-placement="'inside'" processing /> |
| 30 | </div> | 30 | </div> |
| 31 | </div> | 31 | </div> |
| 32 | - <div v-else>您的浏览器不支持WebGL!</div> | ||
| 33 | </div> | 32 | </div> |
| 34 | </template> | 33 | </template> |
| 35 | <script setup lang="ts"> | 34 | <script setup lang="ts"> |
| 36 | -import { PropType, toRefs, ref, nextTick, computed, watch } from 'vue' | 35 | +import { PropType, toRefs, ref, nextTick, computed } from 'vue' |
| 37 | import { CreateComponentType } from '@/packages/index.d' | 36 | import { CreateComponentType } from '@/packages/index.d' |
| 38 | import { vue3dLoader } from 'vue-3d-loader' | 37 | import { vue3dLoader } from 'vue-3d-loader' |
| 39 | import { useDesignStore } from '@/store/modules/designStore/designStore' | 38 | import { useDesignStore } from '@/store/modules/designStore/designStore' |
| 40 | -import { useDetectWebGLContext } from '@/utils/external/useSupportWebGL' | ||
| 41 | 39 | ||
| 42 | const designStore = useDesignStore() | 40 | const designStore = useDesignStore() |
| 43 | 41 | ||
| @@ -54,7 +52,6 @@ const themeColor = computed(() => { | @@ -54,7 +52,6 @@ const themeColor = computed(() => { | ||
| 54 | 52 | ||
| 55 | const vue3dLoaderRef = ref(null) | 53 | const vue3dLoaderRef = ref(null) |
| 56 | 54 | ||
| 57 | -//threejs配置 | ||
| 58 | const webGLRendererOptions = { | 55 | const webGLRendererOptions = { |
| 59 | alpha: true, // 透明 | 56 | alpha: true, // 透明 |
| 60 | antialias: true, // 抗锯齿 | 57 | antialias: true, // 抗锯齿 |
| @@ -96,13 +93,9 @@ const { | @@ -96,13 +93,9 @@ const { | ||
| 96 | rotation, | 93 | rotation, |
| 97 | lights, | 94 | lights, |
| 98 | showFps, | 95 | showFps, |
| 99 | - labels | ||
| 100 | -} = toRefs(props.chartConfig.option) as any | ||
| 101 | - | ||
| 102 | -watch(dataset, (newData: string) => { | ||
| 103 | - //dateset为空则清除场景 | ||
| 104 | - if(!newData) clearScene.value=true | ||
| 105 | -}) | 96 | + labels, |
| 97 | + lightInput | ||
| 98 | +} = toRefs(props.chartConfig.option) as any | ||
| 106 | </script> | 99 | </script> |
| 107 | 100 | ||
| 108 | <style lang="scss" scoped> | 101 | <style lang="scss" scoped> |
| 1 | <template> | 1 | <template> |
| 2 | <div> | 2 | <div> |
| 3 | <n-tree | 3 | <n-tree |
| 4 | + ref="nTreeRef" | ||
| 4 | :accordion="treeConfig.accordion" | 5 | :accordion="treeConfig.accordion" |
| 5 | :checkable="treeConfig.checkable" | 6 | :checkable="treeConfig.checkable" |
| 6 | :default-expand-all="treeConfig.defaultExpandAll" | 7 | :default-expand-all="treeConfig.defaultExpandAll" |
| @@ -11,17 +12,20 @@ | @@ -11,17 +12,20 @@ | ||
| 11 | label-field="name" | 12 | label-field="name" |
| 12 | children-field="children" | 13 | children-field="children" |
| 13 | @update:selected-keys="onClick" | 14 | @update:selected-keys="onClick" |
| 15 | + @update:checked-keys="onClick" | ||
| 16 | + :checked-keys="checkedKeys" | ||
| 14 | /> | 17 | /> |
| 15 | </div> | 18 | </div> |
| 16 | </template> | 19 | </template> |
| 17 | 20 | ||
| 18 | <script setup lang="ts"> | 21 | <script setup lang="ts"> |
| 19 | -import { PropType, toRefs } from 'vue' | 22 | +import { PropType, toRefs, ref } from 'vue' |
| 20 | import { CreateComponentType } from '@/packages/index.d' | 23 | import { CreateComponentType } from '@/packages/index.d' |
| 21 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | 24 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 22 | import { useChartInteract } from '@/hooks/external/useChartSelectInteract.hook' | 25 | import { useChartInteract } from '@/hooks/external/useChartSelectInteract.hook' |
| 23 | import { InteractEventOn } from '@/enums/eventEnum' | 26 | import { InteractEventOn } from '@/enums/eventEnum' |
| 24 | import { ComponentInteractParamsEnum } from './interact' | 27 | import { ComponentInteractParamsEnum } from './interact' |
| 28 | +import { NTree } from 'naive-ui' | ||
| 25 | 29 | ||
| 26 | const props = defineProps({ | 30 | const props = defineProps({ |
| 27 | chartConfig: { | 31 | chartConfig: { |
| @@ -32,7 +36,16 @@ const props = defineProps({ | @@ -32,7 +36,16 @@ const props = defineProps({ | ||
| 32 | 36 | ||
| 33 | const { dataset, treeConfig } = toRefs(props.chartConfig.option) | 37 | const { dataset, treeConfig } = toRefs(props.chartConfig.option) |
| 34 | 38 | ||
| 39 | +const nTreeRef = ref<null | InstanceType<typeof NTree>>(null) | ||
| 40 | + | ||
| 41 | +const checkedKeys = ref([]) | ||
| 42 | + | ||
| 35 | const onClick = (v: string[]) => { | 43 | const onClick = (v: string[]) => { |
| 44 | + // nTreeRef.value?.selectedKeys(v) | ||
| 45 | + console.log(nTreeRef.value) | ||
| 46 | + console.log(v) | ||
| 47 | + // nTreeRef.value?.onUpdateCheckedKeys(v) | ||
| 48 | + if (Array.isArray(v) && v.length == 0) return | ||
| 36 | useChartInteract( | 49 | useChartInteract( |
| 37 | props.chartConfig, | 50 | props.chartConfig, |
| 38 | useChartEditStore, | 51 | useChartEditStore, |
| @@ -2,24 +2,11 @@ | @@ -2,24 +2,11 @@ | ||
| 2 | <collapse-item name="属性" :expanded="true"> | 2 | <collapse-item name="属性" :expanded="true"> |
| 3 | <setting-item-box name="上传图片" :alone="true"> | 3 | <setting-item-box name="上传图片" :alone="true"> |
| 4 | <setting-item> | 4 | <setting-item> |
| 5 | - <n-card class="upload-box"> | ||
| 6 | - <n-upload | ||
| 7 | - :show-file-list="false" | ||
| 8 | - v-model:file-list="uploadFileListRef" | ||
| 9 | - :customRequest="customRequest" | ||
| 10 | - :onBeforeUpload="beforeUploadHandle" | ||
| 11 | - > | ||
| 12 | - <n-upload-dragger> | ||
| 13 | - <img v-if="optionData.dataset" class="upload-show" :src="optionData.dataset" alt="背景" /> | ||
| 14 | - <div class="upload-img" v-show="!optionData.dataset"> | ||
| 15 | - <img src="@/assets/images/canvas/noImage.png" /> | ||
| 16 | - <n-text class="upload-desc" depth="3"> | ||
| 17 | - 图片需小于 {{ backgroundImageSize }}M ,格式为 png/jpg/gif 的文件 | ||
| 18 | - </n-text> | ||
| 19 | - </div> | ||
| 20 | - </n-upload-dragger> | ||
| 21 | - </n-upload> | ||
| 22 | - </n-card> | 5 | + <TKUpload |
| 6 | + :uploadImageUrl="props.optionData.dataset" | ||
| 7 | + @sendFile="handleSendFile" | ||
| 8 | + @removeFile="handleRemoveFile" | ||
| 9 | + /> | ||
| 23 | </setting-item> | 10 | </setting-item> |
| 24 | </setting-item-box> | 11 | </setting-item-box> |
| 25 | <setting-item-box name="样式"> | 12 | <setting-item-box name="样式"> |
| @@ -39,14 +26,10 @@ | @@ -39,14 +26,10 @@ | ||
| 39 | </template> | 26 | </template> |
| 40 | 27 | ||
| 41 | <script setup lang="ts"> | 28 | <script setup lang="ts"> |
| 42 | -import { PropType, ref, nextTick } from 'vue' | 29 | +import { PropType } from 'vue' |
| 43 | import { option } from './config' | 30 | import { option } from './config' |
| 44 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | 31 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 45 | -import { FileTypeEnum } from '@/enums/fileTypeEnum' | ||
| 46 | -import { uploadFile } from '@/api/external/contentSave/content' | ||
| 47 | -import { UploadCustomRequestOptions } from 'naive-ui' | ||
| 48 | -import { backgroundImageSize } from '@/settings/designSetting' | ||
| 49 | -import { fetchRouteParamsLocation } from '@/utils' | 32 | +import { TKUpload } from '@/components/external/Common/TKUpload' |
| 50 | 33 | ||
| 51 | const props = defineProps({ | 34 | const props = defineProps({ |
| 52 | optionData: { | 35 | optionData: { |
| @@ -55,48 +38,12 @@ const props = defineProps({ | @@ -55,48 +38,12 @@ const props = defineProps({ | ||
| 55 | } | 38 | } |
| 56 | }) | 39 | }) |
| 57 | 40 | ||
| 58 | -const uploadFileListRef = ref() | ||
| 59 | - | ||
| 60 | -// 上传图片前置处理 | ||
| 61 | -// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| 62 | -//@ts-ignore | ||
| 63 | -const beforeUploadHandle = async ({ file }) => { | ||
| 64 | - uploadFileListRef.value = [] | ||
| 65 | - const type = file.file.type | ||
| 66 | - const size = file.file.size | ||
| 67 | - | ||
| 68 | - if (size > 1024 * 1024 * backgroundImageSize) { | ||
| 69 | - window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`) | ||
| 70 | - return false | ||
| 71 | - } | ||
| 72 | - if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) { | ||
| 73 | - window['$message'].warning('文件格式不符合,请重新上传!') | ||
| 74 | - return false | ||
| 75 | - } | ||
| 76 | - return true | 41 | +const handleSendFile = (file: string) => { |
| 42 | + if (!file) return | ||
| 43 | + props.optionData.dataset = file | ||
| 77 | } | 44 | } |
| 78 | 45 | ||
| 79 | -// 自定义上传操作 | ||
| 80 | -const customRequest = (options: UploadCustomRequestOptions) => { | ||
| 81 | - const { file } = options | ||
| 82 | - nextTick(async () => { | ||
| 83 | - if (file.file) { | ||
| 84 | - // 修改名称 | ||
| 85 | - const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_background.png`, { | ||
| 86 | - type: file.file.type | ||
| 87 | - }) | ||
| 88 | - let uploadParams = new FormData() | ||
| 89 | - uploadParams.append('file', newNameFile) | ||
| 90 | - const uploadRes = await uploadFile(uploadParams) | ||
| 91 | - if (uploadRes) { | ||
| 92 | - props.optionData.dataset = uploadRes?.fileStaticUri | ||
| 93 | - window['$message'].success('添加图片成功!') | ||
| 94 | - } | ||
| 95 | - } else { | ||
| 96 | - window['$message'].error('添加图片失败,请稍后重试!') | ||
| 97 | - } | ||
| 98 | - }) | ||
| 99 | -} | 46 | +const handleRemoveFile = (status: boolean) => (status ? (props.optionData.dataset = '') : null) |
| 100 | 47 | ||
| 101 | // 适应类型 | 48 | // 适应类型 |
| 102 | const fitList = [ | 49 | const fitList = [ |
| @@ -122,35 +69,3 @@ const fitList = [ | @@ -122,35 +69,3 @@ const fitList = [ | ||
| 122 | } | 69 | } |
| 123 | ] | 70 | ] |
| 124 | </script> | 71 | </script> |
| 125 | -<style lang="scss" scoped> | ||
| 126 | -$uploadHeight: 193px; | ||
| 127 | -.upload-box { | ||
| 128 | - cursor: pointer; | ||
| 129 | - margin-bottom: 20px; | ||
| 130 | - @include deep() { | ||
| 131 | - .n-card__content { | ||
| 132 | - padding: 0; | ||
| 133 | - overflow: hidden; | ||
| 134 | - } | ||
| 135 | - .n-upload-dragger { | ||
| 136 | - padding: 5px; | ||
| 137 | - } | ||
| 138 | - } | ||
| 139 | - .upload-show { | ||
| 140 | - width: -webkit-fill-available; | ||
| 141 | - height: $uploadHeight; | ||
| 142 | - border-radius: 5px; | ||
| 143 | - } | ||
| 144 | - .upload-img { | ||
| 145 | - display: flex; | ||
| 146 | - flex-direction: column; | ||
| 147 | - align-items: center; | ||
| 148 | - img { | ||
| 149 | - height: 150px; | ||
| 150 | - } | ||
| 151 | - .upload-desc { | ||
| 152 | - padding: 10px 0; | ||
| 153 | - } | ||
| 154 | - } | ||
| 155 | -} | ||
| 156 | -</style> |
| 1 | <template> | 1 | <template> |
| 2 | - <div class="videoPlay"> | 2 | + <div class="go-content-box" :style="{ width: w + 'px', height: h + 'px' }"> |
| 3 | <video | 3 | <video |
| 4 | - style="object-fit: cover" | ||
| 5 | - :poster="poster" | ||
| 6 | crossOrigin="anonymous" | 4 | crossOrigin="anonymous" |
| 5 | + :id="`my-player`" | ||
| 7 | ref="videoRef" | 6 | ref="videoRef" |
| 8 | - class="video-js vjs-default-skin vjs-big-play-centered" | ||
| 9 | - controls | ||
| 10 | - > | ||
| 11 | - <source :src="path" /> | ||
| 12 | - </video> | 7 | + class="video-js my-video vjs-theme-city vjs-big-play-centered" |
| 8 | + ></video> | ||
| 13 | </div> | 9 | </div> |
| 14 | </template> | 10 | </template> |
| 15 | -<script lang="ts" setup> | ||
| 16 | -import { nextTick, onBeforeUnmount, onMounted, ref, watch, toRefs } from 'vue' | ||
| 17 | -import videojs, { VideoJsPlayer } from 'video.js' | 11 | +<script setup lang="ts"> |
| 12 | +import { onMounted, ref, onUnmounted, watch, unref } from 'vue' | ||
| 13 | +import videojs from 'video.js' | ||
| 14 | +import 'videojs-flvjs-es6' | ||
| 18 | import type { VideoJsPlayerOptions } from 'video.js' | 15 | import type { VideoJsPlayerOptions } from 'video.js' |
| 19 | -import 'video.js/dist/video-js.css' | ||
| 20 | -import zh from 'video.js/dist/lang/zh-CN.json' | ||
| 21 | - | ||
| 22 | -const props = withDefaults( | ||
| 23 | - defineProps<{ | ||
| 24 | - path: string | ||
| 25 | - autoPlay?: boolean | ||
| 26 | - h?: number | ||
| 27 | - poster?: string | ||
| 28 | - }>(), | ||
| 29 | - { autoPlay: false } | ||
| 30 | -) | 16 | +import 'video.js/dist/video-js.min.css' |
| 17 | +import { getJwtToken, getShareJwtToken } from '@/utils/external/auth' | ||
| 18 | +import { isShareMode } from '@/views/share/hook' | ||
| 19 | +import { getOpenFlvPlayUrl, closeFlvPlay } from '@/api/external/flvPlay' | ||
| 20 | +import { useFingerprint } from '@/utils/external/useFingerprint' | ||
| 21 | +import { GetResult } from '@fingerprintjs/fingerprintjs' | ||
| 31 | 22 | ||
| 32 | -const videoRef = ref() | ||
| 33 | - | ||
| 34 | -let player: VideoJsPlayer | ||
| 35 | - | ||
| 36 | -const initPlay = async () => { | ||
| 37 | - videojs.addLanguage('zh-CN', zh) | ||
| 38 | - await nextTick() | ||
| 39 | - const options: VideoJsPlayerOptions = { | ||
| 40 | - muted: true, | ||
| 41 | - controls: true, | ||
| 42 | - autoplay: props.autoPlay, | ||
| 43 | - src: props?.path, | ||
| 44 | - poster: props?.poster, | ||
| 45 | - language: 'zh-CN', | ||
| 46 | - techOrder: ['html5'], | ||
| 47 | - preload: 'none' | 23 | +const props = defineProps({ |
| 24 | + sourceSrc: { | ||
| 25 | + type: String | ||
| 26 | + }, | ||
| 27 | + autoplay: { | ||
| 28 | + type: Boolean | ||
| 29 | + }, | ||
| 30 | + name: { | ||
| 31 | + type: String | ||
| 32 | + }, | ||
| 33 | + avatar: { | ||
| 34 | + type: String | ||
| 35 | + }, | ||
| 36 | + w: { | ||
| 37 | + type: Number, | ||
| 38 | + default: 300 | ||
| 39 | + }, | ||
| 40 | + h: { | ||
| 41 | + type: Number, | ||
| 42 | + default: 300 | ||
| 48 | } | 43 | } |
| 49 | - player = videojs(videoRef.value, options, () => { | ||
| 50 | - videojs.log('Video is reading') | ||
| 51 | - if (props.autoPlay) { | ||
| 52 | - player.play() | 44 | +}) |
| 45 | + | ||
| 46 | +enum VideoPlayerType { | ||
| 47 | + m3u8 = 'application/x-mpegURL', | ||
| 48 | + mp4 = 'video/mp4', | ||
| 49 | + webm = 'video/webm', | ||
| 50 | + flv = 'video/x-flv' | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +const isRtspProtocol = (url: string) => { | ||
| 54 | + const reg = /^rtsp:\/\//g | ||
| 55 | + return reg.test(url) | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +const getVideoTypeByUrl = (url = '') => { | ||
| 59 | + try { | ||
| 60 | + const { protocol, pathname } = new URL(url) | ||
| 61 | + if (protocol.startsWith('rtsp:')) return VideoPlayerType.flv | ||
| 62 | + const reg = /[^.]\w*$/ | ||
| 63 | + const mathValue = pathname.match(reg) || [] | ||
| 64 | + const ext = (mathValue[0] as keyof typeof VideoPlayerType) || 'webm' | ||
| 65 | + const type = VideoPlayerType[ext] | ||
| 66 | + return type ? type : VideoPlayerType.webm | ||
| 67 | + } catch (error) { | ||
| 68 | + console.error(error) | ||
| 69 | + return VideoPlayerType.webm | ||
| 70 | + } | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +// video标签 | ||
| 74 | +const videoRef = ref<HTMLElement | null>(null) | ||
| 75 | + | ||
| 76 | +// video实例对象 | ||
| 77 | +let videoPlayer: videojs.Player | null = null | ||
| 78 | + | ||
| 79 | +const fingerprintResult = ref<Nullable<GetResult>>(null) | ||
| 80 | + | ||
| 81 | +//options配置 | ||
| 82 | +const options: VideoJsPlayerOptions & Recordable = { | ||
| 83 | + language: 'zh-CN', // 设置语言 | ||
| 84 | + controls: true, // 是否显示控制条 | ||
| 85 | + preload: 'auto', // 预加载 | ||
| 86 | + autoplay: true, // 是否自动播放 | ||
| 87 | + fluid: false, // 自适应宽高 | ||
| 88 | + poster: props?.avatar || '', | ||
| 89 | + // src: getSource() || '', // 要嵌入的视频源的源 URL | ||
| 90 | + sources: [], | ||
| 91 | + muted: true, | ||
| 92 | + userActions: { | ||
| 93 | + hotkeys: true | ||
| 94 | + }, | ||
| 95 | + techOrder: ['html5', 'flvjs'], | ||
| 96 | + flvjs: { | ||
| 97 | + mediaDataSource: { | ||
| 98 | + isLive: true, | ||
| 99 | + cors: true, | ||
| 100 | + withCredentials: false, | ||
| 101 | + hasAudio: false | ||
| 102 | + }, | ||
| 103 | + config: { | ||
| 104 | + autoCleanupSourceBuffer: true | ||
| 53 | } | 105 | } |
| 54 | - player.on('ended', () => { | ||
| 55 | - videojs.log('Play end') | ||
| 56 | - }) | ||
| 57 | - player.on('error', () => { | ||
| 58 | - player.errorDisplay.close() | ||
| 59 | - videojs.log('Play parse error') | ||
| 60 | - }) | ||
| 61 | - }) | 106 | + } |
| 62 | } | 107 | } |
| 63 | 108 | ||
| 64 | -onMounted(() => { | ||
| 65 | - initPlay() | ||
| 66 | -}) | 109 | +const { getResult } = useFingerprint() |
| 110 | +async function getSource() { | ||
| 111 | + fingerprintResult.value = await getResult() | ||
| 112 | + let src = props.sourceSrc || '' | ||
| 113 | + if (isRtspProtocol(props.sourceSrc!)) { | ||
| 114 | + src = getOpenFlvPlayUrl(src, unref(fingerprintResult)?.visitorId || '') | ||
| 115 | + } | ||
| 116 | + return [ | ||
| 117 | + { | ||
| 118 | + type: getVideoTypeByUrl(props.sourceSrc), | ||
| 119 | + src | ||
| 120 | + } | ||
| 121 | + ] | ||
| 122 | +} | ||
| 67 | 123 | ||
| 68 | -//直接改变路径测试 | ||
| 69 | -watch( | ||
| 70 | - () => props.path, | ||
| 71 | - () => { | ||
| 72 | - if (props.path && props.autoPlay) { | ||
| 73 | - try { | ||
| 74 | - player?.pause() | ||
| 75 | - player?.load() | ||
| 76 | - player?.src(props.path) | ||
| 77 | - player?.play() | ||
| 78 | - } catch (e) { | ||
| 79 | - console.log(e) | 124 | +// 初始化videojs |
| 125 | +const initVideo = async () => { | ||
| 126 | + if (videoRef.value) { | ||
| 127 | + // 创建 video 实例 | ||
| 128 | + options.sources = await getSource() | ||
| 129 | + if (options.sources && options.sources.length) { | ||
| 130 | + if (isRtspProtocol(props.sourceSrc || '')) { | ||
| 131 | + options.flvjs = { | ||
| 132 | + ...(options.flvjs || {}), | ||
| 133 | + config: { | ||
| 134 | + headers: { | ||
| 135 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}` | ||
| 136 | + } | ||
| 137 | + } | ||
| 138 | + } | ||
| 80 | } | 139 | } |
| 140 | + videoPlayer = videojs(videoRef.value, options) | ||
| 81 | } | 141 | } |
| 142 | + } | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +watch( | ||
| 146 | + () => props.sourceSrc, | ||
| 147 | + async (newData: any) => { | ||
| 148 | + const result = await getSource() | ||
| 149 | + // props.sourceSrc = newData | ||
| 150 | + videoPlayer?.src(result) as any | ||
| 151 | + videoPlayer?.play() | ||
| 82 | }, | 152 | }, |
| 83 | { | 153 | { |
| 84 | immediate: true | 154 | immediate: true |
| 85 | } | 155 | } |
| 86 | ) | 156 | ) |
| 87 | 157 | ||
| 88 | -onBeforeUnmount(() => { | ||
| 89 | - player?.dispose() | 158 | +onMounted(() => { |
| 159 | + initVideo() | ||
| 160 | +}) | ||
| 161 | + | ||
| 162 | +onUnmounted(() => { | ||
| 163 | + if (props.sourceSrc) { | ||
| 164 | + closeFlvPlay(props.sourceSrc, unref(fingerprintResult)!.visitorId!) | ||
| 165 | + } | ||
| 166 | + handleVideoDispose() | ||
| 167 | +}) | ||
| 168 | + | ||
| 169 | +//播放 | ||
| 170 | +const handleVideoPlay = () => videoPlayer?.play() | ||
| 171 | + | ||
| 172 | +const handleVideoDispose = () => videoPlayer?.dispose() && videoPlayer?.pause() | ||
| 173 | +//暂停 | ||
| 174 | +defineExpose({ | ||
| 175 | + handleVideoPlay, | ||
| 176 | + handleVideoDispose | ||
| 90 | }) | 177 | }) |
| 91 | </script> | 178 | </script> |
| 92 | -<style> | ||
| 93 | -.vjs-poster { | ||
| 94 | - background-size: cover !important; | ||
| 95 | -} | ||
| 96 | -</style> | 179 | + |
| 97 | <style lang="scss" scoped> | 180 | <style lang="scss" scoped> |
| 98 | -.videoPlay { | ||
| 99 | - flex: 1; | ||
| 100 | - height: v-bind('`${h}px`'); | ||
| 101 | - .video-js { | ||
| 102 | - height: 100%; | ||
| 103 | - width: 100%; | 181 | +.go-content-box { |
| 182 | + display: flex; | ||
| 183 | + align-items: center; | ||
| 184 | + justify-content: center; | ||
| 185 | + | ||
| 186 | + .my-video { | ||
| 187 | + width: 100% !important; | ||
| 188 | + height: 100% !important; | ||
| 189 | + position: relative; | ||
| 104 | } | 190 | } |
| 105 | } | 191 | } |
| 106 | </style> | 192 | </style> |
| @@ -2,29 +2,12 @@ | @@ -2,29 +2,12 @@ | ||
| 2 | <CollapseItem name="播放器配置" :expanded="true"> | 2 | <CollapseItem name="播放器配置" :expanded="true"> |
| 3 | <setting-item-box name="上传图片" :alone="true"> | 3 | <setting-item-box name="上传图片" :alone="true"> |
| 4 | <setting-item> | 4 | <setting-item> |
| 5 | - <n-card class="upload-box"> | ||
| 6 | - <n-upload | ||
| 7 | - :show-file-list="false" | ||
| 8 | - v-model:file-list="uploadFileListRef" | ||
| 9 | - :customRequest="customRequest" | ||
| 10 | - :onBeforeUpload="beforeUploadHandle" | ||
| 11 | - > | ||
| 12 | - <n-upload-dragger> | ||
| 13 | - <img v-if="optionData.poster" class="upload-show" :src="optionData.poster" alt="背景" /> | ||
| 14 | - <div class="upload-img" v-show="!optionData.poster"> | ||
| 15 | - <img src="@/assets/images/canvas/noImage.png" /> | ||
| 16 | - <n-text class="upload-desc" depth="3"> | ||
| 17 | - 图片需小于 {{ backgroundImageSize }}M ,格式为 png/jpg/gif 的文件 | ||
| 18 | - </n-text> | ||
| 19 | - </div> | ||
| 20 | - </n-upload-dragger> | ||
| 21 | - </n-upload> | ||
| 22 | - </n-card> | 5 | + <TKUpload :uploadImageUrl="optionData.poster" @sendFile="handleSendFile" @removeFile="handleRemoveFile" /> |
| 23 | </setting-item> | 6 | </setting-item> |
| 24 | </setting-item-box> | 7 | </setting-item-box> |
| 25 | <setting-item-box name="源类型" :alone="true"> | 8 | <setting-item-box name="源类型" :alone="true"> |
| 26 | <setting-item> | 9 | <setting-item> |
| 27 | - <n-radio-group @change="handleChecked" v-model:value="optionData.sourceType" name="radiogroup"> | 10 | + <n-radio-group @update:value="handleChecked" v-model:value="optionData.sourceType" name="radiogroup"> |
| 28 | <n-space> | 11 | <n-space> |
| 29 | <n-radio v-for="(item, index) in sourceTypes" :key="item.value" :value="item.value"> | 12 | <n-radio v-for="(item, index) in sourceTypes" :key="item.value" :value="item.value"> |
| 30 | {{ item.label }} | 13 | {{ item.label }} |
| @@ -55,7 +38,12 @@ | @@ -55,7 +38,12 @@ | ||
| 55 | </setting-item-box> | 38 | </setting-item-box> |
| 56 | <setting-item-box v-if="optionData.sourceType === sourceTypeEnum.PLATFORM" name="视频" :alone="true"> | 39 | <setting-item-box v-if="optionData.sourceType === sourceTypeEnum.PLATFORM" name="视频" :alone="true"> |
| 57 | <setting-item> | 40 | <setting-item> |
| 58 | - <n-select @update:value="handleSelect" v-model:value="optionData.dataset" :options="videoOptions" /> | 41 | + <n-select |
| 42 | + @update:value="handleSelect" | ||
| 43 | + v-model:value="optionData.dataset" | ||
| 44 | + :options="videoOptions" | ||
| 45 | + placeholder="请选择视频地址" | ||
| 46 | + /> | ||
| 59 | </setting-item> | 47 | </setting-item> |
| 60 | </setting-item-box> | 48 | </setting-item-box> |
| 61 | <setting-item-box name="自动播放"> | 49 | <setting-item-box name="自动播放"> |
| @@ -67,15 +55,21 @@ | @@ -67,15 +55,21 @@ | ||
| 67 | </template> | 55 | </template> |
| 68 | 56 | ||
| 69 | <script setup lang="ts"> | 57 | <script setup lang="ts"> |
| 70 | -import { PropType, ref, nextTick, onMounted } from 'vue' | 58 | +import { PropType, ref, onMounted } from 'vue' |
| 71 | import { option, sourceTypeEnum } from './config' | 59 | import { option, sourceTypeEnum } from './config' |
| 72 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | 60 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 73 | -import { FileTypeEnum } from '@/enums/fileTypeEnum' | ||
| 74 | -import { uploadFile } from '@/api/external/contentSave/content' | ||
| 75 | -import { UploadCustomRequestOptions, NTreeSelect } from 'naive-ui' | ||
| 76 | -import { backgroundImageSize } from '@/settings/designSetting' | ||
| 77 | -import { fetchRouteParamsLocation } from '@/utils' | 61 | +import { NTreeSelect } from 'naive-ui' |
| 78 | import { getOrganizationList, getVideoList, getVideoUrl } from '@/api/external/common/index' | 62 | import { getOrganizationList, getVideoList, getVideoUrl } from '@/api/external/common/index' |
| 63 | +import { TKUpload } from '@/components/external/Common/TKUpload' | ||
| 64 | + | ||
| 65 | +interface videoListIF { | ||
| 66 | + name: string | ||
| 67 | + accessMode: number | ||
| 68 | + id: string | ||
| 69 | + videoUrl: string | ||
| 70 | + label: string | ||
| 71 | + value: string | ||
| 72 | +} | ||
| 79 | 73 | ||
| 80 | const props = defineProps({ | 74 | const props = defineProps({ |
| 81 | optionData: { | 75 | optionData: { |
| @@ -84,8 +78,6 @@ const props = defineProps({ | @@ -84,8 +78,6 @@ const props = defineProps({ | ||
| 84 | } | 78 | } |
| 85 | }) | 79 | }) |
| 86 | 80 | ||
| 87 | -const uploadFileListRef = ref() | ||
| 88 | - | ||
| 89 | const sourceTypes = [ | 81 | const sourceTypes = [ |
| 90 | { | 82 | { |
| 91 | value: 'custom', | 83 | value: 'custom', |
| @@ -99,7 +91,7 @@ const sourceTypes = [ | @@ -99,7 +91,7 @@ const sourceTypes = [ | ||
| 99 | 91 | ||
| 100 | const originationOption = ref([]) | 92 | const originationOption = ref([]) |
| 101 | 93 | ||
| 102 | -const videoOptions = ref([]) | 94 | +const videoOptions = ref<videoListIF[]>([]) |
| 103 | 95 | ||
| 104 | const getOriginationList = async () => { | 96 | const getOriginationList = async () => { |
| 105 | const res = await getOrganizationList() | 97 | const res = await getOrganizationList() |
| @@ -113,7 +105,8 @@ const handleUpdateTreeValue = (value: string) => { | @@ -113,7 +105,8 @@ const handleUpdateTreeValue = (value: string) => { | ||
| 113 | 105 | ||
| 114 | const getVideoLists = async (organizationId: string) => { | 106 | const getVideoLists = async (organizationId: string) => { |
| 115 | const res = await getVideoList({ organizationId }) | 107 | const res = await getVideoList({ organizationId }) |
| 116 | - videoOptions.value = res?.data?.map((item: any) => ({ | 108 | + if (!res) return |
| 109 | + videoOptions.value = res?.data?.map((item: videoListIF) => ({ | ||
| 117 | label: item.name, | 110 | label: item.name, |
| 118 | value: item.accessMode === 1 ? item.id : item.videoUrl, | 111 | value: item.accessMode === 1 ? item.id : item.videoUrl, |
| 119 | id: item.id, | 112 | id: item.id, |
| @@ -128,16 +121,16 @@ const getVideoUrlById = async (id: string) => { | @@ -128,16 +121,16 @@ const getVideoUrlById = async (id: string) => { | ||
| 128 | props.optionData.url = url | 121 | props.optionData.url = url |
| 129 | } | 122 | } |
| 130 | 123 | ||
| 131 | -const handleChecked = ({ target }: any) => { | 124 | +const handleChecked = (value: string) => { |
| 132 | props.optionData.dataset = '' | 125 | props.optionData.dataset = '' |
| 133 | - const { value } = target | ||
| 134 | if (value === sourceTypeEnum.PLATFORM) { | 126 | if (value === sourceTypeEnum.PLATFORM) { |
| 135 | getOriginationList() | 127 | getOriginationList() |
| 136 | } | 128 | } |
| 137 | } | 129 | } |
| 138 | 130 | ||
| 139 | -const handleSelect = (value: string, e: any) => { | 131 | +const handleSelect = (_: string, e: videoListIF) => { |
| 140 | const { accessMode, id } = e | 132 | const { accessMode, id } = e |
| 133 | + //1表示,需要从服务端调取接口换取播放的地址,0则不需要 | ||
| 141 | if (accessMode === 1) { | 134 | if (accessMode === 1) { |
| 142 | getVideoUrlById(id) | 135 | getVideoUrlById(id) |
| 143 | } else { | 136 | } else { |
| @@ -154,76 +147,10 @@ onMounted(() => { | @@ -154,76 +147,10 @@ onMounted(() => { | ||
| 154 | } | 147 | } |
| 155 | }) | 148 | }) |
| 156 | 149 | ||
| 157 | -// 上传图片前置处理 | ||
| 158 | -// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| 159 | -//@ts-ignore | ||
| 160 | -const beforeUploadHandle = async ({ file }) => { | ||
| 161 | - uploadFileListRef.value = [] | ||
| 162 | - const type = file.file.type | ||
| 163 | - const size = file.file.size | ||
| 164 | - | ||
| 165 | - if (size > 1024 * 1024 * backgroundImageSize) { | ||
| 166 | - window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`) | ||
| 167 | - return false | ||
| 168 | - } | ||
| 169 | - if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) { | ||
| 170 | - window['$message'].warning('文件格式不符合,请重新上传!') | ||
| 171 | - return false | ||
| 172 | - } | ||
| 173 | - return true | 150 | +const handleSendFile = (file: string) => { |
| 151 | + if (!file) return | ||
| 152 | + props.optionData.poster = file | ||
| 174 | } | 153 | } |
| 175 | 154 | ||
| 176 | -// 自定义上传操作 | ||
| 177 | -const customRequest = (options: UploadCustomRequestOptions) => { | ||
| 178 | - const { file } = options | ||
| 179 | - nextTick(async () => { | ||
| 180 | - if (file.file) { | ||
| 181 | - // 修改名称 | ||
| 182 | - const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_background.png`, { | ||
| 183 | - type: file.file.type | ||
| 184 | - }) | ||
| 185 | - let uploadParams = new FormData() | ||
| 186 | - uploadParams.append('file', newNameFile) | ||
| 187 | - const uploadRes = await uploadFile(uploadParams) | ||
| 188 | - if (uploadRes) { | ||
| 189 | - props.optionData.poster = uploadRes?.fileStaticUri | ||
| 190 | - window['$message'].success('添加图片成功!') | ||
| 191 | - } | ||
| 192 | - } else { | ||
| 193 | - window['$message'].error('添加图片失败,请稍后重试!') | ||
| 194 | - } | ||
| 195 | - }) | ||
| 196 | -} | 155 | +const handleRemoveFile = (status: boolean) => (status ? (props.optionData.poster = '') : null) |
| 197 | </script> | 156 | </script> |
| 198 | -<style lang="scss" scoped> | ||
| 199 | -$uploadHeight: 193px; | ||
| 200 | -.upload-box { | ||
| 201 | - cursor: pointer; | ||
| 202 | - margin-bottom: 20px; | ||
| 203 | - @include deep() { | ||
| 204 | - .n-card__content { | ||
| 205 | - padding: 0; | ||
| 206 | - overflow: hidden; | ||
| 207 | - } | ||
| 208 | - .n-upload-dragger { | ||
| 209 | - padding: 5px; | ||
| 210 | - } | ||
| 211 | - } | ||
| 212 | - .upload-show { | ||
| 213 | - width: -webkit-fill-available; | ||
| 214 | - height: $uploadHeight; | ||
| 215 | - border-radius: 5px; | ||
| 216 | - } | ||
| 217 | - .upload-img { | ||
| 218 | - display: flex; | ||
| 219 | - flex-direction: column; | ||
| 220 | - align-items: center; | ||
| 221 | - img { | ||
| 222 | - height: 150px; | ||
| 223 | - } | ||
| 224 | - .upload-desc { | ||
| 225 | - padding: 10px 0; | ||
| 226 | - } | ||
| 227 | - } | ||
| 228 | -} | ||
| 229 | -</style> |
| 1 | <template> | 1 | <template> |
| 2 | <div> | 2 | <div> |
| 3 | - <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" :poster="option.poster" /> | 3 | + <VideoPlay :w="w" :h="h" :sourceSrc="option.dataset" :autoPlay="autoplay" :avatar="option.poster" /> |
| 4 | </div> | 4 | </div> |
| 5 | </template> | 5 | </template> |
| 6 | <script setup lang="ts"> | 6 | <script setup lang="ts"> |
| @@ -16,7 +16,7 @@ const props = defineProps({ | @@ -16,7 +16,7 @@ const props = defineProps({ | ||
| 16 | } | 16 | } |
| 17 | }) | 17 | }) |
| 18 | 18 | ||
| 19 | -const { h } = toRefs(props.chartConfig.attr) | 19 | +const { w, h } = toRefs(props.chartConfig.attr) |
| 20 | 20 | ||
| 21 | const { autoplay, dataset, poster, url } = toRefs(props.chartConfig.option) | 21 | const { autoplay, dataset, poster, url } = toRefs(props.chartConfig.option) |
| 22 | 22 |
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | <collapse-item name="信息" :expanded="true"> | 2 | <collapse-item name="信息" :expanded="true"> |
| 3 | <setting-item-box name="文字" :alone="true"> | 3 | <setting-item-box name="文字" :alone="true"> |
| 4 | <setting-item> | 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 | </setting-item> | 6 | </setting-item> |
| 7 | </setting-item-box> | 7 | </setting-item-box> |
| 8 | </collapse-item> | 8 | </collapse-item> |
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | <collapse-item name="信息" :expanded="true"> | 2 | <collapse-item name="信息" :expanded="true"> |
| 3 | <setting-item-box name="文字" :alone="true"> | 3 | <setting-item-box name="文字" :alone="true"> |
| 4 | <setting-item> | 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 | </setting-item> | 6 | </setting-item> |
| 7 | </setting-item-box> | 7 | </setting-item-box> |
| 8 | <setting-item-box name="链接" :alone="true"> | 8 | <setting-item-box name="链接" :alone="true"> |
| 1 | <template> | 1 | <template> |
| 2 | <div class="go-text-box"> | 2 | <div class="go-text-box"> |
| 3 | <div class="content"> | 3 | <div class="content"> |
| 4 | - <span style="cursor: pointer" v-if="link" @click="click">{{ option.dataset }}</span> | ||
| 5 | - <span v-else>{{ option.dataset }}</span> | 4 | + <span style="cursor: pointer;white-space: pre-wrap" v-if="link" @click="click">{{ option.dataset }}</span> |
| 5 | + <span style="white-space: pre-wrap" v-else>{{ option.dataset }}</span> | ||
| 6 | </div> | 6 | </div> |
| 7 | </div> | 7 | </div> |
| 8 | </template> | 8 | </template> |
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | <collapse-item name="信息" :expanded="true"> | 2 | <collapse-item name="信息" :expanded="true"> |
| 3 | <setting-item-box name="文字" :alone="true"> | 3 | <setting-item-box name="文字" :alone="true"> |
| 4 | <setting-item> | 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 | </setting-item> | 6 | </setting-item> |
| 7 | </setting-item-box> | 7 | </setting-item-box> |
| 8 | </collapse-item> | 8 | </collapse-item> |
| @@ -353,9 +353,9 @@ useChartDataFetch(props.chartConfig, useChartEditStore, async (resData: any[], r | @@ -353,9 +353,9 @@ useChartDataFetch(props.chartConfig, useChartEditStore, async (resData: any[], r | ||
| 353 | attribute.forEach((item: any) => { | 353 | attribute.forEach((item: any) => { |
| 354 | if (item.identifier === curr[0]) { | 354 | if (item.identifier === curr[0]) { |
| 355 | curr[0] = item.name | 355 | curr[0] = item.name |
| 356 | + acc.push(curr) | ||
| 356 | } | 357 | } |
| 357 | }) | 358 | }) |
| 358 | - acc.push(curr) | ||
| 359 | return [...acc] | 359 | return [...acc] |
| 360 | }, []) | 360 | }, []) |
| 361 | props.chartConfig.option.dataset = resDataFormat | 361 | props.chartConfig.option.dataset = resDataFormat |
| @@ -27,6 +27,16 @@ export let packagesList: PackagesType = { | @@ -27,6 +27,16 @@ export let packagesList: PackagesType = { | ||
| 27 | [PackagesCategoryEnum.ICONS]: IconList | 27 | [PackagesCategoryEnum.ICONS]: IconList |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | +// 组件缓存, 可以大幅度提升组件加载速度 | ||
| 31 | +const componentCacheMap = new Map<string, any>() | ||
| 32 | +const loadConfig = (packageName: string, categoryName: string, keyName: string) => { | ||
| 33 | + const key = packageName + categoryName + keyName | ||
| 34 | + if (!componentCacheMap.has(key)) { | ||
| 35 | + componentCacheMap.set(key, import(`./components/${packageName}/${categoryName}/${keyName}/config.ts`)) | ||
| 36 | + } | ||
| 37 | + return componentCacheMap.get(key) | ||
| 38 | +} | ||
| 39 | + | ||
| 30 | /** | 40 | /** |
| 31 | * * 获取目标组件配置信息 | 41 | * * 获取目标组件配置信息 |
| 32 | * @param targetData | 42 | * @param targetData |
| @@ -36,10 +46,10 @@ export const createComponent = async (targetData: ConfigType) => { | @@ -36,10 +46,10 @@ export const createComponent = async (targetData: ConfigType) => { | ||
| 36 | // redirectComponent 是给图片组件库和图标组件库使用的 | 46 | // redirectComponent 是给图片组件库和图标组件库使用的 |
| 37 | if (redirectComponent) { | 47 | if (redirectComponent) { |
| 38 | const [packageName, categoryName, keyName] = redirectComponent.split('/') | 48 | const [packageName, categoryName, keyName] = redirectComponent.split('/') |
| 39 | - const redirectChart = await import(`./components/${packageName}/${categoryName}/${keyName}/config.ts`) | 49 | + const redirectChart = await loadConfig(packageName, categoryName, keyName) |
| 40 | return new redirectChart.default() | 50 | return new redirectChart.default() |
| 41 | } | 51 | } |
| 42 | - const chart = await import(`./components/${targetData.package}/${category}/${key}/config.ts`) | 52 | + const chart = await loadConfig(targetData.package, category, key) |
| 43 | return new chart.default() | 53 | return new chart.default() |
| 44 | } | 54 | } |
| 45 | 55 | ||
| @@ -98,5 +108,4 @@ export const fetchImages = async (targetData?: ConfigType) => { | @@ -98,5 +108,4 @@ export const fetchImages = async (targetData?: ConfigType) => { | ||
| 98 | } | 108 | } |
| 99 | return '' | 109 | return '' |
| 100 | } | 110 | } |
| 101 | - | ||
| 102 | useInjectLib(packagesList as any) // THINGS_KIT 修改注册组件 | 111 | useInjectLib(packagesList as any) // THINGS_KIT 修改注册组件 |
| @@ -24,7 +24,6 @@ import cloneDeep from 'lodash/cloneDeep' | @@ -24,7 +24,6 @@ import cloneDeep from 'lodash/cloneDeep' | ||
| 24 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | 24 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 |
| 25 | * 源代码 requestDataType: RequestDataTypeEnum.STATIC, | 25 | * 源代码 requestDataType: RequestDataTypeEnum.STATIC, |
| 26 | * 修改后的代码 requestDataType: RequestDataTypeEnum.Pond, | 26 | * 修改后的代码 requestDataType: RequestDataTypeEnum.Pond, |
| 27 | - * 修改后代码在//ft之间 | ||
| 28 | */ | 27 | */ |
| 29 | // 请求基础属性 | 28 | // 请求基础属性 |
| 30 | export const requestConfig: RequestConfigType = { | 29 | export const requestConfig: RequestConfigType = { |
| @@ -49,7 +48,6 @@ export const requestConfig: RequestConfigType = { | @@ -49,7 +48,6 @@ export const requestConfig: RequestConfigType = { | ||
| 49 | Params: {} | 48 | Params: {} |
| 50 | } | 49 | } |
| 51 | } | 50 | } |
| 52 | -//ft之间 | ||
| 53 | 51 | ||
| 54 | // 单实例类 | 52 | // 单实例类 |
| 55 | export class PublicConfigClass implements PublicConfigType { | 53 | export class PublicConfigClass implements PublicConfigType { |
| @@ -162,17 +162,17 @@ export const useChartEditStore = defineStore({ | @@ -162,17 +162,17 @@ export const useChartEditStore = defineStore({ | ||
| 162 | }, | 162 | }, |
| 163 | getComponentList(): Array<CreateComponentType | CreateComponentGroupType> { | 163 | getComponentList(): Array<CreateComponentType | CreateComponentGroupType> { |
| 164 | return this.componentList | 164 | return this.componentList |
| 165 | - }, | ||
| 166 | - // 获取需要存储的数据项 | 165 | + } |
| 166 | + }, | ||
| 167 | + actions: { | ||
| 168 | + // * 获取需要存储的数据项 | ||
| 167 | getStorageInfo(): ChartEditStorage { | 169 | getStorageInfo(): ChartEditStorage { |
| 168 | return { | 170 | return { |
| 169 | [ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: this.getEditCanvasConfig, | 171 | [ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: this.getEditCanvasConfig, |
| 170 | [ChartEditStoreEnum.COMPONENT_LIST]: this.getComponentList, | 172 | [ChartEditStoreEnum.COMPONENT_LIST]: this.getComponentList, |
| 171 | [ChartEditStoreEnum.REQUEST_GLOBAL_CONFIG]: this.getRequestGlobalConfig | 173 | [ChartEditStoreEnum.REQUEST_GLOBAL_CONFIG]: this.getRequestGlobalConfig |
| 172 | } | 174 | } |
| 173 | - } | ||
| 174 | - }, | ||
| 175 | - actions: { | 175 | + }, |
| 176 | // * 设置 editCanvas 数据项 | 176 | // * 设置 editCanvas 数据项 |
| 177 | setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) { | 177 | setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) { |
| 178 | this.editCanvas[key] = value | 178 | this.editCanvas[key] = value |
src/utils/external/imageUrlToBase64.ts
0 → 100644
| 1 | +/** | ||
| 2 | + * | ||
| 3 | + * @param url 图片路径 | ||
| 4 | + * @param ext 图片格式 | ||
| 5 | + * @param callback 结果回调 | ||
| 6 | + * 类型暂定any | ||
| 7 | + */ | ||
| 8 | +export function getUrlBase64(url: any, ext: any, callback: any) { | ||
| 9 | + let canvas = document.createElement("canvas") as any; //创建canvas DOM元素 | ||
| 10 | + const ctx = canvas.getContext("2d") as any; | ||
| 11 | + const img = new Image; | ||
| 12 | + img.crossOrigin = 'Anonymous'; | ||
| 13 | + img.src = url; | ||
| 14 | + img.onload = function () { | ||
| 15 | + canvas.height = 35; //指定画板的高度,自定义 | ||
| 16 | + canvas.width = 35; //指定画板的宽度,自定义 | ||
| 17 | + ctx.drawImage(img, 0, 0, 35, 35); //参数可自定义 | ||
| 18 | + const dataURL = canvas.toDataURL("image/" + ext); | ||
| 19 | + callback.call(this, dataURL); //回掉函数获取Base64编码 | ||
| 20 | + canvas = null; | ||
| 21 | + }; | ||
| 22 | +} |
| @@ -82,7 +82,9 @@ import { fetchConfigComponent, fetchChartComponent } from '@/packages/index' | @@ -82,7 +82,9 @@ import { fetchConfigComponent, fetchChartComponent } from '@/packages/index' | ||
| 82 | import { componentInstall, loadingStart, loadingFinish, loadingError } from '@/utils' | 82 | import { componentInstall, loadingStart, loadingFinish, loadingError } from '@/utils' |
| 83 | import { ChartGlobImage } from '@/components/Pages/ChartGlobImage' | 83 | import { ChartGlobImage } from '@/components/Pages/ChartGlobImage' |
| 84 | import { Icon } from '@iconify/vue' | 84 | import { Icon } from '@iconify/vue' |
| 85 | - | 85 | +// THINGS_KIT 修改搜索未过滤隐藏的组件 |
| 86 | +import { hideAsideComponentsObj } from '../../external/components/ChartsOptionContent/config' | ||
| 87 | +// | ||
| 86 | const props = defineProps({ | 88 | const props = defineProps({ |
| 87 | menuOptions: { | 89 | menuOptions: { |
| 88 | type: Array, | 90 | type: Array, |
| @@ -136,6 +138,9 @@ const searchHandle = (key: string | null) => { | @@ -136,6 +138,9 @@ const searchHandle = (key: string | null) => { | ||
| 136 | searchRes.value = List.filter( | 138 | searchRes.value = List.filter( |
| 137 | (e: ConfigType) => !e.disabled && (!key || e.title.toLowerCase().includes(key.toLowerCase())) | 139 | (e: ConfigType) => !e.disabled && (!key || e.title.toLowerCase().includes(key.toLowerCase())) |
| 138 | ) | 140 | ) |
| 141 | + // THINGS_KIT 修改搜索未过滤隐藏的组件 | ||
| 142 | + searchRes.value = searchRes.value.filter((e: ConfigType) =>!hideAsideComponentsObj['all'].includes(e.chartKey)) | ||
| 143 | + // | ||
| 139 | setTimeout(() => { | 144 | setTimeout(() => { |
| 140 | loading.value = undefined | 145 | loading.value = undefined |
| 141 | }, 500) | 146 | }, 500) |
| @@ -10,13 +10,7 @@ import { useInjectAside } from './external/useInjectAside' | @@ -10,13 +10,7 @@ import { useInjectAside } from './external/useInjectAside' | ||
| 10 | 10 | ||
| 11 | // 图标 | 11 | // 图标 |
| 12 | const { AirPlaneOutlineIcon, ImageIcon, BarChartIcon } = icon.ionicons5 | 12 | const { AirPlaneOutlineIcon, ImageIcon, BarChartIcon } = icon.ionicons5 |
| 13 | -const { | ||
| 14 | - TableSplitIcon, | ||
| 15 | - RoadmapIcon, | ||
| 16 | - SpellCheckIcon, | ||
| 17 | - GraphicalDataFlowIcon, | ||
| 18 | -} = icon.carbon | ||
| 19 | - | 13 | +const { TableSplitIcon, RoadmapIcon, SpellCheckIcon, GraphicalDataFlowIcon } = icon.carbon |
| 20 | 14 | ||
| 21 | // 图表 | 15 | // 图表 |
| 22 | export type MenuOptionsType = { | 16 | export type MenuOptionsType = { |
| @@ -9,7 +9,7 @@ export const exportHandle = () => { | @@ -9,7 +9,7 @@ export const exportHandle = () => { | ||
| 9 | 9 | ||
| 10 | // 导出数据 | 10 | // 导出数据 |
| 11 | downloadTextFile( | 11 | downloadTextFile( |
| 12 | - JSONStringify(chartEditStore.getStorageInfo || []), | 12 | + JSONStringify(chartEditStore.getStorageInfo() || []), |
| 13 | undefined, | 13 | undefined, |
| 14 | 'json' | 14 | 'json' |
| 15 | ) | 15 | ) |
| @@ -19,14 +19,14 @@ export const syncData = () => { | @@ -19,14 +19,14 @@ export const syncData = () => { | ||
| 19 | transformOrigin: 'center', | 19 | transformOrigin: 'center', |
| 20 | onPositiveCallback: () => { | 20 | onPositiveCallback: () => { |
| 21 | window['$message'].success('正在同步编辑器...') | 21 | window['$message'].success('正在同步编辑器...') |
| 22 | - dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo })) | 22 | + dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo() })) |
| 23 | } | 23 | } |
| 24 | }) | 24 | }) |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | // 同步数据到预览页 | 27 | // 同步数据到预览页 |
| 28 | export const syncDataToPreview = () => { | 28 | export const syncDataToPreview = () => { |
| 29 | - dispatchEvent(new CustomEvent(SavePageEnum.CHART_TO_PREVIEW, { detail: chartEditStore.getStorageInfo })) | 29 | + dispatchEvent(new CustomEvent(SavePageEnum.CHART_TO_PREVIEW, { detail: chartEditStore.getStorageInfo() })) |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | // 侦听器更新 | 32 | // 侦听器更新 |
| @@ -160,7 +160,7 @@ const editHandle = () => { | @@ -160,7 +160,7 @@ const editHandle = () => { | ||
| 160 | 160 | ||
| 161 | // 把内存中的数据同步到SessionStorage 便于传递给新窗口初始化数据 | 161 | // 把内存中的数据同步到SessionStorage 便于传递给新窗口初始化数据 |
| 162 | const updateToSession = (id: string) => { | 162 | const updateToSession = (id: string) => { |
| 163 | - const storageInfo = chartEditStore.getStorageInfo | 163 | + const storageInfo = chartEditStore.getStorageInfo() |
| 164 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] | 164 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] |
| 165 | 165 | ||
| 166 | if (sessionStorageInfo?.length) { | 166 | if (sessionStorageInfo?.length) { |
| @@ -9,7 +9,7 @@ export const exportHandle = () => { | @@ -9,7 +9,7 @@ export const exportHandle = () => { | ||
| 9 | 9 | ||
| 10 | // 导出数据 | 10 | // 导出数据 |
| 11 | downloadTextFile( | 11 | downloadTextFile( |
| 12 | - JSONStringify(chartEditStore.getStorageInfo || []), | 12 | + JSONStringify(chartEditStore.getStorageInfo() || []), |
| 13 | undefined, | 13 | undefined, |
| 14 | 'json' | 14 | 'json' |
| 15 | ) | 15 | ) |
| @@ -32,7 +32,7 @@ const previewHandle = () => { | @@ -32,7 +32,7 @@ const previewHandle = () => { | ||
| 32 | const { id } = routerParamsInfo.params | 32 | const { id } = routerParamsInfo.params |
| 33 | // id 标识 | 33 | // id 标识 |
| 34 | const previewId = typeof id === 'string' ? id : id[0] | 34 | const previewId = typeof id === 'string' ? id : id[0] |
| 35 | - const storageInfo = chartEditStore.getStorageInfo | 35 | + const storageInfo = chartEditStore.getStorageInfo() |
| 36 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] | 36 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] |
| 37 | 37 | ||
| 38 | if (sessionStorageInfo?.length) { | 38 | if (sessionStorageInfo?.length) { |
| @@ -32,7 +32,7 @@ const previewHandle = () => { | @@ -32,7 +32,7 @@ const previewHandle = () => { | ||
| 32 | const { id } = routerParamsInfo.params | 32 | const { id } = routerParamsInfo.params |
| 33 | // id 标识 | 33 | // id 标识 |
| 34 | const previewId = typeof id === 'string' ? id : id[0] | 34 | const previewId = typeof id === 'string' ? id : id[0] |
| 35 | - const storageInfo = chartEditStore.getStorageInfo | 35 | + const storageInfo = chartEditStore.getStorageInfo() |
| 36 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] | 36 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] |
| 37 | 37 | ||
| 38 | if (sessionStorageInfo?.length) { | 38 | if (sessionStorageInfo?.length) { |
| @@ -76,7 +76,7 @@ const handleBlur = async () => { | @@ -76,7 +76,7 @@ const handleBlur = async () => { | ||
| 76 | dataViewId, | 76 | dataViewId, |
| 77 | dataViewContent: { | 77 | dataViewContent: { |
| 78 | id: dataViewContent.id, | 78 | id: dataViewContent.id, |
| 79 | - content: JSON.stringify(chartEditStore.getStorageInfo) | 79 | + content: JSON.stringify(chartEditStore.getStorageInfo()) |
| 80 | } | 80 | } |
| 81 | } as unknown as BaseUpdateContentParams | 81 | } as unknown as BaseUpdateContentParams |
| 82 | await contentUpdateApi(saveContent) | 82 | await contentUpdateApi(saveContent) |
| @@ -92,7 +92,7 @@ export const useSyncRemote = () => { | @@ -92,7 +92,7 @@ export const useSyncRemote = () => { | ||
| 92 | const saveContent = { | 92 | const saveContent = { |
| 93 | dataViewContent: { | 93 | dataViewContent: { |
| 94 | id: dataViewContent.id, | 94 | id: dataViewContent.id, |
| 95 | - content: JSONStringify(chartEditStore.getStorageInfo || {}) | 95 | + content: JSONStringify(chartEditStore.getStorageInfo() || {}) |
| 96 | }, | 96 | }, |
| 97 | dataViewName, | 97 | dataViewName, |
| 98 | dataViewId | 98 | dataViewId |
| 1 | <template> | 1 | <template> |
| 2 | <div | 2 | <div |
| 3 | - class="chart-item" | ||
| 4 | - v-for="item in groupData.groupList" | ||
| 5 | - :class="animationsClass(item.styles.animations)" | ||
| 6 | - :key="item.id" | 3 | + :class="animationsClass(groupData.styles.animations)" |
| 7 | :style="{ | 4 | :style="{ |
| 8 | - ...getComponentAttrStyle(item.attr, groupIndex), | ||
| 9 | - ...getFilterStyle(item.styles), | ||
| 10 | - ...getTransformStyle(item.styles), | ||
| 11 | - ...getStatusStyle(item.status), | ||
| 12 | - ...getPreviewConfigStyle(item.preview), | ||
| 13 | - ...getBlendModeStyle(item.styles) as any | 5 | + ...getSizeStyle(groupData.attr), |
| 6 | + ...getFilterStyle(groupData.styles), | ||
| 14 | }" | 7 | }" |
| 15 | > | 8 | > |
| 9 | + <div | ||
| 10 | + class="chart-item" | ||
| 11 | + v-for="item in groupData.groupList" | ||
| 12 | + :class="animationsClass(item.styles.animations)" | ||
| 13 | + :key="item.id" | ||
| 14 | + :style="{ | ||
| 15 | + ...getComponentAttrStyle(item.attr, groupIndex), | ||
| 16 | + ...getStatusStyle(item.status), | ||
| 17 | + ...getPreviewConfigStyle(item.preview), | ||
| 18 | + ...getBlendModeStyle(item.styles) as any | ||
| 19 | + }" | ||
| 20 | + > | ||
| 16 | <component | 21 | <component |
| 17 | :is="item.chartConfig.chartKey" | 22 | :is="item.chartConfig.chartKey" |
| 18 | :id="item.id" | 23 | :id="item.id" |
| 19 | :chartConfig="item" | 24 | :chartConfig="item" |
| 20 | :themeSetting="themeSetting" | 25 | :themeSetting="themeSetting" |
| 21 | :themeColor="themeColor" | 26 | :themeColor="themeColor" |
| 22 | - :style="{ ...getSizeStyle(item.attr) }" | ||
| 23 | - v-on="useLifeHandler(item)" | ||
| 24 | - ></component> | 27 | + :style="{ |
| 28 | + ...getSizeStyle(item.attr), | ||
| 29 | + ...getFilterStyle(item.styles), | ||
| 30 | + ...getTransformStyle(item.styles) | ||
| 31 | + }" | ||
| 32 | + v-on="useLifeHandler(item)" | ||
| 33 | + ></component> | ||
| 34 | + </div> | ||
| 25 | </div> | 35 | </div> |
| 26 | </template> | 36 | </template> |
| 27 | 37 |
| @@ -6,7 +6,6 @@ | @@ -6,7 +6,6 @@ | ||
| 6 | :key="item.id" | 6 | :key="item.id" |
| 7 | :style="{ | 7 | :style="{ |
| 8 | ...getComponentAttrStyle(item.attr, index), | 8 | ...getComponentAttrStyle(item.attr, index), |
| 9 | - ...getFilterStyle(item.styles), | ||
| 10 | ...getTransformStyle(item.styles), | 9 | ...getTransformStyle(item.styles), |
| 11 | ...getStatusStyle(item.status), | 10 | ...getStatusStyle(item.status), |
| 12 | ...getPreviewConfigStyle(item.preview), | 11 | ...getPreviewConfigStyle(item.preview), |
| @@ -31,7 +30,10 @@ | @@ -31,7 +30,10 @@ | ||
| 31 | :chartConfig="item" | 30 | :chartConfig="item" |
| 32 | :themeSetting="themeSetting" | 31 | :themeSetting="themeSetting" |
| 33 | :themeColor="themeColor" | 32 | :themeColor="themeColor" |
| 34 | - :style="{ ...getSizeStyle(item.attr) }" | 33 | + :style="{ |
| 34 | + ...getSizeStyle(item.attr), | ||
| 35 | + ...getFilterStyle(item.styles) | ||
| 36 | + }" | ||
| 35 | v-on="useLifeHandler(item)" | 37 | v-on="useLifeHandler(item)" |
| 36 | ></component> | 38 | ></component> |
| 37 | </div> | 39 | </div> |
| @@ -33,7 +33,7 @@ import { getFilterStyle, setTitle } from '@/utils' | @@ -33,7 +33,7 @@ import { getFilterStyle, setTitle } from '@/utils' | ||
| 33 | 33 | ||
| 34 | // THINGS_KIT 重写预览逻辑,调用接口 | 34 | // THINGS_KIT 重写预览逻辑,调用接口 |
| 35 | import { dragCanvas } from './utils' | 35 | import { dragCanvas } from './utils' |
| 36 | -import { getEditCanvasConfigStyle,keyRecordHandle } from './utils' | 36 | +import { getEditCanvasConfigStyle, keyRecordHandle } from './utils' |
| 37 | import { getSessionStorageInfo } from './external/usePreview' | 37 | import { getSessionStorageInfo } from './external/usePreview' |
| 38 | 38 | ||
| 39 | import { useComInstall } from './hooks/useComInstall.hook' | 39 | import { useComInstall } from './hooks/useComInstall.hook' |
| @@ -52,6 +52,7 @@ setTitle(`预览-${chartEditStore.editCanvasConfig.projectName}`) | @@ -52,6 +52,7 @@ setTitle(`预览-${chartEditStore.editCanvasConfig.projectName}`) | ||
| 52 | 52 | ||
| 53 | const previewRefStyle = computed(() => { | 53 | const previewRefStyle = computed(() => { |
| 54 | return { | 54 | return { |
| 55 | + overflow: 'hidden', | ||
| 55 | ...getEditCanvasConfigStyle(chartEditStore.editCanvasConfig), | 56 | ...getEditCanvasConfigStyle(chartEditStore.editCanvasConfig), |
| 56 | ...getFilterStyle(chartEditStore.editCanvasConfig) | 57 | ...getFilterStyle(chartEditStore.editCanvasConfig) |
| 57 | } | 58 | } |
| @@ -15,7 +15,7 @@ let key = ref(Date.now()) | @@ -15,7 +15,7 @@ let key = ref(Date.now()) | ||
| 15 | 15 | ||
| 16 | // 数据变更 -> 组件销毁重建 | 16 | // 数据变更 -> 组件销毁重建 |
| 17 | ;[SavePageEnum.JSON, SavePageEnum.CHART_TO_PREVIEW].forEach((saveEvent: string) => { | 17 | ;[SavePageEnum.JSON, SavePageEnum.CHART_TO_PREVIEW].forEach((saveEvent: string) => { |
| 18 | - if (!window.opener) return | 18 | + if (!window.opener && !window.opener.addEventListener) return |
| 19 | window.opener.addEventListener(saveEvent, async (e: any) => { | 19 | window.opener.addEventListener(saveEvent, async (e: any) => { |
| 20 | const localStorageInfo: ChartEditStorageType = await getSessionStorageInfo() as unknown as ChartEditStorageType | 20 | const localStorageInfo: ChartEditStorageType = await getSessionStorageInfo() as unknown as ChartEditStorageType |
| 21 | setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }]) | 21 | setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }]) |