Merge branch 'dev' into dev-commet

This commit is contained in:
tnt group 2022-11-01 08:28:43 +08:00
commit 97c92f7d73
73 changed files with 2122 additions and 224 deletions

View File

@ -11,6 +11,8 @@
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix" "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix"
}, },
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@amap/amap-jsapi-types": "^0.0.8",
"@types/color": "^3.0.3", "@types/color": "^3.0.3",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/keymaster": "^1.6.30", "@types/keymaster": "^1.6.30",

View File

@ -15,6 +15,7 @@ export const radarUrl = '/mock/radarData'
export const heatMapUrl = '/mock/heatMapData' export const heatMapUrl = '/mock/heatMapData'
export const scatterBasicUrl = '/mock/scatterBasic' export const scatterBasicUrl = '/mock/scatterBasic'
export const mapUrl = '/mock/map' export const mapUrl = '/mock/map'
export const capsuleUrl = '/mock/capsule'
export const wordCloudUrl = '/mock/wordCloud' export const wordCloudUrl = '/mock/wordCloud'
export const treemapUrl = '/mock/treemap' export const treemapUrl = '/mock/treemap'
export const threeEarth01Url = '/mock/threeEarth01Data' export const threeEarth01Url = '/mock/threeEarth01Data'
@ -82,6 +83,11 @@ const mockObject: MockMethod[] = [
method: RequestHttpEnum.GET, method: RequestHttpEnum.GET,
response: () => test.fetchMap response: () => test.fetchMap
}, },
{
url: capsuleUrl,
method: RequestHttpEnum.GET,
response: () => test.fetchCapsule
},
{ {
url: wordCloudUrl, url: wordCloudUrl,
method: RequestHttpEnum.GET, method: RequestHttpEnum.GET,

View File

@ -1,79 +1,9 @@
{ {
"point": [ "markers|50": [
{ {
"name": "北京", "name": "某某地市",
"value": [116.405285, 39.904989, 200] "value": "@integer(2, 20)",
}, "position": ["@float(115, 117, 1, 6)", "@float(38, 40, 1, 6)"]
{
"name": "郑州",
"value": [113.665412, 34.757975, 888]
},
{
"name": "青海",
"value": [101.778916, 36.623178, 666]
},
{
"name": "宁夏回族自治区",
"value": [106.278179, 38.46637, 66]
},
{
"name": "哈尔滨市",
"value": [126.642464, 45.756967, 101]
} }
],
"map": [
{
"name": "北京市",
"value": "@integer(0, 1000)"
},
{
"name": "河北省",
"value": "@integer(0, 1000)"
},
{
"name": "江苏省",
"value": "@integer(0, 1000)"
},
{
"name": "福建省",
"value": "@integer(0, 1000)"
},
{
"name": "山东省",
"value": "@integer(0, 1000)"
},
{
"name": "河南省",
"value": "@integer(0, 1000)"
},
{
"name": "湖北省",
"value": "@integer(0, 1000)"
},
{
"name": "广西壮族自治区",
"value": "@integer(0, 1000)"
},
{
"name": "海南省",
"value": "@integer(0, 1000)"
},
{
"name": "青海省",
"value": "@integer(0, 1000)"
},
{
"name": "新疆维吾尔自治区",
"value": "@integer(0, 1000)"
}
],
"pieces": [
{ "gte": 1000, "label": ">1000" },
{ "gte": 600, "lte": 999, "label": "600-999" },
{ "gte": 200, "lte": 599, "label": "200-599" },
{ "gte": 50, "lte": 199, "label": "49-199" },
{ "gte": 10, "lte": 49, "label": "10-49" },
{ "lte": 9, "label": "<9" }
] ]
} }

View File

@ -11,27 +11,7 @@ export default {
msg: '请求成功', msg: '请求成功',
data: { data: {
dimensions: ['product', 'dataOne'], dimensions: ['product', 'dataOne'],
source: [ 'source|50': [
{
product: '@name',
'dataOne|0-900': 3
},
{
product: '@name',
'dataOne|0-900': 3
},
{
product: '@name',
'dataOne|0-900': 3
},
{
product: '@name',
'dataOne|0-900': 3
},
{
product: '@name',
'dataOne|0-900': 3
},
{ {
product: '@name', product: '@name',
'dataOne|0-900': 3 'dataOne|0-900': 3
@ -39,6 +19,22 @@ export default {
] ]
} }
}, },
// 胶囊图
fetchCapsule: {
code: 0,
status: 200,
msg: '请求成功',
data: {
dimensions: ['name', 'value'],
source: [
{ name: '厦门', 'value|0-40': 20 },
{ name: '南阳', 'value|20-60': 40 },
{ name: '北京', 'value|40-80': 60 },
{ name: '上海', 'value|60-100': 80 },
{ name: '新疆', value: 100 }
]
}
},
// 图表 // 图表
fetchMockData: { fetchMockData: {
code: 0, code: 0,
@ -46,32 +42,7 @@ export default {
msg: '请求成功', msg: '请求成功',
data: { data: {
dimensions: ['product', 'dataOne', 'dataTwo'], dimensions: ['product', 'dataOne', 'dataTwo'],
source: [ 'source|50': [
{
product: '@name',
'dataOne|100-900': 3,
'dataTwo|100-900': 3
},
{
product: '@name',
'dataOne|100-900': 3,
'dataTwo|100-900': 3
},
{
product: '@name',
'dataOne|100-900': 3,
'dataTwo|100-900': 3
},
{
product: '@name',
'dataOne|100-900': 3,
'dataTwo|100-900': 3
},
{
product: '@name',
'dataOne|100-900': 3,
'dataTwo|100-900': 3
},
{ {
product: '@name', product: '@name',
'dataOne|100-900': 3, 'dataOne|100-900': 3,
@ -85,21 +56,7 @@ export default {
code: 0, code: 0,
status: 200, status: 200,
msg: '请求成功', msg: '请求成功',
data: [ 'data|50': [{ name: '@name', 'value|100-900': 5 }]
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 },
{ name: '@name', 'value|100-900': 5 }
]
}, },
// 轮播表格 // 轮播表格
fetchScrollBoard: { fetchScrollBoard: {
@ -262,12 +219,7 @@ export default {
data: [ data: [
{ {
startArray: { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }, startArray: { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
endArray: [ 'endArray|10': [{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }]
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }
]
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -11,7 +11,7 @@
</template> </template>
<n-list-item> <n-list-item>
<n-space :size="20"> <n-space class="go-my-2" :size="20">
<n-text class="item-left">版权声明</n-text> <n-text class="item-left">版权声明</n-text>
<n-text> <n-text>
GoView 版权属于 GoView 版权属于
@ -21,8 +21,7 @@
</n-list-item> </n-list-item>
<n-list-item> <n-list-item>
<n-divider style="margin-top: 0" /> <n-space class="go-my-2" :size="20">
<n-space :size="20">
<n-text class="item-left">协议备注</n-text> <n-text class="item-left">协议备注</n-text>
<n-text> <n-text>
请遵守开源 MIT 协议以上声明 <n-text type="error">不可删除</n-text> 请遵守开源 MIT 协议以上声明 <n-text type="error">不可删除</n-text>
@ -31,8 +30,7 @@
</n-list-item> </n-list-item>
<n-list-item> <n-list-item>
<n-divider style="margin-top: 0" /> <n-space class="go-mt-2" :size="20">
<n-space :size="20">
<n-text class="item-left">商业授权</n-text> <n-text class="item-left">商业授权</n-text>
<n-text> <n-text>
若不想保留版权声明请通过仓库/交流群 联系项目作者进行授权 若不想保留版权声明请通过仓库/交流群 联系项目作者进行授权

View File

@ -1,4 +1,5 @@
import Flipper from './index.vue' import Flipper from './index.vue'
type FlipType = 'up' | 'down' type FlipType = 'up' | 'down'
export { Flipper, FlipType } export { Flipper, FlipType }

View File

@ -1,19 +1,13 @@
<template> <template>
<div class="M-Flipper" :class="[flipType, { go: isFlipping }]"> <div class="go-Flipper" :class="[flipType, { go: isFlipping }]">
<div class="digital front" :data-front="frontTextFromData"></div> <div class="digital front" :data-front="frontTextFromData"></div>
<div class="digital back" :data-back="backTextFromData"></div> <div class="digital back" :data-back="backTextFromData"></div>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'Flipper'
}
</script>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, PropType, watch } from 'vue' import { ref, PropType, watch } from 'vue'
import { FlipType } from '.' import { FlipType } from './index'
const props = defineProps({ const props = defineProps({
flipType: { flipType: {
@ -131,7 +125,7 @@ $lineColor: #4a9ef8;
} }
// #endregion // #endregion
.M-Flipper { .go-Flipper {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: $width; width: $width;

View File

@ -9,6 +9,12 @@ export enum DragKeyEnum {
DRAG_KEY = 'ChartData' DRAG_KEY = 'ChartData'
} }
// 不同页面保存操作
export enum SavePageEnum {
CHART = 'SaveChart',
JSON = 'SaveJSON'
}
// 操作枚举 // 操作枚举
export enum MenuEnum { export enum MenuEnum {
// 移动 // 移动
@ -42,6 +48,8 @@ export enum MenuEnum {
BACK = 'back', BACK = 'back',
// 前进 // 前进
FORWORD = 'forward', FORWORD = 'forward',
// 保存
SAVE = 'save',
// 锁定 // 锁定
LOCK = 'lock', LOCK = 'lock',
// 解除锁定 // 解除锁定

View File

@ -12,6 +12,12 @@ export enum PreviewEnum {
CHART_PREVIEW_NAME = 'ChartPreview', CHART_PREVIEW_NAME = 'ChartPreview',
} }
export enum EditEnum {
// 图表JSON编辑
CHART_EDIT = '/chart/edit/:id(.*)*',
CHART_EDIT_NAME = 'ChartEdit',
}
export enum PageEnum { export enum PageEnum {
// 登录 // 登录
BASE_LOGIN = '/login', BASE_LOGIN = '/login',

View File

@ -1,4 +1,5 @@
export * from '@/hooks/useTheme.hook' export * from '@/hooks/useTheme.hook'
export * from '@/hooks/usePreviewScale.hook' export * from '@/hooks/usePreviewScale.hook'
export * from '@/hooks/useCode.hook' export * from '@/hooks/useCode.hook'
export * from '@/hooks/useChartDataFetch.hook' export * from '@/hooks/useChartDataFetch.hook'
export * from '@/hooks/useLifeHandler.hook'

View File

@ -0,0 +1,47 @@
import { CreateComponentType, EventLife } from '@/packages/index.d'
import * as echarts from 'echarts'
// 所有图表组件集合对象
const components: { [K in string]?: any } = {}
// 项目提供的npm 包变量
export const npmPkgs = { echarts }
export const useLifeHandler = (chartConfig: CreateComponentType) => {
const events = chartConfig.events || {}
// 生成生命周期事件
const lifeEvents = {
[EventLife.BEFORE_MOUNT](e: any) {
// 存储组件
components[chartConfig.id] = e.component
const fnStr = (events[EventLife.BEFORE_MOUNT] || '').trim()
generateFunc(fnStr, e)
},
[EventLife.MOUNTED](e: any) {
const fnStr = (events[EventLife.MOUNTED] || '').trim()
generateFunc(fnStr, e)
}
}
return lifeEvents
}
/**
*
* @param fnStr
* @param e
*/
function generateFunc(fnStr: string, e: any) {
try {
// npmPkgs 便于拷贝 echarts 示例时设置option 的formatter等相关内容
Function(`
"use strict";
return (
async function(e, components, node_modules){
const {${Object.keys(npmPkgs).join()}} = node_modules;
${fnStr}
}
)`)().bind(e?.component)(e, components, npmPkgs)
} catch (error) {
console.error(error)
}
}

View File

@ -3,12 +3,6 @@
<global-setting :optionData="optionData"></global-setting> <global-setting :optionData="optionData"></global-setting>
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true"> <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
<SettingItemBox name="图形"> <SettingItemBox name="图形">
<SettingItem name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color"></n-color-picker>
</SettingItem>
<SettingItem>
<n-button size="small" @click="item.itemStyle.color = null"> 恢复默认 </n-button>
</SettingItem>
<SettingItem name="宽度"> <SettingItem name="宽度">
<n-input-number <n-input-number
v-model:value="item.barWidth" v-model:value="item.barWidth"

View File

@ -3,12 +3,6 @@
<global-setting :optionData="optionData"></global-setting> <global-setting :optionData="optionData"></global-setting>
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index+1}`" :expanded="true"> <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index+1}`" :expanded="true">
<SettingItemBox name="图形"> <SettingItemBox name="图形">
<SettingItem name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color"></n-color-picker>
</SettingItem>
<SettingItem>
<n-button size="small" @click="item.itemStyle.color = null">恢复默认</n-button>
</SettingItem>
<SettingItem name="宽度"> <SettingItem name="宽度">
<n-input-number <n-input-number
v-model:value="item.barWidth" v-model:value="item.barWidth"

View File

@ -0,0 +1,25 @@
import { PublicConfigClass } from '@/packages/public'
import { CapsuleChartConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
import { chartInitConfig } from '@/settings/designSetting'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const option = {
dataset: dataJson,
colors: ['#c4ebad', '#6be6c1', '#a0a7e6', '#96dee8', '#3fb1e3' ],
unit: '',
itemHeight: 10,
valueFontSize: 16,
paddingRight: 50,
paddingLeft: 50,
showValue: true
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key: string = CapsuleChartConfig.key
public attr = { ...chartInitConfig, zIndex: -1 }
public chartConfig = cloneDeep(CapsuleChartConfig)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,53 @@
<template>
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"> </global-setting>
<!-- 胶囊柱图 -->
<collapse-item name="胶囊柱图" expanded>
<SettingItemBox name="布局">
<setting-item name="左侧边距">
<n-input-number v-model:value="optionData.paddingLeft" :min="10" :step="1" size="small"></n-input-number>
</setting-item>
<setting-item name="右侧边距">
<n-input-number v-model:value="optionData.paddingRight" :min="10" :step="1" size="small"></n-input-number>
</setting-item>
<setting-item name="每块高度(px)">
<n-input-number v-model:value="optionData.itemHeight" :min="0" :step="1" size="small"></n-input-number>
</setting-item>
</SettingItemBox>
<SettingItemBox name="文本">
<setting-item name="所有文字大小">
<n-input-number v-model:value="optionData.valueFontSize" :min="0" :step="1" size="small"></n-input-number>
</setting-item>
<setting-item name="单位">
<n-input v-model:value="optionData.unit" size="small"></n-input>
</setting-item>
<SettingItem>
<n-space>
<n-switch v-model:value="optionData.showValue" size="small"></n-switch>
<n-text>显示数值</n-text>
</n-space>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="颜色">
<setting-item v-for="(item, index) in optionData.colors" :key="index" :name="`颜色${index}`">
<n-color-picker v-model:value="optionData.colors[index]" size="small" :modes="['hex']"></n-color-picker>
</setting-item>
</SettingItemBox>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import { option } from './config'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option & GlobalThemeJsonType>,
required: true
}
})
</script>

View File

@ -0,0 +1,10 @@
{
"dimensions": ["name", "value"],
"source": [
{ "name": "厦门", "value": 20 },
{ "name": "南阳", "value": 40 },
{ "name": "北京", "value": 60 },
{ "name": "上海", "value": 80 },
{ "name": "新疆", "value": 100 }
]
}

View File

@ -0,0 +1,15 @@
import image from '@/assets/images/chart/charts/capsule.png'
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const CapsuleChartConfig: ConfigType = {
key: 'CapsuleChart',
chartKey: 'VCapsuleChart',
conKey: 'VCCapsuleChart',
title: '胶囊柱图',
category: ChatCategoryEnum.BAR,
categoryName: ChatCategoryEnumName.BAR,
package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.COMMON,
image
}

View File

@ -0,0 +1,228 @@
<template>
<div
v-if="state.mergedConfig"
class="go-dv-capsule-chart"
:style="{
fontSize: numberSizeHandle(state.mergedConfig.valueFontSize),
paddingLeft: numberSizeHandle(state.mergedConfig.paddingLeft),
paddingRight: numberSizeHandle(state.mergedConfig.paddingRight)
}"
>
<div class="label-column">
<div
v-for="item in state.mergedConfig.dataset.source"
:key="item[state.mergedConfig.dataset.dimensions[0]]"
:style="{ height: state.capsuleItemHeight, lineHeight: state.capsuleItemHeight }"
>
{{ item[state.mergedConfig.dataset.dimensions[0]] }}
</div>
<div class="laset">&nbsp;</div>
</div>
<div class="capsule-container">
<div
v-for="(capsule, index) in state.capsuleLength"
:key="index"
class="capsule-item"
:style="{ height: state.capsuleItemHeight }"
>
<div
class="capsule-item-column"
:style="`width: ${capsule * 100}%; background-color: ${
state.mergedConfig.colors[index % state.mergedConfig.colors.length]
};height:calc(100% - ${2}px);`"
>
<div v-if="state.mergedConfig.showValue" class="capsule-item-value">
{{ state.capsuleValue[index] }}
</div>
</div>
</div>
<div class="unit-label">
<div v-for="(label, index) in state.labelData" :key="label + index">
{{ label }}
</div>
</div>
</div>
<div v-if="state.mergedConfig.unit" class="unit-text">
{{ state.mergedConfig.unit }}
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, watch, reactive, PropType } from 'vue'
import merge from 'lodash/merge'
import cloneDeep from 'lodash/cloneDeep'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import config, { option } from './config'
type DataProps = {
name: string | number
value: string | number
[key: string]: string | number
}
interface StateProps {
defaultConfig: {
dataset: {
dimensions: Array<string>
source: Array<DataProps>
}
colors: Array<string>
unit: string
showValue: boolean
itemHeight: number
valueFontSize: number
paddingLeft: number
paddingRight: number
}
mergedConfig: any
capsuleLength: Array<number>
capsuleValue: Array<string | Object>
labelData: Array<number>
capsuleItemHeight: string
}
const props = defineProps({
chartConfig: {
type: Object as PropType<config>,
default: () => ({})
}
})
const state = reactive<StateProps>({
defaultConfig: option,
mergedConfig: null,
capsuleLength: [],
capsuleValue: [],
labelData: [],
capsuleItemHeight: ''
})
watch(
() => props.chartConfig.option,
newVal => {
calcData(newVal)
},
{
deep: true
}
)
const calcData = (data: any) => {
mergeConfig(props.chartConfig.option)
calcCapsuleLengthAndLabelData()
}
const mergeConfig = (data: any) => {
state.mergedConfig = merge(cloneDeep(state.defaultConfig), data || {})
}
//
const calcCapsuleLengthAndLabelData = () => {
const { source } = state.mergedConfig.dataset
if (!source.length) return
state.capsuleItemHeight = numberSizeHandle(state.mergedConfig.itemHeight)
const capsuleValue = source.map((item: DataProps) => item[state.mergedConfig.dataset.dimensions[1]])
const maxValue = Math.max(...capsuleValue)
state.capsuleValue = capsuleValue
state.capsuleLength = capsuleValue.map((v: any) => (maxValue ? v / maxValue : 0))
const oneFifth = maxValue / 5
const labelData = Array.from(new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth))))
state.labelData = labelData
}
const numberSizeHandle = (val: string | number) => {
return val + 'px'
}
onMounted(() => {
calcData(props.chartConfig.option)
})
//
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
calcData(newData)
})
</script>
<style lang="scss" scoped>
@include go('dv-capsule-chart') {
position: relative;
display: flex;
flex-direction: row;
box-sizing: border-box;
padding: 20px;
padding-right: 50px;
color: #b9b8cc;
.label-column {
display: flex;
flex-direction: column;
justify-content: space-between;
box-sizing: border-box;
padding-right: 10px;
text-align: right;
> div:not(:last-child) {
margin: 5px 0;
}
}
.capsule-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.capsule-item {
box-shadow: 0 0 3px #999;
height: 10px;
margin: 5px 0px;
border-radius: 5px;
.capsule-item-column {
position: relative;
height: 8px;
margin-top: 1px;
border-radius: 5px;
transition: all 0.3s;
display: flex;
justify-content: flex-end;
align-items: center;
.capsule-item-value {
padding-left: 10px;
transform: translateX(100%);
}
}
}
.unit-label {
height: 20px;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
}
.unit-text {
text-align: right;
display: flex;
align-items: flex-end;
line-height: 20px;
margin-left: 10px;
}
}
</style>

View File

@ -1,4 +1,5 @@
import { BarCommonConfig } from './BarCommon/index' import { BarCommonConfig } from './BarCommon/index'
import { BarCrossrangeConfig } from './BarCrossrange/index' import { BarCrossrangeConfig } from './BarCrossrange/index'
import { CapsuleChartConfig } from './CapsuleChart/index'
export default [BarCommonConfig, BarCrossrangeConfig] export default [BarCommonConfig, BarCrossrangeConfig, CapsuleChartConfig]

View File

@ -3,9 +3,6 @@
<global-setting :optionData="optionData"></global-setting> <global-setting :optionData="optionData"></global-setting>
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true"> <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true">
<SettingItemBox name="线条"> <SettingItemBox name="线条">
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.lineStyle.color"></n-color-picker>
</setting-item>
<SettingItem name="宽度"> <SettingItem name="宽度">
<n-input-number <n-input-number
v-model:value="item.lineStyle.width" v-model:value="item.lineStyle.width"

View File

@ -0,0 +1,83 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { MapAmapConfig } from './index'
import { chartInitConfig } from '@/settings/designSetting'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export enum ThemeEnum {
NORMAL = 'normal',
DARK = 'dark',
LIGHT = 'light',
WHITES_MOKE = 'whitesmoke',
FRESH = 'fresh',
GREY = 'grey',
GRAFFITI = 'graffiti',
MACARON = 'macaron',
BLUE = 'blue',
DARKBLUE = 'darkblue',
WINE = 'wine'
}
export enum LangEnum {
ZH_CN = 'zh_cn',
EN = 'en',
ZH_EN = 'zh_en'
}
export enum ViewModeEnum {
PLANE = '2D',
STEREOSCOPIC = '3D'
}
export enum FeaturesEnum {
BG = 'bg',
POINT = 'point',
ROAD = 'road',
BUILDING = 'building'
}
export enum MarkerEnum {
// 圆圈
CIRCLE_MARKER = 'CircleMarker',
// 定位标点
MARKER = 'Marker',
// 暂无
NONE = 'none'
}
export const option = {
dataset: dataJson,
mapOptions: {
pitch: 60,
skyColor: '#53A9DE',
amapKey: 'd5f3e16589dbecae64d05fe90e2ba4f2',
amapStyleKey: ThemeEnum.DARK,
amapStyleKeyCustom: '',
amapLon: 116.397428,
amapLat: 39.90923,
amapZindex: 11,
marker: {
fillColor: '#E98984FF',
fillOpacity: 0.5,
strokeColor: 'white',
strokeWeight: 2,
strokeOpacity: 0.5,
zIndex: 10,
bubble: true,
cursor: 'pointer',
clickable: true
},
mapMarkerType: MarkerEnum.CIRCLE_MARKER,
viewMode: ViewModeEnum.PLANE,
lang: LangEnum.ZH_CN,
features: [FeaturesEnum.BG, FeaturesEnum.POINT, FeaturesEnum.ROAD, FeaturesEnum.BUILDING]
}
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = MapAmapConfig.key
public attr = { ...chartInitConfig, w: 1000, h: 800, zIndex: -1 }
public chartConfig = cloneDeep(MapAmapConfig)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,199 @@
<template>
<collapse-item name="基础" :expanded="true">
<setting-item-box name="语言类型" :alone="true">
<setting-item>
<n-select size="small" v-model:value="optionData.mapOptions.lang" :options="langOptions" />
</setting-item>
</setting-item-box>
<setting-item-box name="Key" :alone="true">
<setting-item name="请务必使用自己的高德应用 key">
<n-input v-model:value="optionData.mapOptions.amapKey" size="small"></n-input>
</setting-item>
</setting-item-box>
<setting-item-box name="自定义地图样式ID" :alone="true">
<setting-item>
<n-input size="small" v-model:value="optionData.mapOptions.amapStyleKeyCustom" />
</setting-item>
</setting-item-box>
</collapse-item>
<collapse-item name="地图" :expanded="true">
<setting-item-box name="主题">
<setting-item>
<n-select size="small" v-model:value="optionData.mapOptions.amapStyleKey" :options="themeOptions" />
</setting-item>
</setting-item-box>
<setting-item-box name="内容" :alone="true">
<n-checkbox-group v-model:value="optionData.mapOptions.features">
<n-space item-style="display: flex;">
<n-checkbox :value="item.value" :label="item.label" v-for="(item, index) in featuresOptions" :key="index" />
</n-space>
</n-checkbox-group>
</setting-item-box>
<setting-item-box name="位置">
<setting-item name="经度">
<n-input-number v-model:value="optionData.mapOptions.amapLon" :show-button="false" size="small">
<template #suffix>°</template>
</n-input-number>
</setting-item>
<setting-item name="纬度">
<n-input-number v-model:value="optionData.mapOptions.amapLat" :show-button="false" size="small">
<template #suffix>°</template>
</n-input-number>
</setting-item>
<setting-item name="初始缩放">
<n-input-number v-model:value="optionData.mapOptions.amapZindex" :min="0" size="small"></n-input-number>
</setting-item>
</setting-item-box>
<setting-item-box name="模式" :alone="true">
<setting-item>
<n-radio-group v-model:value="optionData.mapOptions.viewMode" name="radiogroup">
<n-space>
<n-radio v-for="song in viewModeOptions" :key="song.value" :value="song.value">
{{ song.label }}
</n-radio>
</n-space>
</n-radio-group>
</setting-item>
</setting-item-box>
<template v-if="optionData.mapOptions.viewMode === '3D'">
<setting-item-box>
<setting-item name="天空色">
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.mapOptions.skyColor"></n-color-picker>
</setting-item>
<setting-item name="俯仰角">
<n-input-number v-model:value="optionData.mapOptions.pitch" :min="0" :max="83" size="small"></n-input-number>
</setting-item>
</setting-item-box>
</template>
</collapse-item>
<collapse-item name="标记" :expanded="true">
<setting-item-box name="样式">
<setting-item name="类型">
<n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" />
</setting-item>
<setting-item name="颜色">
<n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker>
</setting-item>
</setting-item-box>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true
}
})
const themeOptions = [
{
value: ThemeEnum.NORMAL,
label: '标准'
},
{
value: ThemeEnum.DARK,
label: '幻影黑'
},
{
value: ThemeEnum.LIGHT,
label: '月光银'
},
{
value: ThemeEnum.WHITES_MOKE,
label: '远山黛'
},
{
value: ThemeEnum.FRESH,
label: '草色青'
},
{
value: ThemeEnum.GREY,
label: '雅士灰'
},
{
value: ThemeEnum.GRAFFITI,
label: '涂鸦'
},
{
value: ThemeEnum.MACARON,
label: '马卡龙'
},
{
value: ThemeEnum.BLUE,
label: '靛青蓝'
},
{
value: ThemeEnum.DARKBLUE,
label: '极夜蓝'
},
{
value: ThemeEnum.WINE,
label: '酱籽'
}
]
const langOptions = [
{
value: LangEnum.ZH_CN,
label: '中文简体'
},
{
value: LangEnum.EN,
label: '英文'
},
{
value: LangEnum.ZH_EN,
label: '中英文对照'
}
]
const viewModeOptions = [
{
value: ViewModeEnum.PLANE,
label: '2D'
},
{
value: ViewModeEnum.STEREOSCOPIC,
label: '3D'
}
]
const featuresOptions = [
{
value: FeaturesEnum.BG,
label: '显示地图背景'
},
{
value: FeaturesEnum.POINT,
label: '显示标识'
},
{
value: FeaturesEnum.ROAD,
label: '显示道路'
},
{
value: FeaturesEnum.BUILDING,
label: '显示建筑'
}
]
const MarkerOptions = [
{
value: MarkerEnum.CIRCLE_MARKER,
label: '圆形标点'
},
{
value: MarkerEnum.MARKER,
label: '定位标点'
},
{
value: MarkerEnum.NONE,
label: '隐藏标点'
}
]
</script>

View File

@ -0,0 +1,19 @@
{
"markers": [
{
"name": "某某地市",
"value": 10,
"position": [116.300467, 39.907761]
},
{
"name": "某某地市",
"value": 15,
"position": [116.400567, 39.908761]
},
{
"name": "某某地市",
"value": 20,
"position": [116.200467, 39.937761]
}
]
}

View File

@ -0,0 +1,15 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import image from '@/assets/images/chart/charts/map_amap.png'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const MapAmapConfig: ConfigType = {
key: 'MapAmap',
chartKey: 'VMapAmap',
conKey: 'VCMapAmap',
title: '高德地图',
category: ChatCategoryEnum.MAP,
categoryName: ChatCategoryEnumName.MAP,
package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.COMMON,
image
}

View File

@ -0,0 +1,130 @@
<template>
<div ref="vChartRef"></div>
</template>
<script setup lang="ts">
import { ref, PropType, toRefs, watch } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { MarkerEnum } from './config'
import { isArray } from '@/utils'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
let {
amapKey,
amapStyleKey,
amapLon,
amapLat,
amapZindex,
mapMarkerType,
lang,
amapStyleKeyCustom,
features,
viewMode,
pitch,
skyColor,
marker
} = toRefs(props.chartConfig.option.mapOptions)
let mapIns: any = null
let markers: any = []
let AMapIns: any = null
const vChartRef = ref<HTMLElement>()
const initMap = (newData: any) => {
//
AMapLoader.load({
key: amapKey.value, //apikey--public使
version: '1.4.8', // JSAPI 1.4.15
plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete'] // 使
})
.then(AMap => {
AMapIns = AMap
mapIns = new AMap.Map(vChartRef.value, {
resizeEnable: true,
zoom: amapZindex.value, //
center: [amapLon.value, amapLat.value],
mapStyle: `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}`, //
lang: lang.value,
features: features.value,
pitch: pitch.value, // 0 - 83
skyColor: skyColor.value,
viewMode: viewMode.value, //
willReadFrequently: true
})
dataHandle(props.chartConfig.option.dataset)
})
.catch(e => {})
}
const dataHandle = (newData: any) => {
if (!mapIns && !AMapIns) {
initMap(props.chartConfig.option)
return
}
if (isArray(newData.markers)) {
//
mapIns.remove(markers)
markers = []
//
if (mapMarkerType.value === MarkerEnum.MARKER) {
newData.markers.forEach((markerItem: any) => {
const markerInstance = new AMapIns.Marker({
position: [markerItem.position[0], markerItem.position[1]],
offset: new AMapIns.Pixel(-13, -30)
})
markers.push(markerInstance)
markerInstance.setMap(mapIns)
})
} else if (mapMarkerType.value === MarkerEnum.CIRCLE_MARKER) {
newData.markers.forEach((markerItem: any) => {
const markerInstance = new AMapIns.CircleMarker({
center: [markerItem.position[0], markerItem.position[1]],
radius: markerItem.value,
...marker.value
})
markers.push(markerInstance)
markerInstance.setMap(mapIns)
})
}
}
}
const stopWatch = watch(
() => props.chartConfig.option.mapOptions,
option => {
initMap(option)
},
{
immediate: true,
deep: true
}
)
watch(
() => props.chartConfig.option.dataset,
newData => {
try {
dataHandle(newData)
} catch (error) {
console.log(error)
}
},
{
deep: false
}
)
//
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
stopWatch()
dataHandle(newData)
})
</script>

View File

@ -1,3 +1,4 @@
import { MapBaseConfig } from './MapBase/index' import { MapBaseConfig } from './MapBase/index'
import { MapAmapConfig } from './MapAmap/index'
export default [ MapBaseConfig ] export default [MapBaseConfig, MapAmapConfig]

View File

@ -3,7 +3,7 @@ import { CreateComponentType } from '@/packages/index.d'
import { CountDownConfig } from './index' import { CountDownConfig } from './index'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import { chartInitConfig } from '@/settings/designSetting' import { chartInitConfig } from '@/settings/designSetting'
import { FlipType } from '@/components/Flipper' import { FlipType } from '@/components/Pages/Flipper'
type STYLE = '时分秒' | '冒号' type STYLE = '时分秒' | '冒号'

View File

@ -76,7 +76,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { PropType, toRefs, watch, ref, onMounted } from 'vue' import { PropType, toRefs, watch, ref, onMounted } from 'vue'
import { CreateComponentType } from '@/packages/index.d' import { CreateComponentType } from '@/packages/index.d'
import { Flipper } from '@/components/Flipper' import { Flipper } from '@/components/Pages/Flipper'
import { OptionType } from './config' import { OptionType } from './config'
import { CountdownInst, CountdownProps } from 'naive-ui/es/countdown/src/Countdown' import { CountdownInst, CountdownProps } from 'naive-ui/es/countdown/src/Countdown'

View File

@ -3,7 +3,7 @@ import { CreateComponentType } from '@/packages/index.d'
import { FlipperNumberConfig } from './index' import { FlipperNumberConfig } from './index'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import { chartInitConfig } from '@/settings/designSetting' import { chartInitConfig } from '@/settings/designSetting'
import { FlipType } from '@/components/Flipper' import { FlipType } from '@/components/Pages/Flipper'
export interface OptionType { export interface OptionType {
dataset: number | string dataset: number | string

View File

@ -21,7 +21,7 @@ import { PropType, toRefs, watch, ref } from 'vue'
import { CreateComponentType } from '@/packages/index.d' import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useChartDataFetch } from '@/hooks' import { useChartDataFetch } from '@/hooks'
import { Flipper } from '@/components/Flipper' import { Flipper } from '@/components/Pages/Flipper'
import { OptionType } from './config' import { OptionType } from './config'
const props = defineProps({ const props = defineProps({

View File

@ -0,0 +1,20 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { chartInitConfig } from '@/settings/designSetting'
import { IframeConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
export const option = {
// 网站路径
dataset: "https://cn.vuejs.org/",
// 圆角
borderRadius: 10
}
export default class Config extends PublicConfigClass implements CreateComponentType
{
public key = IframeConfig.key
public attr = { ...chartInitConfig, w: 800, h: 800, zIndex: -1 }
public chartConfig = cloneDeep(IframeConfig)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,36 @@
<template>
<collapse-item name="属性" :expanded="true">
<setting-item-box name="路径" :alone="true">
<setting-item name="请填写 https 协议的网址">
<n-input v-model:value="optionData.dataset" size="small"></n-input>
</setting-item>
</setting-item-box>
<setting-item-box name="样式">
<setting-item name="圆角">
<n-input-number
v-model:value="optionData.borderRadius"
size="small"
:min="0"
placeholder="圆角"
></n-input-number>
</setting-item>
</setting-item-box>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType } from "vue";
import { option } from "./config";
import {
CollapseItem,
SettingItemBox,
SettingItem,
} from "@/components/Pages/ChartItemSetting";
const props = defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true,
},
});
</script>

View File

@ -0,0 +1,15 @@
import image from '@/assets/images/chart/informations/iframe.png'
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
export const IframeConfig: ConfigType = {
key: 'Iframe',
chartKey: 'VIframe',
conKey: 'VCIframe',
title: '远程网页',
category: ChatCategoryEnum.MORE,
categoryName: ChatCategoryEnumName.MORE,
package: PackagesCategoryEnum.INFORMATIONS,
chartFrame: ChartFrameEnum.COMMON,
image
}

View File

@ -0,0 +1,49 @@
<template>
<div :style="getStyle(borderRadius)">
<iframe :src="option.dataset" :width="w" :height="h" style="border-width: 0"></iframe>
</div>
</template>
<script setup lang="ts">
import { PropType, shallowReactive, watch, toRefs } from 'vue'
import { useChartDataFetch } from '@/hooks'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const { w, h } = toRefs(props.chartConfig.attr)
const { borderRadius } = toRefs(props.chartConfig.option)
const option = shallowReactive({
dataset: ''
})
const getStyle = (radius: number) => {
return {
borderRadius: `${radius}px`,
overflow: 'hidden'
}
}
//
watch(
() => props.chartConfig.option.dataset,
(newData: string) => {
option.dataset = newData
},
{
immediate: true
}
)
//
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: string) => {
option.dataset = newData
})
</script>

View File

@ -46,8 +46,7 @@ watch(
option.dataset = newData option.dataset = newData
}, },
{ {
immediate: true, immediate: true
deep: false
} }
) )

View File

@ -10,6 +10,6 @@ export const VideoConfig: ConfigType = {
category: ChatCategoryEnum.MORE, category: ChatCategoryEnum.MORE,
categoryName: ChatCategoryEnumName.MORE, categoryName: ChatCategoryEnumName.MORE,
package: PackagesCategoryEnum.INFORMATIONS, package: PackagesCategoryEnum.INFORMATIONS,
chartFrame: ChartFrameEnum.ECHARTS, chartFrame: ChartFrameEnum.COMMON,
image image
} }

View File

@ -1,5 +1,6 @@
import { ImageConfig } from './Image/index' import { ImageConfig } from './Image/index'
import { IframeConfig } from './Iframe/index'
import { VideoConfig } from './Video/index' import { VideoConfig } from './Video/index'
import { WordCloudConfig } from './WordCloud/index' import { WordCloudConfig } from './WordCloud/index'
export default [ImageConfig, VideoConfig, WordCloudConfig] export default [WordCloudConfig, ImageConfig, VideoConfig, IframeConfig]

View File

@ -0,0 +1,42 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { TextBarrageConfig } from './index'
import { chartInitConfig } from '@/settings/designSetting'
import cloneDeep from 'lodash/cloneDeep'
export enum FontWeightEnum {
NORMAL = '常规',
BOLD = '加粗',
}
export const FontWeightObject = {
[FontWeightEnum.NORMAL]: 'normal',
[FontWeightEnum.BOLD]: 'bold',
}
export const option = {
dataset: '让数字化看得见',
fontSize: 32,
fontColor: '#ffffff',
fontWeight: 'normal',
// 字间距
letterSpacing: 5,
//阴影
showShadow: true,
boxShadow: 'none',
hShadow: 0,
vShadow: 0,
blurShadow: 8,
colorShadow: '#0075ff',
//动画
animationTime: 0,
animationSpeed: 50,
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = TextBarrageConfig.key
public attr = { ...chartInitConfig, w: 500, h: 70, zIndex: -1 }
public chartConfig = cloneDeep(TextBarrageConfig)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,89 @@
<template>
<collapse-item name="信息" :expanded="true">
<setting-item-box name="文字" :alone="true">
<setting-item>
<n-input v-model:value="optionData.dataset" size="small"></n-input>
</setting-item>
</setting-item-box>
</collapse-item>
<collapse-item name="样式" :expanded="true">
<setting-item-box name="文字">
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.fontColor"></n-color-picker>
</setting-item>
<setting-item name="字体大小">
<n-input-number v-model:value="optionData.fontSize" size="small" placeholder="字体大小"></n-input-number>
</setting-item>
<setting-item name="字体粗细">
<n-select v-model:value="optionData.fontWeight" size="small" :options="fontWeightOptions" />
</setting-item>
<setting-item name="字间距">
<n-input-number v-model:value="optionData.letterSpacing" size="small" placeholder="输入字间距"></n-input-number>
</setting-item>
</setting-item-box>
<setting-item-box name="阴影">
<setting-item>
<n-space>
<n-switch v-model:value="optionData.showShadow" size="small" />
<n-text>展示阴影</n-text>
</n-space>
</setting-item>
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.colorShadow"></n-color-picker
></setting-item>
<setting-item name="x">
<n-input-number v-model:value="optionData.hShadow" size="small"></n-input-number
></setting-item>
<setting-item name="y">
<n-input-number v-model:value="optionData.vShadow" size="small"></n-input-number
></setting-item>
<setting-item name="模糊">
<n-input-number v-model:value="optionData.blurShadow" size="small"></n-input-number
></setting-item>
</setting-item-box>
<setting-item-box name="动画">
<setting-item name="动画速度">
<n-input-number
v-model:value="optionData.animationSpeed"
size="small"
placeholder="动画速度"
:min="0"
></n-input-number>
</setting-item>
<setting-item name="动画间隔">
<n-input-number
v-model:value="optionData.animationTime"
size="small"
placeholder="动画间隔"
:min="0"
></n-input-number>
</setting-item>
</setting-item-box>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { option, FontWeightEnum, FontWeightObject } from './config'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true
}
})
const fontWeightOptions = [
{
label: FontWeightEnum.NORMAL,
value: FontWeightObject[FontWeightEnum.NORMAL]
},
{
label: FontWeightEnum.BOLD,
value: FontWeightObject[FontWeightEnum.BOLD]
}
]
</script>

View File

@ -0,0 +1,14 @@
import image from '@/assets/images/chart/informations/text_barrage.png'
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
export const TextBarrageConfig: ConfigType = {
key: 'TextBarrage',
chartKey: 'VTextBarrage',
conKey: 'VCTextBarrage',
title: '弹幕文字',
category: ChatCategoryEnum.TEXT,
categoryName: ChatCategoryEnumName.TEXT,
package: PackagesCategoryEnum.INFORMATIONS,
image
}

View File

@ -0,0 +1,102 @@
<template>
<div class="go-text-box">
<div class="content">
<span>
{{ option.dataset }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType, toRefs, shallowReactive, watch, computed, ref } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { option as configOption } from './config'
import { values } from 'lodash'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType & typeof option>,
required: true
}
})
const { w } = toRefs(props.chartConfig.attr)
const { fontColor, fontSize, letterSpacing, fontWeight, animationTime, animationSpeed, boxShadow } = toRefs(
props.chartConfig.option
)
const option = shallowReactive({
dataset: configOption.dataset
})
//
watch(
() => props.chartConfig.option.dataset,
(newData: any) => {
option.dataset = newData
},
{
immediate: true,
deep: false
}
)
//
watch(
props.chartConfig.option,
() => {
try {
if (props.chartConfig.option.showShadow) {
boxShadow.value = `${props.chartConfig.option.hShadow}px ${props.chartConfig.option.vShadow}px ${props.chartConfig.option.blurShadow}px ${props.chartConfig.option.colorShadow}`
} else {
boxShadow.value = 'none'
}
} catch (error) {
console.log(error)
}
},
{
immediate: true
}
)
const transitionDuration = computed(() => {
return Math.floor((w.value as any) / (animationSpeed.value as any))
})
//
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: string) => {
option.dataset = newData
})
</script>
<style lang="scss" scoped>
@include go('text-box') {
display: flex;
align-items: center;
.content {
width: 100%;
color: v-bind('fontColor');
font-size: v-bind('fontSize + "px"');
letter-spacing: v-bind('letterSpacing + "px"');
font-weight: v-bind('fontWeight');
text-shadow: v-bind('boxShadow');
position: absolute;
animation: barrage v-bind('transitionDuration + "s"') linear v-bind('animationTime + "s"') infinite;
}
@keyframes barrage {
from {
left: 100%;
transform: translateX(0);
}
to {
left: 0;
transform: translateX(-100%);
}
}
}
</style>

View File

@ -1,4 +1,5 @@
import { TextCommonConfig } from './TextCommon/index' import { TextCommonConfig } from './TextCommon/index'
import { TextBarrageConfig } from './TextBarrage/index'
import { TextGradientConfig } from './TextGradient/index' import { TextGradientConfig } from './TextGradient/index'
export default [TextCommonConfig, TextGradientConfig] export default [TextCommonConfig, TextGradientConfig, TextBarrageConfig]

View File

@ -90,6 +90,14 @@ export const BlendModeEnumList = [
{ label: '亮度', value: 'luminosity' } { label: '亮度', value: 'luminosity' }
] ]
// vue3 生命周期事件
export enum EventLife {
// 渲染之后
MOUNTED = 'vnodeMounted',
// 渲染之前
BEFORE_MOUNT = 'vnodeBeforeMount',
}
// 组件实例类 // 组件实例类
export interface PublicConfigType { export interface PublicConfigType {
id: string id: string
@ -115,12 +123,15 @@ export interface PublicConfigType {
} }
filter?: string filter?: string
status: StatusType status: StatusType
events?: {
[K in EventLife]?: string
}
} }
export interface CreateComponentType extends PublicConfigType, requestConfig { export interface CreateComponentType extends PublicConfigType, requestConfig {
key: string key: string
chartConfig: ConfigType chartConfig: ConfigType
option: GlobalThemeJsonType option: GlobalThemeJsonType,
} }
// 组件成组实例类 // 组件成组实例类

View File

@ -81,6 +81,8 @@ export class PublicConfigClass implements PublicConfigType {
public request = cloneDeep(requestConfig) public request = cloneDeep(requestConfig)
// 数据过滤 // 数据过滤
public filter = undefined public filter = undefined
// 事件
public events = undefined
} }
// 多选成组类 // 多选成组类

View File

@ -27,6 +27,7 @@ import {
LockClosedOutline as LockClosedOutlineIcon, LockClosedOutline as LockClosedOutlineIcon,
HelpCircleOutline as HelpOutlineIcon, HelpCircleOutline as HelpOutlineIcon,
CodeSlash as CodeSlashIcon, CodeSlash as CodeSlashIcon,
Create as CreateIcon,
Rocket as RocketIcon, Rocket as RocketIcon,
Duplicate as DuplicateIcon, Duplicate as DuplicateIcon,
DuplicateOutline as DuplicateOutlineIcon, DuplicateOutline as DuplicateOutlineIcon,
@ -106,6 +107,8 @@ const ionicons5 = {
DuplicateOutlineIcon, DuplicateOutlineIcon,
// 代码 // 代码
CodeSlashIcon, CodeSlashIcon,
// 修改代码
CreateIcon,
// 事件(火箭) // 事件(火箭)
RocketIcon, RocketIcon,
// 退出 // 退出

View File

@ -21,7 +21,8 @@ const RootRoute: Array<RouteRecordRaw> = [
...HttpErrorPage, ...HttpErrorPage,
modules.projectRoutes, modules.projectRoutes,
modules.chartRoutes, modules.chartRoutes,
modules.previewRoutes modules.previewRoutes,
modules.editRoutes
] ]
} }
] ]

View File

@ -12,7 +12,8 @@ const chartRoutes: RouteRecordRaw = {
component: importPath['ChartEnum.CHART_HOME_NAME'], component: importPath['ChartEnum.CHART_HOME_NAME'],
meta: { meta: {
title: '工作空间', title: '工作空间',
isRoot: true isRoot: true,
noKeepAlive: true,
} }
} }

View File

@ -0,0 +1,20 @@
import { RouteRecordRaw } from 'vue-router'
import { EditEnum } from '@/enums/pageEnum'
// 引入路径
const importPath = {
[EditEnum.CHART_EDIT_NAME]: () => import('@/views/edit/index.vue')
}
const chartRoutes: RouteRecordRaw = {
path: EditEnum.CHART_EDIT,
name: EditEnum.CHART_EDIT_NAME,
component: importPath[EditEnum.CHART_EDIT_NAME],
meta: {
title: '编辑',
isRoot: true
}
}
export default chartRoutes

View File

@ -1,9 +1,11 @@
import projectRoutes from './project.router' import projectRoutes from './project.router'
import chartRoutes from './chart.route' import chartRoutes from './chart.route'
import previewRoutes from './preview.route' import previewRoutes from './preview.route'
import editRoutes from './edit.route'
export default { export default {
projectRoutes, projectRoutes,
chartRoutes, chartRoutes,
previewRoutes previewRoutes,
editRoutes
} }

View File

@ -3,7 +3,7 @@ import { PreviewEnum } from '@/enums/pageEnum'
// 引入路径 // 引入路径
const importPath = { const importPath = {
'PreviewEnum.CHART_PREVIEW_NAME': () => import('@/views/preview/index.vue') 'PreviewEnum.CHART_PREVIEW_NAME': () => import('@/views/preview/wrapper.vue')
} }
const chartRoutes: RouteRecordRaw = { const chartRoutes: RouteRecordRaw = {

View File

@ -11,7 +11,6 @@ export const animations = [
{ label: '放大晃动缩小', value: 'tada' }, { label: '放大晃动缩小', value: 'tada' },
{ label: '扇形摇摆', value: 'wobble' }, { label: '扇形摇摆', value: 'wobble' },
{ label: '左右上下晃动', value: 'jello' }, { label: '左右上下晃动', value: 'jello' },
{ label: 'Y轴旋转', value: 'flip' }
] ]
}, },
{ {

View File

@ -55,6 +55,9 @@ export const backgroundImageSize = 5
// 预览展示方式 // 预览展示方式
export const previewScaleType = PreviewScaleEnum.FIT export const previewScaleType = PreviewScaleEnum.FIT
// 编辑工作台同步到 JSON 的轮询间隔5S
export const editToJsonInterval = 5000
// 数据请求间隔 // 数据请求间隔
export const requestInterval = 30 export const requestInterval = 30

View File

@ -1 +1,9 @@
// 页面全局样式 // 页面全局样式
// 去除高德地图 logo
.amap-logo {
display: none !important;
opacity: 0 !important;
}
.amap-copyright {
opacity: 0 !important;
}

View File

@ -116,7 +116,8 @@ $centerHeight: 100px;
height: $centerHeight; height: $centerHeight;
overflow: hidden; overflow: hidden;
.list-img { .list-img {
height: 100%; height: 100px;
width: 140px;
border-radius: 6px; border-radius: 6px;
@extend .go-transition; @extend .go-transition;
} }

View File

@ -73,6 +73,7 @@
<n-text>SQL 类型不支持 Get 请求请使用其它方式</n-text> <n-text>SQL 类型不支持 Get 请求请使用其它方式</n-text>
</template> </template>
<template v-else> <template v-else>
<n-tag type="warning">需要后台提供专门处理 sql 的接口</n-tag>
<setting-item-box name="键名"> <setting-item-box name="键名">
<n-tag type="primary" :bordered="false" style="width: 40px; font-size: 16px"> sql </n-tag> <n-tag type="primary" :bordered="false" style="width: 40px; font-size: 16px"> sql </n-tag>
</setting-item-box> </setting-item-box>

View File

@ -75,6 +75,7 @@ import {
heatMapUrl, heatMapUrl,
scatterBasicUrl, scatterBasicUrl,
mapUrl, mapUrl,
capsuleUrl,
wordCloudUrl, wordCloudUrl,
treemapUrl, treemapUrl,
threeEarth01Url threeEarth01Url
@ -122,6 +123,9 @@ const apiList = [
{ {
value: `【地图数据】${mapUrl}` value: `【地图数据】${mapUrl}`
}, },
{
value: `【胶囊柱图】${capsuleUrl}`
},
{ {
value: `【词云】${wordCloudUrl}` value: `【词云】${wordCloudUrl}`
}, },

View File

@ -0,0 +1,177 @@
// 获取实例
const eTemplateString = `
console.log(e)
`
// 获取全局 echarts 实例
const echartsTemplateString = `
console.log(echarts)
`
// 获取当前组件图表集合
const componentsTemplateString = `
console.log(components)
`
// 获取 nodeModules 实例
const nodeModulesTemplateString = `
console.log(node_modules)
`
// 异步引入
const importTemplateString = `
await import('https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.js')
// lodash 默认赋值给 "_"
console.log('isEqual', _.isEqual(['1'], ['1']))
`
// 修改图表 tooltip
const tooltipTemplateString =
`
// 获取echart实例
const chart = this.refs.vChartRef.chart
// 图表设置tooltip
chart.setOption({
tooltip: {
trigger: 'axis', //item
enterable: true,
formatter (params) {
return` +
'`' +
`
<div>
<img src="https://portrait.gitee.com/uploads/avatars/user/1654/4964818_MTrun_1653229420.png!avatar30">
<b><a href="https://gitee.com/dromara/go-view">tooltip</a></b>
<div>
<div style='border-radius:35px;color:#666'>
` +
'$' +
`{Object.entries(params[0].value).map(kv => ` +
'`' +
`<div>` +
'$' +
`{kv[0]}:` +
'$' +
`{kv[1]}</div>` +
'`' +
`).join('')}
</div>
` +
'`;' +
`
},
}
})
`
// 添加【轮播列表】样式
const addStyleString =
`
// 组件样式作用域标识
const scoped = this.subTree.scopeId
function loadStyleString(css){
let style = document.createElement('style')
style.type = 'text/css'
style.appendChild(document.createTextNode(css))
let head = document.getElementsByTagName('head')[0]
head.appendChild(style)
}
loadStyleString(` +
'`' +
`
.dv-scroll-board[` +
'$' +
`{scoped}] {
position: relative;
overflow: hidden;
}
.dv-scroll-board[` +
'$' +
`{scoped}]::before {
content: '';
display: block;
position: absolute;
top: -20%;
left: -100%;
width: 550px;
height: 60px;
transform: rotate(-45deg);
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0));
animation: cross 2s infinite;
}
@keyframes cross{
to{
top: 80%;
left: 100%;
transform: rotate(-45deg);
}
}
` +
'`' +
`)
`
// 修改地图原点大小
const editMapPointString = `
const chart = this.refs.vChartRef.chart
// 定义地图原点大小 同理可自定义标签等等内容
this.props.chartConfig.option.series[0].symbolSize = (val) => {
return Math.sqrt(val[2]) / 3;
}
this.setupState.vEchartsSetOption();
let i = 0; // 当前轮播索引
const len = 3; // 轮播部分提示
(function showTips() {
const action = (type, dataIndex) => {
chart.dispatchAction({
type,
dataIndex,
seriesIndex: 0,
});
}
setInterval(() => {
action("downplay", i);
action("hideTip", i);
if (i === len) i = 0;
i++;
action("highlight", i);
action("showTip", i);
}, 2000);
})()
`
export const templateList = [
{
description: '获取当前组件实例',
code: eTemplateString
},
{
description: '获取全局 echarts 实例',
code: echartsTemplateString
},
{
description: '获取组件图表集合',
code: componentsTemplateString
},
{
description: '获取 nodeModules 实例',
code: nodeModulesTemplateString
},
{
description: '获取远程 CDN 库',
code: importTemplateString
},
{
description: '修改图表 tooltip',
code: tooltipTemplateString
},
{
description: '添加【轮播列表】样式',
code: addStyleString
},
{
description: '修改【地图】圆点,新增提示自动轮播',
code: editMapPointString
}
]

View File

@ -0,0 +1,3 @@
import ChartEventMonacoEditor from './index.vue'
export { ChartEventMonacoEditor }

View File

@ -0,0 +1,285 @@
<template>
<n-collapse-item title="高级事件配置" name="2">
<template #header-extra>
<n-button type="primary" tertiary size="small" @click.stop="showModal = true">
<template #icon>
<n-icon>
<pencil-icon />
</n-icon>
</template>
编辑
</n-button>
</template>
<n-card>
<!-- 函数体 -->
<div v-for="eventName in EventLife" :key="eventName">
<p>
<span class="func-keyword">async {{ eventName }}</span> (e, components, echarts, node_modules) {
</p>
<p class="go-ml-4"><n-code :code="(targetData.events || {})[eventName]" language="typescript"></n-code></p>
<p>}<span>,</span></p>
</div>
</n-card>
</n-collapse-item>
<!-- 弹窗 -->
<n-modal class="go-chart-data-monaco-editor" v-model:show="showModal" :mask-closable="false">
<n-card :bordered="false" role="dialog" size="small" aria-modal="true" style="width: 1200px; height: 700px">
<template #header>
<n-space>
<n-text>高级事件编辑器配合源码使用</n-text>
</n-space>
</template>
<template #header-extra> </template>
<n-layout has-sider sider-placement="right">
<n-layout style="height: 580px; padding-right: 20px">
<n-tabs v-model:value="editTab" type="card" tab-style="min-width: 100px;">
<!-- 提示 -->
<template #suffix>
<n-text class="tab-tip" type="warning">tips: {{ EventLifeTip[editTab] }}</n-text>
</template>
<n-tab-pane
v-for="(eventName, index) in EventLife"
:key="index"
:tab="`${EventLifeName[eventName]}-${eventName}`"
:name="eventName"
>
<!-- 函数名称 -->
<p class="go-pl-3">
<span class="func-keyword">async function &nbsp;&nbsp;</span>
<span class="func-keyNameWord">{{ eventName }}(e, components, echarts, node_modules)&nbsp;&nbsp;{</span>
</p>
<!-- 编辑主体 -->
<monaco-editor v-model:modelValue="events[eventName]" height="480px" language="javascript" />
<!-- 函数结束 -->
<p class="go-pl-3 func-keyNameWord">}</p>
</n-tab-pane>
</n-tabs>
</n-layout>
<n-layout-sider
:collapsed-width="14"
:width="340"
show-trigger="bar"
collapse-mode="transform"
content-style="padding: 12px 12px 0px 12px;margin-left: 3px;"
>
<n-tabs default-value="1" justify-content="space-evenly" type="segment">
<!-- 验证结果 -->
<n-tab-pane tab="验证结果" name="1" size="small">
<n-scrollbar trigger="none" style="max-height: 505px">
<n-collapse class="go-px-3" arrow-placement="right" :default-expanded-names="[1, 2, 3]">
<template v-for="error in [validEvents()]" :key="error">
<n-collapse-item title="错误函数" :name="1">
<n-text depth="3">{{ error.errorFn || '暂无' }}</n-text>
</n-collapse-item>
<n-collapse-item title="错误信息" :name="2">
<n-text depth="3">{{ error.name || '暂无' }}</n-text>
</n-collapse-item>
<n-collapse-item title="堆栈信息" :name="3">
<n-text depth="3">{{ error.message || '暂无' }}</n-text>
</n-collapse-item>
</template>
</n-collapse>
</n-scrollbar>
</n-tab-pane>
<!-- 辅助说明 -->
<n-tab-pane tab="变量说明" name="2">
<n-scrollbar trigger="none" style="max-height: 505px">
<n-collapse class="go-px-3" arrow-placement="right" :default-expanded-names="[1, 2, 3, 4]">
<n-collapse-item title="e" :name="1">
<n-text depth="3">触发对应生命周期事件时接收的参数</n-text>
</n-collapse-item>
<n-collapse-item title="this" :name="2">
<n-text depth="3">图表组件实例</n-text>
<br />
<n-tag class="go-m-1" v-for="prop in ['refs', 'setupState', 'ctx', 'props', '...']" :key="prop">{{
prop
}}</n-tag>
</n-collapse-item>
<n-collapse-item title="components" :name="3">
<n-text depth="3"
>当前大屏内所有组件的集合id 图表组件中的配置id可以获取其他图表组件进行控制</n-text
>
<n-code :code="`{\n [id]: component\n}`" language="typescript"></n-code>
</n-collapse-item>
<n-collapse-item title="node_modules" :name="4">
<n-text depth="3">以下是内置在代码环境中可用的包变量</n-text>
<br />
<n-tag class="go-m-1" v-for="pkg in Object.keys(npmPkgs || {})" :key="pkg">{{ pkg }}</n-tag>
</n-collapse-item>
</n-collapse>
</n-scrollbar>
</n-tab-pane>
<!-- 介绍案例 -->
<n-tab-pane tab="介绍案例" name="3">
<n-scrollbar trigger="none" style="max-height: 505px">
<n-collapse arrow-placement="right">
<n-collapse-item
v-for="(item, index) in templateList"
:key="index"
:title="`案例${index + 1}${item.description}`"
:name="index"
>
<n-code :code="item.code" language="typescript"></n-code>
</n-collapse-item>
</n-collapse>
</n-scrollbar>
</n-tab-pane>
</n-tabs>
</n-layout-sider>
</n-layout>
<template #action>
<n-space justify="space-between">
<div class="go-flex-items-center">
<n-tag :bordered="false" type="primary">
<template #icon>
<n-icon :component="DocumentTextIcon" />
</template>
提示
</n-tag>
<n-text class="go-ml-2" depth="2">通过提供的参数可为图表增加定制化的tooltip交互事件等等</n-text>
</div>
<n-space>
<n-button size="medium" @click="closeEvents">取消</n-button>
<n-button size="medium" type="primary" @click="saveEvents">保存</n-button>
</n-space>
</n-space>
</template>
</n-card>
</n-modal>
</template>
<script lang="ts" setup>
import { ref, computed, watch, toRefs, toRaw } from 'vue'
import { MonacoEditor } from '@/components/Pages/MonacoEditor'
import { useTargetData } from '../../../hooks/useTargetData.hook'
import { templateList } from './importTemplate'
import { npmPkgs } from '@/hooks'
import { icon } from '@/plugins'
import { goDialog, toString } from '@/utils'
import { CreateComponentType, EventLife } from '@/packages/index.d'
import { Script } from 'vm'
const { targetData, chartEditStore } = useTargetData()
const { DocumentTextIcon, ChevronDownIcon, PencilIcon } = icon.ionicons5
const EventLifeName = {
[EventLife.BEFORE_MOUNT]: '渲染之前',
[EventLife.MOUNTED]: '渲染之后'
}
const EventLifeTip = {
[EventLife.BEFORE_MOUNT]: '此时组件 DOM 还未存在',
[EventLife.MOUNTED]: '此时组件 DOM 已经存在'
}
//
const showModal = ref(false)
//
const editTab = ref(EventLife.MOUNTED)
// events
let events = ref({ ...targetData.value.events })
//
const errorFlag = ref(false)
//
const validEvents = () => {
let errorFn = ''
let message = ''
let name = ''
errorFlag.value = Object.entries(events.value).every(([eventName, str]) => {
try {
// await
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
new AsyncFunction(str)
return true
} catch (error: any) {
message = error.message
name = error.name
errorFn = eventName
return false
}
})
return {
errorFn,
message,
name
}
}
//
const closeEvents = () => {
showModal.value = false
}
//
const saveEvents = () => {
if (validEvents().errorFn) {
window['$message'].error('事件函数错误,无法进行保存')
return
}
if (Object.values(events.value).join('').trim() === '') {
//
targetData.value.events = undefined
} else {
targetData.value.events = { ...events.value }
}
closeEvents()
}
watch(
() => showModal.value,
(newData: boolean) => {
if (newData) {
events.value = { ...targetData.value.events }
}
}
)
</script>
<style lang="scss" scoped>
/* 外层也要使用 */
.func-keyword {
color: #b478cf;
}
@include go('chart-data-monaco-editor') {
.func-keyNameWord {
color: #70c0e8;
}
.tab-tip {
font-size: 12px;
}
&.n-card.n-modal,
.n-card {
@extend .go-background-filter;
}
}
@include deep() {
.n-layout,
.n-layout-sider {
background-color: transparent;
}
.go-editor-area {
max-height: 530px;
}
.checkbox--hidden:checked {
& + label {
.n-icon {
transition: all 0.3s;
transform: rotate(180deg);
}
}
& ~ .go-editor-area {
display: none;
}
}
//
.n-code > pre {
white-space: break-spaces;
}
}
</style>

View File

@ -0,0 +1,24 @@
<template>
<!-- 事件配置 -->
<n-collapse class="go-mt-3" arrow-placement="right" :default-expanded-names="['1', '2']">
<n-text depth="3">
组件 id
<n-text>{{ targetData.id }}</n-text>
</n-text>
<n-collapse-item title="基础事件配置" name="1">
<div class="go-event">
<n-text depth="3"> 单击双击移入移出尽情期待 </n-text>
</div>
</n-collapse-item>
<chart-event-monaco-editor></chart-event-monaco-editor>
</n-collapse>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ChartEventMonacoEditor } from './components/ChartEventMonacoEditor'
import { useTargetData } from '../hooks/useTargetData.hook'
const { targetData } = useTargetData()
const showModal = ref(false)
</script>

View File

@ -3,4 +3,5 @@ export enum TabsEnum {
CHART_SETTING = 'chartSetting', CHART_SETTING = 'chartSetting',
CHART_ANIMATION = 'chartAnimation', CHART_ANIMATION = 'chartAnimation',
CHART_DATA = 'chartData', CHART_DATA = 'chartData',
CHART_EVENT = 'chartEvent'
} }

View File

@ -75,12 +75,13 @@ const { getDetails } = toRefs(useChartLayoutStore())
const { setItem } = useChartLayoutStore() const { setItem } = useChartLayoutStore()
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const { ConstructIcon, FlashIcon, DesktopOutlineIcon, LeafIcon } = icon.ionicons5 const { ConstructIcon, FlashIcon, DesktopOutlineIcon, LeafIcon, RocketIcon } = icon.ionicons5
const ContentEdit = loadAsyncComponent(() => import('../ContentEdit/index.vue')) const ContentEdit = loadAsyncComponent(() => import('../ContentEdit/index.vue'))
const CanvasPage = loadAsyncComponent(() => import('./components/CanvasPage/index.vue')) const CanvasPage = loadAsyncComponent(() => import('./components/CanvasPage/index.vue'))
const ChartSetting = loadAsyncComponent(() => import('./components/ChartSetting/index.vue')) const ChartSetting = loadAsyncComponent(() => import('./components/ChartSetting/index.vue'))
const ChartData = loadAsyncComponent(() => import('./components/ChartData/index.vue')) const ChartData = loadAsyncComponent(() => import('./components/ChartData/index.vue'))
const ChartEvent = loadAsyncComponent(() => import('./components/ChartEvent/index.vue'))
const ChartAnimation = loadAsyncComponent(() => import('./components/ChartAnimation/index.vue')) const ChartAnimation = loadAsyncComponent(() => import('./components/ChartAnimation/index.vue'))
const collapsed = ref<boolean>(getDetails.value) const collapsed = ref<boolean>(getDetails.value)
@ -148,6 +149,12 @@ const chartsTabList = [
title: '数据', title: '数据',
icon: FlashIcon, icon: FlashIcon,
render: ChartData render: ChartData
},
{
key: TabsEnum.CHART_EVENT,
title: '事件',
icon: RocketIcon,
render: ChartEvent
} }
] ]
</script> </script>

View File

@ -135,9 +135,11 @@ watchEffect(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
$min-width: 500px; $min-width: 500px;
$max-width: 670px;
@include go('edit-bottom') { @include go('edit-bottom') {
width: 100%; width: 100%;
min-width: $min-width; min-width: $min-width;
min-width: $max-width;
padding: 0 10px; padding: 0 10px;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -7,7 +7,11 @@
@mouseleave="toolsMouseoutHandle" @mouseleave="toolsMouseoutHandle"
> >
<!-- PawIcon --> <!-- PawIcon -->
<n-icon v-show="settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE && isMiniComputed " class="asideLogo" size="22"> <n-icon
v-show="settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE && isMiniComputed"
class="asideLogo"
size="22"
>
<PawIcon></PawIcon> <PawIcon></PawIcon>
</n-icon> </n-icon>
@ -58,17 +62,28 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, h } from 'vue' import { ref, computed, h, watch } from 'vue'
import { useSettingStore } from '@/store/modules/settingStore/settingStore' import { useSettingStore } from '@/store/modules/settingStore/settingStore'
import { ToolsStatusEnum } from '@/store/modules/settingStore/settingStore.d' import { ToolsStatusEnum } from '@/store/modules/settingStore/settingStore.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { fetchPathByName, routerTurnByPath, setSessionStorage, getLocalStorage } from '@/utils'
import { editToJsonInterval } from '@/settings/designSetting'
import { EditEnum, ChartEnum } from '@/enums/pageEnum'
import { StorageEnum } from '@/enums/storageEnum'
import { useRoute } from 'vue-router'
import { useSync } from '@/views/chart/hooks/useSync.hook'
import { SavePageEnum } from '@/enums/editPageEnum'
import { GoSystemSet } from '@/components/GoSystemSet/index' import { GoSystemSet } from '@/components/GoSystemSet/index'
import { exportHandle } from './utils' import { exportHandle } from './utils'
import { useFile } from './hooks/useFile.hooks' import { useFile } from './hooks/useFile.hooks'
import { BtnListType, TypeEnum } from './index.d' import { BtnListType, TypeEnum } from './index.d'
import { icon } from '@/plugins' import { icon } from '@/plugins'
const { DownloadIcon, ShareIcon, PawIcon, SettingsSharpIcon } = icon.ionicons5 const { DownloadIcon, ShareIcon, PawIcon, SettingsSharpIcon, CreateIcon } = icon.ionicons5
const settingStore = useSettingStore() const settingStore = useSettingStore()
const chartEditStore = useChartEditStore()
const routerParamsInfo = useRoute()
const { updateComponent } = useSync()
// //
let mouseTime: any = null let mouseTime: any = null
@ -80,38 +95,16 @@ const isMini = ref<boolean>(true)
const asideTootipDis = ref(true) const asideTootipDis = ref(true)
// //
const { importUploadFileListRef, importCustomRequest, importBeforeUpload } = useFile() const { importUploadFileListRef, importCustomRequest, importBeforeUpload } = useFile()
//
const btnList: BtnListType[] = [
{
key: 'export',
type: TypeEnum.BUTTON,
name: '导出',
icon: ShareIcon,
handle: exportHandle
},
{
key: 'import',
type: TypeEnum.IMPORTUPLOAD,
name: '导入',
icon: DownloadIcon
},
{
key: 'setting',
type: TypeEnum.BUTTON,
name: '设置',
icon: SettingsSharpIcon,
handle: () => {
globalSettingModel.value = true
}
}
]
// //
const isAside = computed(() => settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE) const isAside = computed(() => settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE)
// //
const isHide = computed(() => settingStore.getChartToolsStatusHide) const isHide = computed(() => settingStore.getChartToolsStatusHide)
// //
const isMiniComputed = computed(() => isMini.value && isHide.value) const isMiniComputed = computed(() => isMini.value && isHide.value)
// //
const btnListComputed = computed(() => { const btnListComputed = computed(() => {
if (!isAside.value) return btnList if (!isAside.value) return btnList
@ -142,6 +135,119 @@ const toolsMouseoutHandle = () => {
isMini.value = true isMini.value = true
} }
} }
//
const editHandle = () => {
window['$message'].warning('将开启失焦更新与 5 秒同步更新!')
setTimeout(() => {
// id
const path = fetchPathByName(EditEnum.CHART_EDIT_NAME, 'href')
if (!path) return
let { id } = routerParamsInfo.params as any
id = typeof id === 'string' ? id : id[0]
updateToSession(id)
routerTurnByPath(path, [id], undefined, true)
}, 1000)
}
// SessionStorage 便
const updateToSession = (id: string) => {
const storageInfo = chartEditStore.getStorageInfo
const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || []
if (sessionStorageInfo?.length) {
const repeateIndex = sessionStorageInfo.findIndex((e: { id: string }) => e.id === id)
//
if (repeateIndex !== -1) {
sessionStorageInfo.splice(repeateIndex, 1, { ...storageInfo, id })
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
} else {
sessionStorageInfo.push({ ...storageInfo, id })
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
}
} else {
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...storageInfo, id }])
}
}
//
const useSyncUpdate = () => {
//
let timer: any
const updateFn = (e: any) => updateComponent(e!.detail, true, false)
const syncData = () => {
if (routerParamsInfo.name == ChartEnum.CHART_HOME_NAME) {
dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo }))
}
}
//
const use = () => {
// 1
timer = setInterval(() => {
//
document.hasFocus() && syncData()
}, editToJsonInterval)
// 2
addEventListener('blur', syncData)
// JSON
addEventListener(SavePageEnum.JSON, updateFn)
}
//
const unUse = () => {
clearInterval(timer)
removeEventListener(SavePageEnum.JSON, updateFn)
removeEventListener('blur', syncData)
}
//
const watchHandler = (toName: any, fromName: any) => {
if (fromName == ChartEnum.CHART_HOME_NAME) {
unUse()
}
if (toName == ChartEnum.CHART_HOME_NAME) {
use()
}
}
return watchHandler
}
watch(() => routerParamsInfo.name, useSyncUpdate(), { immediate: true })
//
const btnList: BtnListType[] = [
{
key: 'export',
type: TypeEnum.BUTTON,
name: '导出',
icon: ShareIcon,
handle: exportHandle
},
{
key: 'import',
type: TypeEnum.IMPORTUPLOAD,
name: '导入',
icon: DownloadIcon
},
{
key: 'edit',
type: TypeEnum.BUTTON,
name: '编辑JSON',
icon: CreateIcon,
handle: editHandle
},
{
key: 'setting',
type: TypeEnum.BUTTON,
name: '设置',
icon: SettingsSharpIcon,
handle: () => {
globalSettingModel.value = true
}
}
]
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -168,7 +274,7 @@ $asideBottom: 70px;
flex-direction: column-reverse; flex-direction: column-reverse;
height: auto; height: auto;
right: 20px; right: 20px;
padding: 20px 8px; padding: 30px 8px;
bottom: $asideBottom; bottom: $asideBottom;
overflow: hidden; overflow: hidden;
transition: height ease 0.4s; transition: height ease 0.4s;

128
src/views/edit/index.vue Normal file
View File

@ -0,0 +1,128 @@
<template>
<div class="go-edit">
<n-layout>
<n-layout-header class="go-edit-header go-px-5 go-flex-items-center" bordered>
<div>
<n-text class="go-edit-title go-mr-4">页面在线编辑器</n-text>
<n-button v-if="showOpenFilePicker" class="go-mr-3" size="medium" @click="importJSON">
<template #icon>
<n-icon>
<download-icon></download-icon>
</n-icon>
</template>
导入
</n-button>
</div>
<n-space>
<n-tag :bordered="false" type="warning"> 页面失焦保存 </n-tag>
<n-tag :bordered="false" type="warning"> ctrl + s 保存 </n-tag>
</n-space>
</n-layout-header>
<n-layout-content>
<monaco-editor
v-model:modelValue="content"
language="json"
:editorOptions="{
lineNumbers: 'on',
minimap: { enabled: true }
}"
/>
</n-layout-content>
</n-layout>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { MonacoEditor } from '@/components/Pages/MonacoEditor'
import { SavePageEnum } from '@/enums/editPageEnum'
import { getSessionStorageInfo } from '../preview/utils'
import type { ChartEditStorageType } from '../preview/index.d'
import { setSessionStorage } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum'
import { icon } from '@/plugins'
const { ChevronBackOutlineIcon, DownloadIcon } = icon.ionicons5
const showOpenFilePicker: Function = (window as any).showOpenFilePicker
let content = ref('')
// sessionStorage
function getDataBySession() {
const localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
content.value = JSON.stringify(localStorageInfo, undefined, 2)
}
getDataBySession()
//
function back() {
opener.name = Date.now()
window.open(opener.location.href, opener.name)
}
// json
async function importJSON() {
const files = await showOpenFilePicker()
const file = await files[0].getFile()
const fr = new FileReader()
fr.readAsText(file)
fr.onloadend = () => {
content.value = (fr.result || '').toString()
}
}
// [JSONJSONCtrl+S ]
opener.addEventListener(SavePageEnum.CHART, (e: any) => {
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [e.detail])
content.value = JSON.stringify(e.detail, undefined, 2)
})
// + =>
document.addEventListener('keydown', function (e) {
if (e.keyCode == 83 && (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey)) {
e.preventDefault()
updateSync()
}
})
addEventListener('blur', updateSync)
//
function updateSync() {
if (!opener) {
return window['$message'].error('源窗口已关闭,视图同步失败')
}
try {
const detail = JSON.parse(content.value)
delete detail.id
// id
opener.dispatchEvent(new CustomEvent(SavePageEnum.JSON, { detail }))
} catch (e) {
window['$message'].error('内容格式有误')
console.log(e)
}
}
</script>
<style lang="scss" scoped>
.go-edit {
display: flex;
flex-direction: column;
height: 100vh;
.go-edit-header {
display: flex;
align-items: center;
height: 60px;
justify-content: space-between;
.go-edit-title {
position: relative;
bottom: 3px;
font-size: 18px;
font-weight: bold;
}
}
@include deep() {
.go-editor-area {
height: calc(100vh - 60px) !important;
}
}
}
</style>

View File

@ -18,6 +18,7 @@
:themeSetting="themeSetting" :themeSetting="themeSetting"
:themeColor="themeColor" :themeColor="themeColor"
:style="{ ...getSizeStyle(item.attr) }" :style="{ ...getSizeStyle(item.attr) }"
v-on="useLifeHandler(item)"
></component> ></component>
</div> </div>
</template> </template>
@ -27,7 +28,7 @@ import { PropType } from 'vue'
import { CreateComponentGroupType } from '@/packages/index.d' import { CreateComponentGroupType } from '@/packages/index.d'
import { animationsClass, getFilterStyle, getTransformStyle, getBlendModeStyle } from '@/utils' import { animationsClass, getFilterStyle, getTransformStyle, getBlendModeStyle } from '@/utils'
import { getSizeStyle, getComponentAttrStyle, getStatusStyle } from '../../utils' import { getSizeStyle, getComponentAttrStyle, getStatusStyle } from '../../utils'
import { useLifeHandler } from '@/hooks'
const props = defineProps({ const props = defineProps({
groupData: { groupData: {
type: Object as PropType<CreateComponentGroupType>, type: Object as PropType<CreateComponentGroupType>,

View File

@ -29,6 +29,7 @@
:themeSetting="themeSetting" :themeSetting="themeSetting"
:themeColor="themeColor" :themeColor="themeColor"
:style="{ ...getSizeStyle(item.attr) }" :style="{ ...getSizeStyle(item.attr) }"
v-on="useLifeHandler(item)"
></component> ></component>
</div> </div>
</template> </template>
@ -41,7 +42,7 @@ import { CreateComponentGroupType } from '@/packages/index.d'
import { chartColors } from '@/settings/chartThemes/index' import { chartColors } from '@/settings/chartThemes/index'
import { animationsClass, getFilterStyle, getTransformStyle, getBlendModeStyle } from '@/utils' import { animationsClass, getFilterStyle, getTransformStyle, getBlendModeStyle } from '@/utils'
import { getSizeStyle, getComponentAttrStyle, getStatusStyle } from '../../utils' import { getSizeStyle, getComponentAttrStyle, getStatusStyle } from '../../utils'
import { useLifeHandler } from '@/hooks'
const props = defineProps({ const props = defineProps({
localStorageInfo: { localStorageInfo: {
type: Object as PropType<ChartEditStorageType>, type: Object as PropType<ChartEditStorageType>,

View File

@ -0,0 +1,25 @@
<template>
<Preview :key="key"></Preview>
</template>
<script setup lang="ts">
import { getSessionStorageInfo } from './utils'
import type { ChartEditStorageType } from './index.d'
import { SavePageEnum } from '@/enums/editPageEnum'
import { setSessionStorage } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum'
import { ref } from 'vue'
import Preview from './index.vue'
let key = ref(Date.now())
let localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
// -> sessionStorage -> reload Mounted
;[SavePageEnum.JSON, SavePageEnum.CHART].forEach((saveEvent: string) => {
opener.addEventListener(saveEvent, (e: any) => {
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }])
key.value = Date.now()
})
})
</script>

View File

@ -22,6 +22,10 @@ export default defineConfig({
{ {
find: '@', find: '@',
replacement: pathResolve('src') replacement: pathResolve('src')
},
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js', //解决i8n警告
} }
], ],
dedupe: ['vue'] dedupe: ['vue']
@ -63,7 +67,8 @@ export default defineConfig({
build: { build: {
target: 'es2015', target: 'es2015',
outDir: OUTPUT_DIR, outDir: OUTPUT_DIR,
terserOptions: terserOptions, // minify: 'terser', // 如果需要用terser混淆可打开这两行
// terserOptions: terserOptions,
rollupOptions: rollupOptions, rollupOptions: rollupOptions,
brotliSize: brotliSize, brotliSize: brotliSize,
chunkSizeWarningLimit: chunkSizeWarningLimit chunkSizeWarningLimit: chunkSizeWarningLimit