feat: 增加表格和雷达图

This commit is contained in:
huanghao1412 2024-02-03 10:29:31 +08:00
parent 97a3d24939
commit 54a4cafefd
11 changed files with 346 additions and 129 deletions

View File

@ -29,6 +29,7 @@ type ChartEditStoreType = typeof useChartEditStore
export const useChartCommonData = (
targetComponent: CreateComponentType,
useChartEditStore: ChartEditStoreType,
updateCallback?: (...args: any) => any
) => {
const vChartRef = ref<typeof VChart | null>(null)
let fetchInterval: any = 0
@ -139,17 +140,29 @@ export const useChartCommonData = (
// 多值的
if(isMultiple) {
if(Object.prototype.toString.call(data) === '[object Array]') {
if(data.length && data[0].dimensions && data[0].source) echartsUpdateHandle(data[0])
if(data.length && data[0].dimensions && data[0].source) {
echartsUpdateHandle(data[0])
// 更新回调函数
if (updateCallback) updateCallback(data)
}
else throw Error()
}
else if(Object.prototype.toString.call(data) === '[object Object]'){
if(data.dimensions && data.source) echartsUpdateHandle(data)
if(data.dimensions && data.source) {
echartsUpdateHandle(data)
// 更新回调函数
if (updateCallback) updateCallback(data)
}
else throw Error()
}
}
// 单值的
else {
if(data) echartsUpdateHandle(data)
if(data) {
echartsUpdateHandle(data)
// 更新回调函数
if (updateCallback) updateCallback(data)
}
else throw Error()
}
} catch (error) {

View File

@ -12,14 +12,25 @@ export const RadarShapeEnumList = [
{ label: '圆形', value: 'circle' }
]
interface maxMapType {
[k: string]: {
max: number,
min: number
}
}
export const option = {
maxMap: {} as maxMapType,
tooltip: {
show: true
},
legend: {
data: dataJson.seriesData.map(i => i.name)
data: []
},
dataset: {
dimensions: [],
source: []
},
dataset: { ...dataJson },
radar: {
shape: 'polygon',
radius: ['0%', '60%'],
@ -28,8 +39,8 @@ export const option = {
splitLine: { show: true },
axisName: { show: true, color: '#eee', fontSize: 12 },
axisLine: { show: true },
axisTick: { show: true },
indicator: dataJson.radarIndicator
axisTick: { show: false },
indicator: []
},
series: [
{
@ -38,7 +49,7 @@ export const option = {
areaStyle: {
opacity: 0.1
},
data: dataJson.seriesData
data: []
}
]
}

View File

@ -94,12 +94,21 @@
></n-input-number>
</SettingItem>
</SettingItemBox>
<SettingItemBox :name="item.key" v-for="(item, i) in maxList" :key="i">
<SettingItem name="最小值">
<n-input-number :value="item.min" @update:value="v => handleUpdate(item.key, 'min', v)" size="small" :min="0"/>
</SettingItem>
<SettingItem name="最大值">
<n-input-number :value="item.max" @update:value="v => handleUpdate(item.key, 'max', v)" size="small" :min="0"/>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</div>
</template>
<script setup lang="ts">
import { PropType, computed, reactive } from 'vue'
import { PropType, computed, reactive, ref } from 'vue'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { option, RadarShapeEnumList } from './config'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
@ -142,4 +151,22 @@ const updateCenter1 = (value: number) => {
const sliderFormatTooltip = (v: number) => {
return `${v}%`
}
let maxList = computed(() => {
let arr = props.optionData.dataset.source.map(_ => {
let nameKey = props.optionData.dataset.dimensions[0]
return {
key: _[nameKey],
//
max: props.optionData.maxMap[_[nameKey]].max,
min: props.optionData.maxMap[_[nameKey]].min
}
})
return arr
})
const handleUpdate = (k: string, type: string, v: string) => {
if(type === 'min') props.optionData.maxMap[k].min = v
else if(type === 'max') props.optionData.maxMap[k].max = v
}
</script>

View File

@ -3,7 +3,7 @@
</template>
<script setup lang="ts">
import { ref, computed, PropType, watch } from 'vue'
import { ref, computed, PropType, watch, toRefs } from 'vue'
import VChart from 'vue-echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import dataJson from './data.json'
@ -12,10 +12,10 @@ import { CanvasRenderer } from 'echarts/renderers'
import { RadarChart } from 'echarts/charts'
import { includes } from './config'
import { mergeTheme, setOption } from '@/packages/public/chart'
import { useChartDataFetch } from '@/hooks'
import {useChartCommonData, useChartDataFetch} from '@/hooks'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { isPreview } from '@/utils'
import {isPreview, setTooltipPosition} from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
const props = defineProps({
@ -33,6 +33,8 @@ const props = defineProps({
}
})
props.chartConfig.option.tooltip.position = setTooltipPosition(props.chartConfig.attr)
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
use([DatasetComponent, CanvasRenderer, RadarChart, GridComponent, TooltipComponent, LegendComponent])
@ -43,35 +45,80 @@ const option = computed(() => {
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
})
const dataSetHandle = (dataset: typeof dataJson) => {
if (dataset.seriesData) {
props.chartConfig.option.series[0].data = dataset.seriesData
// @ts-ignore
props.chartConfig.option.legend.data = dataset.seriesData.map((i: { name: string }) => i.name)
}
if (dataset.radarIndicator) {
props.chartConfig.option.radar.indicator = dataset.radarIndicator
}
if (vChartRef.value && isPreview()) {
setOption(vChartRef.value, props.chartConfig.option)
}
}
// const dataSetHandle = (dataset: typeof dataJson) => {
// if (dataset.seriesData) {
// props.chartConfig.option.series[0].data = dataset.seriesData
// // @ts-ignore
// props.chartConfig.option.legend.data = dataset.seriesData.map((i: { name: string }) => i.name)
// }
// if (dataset.radarIndicator) {
// props.chartConfig.option.radar.indicator = dataset.radarIndicator
// }
// if (vChartRef.value && isPreview()) {
// setOption(vChartRef.value, props.chartConfig.option)
// }
// }
watch(
() => props.chartConfig.option.dataset,
newData => {
try {
dataSetHandle(newData)
} catch (error) {
console.log(error)
// watch(
// () => props.chartConfig.option.dataset,
// newData => {
// try {
// dataSetHandle(newData)
// } catch (error) {
// console.log(error)
// }
// },
// {
// deep: false
// }
// )
watch(() => props.chartConfig.option.dataset, (v) => {
let { dimensions, source } = v
source.forEach(_ => {
if(!Object.prototype.hasOwnProperty.call(props.chartConfig.option.maxMap, _[dimensions[0]])) {
props.chartConfig.option.maxMap[_[dimensions[0]]] = {
max: null,
min: 0
}
}
},
{
deep: false
}
)
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
dataSetHandle(newData)
})
props.chartConfig.option.radar.indicator = source.map(_ => {
return {
name: _[dimensions[0]],
max: props.chartConfig.option.maxMap[_[dimensions[0]]].max,
min: props.chartConfig.option.maxMap[_[dimensions[0]]].min,
}
})
props.chartConfig.option.series[0].data = dimensions.slice(1).map(k => {
return {
name: k,
value: source.map(_ => _[k])
}
})
props.chartConfig.option.legend.data = dimensions.slice(1)
}, {
immediate: true,
deep: true
})
watch(() => props.chartConfig.option.maxMap, v => {
let { dimensions, source } = props.chartConfig.option.dataset
props.chartConfig.option.radar.indicator = source.map(_ => {
return {
name: _[dimensions[0]],
max: props.chartConfig.option.maxMap[_[dimensions[0]]].max,
min: props.chartConfig.option.maxMap[_[dimensions[0]]].min,
}
})
}, {
immediate: true,
deep: true
})
// useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
// dataSetHandle(newData)
// })
useChartCommonData(props.chartConfig, useChartEditStore)
</script>

View File

@ -10,7 +10,7 @@ import { GraphConfig } from './Graph/index'
export default [
// ProcessConfig,
// RadarConfig,
RadarConfig,
// FunnelConfig,
// HeatmapConfig,
WaterPoloConfig,

View File

@ -4,12 +4,36 @@ import { TableScrollBoardConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export enum AlignEnum {
LEFT = 'left',
CENTER = 'center',
RIGHT = 'right',
}
export type MapType = {
show: boolean
key: string
header: string
align: AlignEnum
columnWidth: number
}
export const option = {
header: ['列1', '列2', '列3'],
dataset: dataJson,
index: true,
columnWidth: [30, 100, 100],
align: ['center', 'right', 'right', 'right'],
headerConfig: [],
headerConfigMap: {
index: {
show: true,
key: '行号',
header: '#',
align: 'left',
columnWidth: 30,
}
},
// header: ['列1', '列2', '列3'],
dataset: { dimensions: [], source: [] },
// index: true,
// columnWidth: [],
// align: [],
rowNum: 5,
waitTime: 2,
headerHeight: 35,

View File

@ -25,21 +25,20 @@
placeholder="请输入表头高度"
></n-input-number>
</SettingItem>
<SettingItem name="显示行号">
<n-switch size="small" v-model:value="optionData.index" />
</SettingItem>
<!-- <SettingItem name="显示行号">-->
<!-- <n-switch size="small" v-model:value="optionData.index" />-->
<!-- </SettingItem>-->
</SettingItemBox>
<SettingItemBox name="配置" :alone="true">
<SettingItem name="表头数据">
<n-input v-model:value="header" :min="1" size="small" placeholder="表头数据(英文','分割)"></n-input>
</SettingItem>
<SettingItem name="列对齐方式">
<n-input v-model:value="align" :min="1" size="small" placeholder="对齐方式(英文','分割)"></n-input>
</SettingItem>
<SettingItem name="列宽度">
<n-input v-model:value="columnWidth" :min="1" size="small" placeholder="列宽度(英文','分割)"></n-input>
</SettingItem>
<!-- <SettingItem name="表头数据">-->
<!-- <n-input v-model:value="header" :min="1" size="small" placeholder="表头数据(英文','分割)"></n-input>-->
<!-- </SettingItem>-->
<!-- <SettingItem name="列对齐方式">-->
<!-- <n-input v-model:value="align" :min="1" size="small" placeholder="对齐方式(英文','分割)"></n-input>-->
<!-- </SettingItem>-->
<!-- <SettingItem name="列宽度">-->
<!-- <n-input v-model:value="columnWidth" :min="1" size="small" placeholder="列宽度(英文','分割)"></n-input>-->
<!-- </SettingItem>-->
<SettingItem name="轮播方式">
<n-select
v-model:value="optionData.carousel"
@ -62,13 +61,34 @@
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.evenRowBGC"></n-color-picker>
</SettingItem>
</SettingItemBox>
<SettingItemBox :name="`列${i + 1}`" v-for="(item, i) in headerConfig" :key="i">
<SettingItem name="展示" style="grid-column: 1 / 3">
<n-space>
<n-switch v-model:value="item.show" size="small"/>
</n-space>
</SettingItem>
<SettingItem name="字段">
<n-text style="height: 28px;line-height: 28px">{{item.key ? item.key : '--'}}</n-text>
</SettingItem>
<SettingItem name="标题">
<n-input v-model:value="item.header" size="small" clearable/>
</SettingItem>
<SettingItem name="列对齐方式">
<n-select v-model:value="item.align" :options="alignOption" size="small"/>
</SettingItem>
<SettingItem name="列宽度">
<n-input-number v-model:value="item.columnWidth" :min="0" size="small"/>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType, ref, watch } from 'vue'
import { PropType, ref, watch, computed, toRefs } from 'vue'
import type { Ref, ToRefs } from 'vue'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { option } from './config'
import { option, AlignEnum, MapType } from './config'
import { PickCreateComponentType } from '@/packages/index.d'
const props = defineProps({
optionData: {
@ -77,33 +97,74 @@ const props = defineProps({
}
})
const header = ref()
const align = ref()
const columnWidth = ref()
watch(
() => props.optionData,
newData => {
header.value = props.optionData.header.toString()
align.value = props.optionData.align.toString()
columnWidth.value = props.optionData.columnWidth.toString()
},
{
deep: false,
immediate: true
}
)
// const header = ref()
// const align = ref()
// const columnWidth = ref()
//
// watch(
// () => props.optionData,
// newData => {
// header.value = props.optionData.header.toString()
// align.value = props.optionData.align.toString()
// columnWidth.value = props.optionData.columnWidth.toString()
// },
// {
// deep: false,
// immediate: true
// }
// )
//
// watch([header, align, columnWidth], ([headerNew, alignNew, columnWidthNew], [headerOld, alignOld, columnWidthOld]) => {
// if (headerNew !== headerOld) {
// props.optionData.header = headerNew.split(',')
// }
// if (alignNew !== alignOld) {
// props.optionData.align = alignNew.split(',')
// }
// if (columnWidthNew !== columnWidthOld) {
// // @ts-ignore
// props.optionData.columnWidth = columnWidthNew.split(',')
// }
// })
watch([header, align, columnWidth], ([headerNew, alignNew, columnWidthNew], [headerOld, alignOld, columnWidthOld]) => {
if (headerNew !== headerOld) {
props.optionData.header = headerNew.split(',')
}
if (alignNew !== alignOld) {
props.optionData.align = alignNew.split(',')
}
if (columnWidthNew !== columnWidthOld) {
// @ts-ignore
props.optionData.columnWidth = columnWidthNew.split(',')
}
// const headerConfigMap: Ref<{ [k: string]: any }> = computed(() => {
// return props.optionData.headerConfigMap
// })
// const headerConfig: Ref<any[]> = computed(() => {
// return props.optionData.headerConfig
// })
const alignOption = [
{ label: '左', value: AlignEnum.LEFT },
{ label: '中', value: AlignEnum.CENTER },
{ label: '右', value: AlignEnum.RIGHT },
]
// const headerConfigMap: Ref<{ [k: string]: any }> = ref(props.optionData.headerConfigMap)
// const headerConfig: Ref<any[]> = ref(props.optionData.headerConfig)
const { headerConfigMap, headerConfig } = toRefs(props.optionData) as ToRefs<{ headerConfigMap: { [k: string] : MapType }, headerConfig: MapType[] }>
watch(() => props.optionData.dataset, (v) => {
v.dimensions.forEach((k: string) => {
//
if(!Object.prototype.hasOwnProperty.call(headerConfigMap.value, k)) {
headerConfigMap.value[k] = {
show: true,
key: k,
header: k,
align: AlignEnum.LEFT,
columnWidth: 100
}
}
headerConfig.value = v.dimensions.map((k: string) => {
return headerConfigMap.value[k]
})
headerConfig.value.unshift(headerConfigMap.value['index'])
})
}, {
immediate: true,
deep: true
})
</script>

View File

@ -50,10 +50,11 @@
<script setup lang="ts">
import { PropType, onUnmounted, reactive, toRefs, watch, onMounted } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import {useChartCommonData, useChartDataFetch} from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import merge from 'lodash/merge'
import cloneDeep from 'lodash/cloneDeep'
import { MapType } from './config'
const props = defineProps({
chartConfig: {
@ -197,19 +198,29 @@ const mergeConfig = () => {
}
const calcHeaderData = () => {
let { header, index, indexHeader } = status.mergedConfig
if (!header.length) {
status.header = []
return
}
header = [...header]
if (index) header.unshift(indexHeader)
status.header = header
let { header, index, indexHeader, headerConfig, headerConfigMap } = status.mergedConfig
// if (!header.length) {
// status.header = []
// return
// }
// header = [...header]
// if (index) header.unshift(indexHeader)
// status.header = header
type ItemType = { show: boolean, header: string }
status.header = headerConfig.filter((_: ItemType) => _.show).map((_: ItemType) => _.header)
}
const calcRowsData = () => {
let { dataset, index, headerBGC, rowNum } = status.mergedConfig
if (index) {
let { dataset: datasetOrigin, index, headerBGC, rowNum } = status.mergedConfig
let { headerConfigMap, headerConfig } = status.mergedConfig
interface RowType { [k: string]: any }
let showCols = headerConfig.filter((_: MapType) => _.show && _.key !== '行号').map((_: MapType) => _.key)
let dataset = datasetOrigin.source.map((row: RowType) => {
return datasetOrigin.dimensions.filter((_: string) => showCols.includes(_)).map((key: string) => {
return row[key]
})
})
if (headerConfigMap['index'].show) {
dataset = dataset.map((row: any, i: number) => {
row = [...row]
const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${
@ -232,17 +243,21 @@ const calcRowsData = () => {
const calcWidths = () => {
const { mergedConfig, rowsData } = status
const { columnWidth, header } = mergedConfig
const usedWidth = columnWidth.reduce((all: any, ws: number) => all + ws, 0)
let columnNum = 0
if (rowsData[0]) {
columnNum = (rowsData[0] as any).ceils.length
} else if (header.length) {
columnNum = header.length
}
const avgWidth = (w.value - usedWidth) / (columnNum - columnWidth.length)
const widths = new Array(columnNum).fill(avgWidth)
status.widths = merge(widths, columnWidth)
const { columnWidth, header, headerConfig } = mergedConfig
// const usedWidth = columnWidth.reduce((all: any, ws: number) => all + ws, 0)
// let columnNum = 0
// if (rowsData[0]) {
// columnNum = (rowsData[0] as any).ceils.length
// } else if (header.length) {
// columnNum = header.length
// }
// const avgWidth = (w.value - usedWidth) / (columnNum - columnWidth.length)
// const widths = new Array(columnNum).fill(avgWidth)
// status.widths = merge(widths, columnWidth)
type ItemType = {show: boolean, columnWidth: number}
let widths = headerConfig.filter((_: ItemType) => _.show).map((_: ItemType) => _.columnWidth)
status.widths = widths
}
const calcHeights = (onresize = false) => {
@ -252,19 +267,22 @@ const calcHeights = (onresize = false) => {
if (header.length) allHeight -= headerHeight
const avgHeight = allHeight / rowNum
status.avgHeight = avgHeight
if (!onresize) status.heights = new Array(dataset.length).fill(avgHeight)
if (!onresize) status.heights = new Array(dataset.source.length).fill(avgHeight)
}
const calcAligns = () => {
const { header, mergedConfig } = status
const { headerConfig } = mergedConfig
const columnNum = header.length
// const columnNum = header.length
//
// let aligns = new Array(columnNum).fill('left')
//
// const { align } = mergedConfig
//
// status.aligns = merge(aligns, align)
let aligns = new Array(columnNum).fill('left')
const { align } = mergedConfig
status.aligns = merge(aligns, align)
status.aligns = headerConfig.map((_: any) => _.align)
}
const animation = async (start = false) => {
@ -339,7 +357,12 @@ watch(
)
// ( dataset)
useChartDataFetch(props.chartConfig, useChartEditStore, (resData: any[]) => {
// useChartDataFetch(props.chartConfig, useChartEditStore, (resData: any[]) => {
// props.chartConfig.option.dataset = resData
// onRestart()
// })
useChartCommonData(props.chartConfig, useChartEditStore, (resData: {}) => {
props.chartConfig.option.dataset = resData
onRestart()
})

View File

@ -11,7 +11,8 @@ export const option = {
// 展示列
header: {
value: [],
options: []
options: [],
map: {},
},
pagination: {
page: 1,

View File

@ -2,16 +2,21 @@
<collapse-item name="表格设置" :expanded="true">
<n-tag type="primary">若配置无响应请在预览页面查看效果</n-tag>
<setting-item-box name="表头" :alone="true">
<div class="rows" v-for="(row, i) in optionData.header.options" :key="i">
<div class="columns">{{ row.value }}</div>
<n-input class="columns" v-model:value="row.label" size="small"/>
<div class="rows">
<div class="columns">字段</div>
<div class="columns">标题</div>
</div>
<div class="rows" v-for="(row: any, i) in optionData.header.options" :key="i">
<div class="columns">{{ row }}</div>
<n-input class="columns" v-model:value="optionData.header.map[row]" size="small"/>
</div>
</setting-item-box>
<setting-item-box name="展示列" :alone="true">
<n-select
v-model:value="optionData.header.value"
:options="optionData.header.options"
:options="optionData.header.options.map(_ => ({label: optionData.header.map[_], value: _}))"
multiple
size="small"
/>
</setting-item-box>
<setting-item-box :alone="true" name="对齐方式">
@ -178,6 +183,8 @@ const props = defineProps({
.rows {
margin-bottom: 10px;
display: flex;
height: 28px;
line-height: 28px;
&:nth-last-child(1){
margin-bottom: 0;
}

View File

@ -80,9 +80,12 @@ watch(
(newData: any) => {
option.dataset = newData
option.header.value = newData.dimensions
console.log(newData.dimensions.toString(), option.header.options.map((_: {value: string}) => _.value).toString())
if(newData.dimensions.toString() === option.header.options.map((_: {value: string}) => _.value).toString()) return
option.header.options = newData.dimensions.map((_: string) => ({label: _, value: _}))
option.header.options = newData.dimensions
newData.dimensions.forEach((key: string) => {
if(!Object.prototype.hasOwnProperty.call(option.header.map, key)) option.header.map[key] = key
})
// if(newData.dimensions.toString() === option.header.options.map((_: {value: string}) => _.value).toString()) return
// option.header.options = newData.dimensions.map((_: string) => ({label: _, value: _}))
// option?.dataset?.dimensions?.forEach((header: any) => {
// header.align = align.value
// })
@ -101,11 +104,11 @@ watch(() => props.chartConfig.option.header, v => {
})
const columns = computed(() => {
let dimensions = option.header.options.filter((_: {label: string, value: string}) => option.header.value.includes(_.value))
dimensions = dimensions.map((_: {label: string, value: string}) => {
let dimensions = option.header.options.filter((_: string) => option.header.value.includes(_))
dimensions = dimensions.map((_: string) => {
return {
title: _.label,
key: _.value,
title: option.header.map[_],
key: _,
align: align.value
}
})