Merge remote-tracking branch 'origin/dev' into feat-ecahrts-legend

This commit is contained in:
tanhao 2023-05-25 11:34:56 +08:00
commit 9ec7d58132
47 changed files with 1957 additions and 1096 deletions

View File

@ -73,7 +73,7 @@
Cloud IDE 代码在线预览地址:[https://idegitee.com/dromara/go-view](https://idegitee.com/dromara/go-view) Cloud IDE 代码在线预览地址:[https://idegitee.com/dromara/go-view](https://idegitee.com/dromara/go-view)
#### 🤯 后端项目看这里! 后端项目 gitee 地址:[https://gitee.com/MTrun/go-view-serve](https://gitee.com/MTrun/go-view-serve)
后端地址(社区实现,仅供参考): 后端地址(社区实现,仅供参考):
@ -129,15 +129,15 @@ Cloud IDE 代码在线预览地址:[https://idegitee.com/dromara/go-view](http
| 名称 | 版本 | 名称 | 版本 | | 名称 | 版本 | 名称 | 版本 |
| ------------------- | ----- | ----------- | ------ | | ------------------- | ----- | ----------- | ------ |
| Vue | 3.2.x | TypeScript4 | 4.6.x | | Vue | 3.2.x | TypeScript4 | 4.6.x |
| Vite | 2.9.x | NaiveUI | 2.27.x | | Vite | 4.2.x | NaiveUI | 2.34.x |
| ECharts | 5.3.x | Pinia | 2.0.x | | ECharts | 5.3.x | Pinia | 2.0.x |
| 详见 `package.json` | 😁 | 🥰 | 🤗 | | 详见 `package.json` | 😁 | 🥰 | 🤗 |
开发环境: 开发环境:
| 名称 | 版本 | 名称 | 版本 | | 名称 | 版本 | 名称 | 版本 |
| ---- | ------- | ------- | ----- | | ---- | ------- | ------- | ----- |
| node | 16.14.x | npm | 8.5.x | | node | 16.16.x | npm | 8.5.x |
| pnpm | 7.1.x | windows | 11 | | pnpm | 7.1.x | windows | 11 |
已完成图表: 已完成图表:
@ -147,12 +147,12 @@ Cloud IDE 代码在线预览地址:[https://idegitee.com/dromara/go-view](http
| 图表 | 柱状图 | 横向柱状图 | 折线图 | 单/多 折线面积图(渐变色) | | 图表 | 柱状图 | 横向柱状图 | 折线图 | 单/多 折线面积图(渐变色) |
| \* | 饼图 | 环形图 | 水球图 | 雷达图 | | \* | 饼图 | 环形图 | 水球图 | 雷达图 |
| \* | NaiveUI 多种进度 | 散点图 | 对数回归散点图 | 热力图 | | \* | NaiveUI 多种进度 | 散点图 | 对数回归散点图 | 热力图 |
| \* | 漏斗图 | 中国地图 | 高德地图 | 🦊 | | \* | 漏斗图 | 中国地图 | 高德地图 | 🦊 |
| 信息 | 文字 | 渐变文字 | 词云 | 嵌套网页 | | 信息 | 文字 | 渐变文字 | 词云 | 嵌套网页 |
| \* | 图片 | 视频 | 😺 | 🐯 | | \* | 图片 | 视频 | 😺 | 🐯 |
| 列表 | 滚动排名列表 | 滚动表格 | 🐮 | 🐐 | | 列表 | 滚动排名列表 | 滚动表格 | 🐮 | 🐐 |
| 小组件 | 边框-01~13 | 装饰-01~05 | 数字翻牌 | 通用时间 | | 小组件 | 边框-01~13 | 装饰-01~05 | 数字翻牌 | 通用时间 |
| \* | 数字计数 | 倒计时 | 时钟 | 🦁 | | \* | 数字计数 | 倒计时 | 时钟 | 🦁 |
## 浏览器支持 ## 浏览器支持

View File

@ -33,6 +33,7 @@
"highlight.js": "^11.5.0", "highlight.js": "^11.5.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"keymaster": "^1.6.2", "keymaster": "^1.6.2",
"mitt": "^3.0.0",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"naive-ui": "2.34.3", "naive-ui": "2.34.3",
"pinia": "^2.0.13", "pinia": "^2.0.13",
@ -49,14 +50,16 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.0.2", "@commitlint/cli": "^17.0.2",
"@commitlint/config-conventional": "^17.0.2", "@commitlint/config-conventional": "^17.0.2",
"@iconify/types": "^2.0.0",
"@iconify/vue": "^4.1.1",
"@types/node": "^16.11.26", "@types/node": "^16.11.26",
"@types/three": "^0.144.0", "@types/three": "^0.144.0",
"@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0", "@typescript-eslint/parser": "^5.18.0",
"@vicons/carbon": "^0.12.0", "@vicons/carbon": "^0.12.0",
"@vicons/ionicons5": "~0.11.0", "@vicons/ionicons5": "~0.11.0",
"@vitejs/plugin-vue": "^1.10.2", "@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^1.3.9", "@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/compiler-sfc": "^3.2.31", "@vue/compiler-sfc": "^3.2.31",
"@vueuse/core": "^7.7.1", "@vueuse/core": "^7.7.1",
"commitlint": "^17.0.2", "commitlint": "^17.0.2",
@ -75,7 +78,7 @@
"sass": "^1.49.11", "sass": "^1.49.11",
"sass-loader": "^12.6.0", "sass-loader": "^12.6.0",
"typescript": "4.6.3", "typescript": "4.6.3",
"vite": "4.2.1", "vite": "4.3.6",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-importer": "^0.2.5", "vite-plugin-importer": "^0.2.5",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",

918
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="go-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" setup> <script lang="ts" setup>
import { ref, PropType, watch } from 'vue' import { ref, PropType, watch, nextTick } from 'vue'
import { FlipType } from './index' import { FlipType } from './index'
const props = defineProps({ const props = defineProps({
flipType: { flipType: {
@ -43,6 +43,10 @@ const props = defineProps({
backColor: { backColor: {
type: String, type: String,
default: '#000000' default: '#000000'
},
borderWidth: {
type: Number,
default: 2
} }
}) })
@ -50,19 +54,27 @@ const isFlipping = ref(false)
const frontTextFromData = ref(props.count || 0) const frontTextFromData = ref(props.count || 0)
const backTextFromData = ref(props.count || 0) const backTextFromData = ref(props.count || 0)
let timeoutID: any = 0
// //
const flip = (front: string | number, back: string | number) => { const flip = async (front: string | number, back: string | number) => {
// //
if (isFlipping.value) return if (isFlipping.value) {
isFlipping.value = false //
clearTimeout(timeoutID) //
await nextTick()
await flip(front, back) //
return
}
// //
backTextFromData.value = back backTextFromData.value = back
frontTextFromData.value = front frontTextFromData.value = front
// true // true
isFlipping.value = true isFlipping.value = true
// //
setTimeout(() => { timeoutID = setTimeout(() => {
isFlipping.value = false // false isFlipping.value = false // false
frontTextFromData.value = back frontTextFromData.value = back
}, props.duration) }, props.duration)
@ -86,6 +98,7 @@ $radius: v-bind('`${props.radius}px`');
$width: v-bind('`${props.width}px`'); $width: v-bind('`${props.width}px`');
$height: v-bind('`${props.height}px`'); $height: v-bind('`${props.height}px`');
$perspective: v-bind('`${props.height * 2}px`'); $perspective: v-bind('`${props.height * 2}px`');
$borderWidth: v-bind('`${props.borderWidth * 2}px`');
$speed: v-bind('`${props.duration / 1000}s`'); $speed: v-bind('`${props.duration / 1000}s`');
$shadowColor: #000000; $shadowColor: #000000;
$lineColor: #4a9ef8; $lineColor: #4a9ef8;
@ -125,13 +138,12 @@ $lineColor: #4a9ef8;
} }
// #endregion // #endregion
.go-Flipper { .go-flipper {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: $width; width: $width;
height: $height; height: $height;
line-height: $height; line-height: $height;
border: solid 1px $backColor;
border-radius: $radius; border-radius: $radius;
background: $frontColor; background: $frontColor;
font-size: $width; font-size: $width;
@ -139,6 +151,17 @@ $lineColor: #4a9ef8;
box-shadow: 0 0 6px rgba($color: $shadowColor, $alpha: 0.5); // box-shadow: 0 0 6px rgba($color: $shadowColor, $alpha: 0.5); //
text-align: center; text-align: center;
// font-family: 'Helvetica Neue'; // font-family: 'Helvetica Neue';
&::after {
content: '';
position: absolute;
z-index: 10;
left: 0;
top: 0;
right: 0;
bottom: 0;
box-shadow: inset 0 0 $borderWidth 0 $frontColor; //
border-radius: $radius;
}
.digital:before, .digital:before,
.digital:after { .digital:after {
@ -186,11 +209,13 @@ $lineColor: #4a9ef8;
&.down.go .front:before { &.down.go .front:before {
transform-origin: 50% 100%; transform-origin: 50% 100%;
animation: frontFlipDown $speed ease-in-out both; animation: frontFlipDown $speed ease-in-out both;
box-shadow: 0 -2px 6px rgba($color: $lineColor, $alpha: 0.3); box-shadow: 0 -2px $borderWidth 0 $frontColor;
backface-visibility: hidden; backface-visibility: hidden;
} }
&.down.go .back:after { &.down.go .back:after {
animation: backFlipDown $speed ease-in-out both; animation: backFlipDown $speed ease-in-out both;
box-shadow: 0 2px $borderWidth 0 $frontColor;
backface-visibility: hidden;
} }
/*向上翻*/ /*向上翻*/
&.up .front:after { &.up .front:after {
@ -208,11 +233,13 @@ $lineColor: #4a9ef8;
&.up.go .front:after { &.up.go .front:after {
transform-origin: 50% 0; transform-origin: 50% 0;
animation: frontFlipUp $speed ease-in-out both; animation: frontFlipUp $speed ease-in-out both;
box-shadow: 0 2px 6px rgba($color: $lineColor, $alpha: 0.3); box-shadow: 0 2px $borderWidth 0 $frontColor;
backface-visibility: hidden; backface-visibility: hidden;
} }
&.up.go .back:before { &.up.go .back:before {
animation: backFlipUp $speed ease-in-out both; animation: backFlipUp $speed ease-in-out both;
box-shadow: 0 -2px $borderWidth 0 $frontColor;
backface-visibility: hidden;
} }
} }
</style> </style>

View File

@ -12,5 +12,7 @@ export enum StorageEnum {
// 工作台布局配置 // 工作台布局配置
GO_CHART_LAYOUT_STORE = 'GO_CHART_LAYOUT', GO_CHART_LAYOUT_STORE = 'GO_CHART_LAYOUT',
// 工作台需要保存的数据 // 工作台需要保存的数据
GO_CHART_STORAGE_LIST = 'GO_CHART_STORAGE_LIST' GO_CHART_STORAGE_LIST = 'GO_CHART_STORAGE_LIST',
// 用户存储的图片媒体
GO_USER_MEDIA_PHOTOS = 'GO_USER_MEDIA_PHOTOS'
} }

View File

@ -18,7 +18,14 @@ export const PieTypeObject = {
[PieTypeEnum.ROSE]: 'rose' [PieTypeEnum.ROSE]: 'rose'
} }
// 其它配置
const otherConfig = {
// 轮播动画
isCarousel: false,
}
const option = { const option = {
...otherConfig,
type: 'ring', type: 'ring',
tooltip: { tooltip: {
show: true, show: true,

View File

@ -7,6 +7,17 @@
<n-select v-model:value="optionData.type" size="small" :options="fontWeightOptions" /> <n-select v-model:value="optionData.type" size="small" :options="fontWeightOptions" />
</SettingItem> </SettingItem>
</SettingItemBox> </SettingItemBox>
<SettingItemBox name="动画" :alone="true">
<SettingItem>
<n-space>
<n-switch v-model:value="optionData.isCarousel" size="small"></n-switch>
<n-text>开启<n-text :depth="3">将自动隐藏图例</n-text></n-text>
</n-space>
</SettingItem>
<SettingItem>
<n-text :depth="3">无鼠标点击图例场景时可强行打开图例</n-text>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="标签"> <SettingItemBox name="标签">
<SettingItem> <SettingItem>
<n-space> <n-space>

View File

@ -1,9 +1,18 @@
<template> <template>
<v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()" autoresize></v-chart> <v-chart
ref="vChartRef"
autoresize
:init-options="initOptions"
:theme="themeColor"
:option="option"
:manual-update="isPreview()"
@mouseover="handleHighlight"
@mouseout="handleDownplay"
></v-chart>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, PropType, reactive, watch } from 'vue' import { computed, PropType, onMounted, watch } from 'vue'
import VChart from 'vue-echarts' import VChart from 'vue-echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import { use } from 'echarts/core' import { use } from 'echarts/core'
@ -15,6 +24,7 @@ import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { isPreview } from '@/utils' import { isPreview } from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import dataJson from './data.json'
const props = defineProps({ const props = defineProps({
themeSetting: { themeSetting: {
@ -30,8 +40,10 @@ const props = defineProps({
required: true required: true
} }
}) })
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
let seriesDataNum = -1
let seriesDataMaxLength = 0
let intervalInstance: any = null
use([DatasetComponent, CanvasRenderer, PieChart, GridComponent, TooltipComponent, LegendComponent]) use([DatasetComponent, CanvasRenderer, PieChart, GridComponent, TooltipComponent, LegendComponent])
@ -39,6 +51,54 @@ const option = computed(() => {
return mergeTheme(props.chartConfig.option, props.themeSetting, includes) return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
}) })
//
const handleSeriesData = () => {
if (seriesDataNum > -1) {
vChartRef.value?.dispatchAction({
type: 'downplay',
dataIndex: seriesDataNum
})
}
seriesDataNum = seriesDataNum >= seriesDataMaxLength - 1 ? 0 : seriesDataNum + 1
vChartRef.value?.dispatchAction({
type: 'highlight',
dataIndex: seriesDataNum
})
}
//
const addPieInterval = (newData?: typeof dataJson, skipPre = false) => {
if (!skipPre && !Array.isArray(newData?.source)) return
if (!skipPre) seriesDataMaxLength = newData?.source.length || 0
clearInterval(intervalInstance)
intervalInstance = setInterval(() => {
handleSeriesData()
}, 1000)
}
//
const clearPieInterval = () => {
vChartRef.value?.dispatchAction({
type: 'downplay',
dataIndex: seriesDataNum
})
clearInterval(intervalInstance)
intervalInstance = null
}
//
const handleHighlight = () => {
clearPieInterval()
}
//
const handleDownplay = () => {
if (props.chartConfig.option.isCarousel && !intervalInstance) {
//
addPieInterval(undefined, true)
}
}
watch( watch(
() => props.chartConfig.option.type, () => props.chartConfig.option.type,
newData => { newData => {
@ -60,5 +120,27 @@ watch(
{ deep: false, immediate: true } { deep: false, immediate: true }
) )
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore) watch(
() => props.chartConfig.option.isCarousel,
newData => {
if (newData) {
addPieInterval(undefined, true)
props.chartConfig.option.legend.show = false
} else {
props.chartConfig.option.legend.show = true
clearPieInterval()
}
}
)
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
addPieInterval(newData)
})
onMounted(() => {
seriesDataMaxLength = dataJson.source.length
if (props.chartConfig.option.isCarousel) {
addPieInterval(undefined, true)
}
})
</script> </script>

View File

@ -16,6 +16,7 @@ export interface OptionType {
flipperGap: number flipperGap: number
flipperType: FlipType flipperType: FlipType
flipperSpeed: number flipperSpeed: number
flipperBorderWidth: number
} }
export const option: OptionType = { export const option: OptionType = {
@ -28,7 +29,8 @@ export const option: OptionType = {
flipperRadius: 5, flipperRadius: 5,
flipperGap: 10, flipperGap: 10,
flipperType: 'down', flipperType: 'down',
flipperSpeed: 450 flipperSpeed: 450,
flipperBorderWidth: 0,
} }
export default class Config extends PublicConfigClass implements CreateComponentType { export default class Config extends PublicConfigClass implements CreateComponentType {

View File

@ -16,12 +16,16 @@
<setting-item name="高度"> <setting-item name="高度">
<n-input-number v-model:value="optionData.flipperHeight" size="small" :min="1"></n-input-number> <n-input-number v-model:value="optionData.flipperHeight" size="small" :min="1"></n-input-number>
</setting-item> </setting-item>
<setting-item name="间隔"> <setting-item name="边框">
<n-input-number v-model:value="optionData.flipperGap" size="small" :min="0"></n-input-number> <n-input-number v-model:value="optionData.flipperBorderWidth" size="small" :min="0" :max="10"></n-input-number>
</setting-item> </setting-item>
<setting-item name="圆角"> <setting-item name="圆角">
<n-input-number v-model:value="optionData.flipperRadius" size="small" :min="0"></n-input-number> <n-input-number v-model:value="optionData.flipperRadius" size="small" :min="0"></n-input-number>
</setting-item> </setting-item>
<setting-item name="翻牌间隔">
<n-input-number v-model:value="optionData.flipperGap" size="small" :min="0"></n-input-number>
</setting-item>
<setting-item />
<setting-item name="背景色"> <setting-item name="背景色">
<n-color-picker <n-color-picker
size="small" size="small"

View File

@ -9,6 +9,7 @@
:radius="flipperRadius" :radius="flipperRadius"
:flip-type="flipperType" :flip-type="flipperType"
:duration="flipperSpeed" :duration="flipperSpeed"
:border-width="flipperBorderWidth"
v-for="(item, index) in flipperData" v-for="(item, index) in flipperData"
:key="index" :key="index"
class="go-d-block" class="go-d-block"
@ -42,7 +43,8 @@ const {
flipperRadius, flipperRadius,
flipperGap, flipperGap,
flipperType, flipperType,
flipperSpeed flipperSpeed,
flipperBorderWidth
} = toRefs(props.chartConfig.option as OptionType) } = toRefs(props.chartConfig.option as OptionType)
const flipperData = ref<string[] | number[]>([]) const flipperData = ref<string[] | number[]>([])

View File

@ -1,4 +1,3 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-nocheck
import { import {
ArcCurve, ArcCurve,

View File

@ -0,0 +1,84 @@
import { IconConfig } from '../Icon/index'
import { PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d'
const iconNames = [
'uim:adobe',
'uim:airplay',
'uim:align',
'uim:angle-double-down',
'uim:angle-double-left',
'uim:angle-double-right',
'uim:angle-double-up',
'uim:angle-down',
'uim:angle-left',
'uim:angle-right',
'uim:angle-up',
'uim:android-alt',
'uim:apple',
'uim:apps',
'uim:bag',
'uim:battery-bolt',
'uim:bing',
'uim:box',
'uim:briefcase',
'uim:calender',
'uim:chart',
'uim:chart-pie',
'uim:circle-layer',
'uim:clinic-medical',
'uim:clock',
'uim:comment-alt',
'uim:comment-alt-dots',
'uim:docker',
'uim:download-alt',
'uim:dribbble',
'uim:dropbox',
'uim:entry',
'uim:exclamation-circle',
'uim:exclamation-triangle',
'uim:exit',
'uim:facebook',
'uim:facebook-messenger',
'uim:facebook-messenger-alt',
'uim:google-drive',
'uim:google-play',
'uim:graph-bar',
'uim:head-side-mask',
'uim:horizontal-align-left',
'uim:hospital',
'uim:house-user',
'uim:image-v',
'uim:key-skeleton',
'uim:layer-group',
'uim:layers-alt',
'uim:link-h',
'uim:microscope',
'uim:microsoft',
'uim:object-group',
'uim:object-ungroup',
'uim:paypal',
'uim:refresh',
'uim:repeat',
'uim:right-indent-alt',
'uim:rocket',
'uim:shield-plus',
'uim:social-distancing',
'uim:telegram-alt',
'uim:user-md',
'uim:toilet-paper',
'uim:youtube'
]
const iconList = iconNames.map(name => ({
...IconConfig,
category: ChatCategoryEnum.COMMON,
categoryName: ChatCategoryEnumName.COMMON,
package: PackagesCategoryEnum.ICONS,
image: name,
icon: name,
dataset: name,
title: name.replace('uim:', ''),
redirectComponent: './components/Icons/Icon' // 虚拟组件路径,尾部不跟 /’,相对于 /packages/index.ts 文件的位置
}))
export default iconList

View File

@ -0,0 +1,20 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { chartInitConfig } from '@/settings/designSetting'
import { IconConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
export const option = {
// 图标名称
dataset: 'uim:apple',
color: '#03A9F4',
size: 64,
rotate: 0 // 旋转角度
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = IconConfig.key
public attr = { ...chartInitConfig, w: 64, h: 64, zIndex: 1 }
public chartConfig = cloneDeep(IconConfig)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,50 @@
<template>
<collapse-item name="属性" :expanded="true">
<setting-item-box name="样式">
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.color"></n-color-picker>
</setting-item>
<setting-item name="尺寸">
<n-input-number v-model:value="optionData.size" size="small" :min="0" placeholder="尺寸"></n-input-number>
</setting-item>
</setting-item-box>
<setting-item-box name="旋转">
<setting-item name="旋转">
<n-select v-model:value="optionData.rotate" size="small" :options="rotateMode"></n-select>
</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
}
})
//
const rotateMode = [
{
value: 0,
label: '0°'
},
{
value: 1,
label: '90°'
},
{
value: 2,
label: '180°'
},
{
value: 3,
label: '270°'
}
]
</script>

View File

@ -0,0 +1,14 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d'
export const IconConfig: ConfigType = {
key: 'Icon',
chartKey: 'VIcon',
conKey: 'VCIcon',
title: '图标',
category: ChatCategoryEnum.COMMON,
categoryName: ChatCategoryEnumName.COMMON,
package: PackagesCategoryEnum.ICONS,
chartFrame: ChartFrameEnum.COMMON,
image: 'icon.png'
}

View File

@ -0,0 +1,31 @@
<template>
<div class="go-icon-box">
<Icon :icon="((dataset || '') as string)" :color="color" :width="size" :rotate="rotate" />
</div>
</template>
<script setup lang="ts">
import { PropType, toRefs } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { Icon } from '@iconify/vue'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const { w, h } = toRefs(props.chartConfig.attr)
const { dataset, color, size, rotate } = toRefs(props.chartConfig.option)
</script>
<style lang="scss" scoped>
@include go('icon-box') {
display: flex;
align-items: center;
justify-content: center;
width: v-bind('`${w}px`');
height: v-bind('`${h}px`');
}
</style>

View File

@ -0,0 +1,45 @@
import { IconConfig } from '../Icon/index'
import { PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d'
const iconNames = [
'line-md:beer-alt-filled-loop',
'line-md:beer-alt-twotone-loop',
'line-md:cloud-braces-loop',
'line-md:cloud-download-loop',
'line-md:cloud-download-outline-loop',
'line-md:cloud-upload-loop',
'line-md:cloud-upload-outline-loop',
'line-md:coffee-half-empty-twotone-loop',
'line-md:coffee-loop',
'line-md:coffee-twotone-loop',
'line-md:downloading-loop',
'line-md:github-loop',
'line-md:light-dark-loop',
'line-md:loading-alt-loop',
'line-md:loading-loop',
'line-md:loading-twotone-loop',
'line-md:moon-alt-loop',
'line-md:moon-alt-to-sunny-outline-loop-transition',
'line-md:moon-filled-loop',
'line-md:moon-filled-to-sunny-filled-loop-transition',
'line-md:star-pulsating-filled-loop',
'line-md:star-pulsating-loop',
'line-md:star-pulsating-twotone-loop',
'line-md:upload-loop',
'line-md:upload-outline-loop',
'line-md:uploading-loop'
]
const iconList = iconNames.map(name => ({
...IconConfig,
category: ChatCategoryEnum.ML,
categoryName: ChatCategoryEnumName.ML,
package: PackagesCategoryEnum.ICONS,
image: name,
icon: name,
dataset: name,
title: name.replace('line-md:', ''),
redirectComponent: './components/Icons/Icon' // 虚拟组件路径,尾部不跟 /’,相对于 /packages/index.ts 文件的位置
}))
export default iconList

View File

@ -0,0 +1,53 @@
import { IconConfig } from '../Icon/index'
import { PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d'
const iconNames = [
'wi:cloud',
'wi:cloudy',
'wi:cloudy-gusts',
'wi:cloudy-windy',
'wi:day-cloudy',
'wi:day-cloudy-high',
'wi:day-hail',
'wi:day-haze',
'wi:day-light-wind',
'wi:day-lightning',
'wi:day-rain',
'wi:day-rain-mix',
'wi:day-rain-wind',
'wi:day-showers',
'wi:day-sleet',
'wi:day-sleet-storm',
'wi:day-snow',
'wi:day-snow-thunderstorm',
'wi:day-snow-wind',
'wi:day-sprinkle',
'wi:day-storm-showers',
'wi:day-sunny',
'wi:day-sunny-overcast',
'wi:hail',
'wi:horizon',
'wi:horizon-alt',
'wi:hot',
'wi:lightning',
'wi:night-alt-cloudy',
'wi:night-alt-cloudy-gusts',
'wi:night-alt-cloudy-high',
'wi:night-alt-hail',
'wi:night-alt-lightning',
'wi:umbrella',
]
const iconList = iconNames.map(name => ({
...IconConfig,
category: ChatCategoryEnum.WEATHER,
categoryName: ChatCategoryEnumName.WEATHER,
package: PackagesCategoryEnum.ICONS,
image: name,
icon: name,
dataset: name,
title: name.replace('wi:', ''),
redirectComponent: './components/Icons/Icon' // 虚拟组件路径,尾部不跟 /’,相对于 /packages/index.ts 文件的位置
}))
export default iconList

View File

@ -0,0 +1,12 @@
export enum ChatCategoryEnum {
ML = 'MaterialLine',
COMMON = 'Common',
WEATHER = "WEATHER"
}
export enum ChatCategoryEnumName {
ML = '动画',
COMMON = '通用',
WEATHER = "天气"
}

View File

@ -0,0 +1,5 @@
import MaterialLine from './MaterialLine'
import Common from './Common'
import Weather from './Weather'
export const IconList = [...MaterialLine, ...Common, ...Weather]

View File

@ -12,7 +12,9 @@ export const option = {
[COMPONENT_INTERACT_EVENT_KET]: ComponentInteractEventEnum.DATE, [COMPONENT_INTERACT_EVENT_KET]: ComponentInteractEventEnum.DATE,
// 下拉展示 // 下拉展示
isPanel: 0, isPanel: 0,
dataset: dayjs().valueOf() dataset: dayjs().valueOf(),
differValue: 0
} }
export default class Config extends PublicConfigClass implements CreateComponentType { export default class Config extends PublicConfigClass implements CreateComponentType {

View File

@ -1,31 +1,36 @@
<template> <template>
<collapse-item name="展示方式" :expanded="true"> <collapse-item name="展示方式" :expanded="true">
<setting-item-box name="选择方式"> <setting-item-box name="选择方式">
<n-select <n-select v-model:value="optionData.isPanel" size="small" :options="panelOptions" />
v-model:value="optionData.isPanel"
size="small"
:options="panelOptions"
/>
</setting-item-box> </setting-item-box>
</collapse-item> </collapse-item>
<collapse-item name="时间配置" :expanded="true"> <collapse-item name="时间配置" :expanded="true">
<setting-item-box name="基础"> <setting-item-box name="基础">
<setting-item name="类型"> <setting-item name="类型">
<n-select <n-select v-model:value="optionData.componentInteractEventKey" size="small" :options="datePickerTypeOptions" />
v-model:value="optionData.componentInteractEventKey"
size="small"
:options="datePickerTypeOptions"
/>
</setting-item> </setting-item>
</setting-item-box> </setting-item-box>
<setting-item-box name="默认值" :alone="true"> <setting-item-box name="默认值" :alone="true">
<n-date-picker <n-date-picker size="small" v-model:value="optionData.dataset" :type="optionData.componentInteractEventKey" />
size="small" </setting-item-box>
v-model:value="optionData.dataset"
:type="optionData.componentInteractEventKey" <setting-item-box :alone="true">
/> <template #name>
<n-text>动态</n-text>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon size="21" :depth="3">
<help-outline-icon></help-outline-icon>
</n-icon>
</template>
<n-text>动态值不为0时默认值:取当天时间相加当前值</n-text>
</n-tooltip>
</template>
<n-input-number v-model:value="optionData.differValue" class="input-num-width" size="small" :min="-40" :max="40">
<template #suffix> </template>
</n-input-number>
</setting-item-box> </setting-item-box>
</collapse-item> </collapse-item>
</template> </template>

View File

@ -56,7 +56,6 @@ const onChange = (v: number | number[]) => {
} }
} }
//
watch( watch(
() => props.chartConfig.option.dataset, () => props.chartConfig.option.dataset,
(newData: number | number[]) => { (newData: number | number[]) => {
@ -68,6 +67,25 @@ watch(
immediate: true immediate: true
} }
) )
//
watch(
() => props.chartConfig.option.differValue,
(newData: number) => {
if (props.chartConfig.option.differValue === 0) return
if (typeof option.dataset === 'object') {
option.dataset[0] = dayjs().add(newData, 'day').valueOf()
option.dataset[1] = dayjs().add(newData, 'day').valueOf()
} else {
option.dataset = dayjs().add(newData, 'day').valueOf()
}
//
onChange(newData)
},
{
immediate: true
}
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -7,7 +7,8 @@
:fallback-src="requireErrorImg()" :fallback-src="requireErrorImg()"
:width="w" :width="w"
:height="h" :height="h"
></n-image> lazy
/>
</div> </div>
</template> </template>

View File

@ -0,0 +1,86 @@
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
import { ImageConfig } from '@/packages/components/Informations/Mores/Image/index'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d'
import { setLocalStorage, getLocalStorage, goDialog } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum'
import { FileTypeEnum } from '@/enums/fileTypeEnum'
import { backgroundImageSize } from '@/settings/designSetting'
import { usePackagesStore } from '@/store/modules/packagesStore/packagesStore'
const StoreKey = StorageEnum.GO_USER_MEDIA_PHOTOS
/**
*
*/
type UploadCompletedEventType = {
fileName: string
url: string
}
const userPhotosList: ConfigType[] = getLocalStorage(StoreKey) || []
const uploadFile = (callback: Function | null = null) => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*' // 这里只允许图片类型
input.onchange = async () => {
if (!input.files || !input.files.length) return
const file = input.files[0]
const { name, size, type } = file
if (size > 1024 * 1024 * backgroundImageSize) {
window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`)
return false
}
if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) {
window['$message'].warning('文件格式不符合,请重新上传!')
return false
}
const reader = new FileReader()
reader.onload = () => {
const eventObj: UploadCompletedEventType = { fileName: name, url: reader.result as string }
callback && callback(eventObj)
}
reader.readAsDataURL(file)
}
input.click()
}
const addConfig = {
...ImageConfig,
category: ChatCategoryEnum.PRIVATE,
categoryName: ChatCategoryEnumName.PRIVATE,
package: PackagesCategoryEnum.PHOTOS,
title: '点击上传图片',
image: 'upload.png',
redirectComponent: './components/Informations/Mores/Image', // 虚拟组件路径,尾部不跟 /’,相对于 /packages/index.ts 文件的位置
disabled: true,
clickHandle: (photoConfig: ConfigType) => {
goDialog({
message: `图片需小于 ${backgroundImageSize}M 且只暂存在浏览器中,请自行对接后端接口!现只编译成 base64 进行渲染,对接后端请返回地址使用!`,
transformOrigin: 'center',
onPositiveCallback: () => {
uploadFile((e: UploadCompletedEventType) => {
// 和上传组件一样配置,更换标题,图片,预设数据
const packagesStore = usePackagesStore()
const newPhoto = {
...ImageConfig,
category: ChatCategoryEnum.PRIVATE,
categoryName: ChatCategoryEnumName.PRIVATE,
package: PackagesCategoryEnum.PHOTOS,
title: e.fileName,
image: e.url,
dataset: e.url,
redirectComponent: './components/Informations/Mores/Image' // 虚拟组件路径,尾部不跟 /’,相对于 /packages/index.ts 文件的位置
}
userPhotosList.unshift(newPhoto)
// 存储在本地数据中
setLocalStorage(StoreKey, userPhotosList)
// 插入到上传按钮前的位置
packagesStore.addPhotos(newPhoto, 1)
})
}
})
}
}
export default [addConfig, ...userPhotosList]

View File

@ -0,0 +1,22 @@
import { PackagesCategoryEnum } from '@/packages/index.d'
import { ImageConfig } from '@/packages/components/Informations/Mores/Image/index'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../index.d'
// 远程共享库(调接口获取图像列表)
const imageList = [
{ imageName: 'carousel1', imageUrl: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' },
{ imageName: 'carousel2', imageUrl: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg' }
]
const photoConfigList = imageList.map(i => ({
...ImageConfig,
category: ChatCategoryEnum.SHARE,
categoryName: ChatCategoryEnumName.SHARE,
package: PackagesCategoryEnum.PHOTOS,
image: i.imageUrl,
dataset: i.imageUrl,
title: i.imageName,
redirectComponent: './components/Informations/Mores/Image' // 虚拟组件路径,尾部不跟 /’,相对于 /packages/index.ts 文件的位置
}))
export default photoConfigList

View File

@ -0,0 +1,9 @@
export enum ChatCategoryEnum {
PRIVATE = 'Private',
SHARE = 'Share'
}
export enum ChatCategoryEnumName {
PRIVATE = '私有图',
SHARE = '共享图'
}

View File

@ -0,0 +1,4 @@
import Private from './Private'
import Share from './Share'
export const PhotoList = [...Private, ...Share]

View File

@ -15,15 +15,34 @@ export enum ChartFrameEnum {
// 组件配置 // 组件配置
export type ConfigType = { export type ConfigType = {
// 组件 key
key: string key: string
// 画布组件 key
chartKey: string chartKey: string
// 右侧设置面板组件 key
conKey: string conKey: string
// 标题
title: string title: string
// 分类
category: string category: string
// 分类名称
categoryName: string categoryName: string
// 所属包
package: string package: string
// 归类
chartFrame?: ChartFrameEnum chartFrame?: ChartFrameEnum
// 预览图
image: string image: string
// 从指定路径创建创建该组件
redirectComponent?: string
// 组件预设的 dataset 值(图片/图标)
dataset?: any
// 禁用 拖拽或双击生成组件
disabled?: boolean
// 图标
icon?: string
// 自定义单击事件
clickHandle?: Function
} }
// 数据请求 // 数据请求
@ -120,7 +139,7 @@ export interface PublicConfigType {
} }
filter?: string filter?: string
status: StatusType status: StatusType
interactActions?: InteractActionsType[], interactActions?: InteractActionsType[]
events: { events: {
baseEvent: { baseEvent: {
[K in BaseEvent]?: string [K in BaseEvent]?: string
@ -140,6 +159,7 @@ export interface CreateComponentType extends PublicConfigType, requestConfig {
key: string key: string
chartConfig: ConfigType chartConfig: ConfigType
option: GlobalThemeJsonType option: GlobalThemeJsonType
groupList?: Array<CreateComponentType>
} }
// 组件成组实例类 // 组件成组实例类
@ -155,6 +175,8 @@ export enum PackagesCategoryEnum {
CHARTS = 'Charts', CHARTS = 'Charts',
TABLES = 'Tables', TABLES = 'Tables',
INFORMATIONS = 'Informations', INFORMATIONS = 'Informations',
PHOTOS = 'Photos',
ICONS = 'Icons',
DECORATES = 'Decorates' DECORATES = 'Decorates'
} }
@ -163,6 +185,8 @@ export enum PackagesCategoryName {
CHARTS = '图表', CHARTS = '图表',
TABLES = '列表', TABLES = '列表',
INFORMATIONS = '信息', INFORMATIONS = '信息',
PHOTOS = '图片',
ICONS = '图标',
DECORATES = '小组件' DECORATES = '小组件'
} }
@ -177,5 +201,7 @@ export type PackagesType = {
[PackagesCategoryEnum.CHARTS]: ConfigType[] [PackagesCategoryEnum.CHARTS]: ConfigType[]
[PackagesCategoryEnum.INFORMATIONS]: ConfigType[] [PackagesCategoryEnum.INFORMATIONS]: ConfigType[]
[PackagesCategoryEnum.TABLES]: ConfigType[] [PackagesCategoryEnum.TABLES]: ConfigType[]
[PackagesCategoryEnum.PHOTOS]: ConfigType[]
[PackagesCategoryEnum.ICONS]: ConfigType[]
[PackagesCategoryEnum.DECORATES]: ConfigType[] [PackagesCategoryEnum.DECORATES]: ConfigType[]
} }

View File

@ -2,6 +2,8 @@ import { ChartList } from '@/packages/components/Charts/index'
import { DecorateList } from '@/packages/components/Decorates/index' import { DecorateList } from '@/packages/components/Decorates/index'
import { InformationList } from '@/packages/components/Informations/index' import { InformationList } from '@/packages/components/Informations/index'
import { TableList } from '@/packages/components/Tables/index' import { TableList } from '@/packages/components/Tables/index'
import { PhotoList } from '@/packages/components/Photos/index'
import { IconList } from '@/packages/components/Icons/index'
import { PackagesCategoryEnum, PackagesType, ConfigType, FetchComFlagType } from '@/packages/index.d' import { PackagesCategoryEnum, PackagesType, ConfigType, FetchComFlagType } from '@/packages/index.d'
const configModules: Record<string, { default: string }> = import.meta.glob('./components/**/config.vue', { const configModules: Record<string, { default: string }> = import.meta.glob('./components/**/config.vue', {
@ -19,7 +21,9 @@ export let packagesList: PackagesType = {
[PackagesCategoryEnum.CHARTS]: ChartList, [PackagesCategoryEnum.CHARTS]: ChartList,
[PackagesCategoryEnum.INFORMATIONS]: InformationList, [PackagesCategoryEnum.INFORMATIONS]: InformationList,
[PackagesCategoryEnum.TABLES]: TableList, [PackagesCategoryEnum.TABLES]: TableList,
[PackagesCategoryEnum.DECORATES]: DecorateList [PackagesCategoryEnum.DECORATES]: DecorateList,
[PackagesCategoryEnum.PHOTOS]: PhotoList,
[PackagesCategoryEnum.ICONS]: IconList
} }
/** /**
@ -27,8 +31,12 @@ export let packagesList: PackagesType = {
* @param targetData * @param targetData
*/ */
export const createComponent = async (targetData: ConfigType) => { export const createComponent = async (targetData: ConfigType) => {
const { category, key } = targetData const { redirectComponent, category, key } = targetData
const chart = await import(`./components/${targetData.package}/${category}/${key}/config.ts`) // redirectComponent 是给图片组件库和图标组件库使用的
const componentPath = redirectComponent
? `${redirectComponent}/config.ts`
: `./components/${targetData.package}/${category}/${key}/config.ts`
const chart = await import(/* @vite-ignore */ componentPath)
return new chart.default() return new chart.default()
} }
@ -71,6 +79,8 @@ export const fetchConfigComponent = (dropData: ConfigType) => {
*/ */
export const fetchImages = async (targetData?: ConfigType) => { export const fetchImages = async (targetData?: ConfigType) => {
if (!targetData) return '' if (!targetData) return ''
// 正则判断图片是否为 url是则直接返回该 url
if (/^(http|https):\/\/([\w.]+\/?)\S*/.test(targetData.image)) return targetData.image
// 新数据动态处理 // 新数据动态处理
const { image, package: targetDataPackage } = targetData const { image, package: targetDataPackage } = targetData
// 兼容旧数据 // 兼容旧数据

View File

@ -61,13 +61,14 @@ import {
Pulse as PulseIcon, Pulse as PulseIcon,
Folder as FolderIcon, Folder as FolderIcon,
FolderOpen as FolderOpenIcon, FolderOpen as FolderOpenIcon,
Image as ImageIcon, ImageOutline as ImageIcon,
Images as ImagesIcon, Images as ImagesIcon,
List as ListIcon, List as ListIcon,
EyeOutline as EyeOutlineIcon, EyeOutline as EyeOutlineIcon,
EyeOffOutline as EyeOffOutlineIcon, EyeOffOutline as EyeOffOutlineIcon,
Albums as AlbumsIcon, Albums as AlbumsIcon,
Analytics as AnalyticsIcon Analytics as AnalyticsIcon,
AirplaneOutline as AirPlaneOutlineIcon
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { import {
@ -241,7 +242,9 @@ const ionicons5 = {
// 图表列表 // 图表列表
AlbumsIcon, AlbumsIcon,
// 分析 // 分析
AnalyticsIcon AnalyticsIcon,
// 飞机
AirPlaneOutlineIcon
} }
const carbon = { const carbon = {

View File

@ -2212,9 +2212,9 @@
"pinyin": "tengluozi" "pinyin": "tengluozi"
}, },
{ {
"CMYK": [57, 62, 16, 2], "CMYK": [34, 39, 0, 24],
"RGB": [128, 109, 158], "RGB": [128, 118, 195],
"hex": "#806d9e", "hex": "#8076c3",
"name": "槿紫", "name": "槿紫",
"pinyin": "jinzi" "pinyin": "jinzi"
}, },

View File

@ -4,5 +4,6 @@ export { ConfigType }
export { PackagesType } export { PackagesType }
export interface PackagesStoreType { export interface PackagesStoreType {
packagesList: PackagesType packagesList: PackagesType,
newPhoto?: ConfigType
} }

View File

@ -1,16 +1,23 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { PackagesStoreType, PackagesType } from './packagesStore.d' import { ConfigType, PackagesStoreType, PackagesType } from './packagesStore.d'
import { packagesList } from '@/packages/index' import { packagesList } from '@/packages/index'
// 组件 pakages // 组件 packages
export const usePackagesStore = defineStore({ export const usePackagesStore = defineStore({
id: 'usePackagesStore', id: 'usePackagesStore',
state: (): PackagesStoreType => ({ state: (): PackagesStoreType => ({
packagesList: Object.freeze(packagesList) packagesList: Object.freeze(packagesList),
newPhoto: undefined
}), }),
getters: { getters: {
getPackagesList(): PackagesType { getPackagesList(): PackagesType {
return this.packagesList return this.packagesList
} }
},
actions: {
addPhotos(newPhoto: ConfigType, index: number) {
this.newPhoto = newPhoto
this.packagesList.Photos.splice(index, 0, newPhoto)
}
} }
}) })

View File

@ -314,6 +314,11 @@ export const JSONStringify = <T>(data: T) => {
) )
} }
export const evalFn = (fn: string) => {
var Fun = Function // 一个变量指向Function防止前端编译工具报错
return new Fun('return ' + fn)()
}
/** /**
* * JSON反序列化 undefined * * JSON反序列化 undefined
* @param data * @param data
@ -329,12 +334,12 @@ export const JSONParse = (data: string) => {
} }
// 还原函数值 // 还原函数值
if (typeof v === 'string' && v.indexOf && (v.indexOf('function') > -1 || v.indexOf('=>') > -1)) { if (typeof v === 'string' && v.indexOf && (v.indexOf('function') > -1 || v.indexOf('=>') > -1)) {
return eval(`(function(){return ${v}})()`) return evalFn(`(function(){return ${v}})()`)
} else if (typeof v === 'string' && v.indexOf && v.indexOf('return ') > -1) { } else if (typeof v === 'string' && v.indexOf && v.indexOf('return ') > -1) {
const baseLeftIndex = v.indexOf('(') const baseLeftIndex = v.indexOf('(')
if (baseLeftIndex > -1) { if (baseLeftIndex > -1) {
const newFn = `function ${v.substring(baseLeftIndex)}` const newFn = `function ${v.substring(baseLeftIndex)}`
return eval(`(function(){return ${newFn}})()`) return evalFn(`(function(){return ${newFn}})()`)
} }
} }
return v return v

View File

@ -8,12 +8,13 @@
<!-- 每一项组件的渲染 --> <!-- 每一项组件的渲染 -->
<div <div
class="item-box" class="item-box"
v-for="(item, index) in menuOptions" v-for="item in menuOptions"
:key="index" :key="item.title"
draggable draggable
@dragstart="dragStartHandle($event, item)" @dragstart="!item.disabled && dragStartHandle($event, item)"
@dragend="dragendHandle" @dragend="!item.disabled && dragendHandle"
@dblclick="dblclickHandle(item)" @dblclick="dblclickHandle(item)"
@click="clickHandle(item)"
> >
<div class="list-header"> <div class="list-header">
<mac-os-control-btn class="list-header-control-btn" :mini="true" :disabled="true"></mac-os-control-btn> <mac-os-control-btn class="list-header-control-btn" :mini="true" :disabled="true"></mac-os-control-btn>
@ -21,8 +22,9 @@
<n-ellipsis>{{ item.title }}</n-ellipsis> <n-ellipsis>{{ item.title }}</n-ellipsis>
</n-text> </n-text>
</div> </div>
<div class="list-center go-flex-center go-transition"> <div class="list-center go-flex-center go-transition" draggable="true">
<chart-glob-image class="list-img" :chartConfig="item"></chart-glob-image> <Icon v-if="item.icon" class="list-img" :icon="item.icon" color="#999" width="48" />
<chart-glob-image v-else class="list-img" :chartConfig="item" />
</div> </div>
<div class="list-bottom"> <div class="list-bottom">
<n-text class="list-bottom-text" depth="3"> <n-text class="list-bottom-text" depth="3">
@ -47,12 +49,13 @@ import { DragKeyEnum } from '@/enums/editPageEnum'
import { createComponent } from '@/packages' import { createComponent } from '@/packages'
import { ConfigType, CreateComponentType } from '@/packages/index.d' import { ConfigType, CreateComponentType } from '@/packages/index.d'
import { fetchConfigComponent, fetchChartComponent } from '@/packages/index' import { fetchConfigComponent, fetchChartComponent } from '@/packages/index'
import { Icon } from '@iconify/vue'
import omit from 'lodash/omit' import omit from 'lodash/omit'
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
defineProps({ const props = defineProps({
menuOptions: { menuOptions: {
type: Array as PropType<ConfigType[]>, type: Array as PropType<ConfigType[]>,
default: () => [] default: () => []
@ -69,6 +72,7 @@ const chartMode: Ref<ChartModeEnum> = computed(() => {
// //
const dragStartHandle = (e: DragEvent, item: ConfigType) => { const dragStartHandle = (e: DragEvent, item: ConfigType) => {
if (item.disabled) return
// //
componentInstall(item.chartKey, fetchChartComponent(item)) componentInstall(item.chartKey, fetchChartComponent(item))
componentInstall(item.conKey, fetchConfigComponent(item)) componentInstall(item.conKey, fetchConfigComponent(item))
@ -85,6 +89,7 @@ const dragendHandle = () => {
// //
const dblclickHandle = async (item: ConfigType) => { const dblclickHandle = async (item: ConfigType) => {
if (item.disabled) return
try { try {
loadingStart() loadingStart()
// //
@ -92,6 +97,10 @@ const dblclickHandle = async (item: ConfigType) => {
componentInstall(item.conKey, fetchConfigComponent(item)) componentInstall(item.conKey, fetchConfigComponent(item))
// //
let newComponent: CreateComponentType = await createComponent(item) let newComponent: CreateComponentType = await createComponent(item)
if (item.redirectComponent) {
item.dataset && (newComponent.option.dataset = item.dataset)
newComponent.chartConfig.title = item.title
}
// //
chartEditStore.addComponentList(newComponent, false, true) chartEditStore.addComponentList(newComponent, false, true)
// //
@ -103,6 +112,9 @@ const dblclickHandle = async (item: ConfigType) => {
} }
} }
//
const clickHandle = (item: ConfigType) => item.clickHandle && item.clickHandle(item)
watch( watch(
() => chartMode.value, () => chartMode.value,
(newValue: ChartModeEnum) => { (newValue: ChartModeEnum) => {
@ -113,6 +125,10 @@ watch(
} }
} }
) )
watch(() => props.menuOptions, (n) => {
console.log(n)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -165,7 +181,7 @@ $halfCenterHeight: 50px;
overflow: hidden; overflow: hidden;
.list-img { .list-img {
height: 100px; height: 100px;
width: 140px; max-width: 140px;
border-radius: 6px; border-radius: 6px;
@extend .go-transition; @extend .go-transition;
} }
@ -196,6 +212,9 @@ $halfCenterHeight: 50px;
.item-box { .item-box {
width: $halfItemWidth; width: $halfItemWidth;
max-width: $maxItemWidth; max-width: $maxItemWidth;
.list-img {
max-width: 76px;
}
} }
.list-center { .list-center {
height: $halfCenterHeight; height: $halfCenterHeight;

View File

@ -11,7 +11,7 @@
@update:value="clickItemHandle" @update:value="clickItemHandle"
></n-menu> ></n-menu>
<div class="chart-content-list"> <div class="chart-content-list">
<n-scrollbar> <n-scrollbar trigger="none">
<charts-item-box :menuOptions="packages.selectOptions"></charts-item-box> <charts-item-box :menuOptions="packages.selectOptions"></charts-item-box>
</n-scrollbar> </n-scrollbar>
</div> </div>
@ -23,8 +23,11 @@ import { ref, watch, computed, reactive } from 'vue'
import { ConfigType } from '@/packages/index.d' import { ConfigType } from '@/packages/index.d'
import { useSettingStore } from '@/store/modules/settingStore/settingStore' import { useSettingStore } from '@/store/modules/settingStore/settingStore'
import { loadAsyncComponent } from '@/utils' import { loadAsyncComponent } from '@/utils'
import { usePackagesStore } from '@/store/modules/packagesStore/packagesStore'
import { PackagesCategoryEnum } from '@/packages/index.d'
const ChartsItemBox = loadAsyncComponent(() => import('../ChartsItemBox/index.vue')) const ChartsItemBox = loadAsyncComponent(() => import('../ChartsItemBox/index.vue'))
const packagesStore = usePackagesStore()
const props = defineProps({ const props = defineProps({
selectOptions: { selectOptions: {
@ -61,7 +64,7 @@ let packages = reactive<{
saveSelectOptions: {} saveSelectOptions: {}
}) })
const selectValue = ref<string>() const selectValue = ref<string>('all')
// //
const setSelectOptions = (categorys: any) => { const setSelectOptions = (categorys: any) => {
@ -79,7 +82,6 @@ watch(
if (!newData) return if (!newData) return
newData.list.forEach((e: ConfigType) => { newData.list.forEach((e: ConfigType) => {
const value: ConfigType[] = (packages.categorys as any)[e.category] const value: ConfigType[] = (packages.categorys as any)[e.category]
// @ts-ignore
packages.categorys[e.category] = value && value.length ? [...value, e] : [e] packages.categorys[e.category] = value && value.length ? [...value, e] : [e]
packages.categoryNames[e.category] = e.categoryName packages.categoryNames[e.category] = e.categoryName
packages.categorys['all'].push(e) packages.categorys['all'].push(e)
@ -100,6 +102,16 @@ watch(
} }
) )
watch(
() => packagesStore.newPhoto,
newPhoto => {
if (!newPhoto) return
const newPhotoCategory = newPhoto.category
packages.categorys[newPhotoCategory].splice(1, 0, newPhoto)
packages.categorys['all'].splice(1, 0, newPhoto)
}
)
// //
const clickItemHandle = (key: string) => { const clickItemHandle = (key: string) => {
packages.selectOptions = packages.categorys[key] packages.selectOptions = packages.categorys[key]
@ -119,6 +131,7 @@ $menuWidth: 65px;
@include fetch-bg-color('background-color2-shallow'); @include fetch-bg-color('background-color2-shallow');
} }
.chart-content-list { .chart-content-list {
width: 200px;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -37,7 +37,8 @@
:title="item.title" :title="item.title"
@click="selectChartHandle(item)" @click="selectChartHandle(item)"
> >
<chart-glob-image class="list-item-img" :chartConfig="item"></chart-glob-image> <Icon v-if="item.icon" class="list-img" :icon="item.icon" color="#999" width="20" />
<chart-glob-image v-else class="list-item-img" :chartConfig="item" />
<n-text class="list-item-fs" depth="2">{{ item.title }}</n-text> <n-text class="list-item-fs" depth="2">{{ item.title }}</n-text>
</div> </div>
</n-scrollbar> </n-scrollbar>
@ -70,7 +71,7 @@ import { ref, onUnmounted } from 'vue'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { createComponent } from '@/packages' import { createComponent } from '@/packages'
import { ConfigType, CreateComponentType } from '@/packages/index.d' import { ConfigType, CreateComponentType } from '@/packages/index.d'
import { themeColor, MenuOptionsType } from '../../hooks/useAside.hook' import { themeColor } from '../../hooks/useLayout.hook'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { ChartModeEnum, ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d' import { ChartModeEnum, ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore' import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
@ -78,6 +79,7 @@ import { isString, addEventListener, removeEventListener } from '@/utils'
import { fetchConfigComponent, fetchChartComponent } from '@/packages/index' import { fetchConfigComponent, fetchChartComponent } from '@/packages/index'
import { componentInstall, loadingStart, loadingFinish, loadingError } from '@/utils' import { componentInstall, loadingStart, loadingFinish, loadingError } from '@/utils'
import { ChartGlobImage } from '@/components/Pages/ChartGlobImage' import { ChartGlobImage } from '@/components/Pages/ChartGlobImage'
import { Icon } from '@iconify/vue'
const props = defineProps({ const props = defineProps({
menuOptions: { menuOptions: {
@ -129,7 +131,9 @@ const searchHandle = (key: string | null) => {
} }
loading.value = true loading.value = true
showPopover.value = true showPopover.value = true
searchRes.value = List.filter((e: ConfigType) => !key || e.title.toLowerCase().includes(key.toLowerCase())) searchRes.value = List.filter(
(e: ConfigType) => !e.disabled && (!key || e.title.toLowerCase().includes(key.toLowerCase()))
)
setTimeout(() => { setTimeout(() => {
loading.value = undefined loading.value = undefined
}, 500) }, 500)
@ -146,6 +150,7 @@ const listenerCloseHandle = (e: Event) => {
// //
const selectChartHandle = async (item: ConfigType) => { const selectChartHandle = async (item: ConfigType) => {
if (item.disabled) return
try { try {
loadingStart() loadingStart()
// //
@ -153,6 +158,10 @@ const selectChartHandle = async (item: ConfigType) => {
componentInstall(item.conKey, fetchConfigComponent(item)) componentInstall(item.conKey, fetchConfigComponent(item))
// //
let newComponent: CreateComponentType = await createComponent(item) let newComponent: CreateComponentType = await createComponent(item)
if (item.redirectComponent) {
item.dataset && (newComponent.option.dataset = item.dataset)
newComponent.chartConfig.title = item.title
}
// //
chartEditStore.addComponentList(newComponent, false, true) chartEditStore.addComponentList(newComponent, false, true)
// //
@ -221,10 +230,16 @@ $searchWidth: 176px;
font-size: 12px; font-size: 12px;
} }
&-img { &-img {
height: 28px; height: 20px;
max-width: 30px;
margin-right: 5px; margin-right: 5px;
border-radius: 5px; border-radius: 5px;
} }
& > svg {
min-width: 20px;
min-height: 20px;
margin-right: 5px;
}
&:hover { &:hover {
&::before { &::before {
content: ''; content: '';

View File

@ -1,92 +1,95 @@
import { shallowReactive, ref } from 'vue' import { ref, watch, computed } from 'vue'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { renderLang, renderIcon } from '@/utils' import { renderLang, renderIcon } from '@/utils'
import { themeColor, setItem, getCharts } from './useLayout.hook' import { themeColor, setItem, getCharts } from './useLayout.hook'
import { PackagesCategoryEnum, PackagesCategoryName, PackagesType } from '@/packages/index.d' import { PackagesCategoryEnum, PackagesCategoryName, ConfigType } from '@/packages/index.d'
// 图表
import { usePackagesStore } from '@/store/modules/packagesStore/packagesStore' import { usePackagesStore } from '@/store/modules/packagesStore/packagesStore'
import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d' import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
// 图标 // 图标
const { BarChartIcon } = icon.ionicons5 const { AirPlaneOutlineIcon, ImageIcon, BarChartIcon } = icon.ionicons5
const { const { TableSplitIcon, RoadmapIcon, SpellCheckIcon, GraphicalDataFlowIcon } = icon.carbon
TableSplitIcon,
RoadmapIcon,
SpellCheckIcon,
GraphicalDataFlowIcon,
} = icon.carbon
// 图表 // 图表
export type MenuOptionsType = { export type MenuOptionsType = {
key: string key: string
icon: ReturnType<typeof renderIcon> icon: ReturnType<typeof renderIcon>
label: ReturnType<typeof renderLang> label: ReturnType<typeof renderLang>
list: PackagesType list: ConfigType[]
} }
const { getPackagesList } = usePackagesStore()
const menuOptions: MenuOptionsType[] = []
const packagesListObj = { const packagesListObj = {
[PackagesCategoryEnum.CHARTS]: { [PackagesCategoryEnum.CHARTS]: {
icon: renderIcon(RoadmapIcon), icon: renderIcon(RoadmapIcon),
label: PackagesCategoryName.CHARTS, label: PackagesCategoryName.CHARTS
}, },
[PackagesCategoryEnum.INFORMATIONS]: { [PackagesCategoryEnum.INFORMATIONS]: {
icon: renderIcon(SpellCheckIcon), icon: renderIcon(SpellCheckIcon),
label: PackagesCategoryName.INFORMATIONS, label: PackagesCategoryName.INFORMATIONS
}, },
[PackagesCategoryEnum.TABLES]: { [PackagesCategoryEnum.TABLES]: {
icon: renderIcon(TableSplitIcon), icon: renderIcon(TableSplitIcon),
label: PackagesCategoryName.TABLES, label: PackagesCategoryName.TABLES
}, },
[PackagesCategoryEnum.DECORATES]: { [PackagesCategoryEnum.DECORATES]: {
icon: renderIcon(GraphicalDataFlowIcon), icon: renderIcon(GraphicalDataFlowIcon),
label: PackagesCategoryName.DECORATES, label: PackagesCategoryName.DECORATES
}, },
} [PackagesCategoryEnum.PHOTOS]: {
icon: renderIcon(ImageIcon),
// 处理列表 label: PackagesCategoryName.PHOTOS
const handlePackagesList = () => { },
for (const val in getPackagesList) { [PackagesCategoryEnum.ICONS]: {
menuOptions.push({ icon: renderIcon(AirPlaneOutlineIcon),
key: val, label: PackagesCategoryName.ICONS
// @ts-ignore
icon: packagesListObj[val].icon,
// @ts-ignore
label: packagesListObj[val].label,
// @ts-ignore
list: getPackagesList[val],
})
} }
} }
handlePackagesList()
// 记录选中值 export const useAsideHook = () => {
let beforeSelect: string = menuOptions[0]['key'] const packagesStore = usePackagesStore()
const selectValue = ref<string>(menuOptions[0]['key']) const menuOptions: MenuOptionsType[] = []
// 选中的对象值 // 处理列表
const selectOptions = ref(menuOptions[0]) const handlePackagesList = () => {
for (const val in packagesStore.getPackagesList) {
// 点击 item menuOptions.push({
const clickItemHandle = (key: string, item: any) => { key: val,
selectOptions.value = item // @ts-ignore
// 处理折叠 icon: packagesListObj[val].icon,
if (beforeSelect === key) { // @ts-ignore
setItem(ChartLayoutStoreEnum.CHARTS, !getCharts.value, false) label: packagesListObj[val].label,
} else { // @ts-ignore
setItem(ChartLayoutStoreEnum.CHARTS, true, false) list: packagesStore.getPackagesList[val]
})
}
} }
beforeSelect = key handlePackagesList()
}
export { // 记录选中值
getCharts, let beforeSelect: string = menuOptions[0]['key']
BarChartIcon, const selectValue = ref<string>(menuOptions[0]['key'])
themeColor,
selectOptions, // 选中的对象值
selectValue, const selectOptions = ref(menuOptions[0])
clickItemHandle,
menuOptions, // 点击 item
const clickItemHandle = (key: string, item: any) => {
selectOptions.value = item
// 处理折叠
if (beforeSelect === key) {
setItem(ChartLayoutStoreEnum.CHARTS, !getCharts.value, false)
} else {
setItem(ChartLayoutStoreEnum.CHARTS, true, false)
}
beforeSelect = key
}
return {
getCharts,
BarChartIcon,
themeColor,
selectOptions,
selectValue,
clickItemHandle,
menuOptions
}
} }

View File

@ -1,12 +1,6 @@
<template> <template>
<!-- 左侧所有组件的展示列表 --> <!-- 左侧所有组件的展示列表 -->
<content-box <content-box class="go-content-charts" :class="{ scoped: !getCharts }" title="组件" :depth="1" :backIcon="false">
class="go-content-charts"
:class="{ scoped: !getCharts }"
title="组件"
:depth="1"
:backIcon="false"
>
<template #icon> <template #icon>
<n-icon size="14" :depth="2"> <n-icon size="14" :depth="2">
<bar-chart-icon></bar-chart-icon> <bar-chart-icon></bar-chart-icon>
@ -28,13 +22,7 @@
@update:value="clickItemHandle" @update:value="clickItemHandle"
></n-menu> ></n-menu>
<div class="menu-component-box"> <div class="menu-component-box">
<go-skeleton <go-skeleton :load="!selectOptions" round text :repeat="2" style="width: 90%"></go-skeleton>
:load="!selectOptions"
round
text
:repeat="2"
style="width: 90%"
></go-skeleton>
<charts-option-content <charts-option-content
v-if="selectOptions" v-if="selectOptions"
:selectOptions="selectOptions" :selectOptions="selectOptions"
@ -50,15 +38,9 @@
import { ContentBox } from '../ContentBox/index' import { ContentBox } from '../ContentBox/index'
import { ChartsOptionContent } from './components/ChartsOptionContent' import { ChartsOptionContent } from './components/ChartsOptionContent'
import { ChartsSearch } from './components/ChartsSearch' import { ChartsSearch } from './components/ChartsSearch'
import { import { useAsideHook } from './hooks/useAside.hook'
getCharts,
BarChartIcon, const { getCharts, BarChartIcon, themeColor, selectOptions, selectValue, clickItemHandle, menuOptions } = useAsideHook()
themeColor,
selectOptions,
selectValue,
clickItemHandle,
menuOptions,
} from './hooks/useAside.hook'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -58,7 +58,7 @@
<help-outline-icon></help-outline-icon> <help-outline-icon></help-outline-icon>
</n-icon> </n-icon>
</template> </template>
<n-text>不支持静态组件分组</n-text> <n-text>不支持静态组件</n-text>
</n-tooltip> </n-tooltip>
</template> </template>
<n-select <n-select
@ -169,7 +169,27 @@ const fnDimensionsAndSource = (interactOn: InteractEventOn | undefined) => {
// //
const fnEventsOptions = (): Array<SelectOption | SelectGroupOption> => { const fnEventsOptions = (): Array<SelectOption | SelectGroupOption> => {
const filterOptionList = chartEditStore.componentList.filter(item => { //
const fnFlattern = (
data: Array<CreateComponentType | CreateComponentGroupType>
): Array<CreateComponentType | CreateComponentGroupType> => {
return data.reduce(
(
iter: Array<CreateComponentType | CreateComponentGroupType>,
val: CreateComponentType | CreateComponentGroupType
) => {
if (val.groupList && val.groupList.length > 0) {
iter.push(val)
} else {
iter.push(val)
}
return val.groupList ? [...iter, ...fnFlattern(val.groupList)] : iter
},
[]
)
}
const filterOptionList = fnFlattern(chartEditStore.componentList).filter(item => {
// //
const isNotSelf = item.id !== targetData.value.id const isNotSelf = item.id !== targetData.value.id
// //

View File

@ -29,9 +29,14 @@ export const dragHandle = async (e: DragEvent) => {
// 修改状态 // 修改状态
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_CREATE, false) chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_CREATE, false)
const dropData: Exclude<ConfigType, ['image']> = JSONParse(drayDataString) const dropData: Exclude<ConfigType, ['image']> = JSONParse(drayDataString)
if (dropData.disabled) return
// 创建新图表组件 // 创建新图表组件
let newComponent: CreateComponentType = await createComponent(dropData) let newComponent: CreateComponentType = await createComponent(dropData)
if (dropData.redirectComponent) {
dropData.dataset && (newComponent.option.dataset = dropData.dataset)
newComponent.chartConfig.title = dropData.title
}
setComponentPosition(newComponent, e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2) setComponentPosition(newComponent, e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)
chartEditStore.addComponentList(newComponent, false, true) chartEditStore.addComponentList(newComponent, false, true)

View File

@ -20,7 +20,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, PropType, ref, watch } from 'vue' import { computed, PropType, ref } from 'vue'
import { requireErrorImg } from '@/utils' import { requireErrorImg } from '@/utils'
import { useDesignStore } from '@/store/modules/designStore/designStore' import { useDesignStore } from '@/store/modules/designStore/designStore'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'

View File

@ -132,6 +132,10 @@ export const useSync = () => {
) => { ) => {
// 补充 class 上的方法 // 补充 class 上的方法
let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig) let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig)
if (_componentInstance.chartConfig.redirectComponent) {
_componentInstance.chartConfig.dataset && (newComponent.option.dataset = _componentInstance.chartConfig.dataset)
newComponent.chartConfig.title = _componentInstance.chartConfig.title
}
if (callBack) { if (callBack) {
if (changeId) { if (changeId) {
callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() })) callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() }))
@ -156,7 +160,7 @@ export const useSync = () => {
// 组件 // 组件
if (key === ChartEditStoreEnum.COMPONENT_LIST) { if (key === ChartEditStoreEnum.COMPONENT_LIST) {
let loadIndex = 0 let loadIndex = 0
const listLength = projectData[key].length; const listLength = projectData[key].length
for (const comItem of projectData[key]) { for (const comItem of projectData[key]) {
// 设置加载数量 // 设置加载数量
let percentage = parseInt((parseFloat(`${++loadIndex / listLength}`) * 100).toString()) let percentage = parseInt((parseFloat(`${++loadIndex / listLength}`) * 100).toString())