fear: 升级合并go-view主分支版本及新功能

优化画布功能,
优化编辑功能,
新转增组件联动功能,
新增输入框,下拉框,选择框,标签选择,时间选择,分页联动控件
新增全局联动配置,
新增带分页数据列表,
新增地图高德地图配置项与区域地图配置项,
新增图标分类组件,
新增图片分类组件,
新增字节系列图表
新增部分装饰组件
新增磨砂玻璃滤镜组件
完善OAUTH2令牌无感刷新等
This commit is contained in:
EarlySummer
2024-10-30 10:59:47 +08:00
parent 5ae14e5c35
commit 0ba33befad
474 changed files with 43700 additions and 2613 deletions

3
.gitignore vendored
View File

@@ -4,3 +4,6 @@ dist
dist-ssr
*.local
.vscode
.idea
.workflow
.husky

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="description" content="GoView 是高效、高性能的拖拽式低代码数据可视化开发平台,将页面元素封装为基础组件,无需编写代码即可完成业务需求。">
<meta name="description" content="GoView 是高效、高性能的拖拽式低代码数据可视化开发平台,将页面元素封装为基础组件,无需编写代码即可完成业务需求。 ">
<meta name="keywords" content="GoViewgoview低代码可视化">
<meta name="author" content="奔跑的面条,面条">
<meta
@@ -14,6 +14,7 @@
<link rel="icon" href="./favicon.ico" />
<title>GoView</title>
<link rel="stylesheet" href="./index.css" />
<script src="js/esobjs-xe2-plugin/dist-web/xe2-assets/js/load-common.js"></script>
</head>
<body>
<div id="appProvider" style="display: none;"></div>

20213
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
{
"type": "module",
"name": "go-view",
"version": "2.1.6",
"engines": {
"node": ">=16.14 <18.0.0"
"node": ">=12.0"
},
"scripts": {
"dev": "vite --host",
@@ -16,34 +17,48 @@
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@amap/amap-jsapi-types": "^0.0.8",
"@iconify/json": "^2.2.158",
"@types/color": "^3.0.3",
"@types/crypto-js": "^4.1.1",
"@types/keymaster": "^1.6.30",
"@types/lodash": "^4.14.184",
"@visactor/vchart": "^1.12.3",
"@visactor/vchart-theme": "^1.12.1",
"animate.css": "^4.1.1",
"axios": "^0.27.2",
"axios": "^1.4.0",
"cesium": "1.99",
"color": "^4.2.3",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.7",
"dom-helpers": "^5.2.1",
"echarts-liquidfill": "^3.1.0",
"echarts-stat": "^1.2.0",
"echarts-wordcloud": "^2.0.0",
"esobjs-xe2-plugin": "^0.1.126-beta-0.3",
"gsap": "^3.11.3",
"highlight.js": "^11.5.0",
"html2canvas": "^1.4.1",
"iconify-icon": "^1.0.8",
"keymaster": "^1.6.2",
"mitt": "^3.0.0",
"monaco-editor": "^0.33.0",
"naive-ui": "2.34.3",
"pinia": "^2.0.13",
"pnpm": "^8.7.0",
"screenfull": "^6.0.1",
"three": "^0.145.0",
"vite-plugin-cesium": "^1.2.23",
"vue": "^3.2.31",
"vue-demi": "^0.13.1",
"vue-i18n": "^9.2.2",
"vue-i18n": "9.2.2",
"vue-router": "4.0.12",
"vue-xe2-plugin": "^0.1.3",
"vue3-lazyload": "^0.2.5-beta",
"vue3-sketch-ruler": "^1.3.3",
"vuedraggable": "^4.1.0"
"vuedraggable": "^4.1.0",
"xbsj-xe2": "^0.1.16",
"xbsj-xe2-assets": "^0.1.16",
"smplotting-xe2-plugin": "^0.1.3"
},
"devDependencies": {
"@commitlint/cli": "^17.0.2",
@@ -54,8 +69,8 @@
"@typescript-eslint/parser": "^5.18.0",
"@vicons/carbon": "^0.12.0",
"@vicons/ionicons5": "~0.11.0",
"@vitejs/plugin-vue": "^1.10.2",
"@vitejs/plugin-vue-jsx": "^1.3.9",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/compiler-sfc": "^3.2.31",
"@vueuse/core": "^7.7.1",
"commitlint": "^17.0.2",
@@ -66,6 +81,7 @@
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.5.0",
"esobjs-xe2-plugin-assets": "^0.1.14",
"husky": "^8.0.1",
"lodash": "~4.17.21",
"mockjs": "^1.1.0",
@@ -74,11 +90,13 @@
"sass": "^1.49.11",
"sass-loader": "^12.6.0",
"typescript": "4.6.3",
"vite": "2.9.9",
"vite": "4.3.6",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-externals": "^0.6.2",
"vite-plugin-importer": "^0.2.5",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-static-copy": "^1.0.6",
"vue-echarts": "^6.0.2",
"vue-tsc": "^0.28.10"
}

6419
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

10
preview.yml Normal file
View File

@@ -0,0 +1,10 @@
# preview.yml
autoOpen: true # 打开工作空间时是否自动开启所有应用的预览
apps:
- port: 3000 # 应用的端口
run: npm i --registry=https://registry.npmmirror.com && npm run dev # 应用的启动命令
command: # 使用此命令启动服务且不执行run
root: ./ # 应用的启动目录
name: GoView # 应用名称
description: 开源、精美、便捷的「数据可视化」低代码开发平台 # 应用描述
autoOpen: true # 打开工作空间时是否自动开启预览(优先级高于根级 autoOpen)

View File

@@ -19,6 +19,34 @@ import { GoAppProvider } from '@/components/GoAppProvider'
import { I18n } from '@/components/I18n'
import { useSystemInit, useDarkThemeHook, useThemeOverridesHook, useCode, useLang } from '@/hooks'
//字节图表全局配置部分
import vScreenVolcanoBlue from '@visactor/vchart-theme/public/vScreenVolcanoBlue.json';
import VChart from '@visactor/vchart';
import {IGlobalMarkThemeByName, IGlobalMarkThemeByType, ITheme} from "@visactor/vchart/esm/theme/interface";
import {languages} from "monaco-editor";
import type {IColorKey} from "@visactor/vchart/esm/theme/color-scheme/interface";
import type {ITokenKey} from "@visactor/vchart/esm/theme/token";
import type {ISeriesTheme} from "@visactor/vchart/esm/series/interface";
import type {IComponentTheme} from "@visactor/vchart/esm/component/interface";
const myVScreenVolcanoBlue: Partial<ITheme> = {
...vScreenVolcanoBlue,
type:"dark",
background:vScreenVolcanoBlue.background as IColorKey,
fontFamily:vScreenVolcanoBlue.fontFamily as ITokenKey,
mark:vScreenVolcanoBlue.mark as IGlobalMarkThemeByType,
markByName:vScreenVolcanoBlue.markByName as IGlobalMarkThemeByName,
series:vScreenVolcanoBlue.series as ISeriesTheme,
component: vScreenVolcanoBlue.component as unknown as IComponentTheme
// 其他字段逐个检查和断言
};
VChart.ThemeManager.registerTheme('vScreenVolcanoBlue',myVScreenVolcanoBlue);
// apply the theme
VChart.ThemeManager.setCurrentTheme('vScreenVolcanoBlue');
//字节图标配置部分END
// 暗黑主题
const darkTheme = useDarkThemeHook()

View File

@@ -1,12 +1,32 @@
import axios, { AxiosResponse, AxiosRequestConfig, Axios } from 'axios'
import { ResultEnum, ModuleTypeEnum } from "@/enums/httpEnum"
import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum"
import {PageEnum, ErrorPageNameMap, PreviewEnum} from "@/enums/pageEnum"
import { StorageEnum } from '@/enums/storageEnum'
import { axiosPre } from '@/settings/httpSetting'
import { SystemStoreEnum, SystemStoreUserInfoEnum } from '@/store/modules/systemStore/systemStore.d'
import { redirectErrorPage, getLocalStorage, routerTurnByName, isPreview } from '@/utils'
import {
redirectErrorPage,
getLocalStorage,
routerTurnByName,
isPreview,
clearAllSessio,
clearAllStorage,
setLocalStorage,
setSessionStorage, fetchRouteParamsLocation, fetchRouteName, logout, fetchRoutePath
} from '@/utils'
import { fetchAllowList } from './axios.config'
import includes from 'lodash/includes'
import {useSystemStore} from "@/store/modules/systemStore/systemStore";
import { useDialog } from 'naive-ui'
// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
// 请求队列
let requestList: any[] = []
// 是否正在刷新中
let isRefreshToken = false
// 请求路径
let base_url=`${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}${axiosPre}`
const dialog = useDialog()
export interface MyResponseType<T> {
code: ResultEnum
@@ -19,7 +39,7 @@ export interface MyRequestInstance extends Axios {
}
const axiosInstance = axios.create({
baseURL: `${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}${axiosPre}`,
baseURL: base_url,
timeout: ResultEnum.TIMEOUT,
}) as unknown as MyRequestInstance
@@ -27,6 +47,7 @@ axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 获取 tenantId
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
// console.log(window.location)
const tenantId = info ? info[SystemStoreEnum.TENANT_INFO]['tenantId'] : undefined
if (tenantId) {
config.headers = {
@@ -57,15 +78,18 @@ axiosInstance.interceptors.request.use(
// 响应拦截器
axiosInstance.interceptors.response.use(
(res: AxiosResponse) => {
async (res: AxiosResponse<any>) => {
const config = res.config
const { code,msg } = res.data as { code: number,msg:string }
if (code === undefined || code === null) return Promise.resolve(res)
// 预览页面错误不进行处理
if (isPreview()) {
if (isPreview()&&code!==ResultEnum.TOKEN_OVERDUE) {
return Promise.resolve(res.data)
}
// 如果是验证码的返回,直接返回数据
const { code } = res.data as { code: number }
if (code === undefined || code === null) return Promise.resolve(res)
// 成功
if (code === ResultEnum.SUCCESS) {
@@ -74,9 +98,68 @@ axiosInstance.interceptors.response.use(
// 登录过期
if (code === ResultEnum.TOKEN_OVERDUE) {
window['$message'].error(window['$t']('http.token_overdue_message'))
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
return Promise.resolve(res.data)
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
console.log('Auth2:-------------------准备刷新令牌-----------------')
if (!isRefreshToken) {
isRefreshToken = true
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
// console.log(window.location)
const refreshToken = info ? info[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_REFRESH_TOKEN] : undefined
// 1. 如果获取不到刷新令牌,则只能执行登出操作
if (!refreshToken) {
console.log('无刷新令牌,即将返回。')
return handleAuthorized()
}
// 2. 进行刷新访问令牌
try {
const systemStore = useSystemStore()
const refreshTokenRes = await getTefreshToken()
// 2.1 刷新成功,则回放队列的请求 + 当前请求
systemStore.setItem(SystemStoreEnum.USER_INFO, {
[SystemStoreUserInfoEnum.USER_TOKEN]: (await refreshTokenRes).data.data.accessToken,
[SystemStoreUserInfoEnum.USER_REFRESH_TOKEN]: (await refreshTokenRes).data.data.refreshToken,
[SystemStoreUserInfoEnum.TOKEN_NAME]: 'Authorization'
})
//修改当前访问令牌
// config.headers!.Authorization = 'Bearer ' + (await refreshTokenRes).data.data.accessToken
const userInfo = info[SystemStoreEnum.USER_INFO]
config.headers = {
...config.headers,
[userInfo[SystemStoreUserInfoEnum.TOKEN_NAME] || 'token']: 'Bearer ' + userInfo[SystemStoreUserInfoEnum.USER_TOKEN] || ''
}
requestList.forEach((cb: any) => {
cb()
})
requestList = []
console.log('Auth2:-------------------令牌刷新成功-----------------')
return axiosInstance(config)
} catch (e) {
// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
// 2.2 刷新失败,只回放队列的请求
requestList.forEach((cb: any) => {
cb()
})
// 提示是否要登出。即不回放当前请求!不然会形成递归
return handleAuthorized()
} finally {
requestList = []
isRefreshToken = false
}
} else {
console.log('Auth2:-------------------已添加刷新队列-----------------')
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
// 添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
requestList.push(() => {
const userInfo = info[SystemStoreEnum.USER_INFO]
config.headers!.Authorization = 'Bearer ' + userInfo[SystemStoreUserInfoEnum.USER_TOKEN] // 让每个请求携带自定义token 请根据实际情况自行修改
resolve(axiosInstance(config))
})
})
}
}
// 固定错误码重定向
@@ -85,6 +168,17 @@ axiosInstance.interceptors.response.use(
return Promise.resolve(res.data)
}
if (code !== 200) {
if (msg === '无效的刷新令牌') {
// hard coding忽略这个提示直接登出
console.log(msg)
return handleAuthorized()
} else {
window['$message'].error(msg)
}
return Promise.reject('error')
}
// 提示错误
window['$message'].error(window['$t']((res.data as any).msg))
return Promise.resolve(res.data)
@@ -94,4 +188,32 @@ axiosInstance.interceptors.response.use(
}
)
const getTefreshToken = async () => {
let tenantData = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
axios.defaults.headers.common['tenant-id'] = tenantData[SystemStoreEnum.TENANT_INFO]['tenantId'] ?? undefined;
return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + tenantData[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_REFRESH_TOKEN] ?? undefined)
}
const handleAuthorized = () =>{
// window['$message'].error(window['$t']('http.token_overdue_message'))
console.log(useDialog())
window['$dialog'].warning( {title: '登录已超时',
content: '登录超时,请重新登录。',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
// session 不需要清除 clearAllSessio() //清除所有Session缓存
clearAllStorage() //清除所有Storage缓存
//临时缓存跳回目录进行登录重新跳回
if('/chart/preview' === fetchRoutePath()){
setSessionStorage('setRedirectPath','/chart/preview')
setSessionStorage('setRedirectPathId', fetchRouteParamsLocation())
}
logout()
return Promise.resolve(window['$t']('http.token_overdue_message'))
}
})
}
export default axiosInstance

100
src/api/mock/graph.json Normal file
View File

@@ -0,0 +1,100 @@
{
"nodes": [
{
"id": "0",
"name": "Myriel",
"symbolSize": "@integer(0, 50)",
"x": -266.82776,
"y": 299.6904,
"value": "@integer(0, 50)",
"category": 3
},
{
"id": "1",
"name": "Napoleon",
"symbolSize": "@integer(0, 50)",
"x": -418.08344,
"y": 446.8853,
"value": "@integer(0, 50)",
"category": 5
},
{
"id": "2",
"name": "MlleBaptistine",
"symbolSize": "@integer(0, 50)",
"x": -212.76357,
"y": 245.29176,
"value": "@integer(0, 50)",
"category": 1
},
{
"id": "3",
"name": "MmeMagloire",
"symbolSize": "@integer(0, 50)",
"x": -242.82404,
"y": 235.26283,
"value": "@integer(0, 50)",
"category": 1
},
{
"id": "4",
"name": "CountessDeLo",
"symbolSize": "@integer(0, 50)",
"x": -379.30386,
"y": 429.06424,
"value": "@integer(0, 50)",
"category": 0
}
],
"links": [
{
"source": "1",
"target": "@integer(2, 4)"
},
{
"source": "2",
"target": "@integer(3, 4)"
},
{
"source": "3",
"target": "@integer(0, 2)"
},
{
"source": "3",
"target": "@integer(0, 1)"
},
{
"source": "4",
"target": "@integer(0, 3)"
}
],
"categories": [
{
"name": "A"
},
{
"name": "B"
},
{
"name": "C"
},
{
"name": "D"
},
{
"name": "E"
},
{
"name": "F"
},
{
"name": "G"
},
{
"name": "H"
},
{
"name": "I"
}
]
}

View File

@@ -19,6 +19,8 @@ export const capsuleUrl = '/mock/capsule'
export const wordCloudUrl = '/mock/wordCloud'
export const treemapUrl = '/mock/treemap'
export const threeEarth01Url = '/mock/threeEarth01Data'
export const sankeyUrl = '/mock/sankey'
export const graphUrl = '/mock/graphData'
const mockObject: MockMethod[] = [
{
@@ -103,6 +105,16 @@ const mockObject: MockMethod[] = [
method: RequestHttpEnum.GET,
response: () => test.threeEarth01Data
},
{
url: sankeyUrl,
method: RequestHttpEnum.GET,
response: () => test.fetchSankey
},
{
url: graphUrl,
method: RequestHttpEnum.GET,
response: () => test.graphData
},
]
export default mockObject

86
src/api/mock/sankey.json Normal file
View File

@@ -0,0 +1,86 @@
{
"label": [
{
"name": "a"
},
{
"name": "b"
},
{
"name": "a1"
},
{
"name": "a2"
},
{
"name": "b1"
},
{
"name": "b2"
}
],
"links": [
{
"source": "a",
"target": "a1",
"value": "@integer(0, 10)"
},
{
"source": "a",
"target": "a2",
"value": "@integer(0, 10)"
},
{
"source": "b",
"target": "b1",
"value": "@integer(0, 10)"
},
{
"source": "a",
"target": "b1",
"value": "@integer(0, 10)"
},
{
"source": "b1",
"target": "a1",
"value": "@integer(0, 10)"
},
{
"source": "b1",
"target": "b2",
"value": "@integer(0, 10)"
}
],
"levels": [
{
"depth": 0,
"itemStyle": {
"color": "#decbe4"
},
"lineStyle": {
"color": "source",
"opacity": 0.9
}
},
{
"depth": 1,
"itemStyle": {
"color": "#b3cde3"
},
"lineStyle": {
"color": "source",
"opacity": 0.6
}
},
{
"depth": 2,
"itemStyle": {
"color": "#ccebc5"
},
"lineStyle": {
"color": "source",
"opacity": 0.6
}
}
]
}

View File

@@ -2,6 +2,8 @@ import heatmapJson from './heatMapData.json'
import scatterJson from './scatter.json'
import mapJson from './map.json'
import tTreemapJson from './treemap.json'
import sankeyJson from './sankey.json'
import graphDataJson from './graph.json'
export default {
// 单图表
@@ -219,5 +221,19 @@ export default {
'endArray|10': [{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }]
}
]
}
},
// 桑基图
fetchSankey: {
code: 0,
status: 200,
msg: '请求成功',
data: sankeyJson
},
// 关系图
graphData: {
code: 0,
status: 200,
msg: '请求成功',
data: graphDataJson
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -0,0 +1,34 @@
<template>
<iconify-icon
:icon="icon"
:rotate="`${rotate}deg`"
:width="width"
:style="{
color: color
}"
></iconify-icon>
</template>
<script setup lang="ts">
defineProps({
icon: {
type: String,
required: true
},
color: {
type: String,
default: '#999999',
required: false
},
width: {
type: [String, Number],
default: '20',
required: false
},
rotate: {
type: [String, Number],
default: '0',
required: false
}
})
</script>

View File

@@ -1,5 +1,5 @@
<template>
<img class="list-img" v-lazy="imageInfo" alt="图表图片" />
<img class="list-img" v-lazy="imageInfo" :alt="imageInfo" />
</template>
<script setup lang="ts">

View File

@@ -0,0 +1,45 @@
<template>
<n-radio-group :value="props.modelValue || INHERIT_VALUE" @update:value="handleChange">
<n-space>
<n-tooltip :show-arrow="false" trigger="hover" v-for="item in rendererList" :key="item.value">
<template #trigger>
<n-radio :value="item.value">
{{ item.value }}
</n-radio>
</template>
{{ item.desc }}
</n-tooltip>
</n-space>
</n-radio-group>
</template>
<script setup lang="ts">
import { type EchartsRenderer } from '@/settings/chartThemes'
const props = defineProps<{ modelValue?: EchartsRenderer; includeInherit?: boolean }>()
const emits = defineEmits(['update:modelValue'])
const INHERIT_VALUE = 'inherit'
const handleChange = (val: EchartsRenderer & typeof INHERIT_VALUE) => {
emits('update:modelValue', val === INHERIT_VALUE ? undefined : val)
}
const rendererList = [
{
value: 'svg',
desc: '在缩放场景下具有更好的表现'
},
{
value: 'canvas',
desc: '数据量较大(经验判断 > 1k、较多交互时建议选择'
},
...(props.includeInherit
? [
{
value: INHERIT_VALUE,
desc: '默认继承全局配置'
}
]
: [])
]
</script>

View File

@@ -1,4 +1,34 @@
<template>
<collapse-item name="渲染器">
<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>所有echarts图表组件默认都将采用所选的渲染器进行渲染</n-text>
</n-tooltip>
</template>
<EchartsRendererSetting v-model="themeSetting.renderer" />
</setting-item-box>
<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>仅当前组件采用指定渲染器渲染</n-text>
</n-tooltip>
</template>
<EchartsRendererSetting v-model="optionData.renderer" includeInherit />
</setting-item-box>
</collapse-item>
<collapse-item v-if="title" name="标题">
<template #header>
<n-switch v-model:value="title.show" size="small"></n-switch>
@@ -227,9 +257,36 @@
<n-switch v-model:value="legend.show" size="small"></n-switch>
</template>
<setting-item-box name="图例文字">
<setting-item>
<setting-item name="颜色">
<n-color-picker size="small" v-model:value="legend.textStyle.color"></n-color-picker>
</setting-item>
<setting-item name="大小">
<n-input-number v-model:value="legend.textStyle.fontSize" :min="1" size="small"></n-input-number>
</setting-item>
</setting-item-box>
<setting-item-box name="图例位置">
<setting-item name="x轴">
<n-select v-model:value="legend.x" size="small" :options="legendConfig.lengendX" />
</setting-item>
<setting-item name="y轴">
<n-select v-model:value="legend.y" size="small" :options="legendConfig.lengendY" />
</setting-item>
</setting-item-box>
<setting-item-box name="图例信息">
<setting-item name="方向">
<n-select v-model:value="legend.orient" size="small" :options="legendConfig.orient" />
</setting-item>
<setting-item name="形状">
<n-select v-model:value="legend.icon" size="small" :options="legendConfig.shape" />
</setting-item>
</setting-item-box>
<setting-item-box name="图例大小">
<setting-item name="宽">
<n-input-number v-model:value="legend.itemWidth" :min="1" size="small"></n-input-number>
</setting-item>
<setting-item name="高">
<n-input-number v-model:value="legend.itemHeight" :min="1" size="small"></n-input-number>
</setting-item>
</setting-item-box>
</collapse-item>
@@ -279,10 +336,15 @@
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { PropType, computed, watch } from 'vue'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import { axisConfig } from '@/packages/chartConfiguration/echarts/index'
import { axisConfig, legendConfig } from '@/packages/chartConfiguration/echarts/index'
import { CollapseItem, SettingItemBox, SettingItem, GlobalSettingPosition } from '@/components/Pages/ChartItemSetting'
import { icon } from '@/plugins'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import EchartsRendererSetting from './EchartsRendererSetting.vue'
const { HelpOutlineIcon } = icon.ionicons5
const props = defineProps({
optionData: {
@@ -296,6 +358,12 @@ const props = defineProps({
}
})
const chartEditStore = useChartEditStore()
const themeSetting = computed(() => {
const chartThemeSetting = chartEditStore.getEditCanvasConfig.chartThemeSetting
return chartThemeSetting
})
const title = computed(() => {
return props.optionData.title
})
@@ -319,4 +387,18 @@ const grid = computed(() => {
const visualMap = computed(() => {
return props.optionData.visualMap
})
// 监听legend color颜色改变type = scroll的颜色
watch(() => legend.value && legend.value.textStyle.color, (newVal) => {
if (legend.value && newVal) {
if (!legend.value.pageTextStyle) {
legend.value.pageTextStyle = { color: newVal }
} else {
legend.value.pageTextStyle.color = newVal
}
}
}, {
immediate: true,
deep: true,
})
</script>

View File

@@ -69,6 +69,22 @@
</setting-item>
</setting-item-box>
<!-- 预设滤镜 -->
<div v-if="presetImageList.length" class="preset-filter">
<n-image
class="preset-img"
width="46"
preview-disabled
object-fit="scale-down"
v-for="(item, index) in presetImageList"
:key="index"
:class="{ 'active-preset': item.hueRotate === chartStyles.hueRotate }"
:style="{ filter: `hue-rotate(${item.hueRotate}deg)` }"
:src="item.src"
@click="() => (chartStyles.hueRotate = item.hueRotate)"
></n-image>
</div>
<!-- 混合模式 -->
<setting-item-box v-if="!isCanvas" :alone="true">
<template #name>
@@ -149,10 +165,12 @@
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { ref, PropType } from 'vue'
import { PickCreateComponentType, BlendModeEnumList } from '@/packages/index.d'
import { SettingItemBox, SettingItem, CollapseItem } from '@/components/Pages/ChartItemSetting'
import { icon } from '@/plugins'
import logoImg from '@/assets/logo.png'
import { useDesignStore } from '@/store/modules/designStore/designStore'
const props = defineProps({
isGroup: {
@@ -175,10 +193,48 @@ const { HelpOutlineIcon } = icon.ionicons5
const sliderFormatTooltip = (v: string) => {
return `${(parseFloat(v) * 100).toFixed(0)}%`
}
// 角度格式化
const degFormatTooltip = (v: string) => {
return `${v}deg`
}
// 预设滤镜
interface presetImageData {
index: number
src: string
hueRotate: number
}
const presetImageList = ref([] as presetImageData[])
for (let i = 1; i <= 12; i++) {
presetImageList.value.push({
index: i,
src: logoImg,
hueRotate: i * 30
})
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// 预设滤镜
.preset-filter {
margin: 20px 0 10px 0;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.preset-img {
margin-bottom: 10px;
padding: 2px;
border-radius: 6px;
transition: 0.2s all;
cursor: pointer;
&:hover {
box-shadow: 0 0 0 2px #66a9c9;
}
}
.active-preset {
box-shadow: 0 0 0 2px #66a9c9;
}
}
</style>

View File

@@ -1,13 +1,13 @@
<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 back" :data-back="backTextFromData"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, PropType, watch } from 'vue'
import { FlipType } from './index'
import { ref, PropType, watch, nextTick } from 'vue'
import { FlipType } from './index'
const props = defineProps({
flipType: {
@@ -43,6 +43,10 @@ const props = defineProps({
backColor: {
type: String,
default: '#000000'
},
borderWidth: {
type: Number,
default: 2
}
})
@@ -50,19 +54,27 @@ const isFlipping = ref(false)
const frontTextFromData = 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
frontTextFromData.value = front
// 设置翻转状态为true
isFlipping.value = true
// 翻牌结束的行为
setTimeout(() => {
timeoutID = setTimeout(() => {
isFlipping.value = false // 设置翻转状态为false
frontTextFromData.value = back
}, props.duration)
@@ -86,6 +98,7 @@ $radius: v-bind('`${props.radius}px`');
$width: v-bind('`${props.width}px`');
$height: v-bind('`${props.height}px`');
$perspective: v-bind('`${props.height * 2}px`');
$borderWidth: v-bind('`${props.borderWidth * 2}px`');
$speed: v-bind('`${props.duration / 1000}s`');
$shadowColor: #000000;
$lineColor: #4a9ef8;
@@ -125,13 +138,12 @@ $lineColor: #4a9ef8;
}
// #endregion
.go-Flipper {
.go-flipper {
display: inline-block;
position: relative;
width: $width;
height: $height;
line-height: $height;
border: solid 1px $backColor;
border-radius: $radius;
background: $frontColor;
font-size: $width;
@@ -139,6 +151,17 @@ $lineColor: #4a9ef8;
box-shadow: 0 0 6px rgba($color: $shadowColor, $alpha: 0.5); // 阴影部分
text-align: center;
// 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:after {
@@ -186,11 +209,13 @@ $lineColor: #4a9ef8;
&.down.go .front:before {
transform-origin: 50% 100%;
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;
}
&.down.go .back:after {
animation: backFlipDown $speed ease-in-out both;
box-shadow: 0 2px $borderWidth 0 $frontColor;
backface-visibility: hidden;
}
/*向上翻*/
&.up .front:after {
@@ -208,11 +233,13 @@ $lineColor: #4a9ef8;
&.up.go .front:after {
transform-origin: 50% 0;
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;
}
&.up.go .back:before {
animation: backFlipUp $speed ease-in-out both;
box-shadow: 0 -2px $borderWidth 0 $frontColor;
backface-visibility: hidden;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More