Compare commits

...

89 Commits

Author SHA1 Message Date
奔跑的面条
f45d4ca5af build: 修改版本到 2.0.1 2022-06-16 10:48:42 +08:00
奔跑的面条
d96e7f71d7 fix: 解决打包之后无法加载页面的问题 2022-06-15 17:54:49 +08:00
奔跑的面条
24fee76237 feat: 新增 preview 模式,修改打包后路径指向 2022-06-15 17:48:16 +08:00
奔跑的面条
d59193bc33 feat: 新增commitlint 2022-06-15 17:18:39 +08:00
奔跑的面条
6928a70d9f docs: 更新文档 2022-06-15 17:15:29 +08:00
奔跑的面条
e0796280f5 doc: 文档更新 2022-06-15 17:14:27 +08:00
奔跑的面条
5d803e3fa6 feat: 新增渐变文本组件 2022-06-15 16:20:25 +08:00
奔跑的面条
749c0a2f39 build:升级依赖 2022-06-15 16:11:58 +08:00
奔跑的面条
737504cef5 fix: 解决npm,yarn 安装依赖报错的问题 2022-06-15 16:08:59 +08:00
奔跑的面条
8115950893 fix: 补充丢失的图片 2022-06-14 12:46:07 +08:00
奔跑的面条
f458a21a2f feat:新增数字滚动组件动态获取数据功能 2022-06-14 12:31:46 +08:00
奔跑的面条
13a9675894 feat: 新增表格滚动组件 2022-06-14 12:31:20 +08:00
奔跑的面条
77b5a41af9 fix:解决边框04展示不全的bug 2022-06-14 12:30:29 +08:00
奔跑的面条
2bf895ad3d feat:新增水球图设置项 2022-06-13 17:29:39 +08:00
奔跑的面条
6a5abd6762 fix: 修改列表页展示问题 2022-06-13 14:28:58 +08:00
奔跑的面条
5cb458c45b feat: 新增环形图,新增NaiveUI-进度组件 2022-06-13 13:18:30 +08:00
奔跑的面条
f828c48ab6 feat: 新增进度条组件 2022-06-12 18:47:47 +08:00
奔跑的面条
2626bc794f fix: 解决缩放比例展示不全的问题 2022-06-12 18:45:31 +08:00
奔跑的面条
734dd68607 chore: 优化拖拽锚点 2022-06-11 15:15:38 +08:00
奔跑的面条
19f53f705c fix:修改请求地址为null时引起的异常bug 2022-06-11 14:37:22 +08:00
奔跑的面条
b93caf1386 chore: 优化发布弹窗 2022-06-11 14:23:16 +08:00
奔跑的面条
aa613e2805 build: 依赖基本升级 2022-06-11 14:15:28 +08:00
奔跑的面条
616584fc19 fix: 修改双折线图X轴无法变更的问题 2022-06-11 14:13:32 +08:00
奔跑的面条
a5e83bfe9f fix: 解决项目列表信息栏会换行的问题 2022-06-09 08:49:07 +08:00
奔跑的面条
1252d266dd fix:解决截图有白边的问题 2022-06-09 08:48:28 +08:00
奔跑的面条
3c820d53a6 fix: 修改复制失败的提示类型错误的问题 2022-06-06 10:43:14 +08:00
奔跑的面条
f0525c7522 chore: 新增路由白名单 2022-06-06 10:42:55 +08:00
奔跑的面条
600f1b2dd2 chore:优化搜索结果列表UI 2022-06-05 11:41:57 +08:00
奔跑的面条
757b79514a chore:优化 dialog 的全局封装代码 2022-06-04 16:25:37 +08:00
奔跑的面条
cf8121eb00 build:修改 fetch 版本号 2022-06-04 15:44:26 +08:00
奔跑的面条
faf2d44fbb chore: 优化页面 UI 2022-06-03 20:21:35 +08:00
奔跑的面条
88516d9491 fix: 修改自动复制粘贴的问题 2022-06-03 18:53:37 +08:00
奔跑的面条
88dbbe03ea fix: 新增发布页面处理 2022-06-03 14:48:58 +08:00
奔跑的面条
ae1fd2e7cf chore: 优化了标题展示和大小样式 2022-06-03 11:19:29 +08:00
奔跑的面条
d3931f47bc type: 修改类型错误 2022-06-01 23:00:28 +08:00
奔跑的面条
bf9bd59b63 fix:处理列表页标题过长的展示问题 2022-06-01 22:58:22 +08:00
奔跑的面条
fb0ff50837 type: 定义全局返回值类型 2022-06-01 22:41:11 +08:00
奔跑的面条
93727a0ac7 build: 修改版本号 0.0.9 2022-06-01 22:28:46 +08:00
奔跑的面条
2641e70c78 chore:优化了路由写法,修改了错误页面的展示,新增未发布提示页面 2022-06-01 22:20:05 +08:00
奔跑的面条
bc44584698 feat: 新增动态预览功能 2022-06-01 22:19:03 +08:00
奔跑的面条
82394dd7a3 fix: 修改组件注册会报错的问题 2022-06-01 22:14:33 +08:00
奔跑的面条
2e688ad686 build:升级依赖包 2022-06-01 22:13:49 +08:00
奔跑的面条
9b998e0c6d fix: 解决列表图片展示缓存问题 2022-06-01 20:16:38 +08:00
奔跑的面条
1045588301 fix: 解决获取数据,但是配置模块不完整的问题 2022-06-01 19:01:05 +08:00
奔跑的面条
ba86399fd3 build: 修改请求地址 2022-05-31 11:19:17 +08:00
奔跑的面条
a89164f885 fix: 修改oss接口不会动态更改的问题 2022-05-31 11:18:34 +08:00
奔跑的面条
7f2344c82c chore:修改请求地址 2022-05-29 16:04:07 +08:00
奔跑的面条
3c8e430533 docs: 修改文档说明 2022-05-29 15:23:04 +08:00
奔跑的面条
f7209fba53 feat: 新增背景图文件上传保存 2022-05-29 14:54:35 +08:00
奔跑的面条
9fae683d8b chore: 去除生成预览图时的标尺 2022-05-28 17:58:07 +08:00
奔跑的面条
79a2b98a1a feat: 新增首页预览图展示 2022-05-28 16:39:27 +08:00
奔跑的面条
98b28a631a Merge branch 'dev' into master-fetch 2022-05-28 15:58:06 +08:00
奔跑的面条
6fec64f515 fix: 修改自动保存预览图无法存储的问题 2022-05-28 15:46:07 +08:00
奔跑的面条
01d5890b35 branch: 合并锚点样式修改 2022-05-28 12:46:32 +08:00
奔跑的面条
fea583eb5b feat: 保存预览图 2022-05-28 11:50:17 +08:00
奔跑的面条
bfe5039a1c fix: 解决打包后无法发送请求的bug 2022-05-28 00:32:32 +08:00
奔跑的面条
7a57d944c8 feat: 新增上传文件接口 2022-05-27 20:09:48 +08:00
奔跑的面条
ca27e87241 chore: 修改项目信息结构 2022-05-27 11:49:25 +08:00
奔跑的面条
e674a1ece4 feat: 新增项目信息修改功能 2022-05-26 01:01:59 +08:00
奔跑的面条
dfb63346d3 fix: 修改导入组件id会重复的问题 2022-05-25 23:00:36 +08:00
奔跑的面条
4d899d48dc feat: 新增快捷键展示 2022-05-24 18:17:41 +08:00
奔跑的面条
fa3a3dfcb0 faet: 新增保存快捷键 2022-05-24 18:16:33 +08:00
奔跑的面条
e36210aa27 feat: 新增自动同步功能 2022-05-24 17:42:49 +08:00
奔跑的面条
20a599594c feat: 新增数据保存接口 2022-05-24 15:05:51 +08:00
奔跑的面条
70f8dbae53 Merge branch 'dev' into master-fetch 2022-05-24 12:28:14 +08:00
奔跑的面条
d8022b2682 feat: 新增获取项目数据功能,新增同步数据功能 2022-05-23 23:50:35 +08:00
奔跑的面条
0d7c5b8ace Merge branch 'dev' into master-fetch 2022-05-23 16:06:13 +08:00
奔跑的面条
88c9850c44 Merge branch 'dev' into master-fetch 2022-05-22 23:27:39 +08:00
奔跑的面条
f1ed62cdca feat: 新增获取项目数据接口 2022-05-22 22:11:56 +08:00
奔跑的面条
bfac86d5dd feat:新增发布和取消发布接口 2022-05-22 16:38:22 +08:00
奔跑的面条
341015c584 style: 调整代码格式,去除多余代码 2022-05-22 15:39:30 +08:00
奔跑的面条
7c5a66978e feat: 新增删除接口 2022-05-22 15:25:07 +08:00
奔跑的面条
b21fc3f5e7 feat: 新增首页列表接口 2022-05-22 15:06:45 +08:00
奔跑的面条
0e52628842 Merge branch 'dev' of https://gitee.com/MTrun/go-view into master-fetch 2022-05-22 14:09:38 +08:00
奔跑的面条
f7922cafa5 feat: 新增项目列表接口 2022-05-22 14:05:57 +08:00
奔跑的面条
8c5496829e Merge branch 'dev' of https://gitee.com/MTrun/go-view into master-fetch 2022-05-22 13:36:30 +08:00
奔跑的面条
8514f051a7 fix: 修改新建项目id错误问题 2022-05-21 21:04:10 +08:00
奔跑的面条
61feb29fe2 fix: 修改i18n错误提示 2022-05-21 19:46:44 +08:00
奔跑的面条
fa29881f04 chore: 修改提示内容 2022-05-21 18:04:52 +08:00
奔跑的面条
46cb8e7d0b feat: 新增创建接口,修改i8n部分内容 2022-05-21 18:03:15 +08:00
奔跑的面条
b6143bc75e feat: 新增退出登录接口,新增全局接口封装,修改登录接口内容 2022-05-21 17:31:01 +08:00
奔跑的面条
92e1ec05d2 Merge branch 'master' of https://gitee.com/MTrun/go-view into master-fetch 2022-05-21 14:01:53 +08:00
奔跑的面条
75f23bb1bf Merge branch 'dev' into master-fetch 2022-05-21 13:44:25 +08:00
奔跑的面条
5f5731f813 feat: 新增登录接口请求 2022-05-20 16:12:27 +08:00
奔跑的面条
55159be0dc fix: 修改plop的问题 2022-05-20 16:12:09 +08:00
奔跑的面条
0c4e1dc7ae Merge branch 'dev' of https://gitee.com/MTrun/go-view into master-fetch 2022-05-20 10:25:46 +08:00
奔跑的面条
b4abdeb246 update README.md. 2022-05-19 01:56:30 +00:00
奔跑的面条
7e61dda4aa update README.md. 2022-05-16 10:38:39 +00:00
奔跑的面条
283aafb27d update README.md. 2022-05-16 04:23:10 +00:00
129 changed files with 3992 additions and 770 deletions

3
.commitlintrc.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ["@commitlint/config-conventional"]
};

12
.env
View File

@@ -1,14 +1,8 @@
# port
VITE_DEV_PORT = '8001'
VITE_DEV_PORT = '8080'
# development path
VITE_DEV_PATH = '/'
VITE_DEV_PATH = 'http://1.117.240.165:8080'
# production path
VITE_PRO_PATH = '/'
# spa-title
VITE_GLOB_APP_TITLE = GoView
# spa shortname
VITE_GLOB_APP_SHORT_NAME = GoView
VITE_PRO_PATH = 'http://1.117.240.165:8080'

1
.gitignore vendored
View File

@@ -2,4 +2,3 @@ node_modules
.DS_Store
dist
dist-ssr
*.local

4
.husky/commit-msg Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint -e

View File

@@ -6,15 +6,20 @@ dev:
dist:
npm run build
view:
npm run preview
lint:
npm run lint
new:
npm run new
help:
@echo " make dev [npm run dev] 开发模式"
@echo " make dist [npm run build] 编译模式"
@echo " make view [npm run preview] 预览打包文件"
@echo " make new [npm run lint] 通过自动化流程创建代码"
@echo " make lint [npm run new] 格式校验"

167
README.md
View File

@@ -1,105 +1,118 @@
## 总览
![logo](readme/logo-t-y.png)
GoView 是一个高效的拖拽式低代码数据可视化开发平台,将图表或页面元素封装为基础组件,无需编写代码即可制作数据大屏,减少心智负担。当然低代码也不是 “银弹”,希望所有人员都能理智看待此技术。
**`master-fetch` 分支是带有后端接口请求的分支**
项目-Demo 地址:[https://www.mtruning.club](https://www.mtruning.club)
**后端项目地址:[https://gitee.com/MTrun/go-view-serve](https://gitee.com/MTrun/go-view-serve)**
文档-在线地址:[http://www.mtruning.club:81/](http://www.mtruning.club:81/)
**接口说明地址:[https://docs.apipost.cn/preview/5aa85d10a59d66ce/ddb813732007ad2b?target_id=84dbc5b0-158f-4bcb-8f74-793ac604ada3#3e053622-1e76-43f9-a039-756aee822dbb](https://docs.apipost.cn/preview/5aa85d10a59d66ce/ddb813732007ad2b?target_id=84dbc5b0-158f-4bcb-8f74-793ac604ada3#3e053622-1e76-43f9-a039-756aee822dbb)**
文档-源码地址:[https://gitee.com/MTrun/go-view-doc](https://gitee.com/MTrun/go-view-doc)
## 使用
技术点:
所有的接口地址位置:`src\api\path\*`
- 框架:基于 `Vue3` 框架编写,使用 `hooks` 写法抽离部分逻辑,使代码结构更加清晰;
- 类型:使用 `TypeScript` 进行类型约束,减少未知错误发生概率,可以大胆修改逻辑内容;
- 性能:多处性能优化,使用页面懒加载、组件动态注册、数据滚动加载等方式,提升页面渲染速度;
- 存储:拥有本地记忆,部分配置项采用 `storage` 存储本地,提升使用体验;
- 封装:项目进行了详细的工具类封装如:路由、存储、加/解密、文件处理、主题、NaiveUI 全局方法、组件等
项目截图:
![项目截图](readme/goView-canvas.png)
主要技术栈为:
| 名称 | 版本 | 名称 | 版本 |
| ------------------- | ----- | ----------- | ------ |
| Vue | 3.2.x | TypeScript4 | 4.6.x |
| Vite | 2.9.x | NaiveUI | 2.27.x |
| ECharts | 5.3.x | Pinia | 2.0.x |
| 详见 `package.json` | 😁 | 🥰 | 🤗 |
开发环境:
| 名称 | 版本 | 名称 | 版本 |
| ---- | ------- | ------- | ----- |
| node | 16.14.x | npm | 8.5.x |
| pnpm | 6.32.x | windows | 11 |
已完成图表:
| 分类 | 名称 | 名称 | 名称 |
| ------ | ---------------- | ---------- | ------ |
| 图表 | 柱状图 | 横向柱状图 | 折线图 |
| \* | 单/多 折线面积图 | 饼图 | 水球图 |
| 信息 | 文字 | 图片 | 😶 |
| 列表 | 滚动排名列表 | 🤠 | 🤓 |
| 小组件 | 边框-01~13 | 装饰-01~05 | 数字翻牌 |
## 浏览器支持
开发和测试平台均在 `Google` 和最新版 `EDGE` 上完成,暂未测试 `IE11` 等其它浏览器,如有需求请自行测试与兼容。
## 安装
本项目采用` pnpm` 进行包管理,经反馈若采用 `npm/yarn` 等方式安装依赖会导致错误,请使用 `pnpm` 安装依赖包。
接口地址修改:`.env`
```shell
#pnpm建议使用nrm切换到淘宝源
pnpm install
# port
VITE_DEV_PORT = '8080'
# development path
VITE_DEV_PATH = 'http://127.0.0.1:8080'
# production path
VITE_PRO_PATH = 'http://127.0.0.1:8080'
```
## 启动
公共前缀修改:`src\settings\httpSetting.ts`
```shell
#pnpm
pnpm dev
# npm
npm run dev
#yarn
yarn dev
#Makefile
make dev
// 请求前缀
export const axiosPre = '/goview'
```
## 编译
接口封装:`src\api\http.ts`
```shell
#pnpm
pnpm run build
```ts
import axiosInstance from './axios'
import { RequestHttpEnum, ContentTypeEnum } from '@/enums/httpEnum'
# npm
npm run build
export const get = (url: string, params?: object) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.GET,
params: params,
})
}
#yarn
yarn run build
export const post = (url: string, data?: object, headersType?: string) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.POST,
data: data,
headers: {
'Content-Type': headersType || ContentTypeEnum.JSON
}
})
}
#Makefile
make dist
export const put = (url: string, data?: object, headersType?: string) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.PUT,
data: data,
headers: {
'Content-Type': headersType || ContentTypeEnum.JSON
}
})
}
export const del = (url: string, params?: object) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.DELETE,
params
})
}
// 获取请求函数默认get
export const http = (type?: RequestHttpEnum) => {
switch (type) {
case RequestHttpEnum.GET:
return get
case RequestHttpEnum.POST:
return post
case RequestHttpEnum.PUT:
return put
case RequestHttpEnum.DELETE:
return del
default:
return get
}
}
```
## 代码提交
* feat: 新功能
* fix: 修复 Bug
* docs: 文档修改
* perf: 性能优化
* revert: 版本回退
* ci: CICD集成相关
* test: 添加测试代码
* refactor: 代码重构
* build: 影响项目构建或依赖修改
* style: 不影响程序逻辑的代码修改
* chore: 不属于以上类型的其他类型(日常事务)
## 交流
QQ 群1030129384
![QQ群](readme/goView-QQ.png)

View File

@@ -1,4 +1,5 @@
export const OUTPUT_DIR = 'dist';
// 打包路径
export const OUTPUT_DIR = 'dist'
// chunk 警告大小
export const chunkSizeWarningLimit = 2000

View File

@@ -1,9 +0,0 @@
/**
* Get the configuration file variable name
* @param env
*/
export const getConfigFileName = (env: Record<string, any>) => {
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
.toUpperCase()
.replace(/\s/g, '');
};

View File

@@ -1,10 +1,12 @@
{
"name": "go-view",
"version": "1.0.2",
"version": "2.0.1",
"scripts": {
"dev": "vite --host",
"build": "vue-tsc --noEmit && vite build",
"new": "plop --plopfile ./plop/plopfile.js"
"preview": "vite preview",
"new": "plop --plopfile ./plop/plopfile.js",
"postinstall": "husky install"
},
"dependencies": {
"@types/color": "^3.0.3",
@@ -18,17 +20,20 @@
"highlight.js": "^11.5.0",
"html2canvas": "^1.4.1",
"keymaster": "^1.6.2",
"naive-ui": "^2.29.0",
"naive-ui": "^2.30.3",
"pinia": "^2.0.13",
"screenfull": "^6.0.1",
"vue": "^3.2.31",
"vue-i18n": "9.1.9",
"vue-demi": "^0.13.1",
"vue-i18n": "9.1.10",
"vue-router": "4.0.12",
"vue3-lazyload": "^0.2.5-beta",
"vue3-sketch-ruler": "^1.3.3",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@commitlint/cli": "^17.0.2",
"@commitlint/config-conventional": "^17.0.2",
"@types/node": "^16.11.26",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
@@ -38,6 +43,7 @@
"@vitejs/plugin-vue-jsx": "^1.3.9",
"@vue/compiler-sfc": "^3.2.31",
"@vueuse/core": "^7.7.1",
"commitlint": "^17.0.2",
"default-passive-events": "^2.0.0",
"echarts": "^5.3.2",
"eslint": "^8.12.0",
@@ -45,6 +51,7 @@
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.5.0",
"husky": "^8.0.1",
"lodash": "~4.17.21",
"mockjs": "^1.1.0",
"plop": "^3.0.5",

1036
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
module.exports = {
printWidth: 80,
printWidth: 120,
tabWidth: 2,
useTabs: false,
singleQuote: true,
semi: false,
trailingComma: "es5",
trailingComma: "none",
bracketSpacing: true,
jsxSingleQuote: true,
jsxBracketSameLine: false,

View File

@@ -18,7 +18,7 @@ import { zhCN, dateZhCN, NConfigProvider } from 'naive-ui'
import { GoAppProvider } from '@/components/GoAppProvider'
import { I18n } from '@/components/I18n'
import { useDarkThemeHook, useThemeOverridesHook, useCode } from '@/hooks'
import { useSystemInit, useDarkThemeHook, useThemeOverridesHook, useCode } from '@/hooks'
// 暗黑主题
const darkTheme = useDarkThemeHook()
@@ -28,4 +28,7 @@ const overridesTheme = useThemeOverridesHook()
// 代码主题
const hljsTheme = useCode()
// 系统全局数据初始化
useSystemInit()
</script>

14
src/api/axios.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import { ModuleTypeEnum } from '@/enums/httpEnum'
// 接口白名单(免登录)
export const fetchAllowList = [
// 登录
`${ModuleTypeEnum.SYSTEM}/login`,
// 获取 OSS 接口
`${ModuleTypeEnum.SYSTEM}/getOssInfo`,
// 预览获取数据
`${ModuleTypeEnum.PROJECT}/getData`,
]
// 接口黑名单
export const fetchBlockList = []

View File

@@ -1,20 +1,36 @@
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
import { ResultEnum } from "@/enums/httpEnum"
import { ErrorPageNameMap } from "@/enums/pageEnum"
import { redirectErrorPage } from '@/utils'
import { ResultEnum, RequestHttpHeaderEnum } from "@/enums/httpEnum"
import { PageEnum, ErrorPageNameMap } 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, httpErrorHandle } from '@/utils'
import { fetchAllowList } from './axios.config'
import includes from 'lodash/includes'
const axiosInstance = axios.create({
baseURL: import.meta.env.DEV ? import.meta.env.VITE_DEV_PATH : import.meta.env.VITE_PRO_PATH,
baseURL: `${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}${axiosPre}`,
timeout: ResultEnum.TIMEOUT,
})
axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => {
config.headers = {}
// 白名单校验
if (includes(fetchAllowList, config.url)) return config
// 获取 token
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
// 重新登录
if (!info) {
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
return config
}
config.headers = {
[RequestHttpHeaderEnum.TOKEN]: info[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_TOKEN] || ''
}
return config
},
(error: AxiosRequestConfig) => {
Promise.reject(error)
(err: AxiosRequestConfig) => {
Promise.reject(err)
}
)
@@ -22,13 +38,31 @@ axiosInstance.interceptors.request.use(
axiosInstance.interceptors.response.use(
(res: AxiosResponse) => {
const { code } = res.data as { code: number }
if (code === ResultEnum.DATA_SUCCESS) return Promise.resolve(res.data)
// 重定向
if (ErrorPageNameMap.get(code)) redirectErrorPage(code)
// 成功
if (code === ResultEnum.SUCCESS) {
return Promise.resolve(res.data)
}
// 登录过期
if (code === ResultEnum.TOKEN_OVERDUE) {
window['$message'].error(window['$t']('http.token_overdue_message'))
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
return Promise.resolve(res.data)
}
// 固定错误码重定向
if (ErrorPageNameMap.get(code)) {
redirectErrorPage(code)
return Promise.resolve(res.data)
}
// 提示错误
window['$message'].error(window['$t']((res.data as any).msg))
return Promise.resolve(res.data)
},
(err: AxiosResponse) => {
window['$message'].error('接口异常,请检查!')
httpErrorHandle()
Promise.reject(err)
}
)

View File

@@ -1,25 +1,26 @@
import axiosInstance from './axios'
import { RequestHttpEnum, ContentTypeEnum } from '@/enums/httpEnum'
export const get = (url: string) => {
export const get = (url: string, params?: object) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.GET,
params: params,
})
}
export const post = (url: string, params: object, headersType?: string) => {
export const post = (url: string, data?: object, headersType?: string) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.POST,
data: params,
data: data,
headers: {
'Content-Type': headersType || ContentTypeEnum.JSON
}
})
}
export const put = (url: string, data?: object, headersType?: string) => {
export const put = (url: string, data?: object, headersType?: ContentTypeEnum) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.PUT,
@@ -30,7 +31,7 @@ export const put = (url: string, data?: object, headersType?: string) => {
})
}
export const del = (url: string, params: object) => {
export const del = (url: string, params?: object) => {
return axiosInstance({
url: url,
method: RequestHttpEnum.DELETE,

2
src/api/path/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from '@/api/path/project.api'
export * from '@/api/path/system.api'

View File

@@ -0,0 +1,84 @@
import { http } from '@/api/http'
import { httpErrorHandle } from '@/utils'
import { ContentTypeEnum, RequestHttpEnum, ModuleTypeEnum } from '@/enums/httpEnum'
// * 项目列表
export const projectListApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.GET)(`${ModuleTypeEnum.PROJECT}/list`, data);
return res;
} catch {
httpErrorHandle();
}
}
// * 新增项目
export const createProjectApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.POST)(`${ModuleTypeEnum.PROJECT}/create`, data);
return res;
} catch {
httpErrorHandle();
}
}
// * 获取项目
export const fetchProjectApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.GET)(`${ModuleTypeEnum.PROJECT}/getData`, data);
return res;
} catch {
httpErrorHandle();
}
}
// * 保存项目
export const saveProjectApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.POST)(`${ModuleTypeEnum.PROJECT}/save/data`, data, ContentTypeEnum.FORM_URLENCODED);
return res;
} catch {
httpErrorHandle();
}
}
// * 修改项目基础信息
export const updateProjectApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.POST)(`${ModuleTypeEnum.PROJECT}/edit`, data);
return res;
} catch {
httpErrorHandle();
}
}
// * 删除项目
export const deleteProjectApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.DELETE)(`${ModuleTypeEnum.PROJECT}/delete`, data);
return res;
} catch {
httpErrorHandle();
}
}
// * 修改发布状态 [-1未发布,1发布]
export const changeProjectReleaseApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.PUT)(`${ModuleTypeEnum.PROJECT}/publish`, data);
return res;
} catch {
httpErrorHandle();
}
}
// * 上传文件
export const uploadFile = async (url:string, data: object) => {
try {
const res = await http(RequestHttpEnum.POST)(url, data, ContentTypeEnum.FORM_DATA);
return res;
} catch {
httpErrorHandle();
}
}

View File

@@ -0,0 +1,42 @@
import { http } from '@/api/http'
import { httpErrorHandle } from '@/utils'
import { RequestHttpEnum, ModuleTypeEnum } from '@/enums/httpEnum'
// * 登录
export const loginApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.POST)(`${ModuleTypeEnum.SYSTEM}/login`, data);
return res;
} catch(err) {
httpErrorHandle();
}
}
// * 新接口
export const newApi = async (data: object) => {
try {
const resonse = await http(RequestHttpEnum.POST)(`新接口的路劲/xxx/xxx`, data)
} catch (error) {
httpErrorHandle();
}
}
// * 登出
export const logoutApi = async () => {
try {
const res = await http(RequestHttpEnum.GET)(`${ModuleTypeEnum.SYSTEM}/logout`);
return res;
} catch(err) {
httpErrorHandle();
}
}
// * 获取 oss 上传接口
export const ossUrlApi = async (data: object) => {
try {
const res = await http(RequestHttpEnum.GET)(`${ModuleTypeEnum.SYSTEM}/getOssInfo`, data);
return res;
} catch(err) {
httpErrorHandle();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -19,7 +19,8 @@ export enum MenuEnum {
DOWN = 'down',
CLEAR = 'clear',
BACK = 'back',
FORWORD = 'forward'
FORWORD = 'forward',
SAVE = 'save'
}
// Win 键盘枚举
@@ -35,4 +36,16 @@ export enum MacKeyboard {
CTRL = '⌘',
SHIFT = '⇧',
ALT = '⌥',
}
// 同步状态枚举
export enum SyncEnum {
// 等待
PENDING,
// 开始
START,
// 成功
SUCCESS,
// 失败
FAILURE
}

View File

@@ -1,12 +1,17 @@
/**
* @description: 请求结果集
*/
// 模块 Path 前缀分类
export enum ModuleTypeEnum {
SYSTEM = 'sys',
PROJECT = 'project',
}
// 请求结果集
export enum ResultEnum {
DATA_SUCCESS = 0,
SUCCESS = 200,
SERVER_ERROR = 500,
SERVER_FORBIDDEN = 403,
NOT_FOUND = 404,
TOKEN_OVERDUE = 886,
TIMEOUT = 10042,
}
@@ -18,9 +23,13 @@ export enum RequestDataTypeEnum {
AJAX = 1,
}
/**
* @description: 请求方法
*/
// 头部
export enum RequestHttpHeaderEnum {
TOKEN = 'Token',
COOKIE = 'Cookie'
}
// 请求方法
export enum RequestHttpEnum {
GET = 'get',
POST = 'post',
@@ -29,16 +38,14 @@ export enum RequestHttpEnum {
DELETE = 'delete',
}
/**
* @description: 常用的contentTyp类型
*/
// 常用的contentTyp类型
export enum ContentTypeEnum {
// json
JSON = 'application/json;charset=UTF-8',
// json
TEXT = 'text/plain;charset=UTF-8',
JSON = 'application/json; charset=UTF-8',
// text
TEXT = 'text/plain; charset=UTF-8',
// form-data 一般配合qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
FORM_URLENCODED = 'application/x-www-form-urlencoded; charset=UTF-8',
// form-data 上传
FORM_DATA = 'multipart/form-data;charset=UTF-8',
FORM_DATA = 'multipart/form-data; charset=UTF-8',
}

View File

@@ -20,10 +20,15 @@ export enum PageEnum {
//重定向
REDIRECT = '/redirect',
REDIRECT_NAME = 'Redirect',
// 未发布
REDIRECT_UN_PUBLISH = '/redirect/unPublish',
REDIRECT_UN_PUBLISH_NAME = 'redirect-un-publish',
// 重载
RELOAD = '/reload',
RELOAD_NAME = 'Reload',
// 首页
BASE_HOME = '/project',
BASE_HOME_NAME = 'Project',

View File

@@ -1,10 +1,8 @@
export enum StorageEnum {
// 全局设置
GO_SYSTEM_SETTING_STORE = 'GO_SYSTEM_SETTING',
// token 等信息
GO_ACCESS_TOKEN_STORE = 'GO_ACCESS_TOKEN',
GO_SETTING_STORE = 'GO_SETTING',
// 登录信息
GO_LOGIN_INFO_STORE = 'GO_LOGIN_INFO',
GO_SYSTEM_STORE = 'GO_SYSTEM',
// 语言
GO_LANG_STORE = 'GO_LANG',
// 当前选择的主题

View File

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

View File

@@ -43,7 +43,7 @@ export const useChartDataFetch = (
if (!completePath) return
fetchInterval = setInterval(async () => {
const res:any = await http(requestHttpType.value)(completePath || '', {})
const res = await http(requestHttpType.value)(completePath || '', {}) as unknown as MyResponseType
if (res.data) {
// 是否是 Echarts 组件
const isECharts =

View File

@@ -0,0 +1,23 @@
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { SystemStoreEnum } from '@/store/modules/systemStore/systemStore.d'
import { ResultEnum } from '@/enums/httpEnum'
import { ossUrlApi } from '@/api/path/'
// * 初始化
export const useSystemInit = async () => {
const systemStore = useSystemStore()
// 获取 OSS 信息
const getOssUrl = async () => {
const res = await ossUrlApi({}) as unknown as MyResponseType
if (res.code === ResultEnum.SUCCESS) {
systemStore.setItem(SystemStoreEnum.FETCH_INFO, {
OSSUrl: res.data?.bucketURL
})
}
}
// 执行
getOssUrl()
}

View File

@@ -11,6 +11,8 @@ const global = {
help: 'Help',
contact: 'Contact Us',
logout: 'Logout',
logout_success: 'Logout success',
logout_failure: 'Logout Failed',
// system setting
sys_set: 'System Setting',
lang_set: 'Language Setting',
@@ -26,8 +28,14 @@ const global = {
r_more: 'More',
}
const http = {
error_message: 'The interface is abnormal, please check the interface!',
token_overdue_message: 'Login expired, please log in again!'
}
export default {
global,
http,
login,
project
}

View File

@@ -2,6 +2,6 @@ export default {
desc: "Login",
form_auto: "Sign in automatically",
form_button: "Login",
login_success: "Login success",
login_message: "Please complete the letter",
login_success: "Login success!",
login_message: "Please complete the letter!",
}

View File

@@ -1,6 +1,8 @@
export default {
create_btn: 'Creat',
create_tip: 'Please select a content for development',
create_success: 'Creat Success!',
create_failure: 'Failed to create, please try again later',
create_tip: 'Please select a content for development!',
project: 'Project',
my: 'My',
new_project: 'New Project',

View File

@@ -11,6 +11,8 @@ const global = {
help: '帮助中心',
contact: '联系我们',
logout: '退出登录',
logout_success: '退出成功!',
logout_failure: '退出失败!',
// 系统设置
sys_set: '系统设置',
lang_set: '语言设置',
@@ -18,16 +20,27 @@ const global = {
r_edit: '编辑',
r_preview: '预览',
r_copy: '克隆',
r_copy_success: '克隆成功!',
r_rename: '重命名',
r_rename_success: '重命名成功!',
r_publish: '发布',
r_publish_success: '成功发布!',
r_unpublish: '取消发布',
r_unpublish_success: '取消成功!',
r_download: '下载',
r_delete: '删除',
r_delete_success: '删除成功!',
r_more: '更多',
}
const http = {
error_message: '获取数据失败,请稍后重试!',
token_overdue_message: '登录过期,请重新登录!'
}
export default {
global,
http,
login,
project
}

View File

@@ -2,6 +2,6 @@ export default {
desc: "登录",
form_auto: "自动登录",
form_button: "登录",
login_success: "登录成功",
login_message: "请填写完整信息",
login_success: "登录成功!",
}

View File

@@ -1,6 +1,8 @@
export default {
// aside
create_btn: '新建',
create_success: '新建成功!',
create_failure: '新建失败,请稍后重试!',
create_tip: '从哪里出发好呢?',
project: '项目',
my: '我的',

View File

@@ -21,7 +21,6 @@ const option = {
xAxis: {
show: true,
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
show: true,

View File

@@ -0,0 +1,47 @@
import { publicConfig } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { ProcessConfig } from './index'
import { chartInitConfig } from '@/settings/designSetting'
import cloneDeep from 'lodash/cloneDeep'
export const types = [
{
label: '线形',
value: 'line'
},
{
label: '圆形',
value: 'circle'
},
{
label: '仪表盘',
value: 'dashboard'
},
]
export const indicatorPlacements = [
{
label: '内部',
value: 'inside'
},
{
label: '外部',
value: 'outside'
}
]
export const option = {
dataset: 36,
type: types[2].value,
color: '#4992FFFF',
// 指标位置(线条时可用)
indicatorPlacement: "outside"
}
export default class Config extends publicConfig implements CreateComponentType {
public key = ProcessConfig.key
public attr = {...chartInitConfig, h: 500, zIndex: -1}
public chartConfig = cloneDeep(ProcessConfig)
public option = cloneDeep(option)
}

View File

@@ -0,0 +1,49 @@
<template>
<!-- 默认展开 -->
<CollapseItem
name="进度条" :expanded="true">
<SettingItemBox name="内容">
<SettingItem name="数值">
<!-- config.ts 里的 option 对应 --><!-- n-input-number NaiveUI 的控件 -->
<n-input-number v-model:value="optionData.dataset" size="small" :min="0" placeholder="进度值"></n-input-number>
</SettingItem>
<!-- 颜色粗细等等... -->
</SettingItemBox>
<SettingItemBox name="外观">
<SettingItem name="形状">
<n-select v-model:value="optionData.type" :options="types" placeholder="选择形状" />
</SettingItem>
<SettingItem name="指标位置" v-if="optionData.type == types[0].value">
<n-select v-model:value="optionData.indicatorPlacement" :options="indicatorPlacements" placeholder="选择形状" />
</SettingItem>
<!-- 颜色粗细等等... -->
<SettingItem name="进度条颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="optionData.color"
></n-color-picker>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
// 以下是封装的设置模块布局组件,具体效果可在官网查看
import {
CollapseItem,
SettingItemBox,
SettingItem,
} from '@/components/Pages/ChartItemSetting'
// 获取 option 的数据,便于使用 typeof 获取类型
import { option, types, indicatorPlacements} from './config'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true
}
})
</script>

View File

@@ -0,0 +1,25 @@
// 展示图片
import image from '@/assets/images/chart/charts/process.png'
// 公共类型声明
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
// 当前[信息模块]分类声明
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
export const ProcessConfig: ConfigType = {
// 唯一key
key: 'Process',
// 图表组件渲染 Components 格式: V + key
chartKey: 'VProcess',
// 配置组件渲染 Components 格式: VC + key
conKey: 'VCProcess',
// 名称
title: 'NaiveUI-进度',
// 子分类目录
category: ChatCategoryEnum.MORE,
// 子分类目录
categoryName: ChatCategoryEnumName.MORE,
// 包分类
package: PackagesCategoryEnum.CHARTS,
// 图片
image: image
}

View File

@@ -0,0 +1,27 @@
<template>
<n-progress
:type="type"
:percentage="dataset"
:indicator-placement="indicatorPlacement"
:color="color"
/>
</template>
<script setup lang="ts">
import { PropType, toRefs, watch } from 'vue'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import config from './config'
const props = defineProps({
chartConfig: {
type: Object as PropType<config>,
required: true,
},
})
// 取配置数据
const { type, color, indicatorPlacement, dataset } = toRefs(props.chartConfig.option)
useChartDataFetch(props.chartConfig, useChartEditStore)
</script>

View File

@@ -3,6 +3,37 @@ import { CreateComponentType } from '@/packages/index.d'
import { WaterPoloConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
export const shapes = [
{
label: '圆形',
value: 'circle'
},
{
label: '正方形',
value: 'rect'
},
{
label: '带圆角的正方形',
value: 'roundRect'
},
{
label: '正三角形',
value: 'triangle'
},
{
label: '菱形',
value: 'diamond'
},
{
label: '水滴',
value: 'pin'
},
{
label: '箭头',
value: 'arrow'
},
]
export const includes = []
export const option = {
@@ -10,6 +41,7 @@ export const option = {
series: [
{
type: 'liquidFill',
shape: shapes[0].value,
radius: '90%',
data: [0],
center: ['50%', '50%'],

View File

@@ -15,19 +15,26 @@
placeholder="水球数值"
></n-input-number>
</SettingItem>
<SettingItem name="形状">
<n-select v-model:value="item.shape" :options="shapes" placeholder="选择形状" />
</SettingItem>
<SettingItem name="文本">
<n-input-number v-model:value="item.label.normal.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="文字大小">
</n-input-number>
</SettingItem>
<SettingItem name="颜色1">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.color[0].colorStops[0].color"
></n-color-picker>
></n-color-picker>
</SettingItem>
<SettingItem name="颜色2">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.color[0].colorStops[1].color"
></n-color-picker>
></n-color-picker>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="背景" :alone="true">
@@ -44,7 +51,7 @@
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { option } from './config'
import { option, shapes } from './config'
import {
CollapseItem,
SettingItemBox,

View File

@@ -1,3 +1,4 @@
import { ProcessConfig } from './Process/index'
import { RadarConfig } from './Radar/index'
import { FunnelConfig } from './Funnel/index'
import { HeatmapConfig } from './Heatmap/index'
@@ -5,4 +6,4 @@ import { PointConfig } from './Point/index'
import { WaterPoloConfig } from './WaterPolo/index'
import { TreeMapConfig } from './TreeMap/index'
export default [RadarConfig, FunnelConfig, HeatmapConfig,PointConfig, WaterPoloConfig, TreeMapConfig]
export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, PointConfig, WaterPoloConfig, TreeMapConfig]

View File

@@ -0,0 +1,63 @@
import { echartOptionProfixHandle, publicConfig } from '@/packages/public'
import { PieCircleConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
export const includes = ['legend']
const option = {
tooltip: {
show: true,
trigger: 'item'
},
legend: {
show: true,
},
dataset: 0.25,
title: {
text: 25 + "%",
x: "center",
y: "center",
textStyle: {
color: "#56B9F8",
fontSize: 30
}
},
series: [
{
type: "pie",
radius: ["75%", "80%"],
center: ["50%", "50%"],
hoverAnimation: true,
color: ["#00bcd44a", "transparent"],
label: {
show: false
},
data: [
{
value: [25],
itemStyle: {
color: "#03a9f4",
shadowBlur: 10,
shadowColor:"#97e2f5"
}
},
{
value: [75],
itemStyle: {
color: "#00bcd44a",
shadowBlur: 0,
shadowColor:"#00bcd44a"
}
}
]
},
]
}
export default class Config extends publicConfig implements CreateComponentType {
public key: string = PieCircleConfig.key
public chartConfig = PieCircleConfig
// 图表配置项
public option = echartOptionProfixHandle(option, includes)
}

View File

@@ -0,0 +1,89 @@
<template>
<!-- 遍历 seriesList -->
<CollapseItem v-for="(item, index) in config.series" :key="index" :name="`圆环`" :expanded="true">
<SettingItemBox name="数据">
<SettingItem name="数值">
<n-input-number v-model:value="config.dataset" :min="0" :max="1" :step="0.01" size="small" placeholder="数值">
</n-input-number>
</SettingItem>
</SettingItemBox>
<!-- Echarts 全局设置 -->
<SettingItemBox name="进度条">
<SettingItem name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.data[0].itemStyle.color"
></n-color-picker>
</SettingItem>
<SettingItem name="阴影模糊等级">
<n-input-number v-model:value="item.data[0].itemStyle.shadowBlur" :min="0" :max="50" :step="1" size="small" placeholder="阴影模糊等级">
</n-input-number>
</SettingItem>
<SettingItem name="阴影颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.data[0].itemStyle.shadowColor"
></n-color-picker>
</SettingItem>
</SettingItemBox>
<!-- 中心标题 -->
<SettingItemBox v-if="config.title" name="标题">
<SettingItem name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="config.title.textStyle.color"
></n-color-picker>
</SettingItem>
<SettingItem name="字体大小">
<n-input-number v-model:value="config.title.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="字体大小">
</n-input-number>
</SettingItem>
</SettingItemBox>
<!-- 其他样式 -->
<SettingItemBox name="轨道样式">
<SettingItem name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.data[1].itemStyle.color"
></n-color-picker>
</SettingItem>
<SettingItem name="阴影模糊等级">
<n-input-number v-model:value="item.data[1].itemStyle.shadowBlur" :min="0" :step="1" size="small" placeholder="阴影模糊等级">
</n-input-number>
</SettingItem>
<SettingItem name="阴影颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.data[1].itemStyle.shadowColor"
></n-color-picker>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
// 以下是封装的设置模块布局组件,具体效果可在官网查看
import {
CollapseItem,
SettingItemBox,
SettingItem
} from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes'
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
}
})
const config = computed(() => {
return props.optionData
})
</script>

View File

@@ -0,0 +1,14 @@
import image from '@/assets/images/chart/charts/pie-circle.png'
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const PieCircleConfig: ConfigType = {
key: 'PieCircle',
chartKey: 'VPieCircle',
conKey: 'VCPieCircle',
title: '饼图-环形',
category: ChatCategoryEnum.PIE,
categoryName: ChatCategoryEnumName.PIE,
package: PackagesCategoryEnum.CHARTS,
image
}

View File

@@ -0,0 +1,71 @@
<template>
<v-chart ref="vChartRef" :theme="themeColor" :option="option.value" :manual-update="isPreview()" autoresize></v-chart>
</template>
<script setup lang="ts">
import {computed, PropType, reactive, watch} from 'vue'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart } from 'echarts/charts'
import { mergeTheme } from '@/packages/public/chart'
import config, { includes } from './config'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { isPreview } from '@/utils'
import {
DatasetComponent,
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
} from 'echarts/components'
const props = defineProps({
themeSetting: {
type: Object,
required: true
},
themeColor: {
type: Object,
required: true
},
chartConfig: {
type: Object as PropType<config>,
required: true
}
})
use([
DatasetComponent,
CanvasRenderer,
PieChart,
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent
])
const option = reactive({
value: {}
})
watch(
() => props.chartConfig.option.dataset,
(newData) => {
// console.log('update:'+newData)
const d = parseFloat(`${newData}`) * 100
let config = props.chartConfig.option
config.title.text = d.toFixed(2) + "%"
config.series[0].data[0].value[0] = d
config.series[0].data[1].value[0] = 100 - d
option.value = mergeTheme(config, props.themeSetting, includes)
option.value = config
},
{
immediate: true,
}
)
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore)
</script>

View File

@@ -1,3 +1,4 @@
import { PieCommonConfig } from './PieCommon/index'
import { PieCircleConfig } from './PieCircle/index'
export default [PieCommonConfig]
export default [PieCommonConfig, PieCircleConfig]

View File

@@ -4,4 +4,4 @@ import Lines from './Lines'
import Mores from './Mores'
import Maps from './Maps'
export const ChartList = [...Bars, ...Pies, ...Lines, ...Maps , ...Mores]
export const ChartList = [...Bars, ...Pies, ...Lines, ...Maps, ...Mores]

View File

@@ -2,7 +2,7 @@
<div class="go-border-box">
<svg :width="w" :height="h">
<defs>
<filter :id="filterId" h="150%" width="150%" x="-25%" y="-25%">
<filter :id="filterId" height="150%" width="150%" x="-25%" y="-25%">
<feMorphology
operator="dilate"
radius="2"

View File

@@ -7,6 +7,7 @@ export const option = {
from: 50000,
to: 100000,
dur: 3,
precision: 0,
showSeparator: true,
numberSize: 24,
numberColor: '#4a9ef8',

View File

@@ -28,6 +28,13 @@
<n-text>展示分割符</n-text>
</n-space>
</SettingItem>
<SettingItem name="精度">
<n-input-number
v-model:value="optionData.precision"
size="small"
:min="0"
></n-input-number>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="数值">

View File

@@ -6,12 +6,8 @@
</span>
</template>
<span :style="`color:${numberColor};font-size:${numberSize}px`">
<n-number-animation
:from="from"
:to="to"
:duration="dur * 1000"
:show-separator="showSeparator"
></n-number-animation>
<n-number-animation :from="option.from" :to="option.to" :duration="dur * 1000" :show-separator="showSeparator"
:precision="precision"></n-number-animation>
</span>
<template #suffix>
<span :style="`color:${suffixColor};font-size:${numberSize}px`">
@@ -22,8 +18,10 @@
</template>
<script setup lang="ts">
import { PropType, toRefs } from 'vue'
import { PropType, toRefs, ref, reactive, watch } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useChartDataFetch } from '@/hooks'
const props = defineProps({
chartConfig: {
@@ -31,20 +29,44 @@ const props = defineProps({
required: true,
},
})
const option = reactive({
from: 0,
to: 0,
})
const { w, h } = toRefs(props.chartConfig.attr)
const {
let {
dur,
showSeparator,
prefixText,
prefixColor,
suffixText,
suffixColor,
from,
to,
precision,
numberSize,
numberColor,
} = toRefs(props.chartConfig.option)
const updateNumber = (newData: number) => {
// 原来的目标值作为新的数字动画的起始值
option.from = option.to
option.to = newData
}
watch(
() => props.chartConfig.option.from,
() => {
option.from = props.chartConfig.option.from
}, { immediate: true }
)
watch(
() => props.chartConfig.option.to,
() => {
option.to = props.chartConfig.option.to
}, { immediate: true }
)
useChartDataFetch(props.chartConfig, useChartEditStore, updateNumber)
</script>
<style lang="scss" scoped>
@include go('decorates-number') {

View File

@@ -0,0 +1,20 @@
import { publicConfig } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { TextGradientConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
export const option = {
dataset: '我是渐变文本',
size: 20,
gradient: {
from: '#0000FFFF',
to: '#00FF00FF',
deg: 45
}
}
export default class Config extends publicConfig implements CreateComponentType {
public key = TextGradientConfig.key
public chartConfig = cloneDeep(TextGradientConfig)
public option = cloneDeep(option)
}

View File

@@ -0,0 +1,45 @@
<template>
<collapse-item name="信息" :expanded="true">
<setting-item-box name="文字" :alone="true">
<setting-item>
<n-input v-model:value="optionData.dataset" size="small"></n-input>
</setting-item>
</setting-item-box>
</collapse-item>
<collapse-item name="样式" :expanded="true">
<setting-item-box name="文字">
<setting-item name="字体大小">
<n-input-number v-model:value="optionData.size" size="small" placeholder="字体大小"></n-input-number>
</setting-item>
</setting-item-box>
<setting-item-box name="渐变色参数">
<setting-item name="起始值">
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.gradient.from"></n-color-picker>
</setting-item>
<setting-item name="结束值">
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.gradient.to"></n-color-picker>
</setting-item>
<setting-item name="偏移角度">
<n-input-number v-model:value="optionData.gradient.deg" size="small" placeholder="颜色旋转"></n-input-number>
</setting-item>
</setting-item-box>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { option } from './config'
import {
CollapseItem,
SettingItemBox,
SettingItem
} from '@/components/Pages/ChartItemSetting'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true
}
})
</script>

View File

@@ -0,0 +1,14 @@
import image from '@/assets/images/chart/informations/text_gradient.png'
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
export const TextGradientConfig: ConfigType = {
key: 'TextGradient',
chartKey: 'VTextGradient',
conKey: 'VCTextGradient',
title: '渐变文字',
category: ChatCategoryEnum.TEXT,
categoryName: ChatCategoryEnumName.TEXT,
package: PackagesCategoryEnum.INFORMATIONS,
image
}

View File

@@ -0,0 +1,52 @@
<template>
<div class="go-text-box">
<n-gradient-text :size="size" :gradient="gradient">
{{ option.dataset }}
</n-gradient-text>
</div>
</template>
<script setup lang="ts">
import { PropType, toRefs, reactive, watch } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true,
},
})
const option = reactive({
dataset: ''
})
const { w, h } = toRefs(props.chartConfig.attr)
const {
size,
gradient
} = toRefs(props.chartConfig.option)
const callback = (newData: string) => {
option.dataset = newData
}
watch(
() => props.chartConfig.option.dataset,
() => {
option.dataset = props.chartConfig.option.dataset
}, { immediate: true }
)
useChartDataFetch(props.chartConfig, useChartEditStore, callback)
</script>
<style lang="scss" scoped>
@include go('text-box') {
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

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

View File

@@ -0,0 +1,29 @@
import { publicConfig } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { TableScrollBoardConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const option = {
header: ['列1', '列2', '列3'],
dataset: dataJson,
index: true,
columnWidth: [30, 100, 100],
align: ['center','right','right','right'],
rowNum: 5,
waitTime: 2,
headerHeight: 35,
carousel: 'single',
headerBGC: '#00BAFF',
oddRowBGC: '#003B51',
evenRowBGC: '#0A2732',
}
export default class Config
extends publicConfig
implements CreateComponentType
{
public key = TableScrollBoardConfig.key
public chartConfig = cloneDeep(TableScrollBoardConfig)
public option = cloneDeep(option)
}

View File

@@ -0,0 +1,121 @@
<template>
<CollapseItem name="列表" :expanded="true">
<SettingItemBox name="基础">
<SettingItem name="表行数">
<n-input-number
v-model:value="optionData.rowNum"
:min="1"
size="small"
placeholder="请输入自动计算"
></n-input-number>
</SettingItem>
<SettingItem name="轮播时间(s)">
<n-input-number
v-model:value="optionData.waitTime"
:min="1"
size="small"
placeholder="请输入轮播时间"
></n-input-number>
</SettingItem>
<SettingItem name="表头高度">
<n-input-number
v-model:value="optionData.headerHeight"
:min="1"
size="small"
placeholder="请输入表头高度"
></n-input-number>
</SettingItem>
<SettingItem name="显示行号">
<n-switch size="small" v-model:value="optionData.index" />
</SettingItem>
</SettingItemBox>
<SettingItemBox name="配置" :alone="true">
<SettingItem name="表头数据">
<n-input
v-model:value="header"
:min="1"
size="small"
placeholder="表头数据(英文','分割)"
></n-input>
</SettingItem>
<SettingItem name="列对齐方式">
<n-input
v-model:value="align"
:min="1"
size="small"
placeholder="对齐方式(英文','分割)"
></n-input>
</SettingItem>
<SettingItem name="列宽度">
<n-input
v-model:value="columnWidth"
:min="1"
size="small"
placeholder="列宽度(英文','分割)"
></n-input>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="样式">
<SettingItem name="表头背景色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="optionData.headerBGC"
></n-color-picker>
</SettingItem>
<SettingItem name="奇数行背景色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="optionData.oddRowBGC"
></n-color-picker>
</SettingItem>
<SettingItem name="偶数行背景色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="optionData.evenRowBGC"
></n-color-picker>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType, ref, watch } from 'vue'
import {
CollapseItem,
SettingItemBox,
SettingItem,
} from '@/components/Pages/ChartItemSetting'
import { option } from './config'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true,
},
})
const header = ref(props.optionData.header.toString())
const align = ref(props.optionData.align.toString())
const columnWidth = ref(props.optionData.columnWidth.toString())
watch([header, align, columnWidth],([headerNew,alignNew,columnWidthNew],[headerOld,alignOld,columnWidthOld])=>{
if(headerNew !== headerOld){
props.optionData.header = headerNew.split(',')
}
if(alignNew !== alignOld){
props.optionData.align = alignNew.split(',')
}
if(columnWidthNew !== columnWidthOld){
// @ts-ignore
props.optionData.columnWidth = columnWidthNew.split(',')
}
})
</script>

View File

@@ -0,0 +1,12 @@
[
["行1列1", "行1列2", "行1列3"],
["行2列1", "行2列2", "行2列3"],
["行3列1", "行3列2", "行3列3"],
["行4列1", "行4列2", "行4列3"],
["行5列1", "行5列2", "行5列3"],
["行6列1", "行6列2", "行6列3"],
["行7列1", "行7列2", "行7列3"],
["行8列1", "行8列2", "行8列3"],
["行9列1", "行9列2", "行9列3"],
["行10列1", "行10列2", "行10列3"]
]

View File

@@ -0,0 +1,14 @@
import image from '@/assets/images/chart/tables/table_scrollboard.png'
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const TableScrollBoardConfig: ConfigType = {
key: 'TableScrollBoard',
chartKey: 'VTableScrollBoard',
conKey: 'VCTableScrollBoard',
title: '轮播列表',
category: ChatCategoryEnum.TABLE,
categoryName: ChatCategoryEnumName.TABLE,
package: PackagesCategoryEnum.TABLES,
image
}

View File

@@ -0,0 +1,355 @@
<template>
<div class="dv-scroll-board">
<div class="header" v-if="status.header.length && status.mergedConfig"
:style="`background-color: ${status.mergedConfig.headerBGC};`">
<div class="header-item" v-for="(headerItem, i) in status.header" :key="`${headerItem}${i}`" :style="`
height: ${status.mergedConfig.headerHeight}px;
line-height: ${status.mergedConfig.headerHeight}px;
width: ${status.widths[i]}px;
`" :align="status.aligns[i]" v-html="headerItem" />
</div>
<div v-if="status.mergedConfig" class="rows"
:style="`height: ${h - (status.header.length ? status.mergedConfig.headerHeight : 0)}px;`">
<div class="row-item" v-for="(row, ri) in status.rows" :key="`${row.toString()}${row.scroll}`" :style="`
height: ${status.heights[ri]}px;
line-height: ${status.heights[ri]}px;
background-color: ${status.mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']};
`">
<div class="ceil" v-for="(ceil, ci) in row.ceils" :key="`${ceil}${ri}${ci}`"
:style="`width: ${status.widths[ci]}px;`" :align="status.aligns[ci]" v-html="ceil" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType, onUnmounted, reactive, toRefs, watch, onMounted } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import merge from 'lodash/merge'
import cloneDeep from 'lodash/cloneDeep'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true,
},
})
// 这里能拿到图表宽高等
const { w, h } = toRefs(props.chartConfig.attr)
// 这里能拿到上面 config.ts 里的 option 数据
// const { rowNum, headerHeight, index, backgroundColor } = toRefs(props.chartConfig.option)
const status = reactive({
defaultConfig: {
/**
* @description Board header
* @type {Array<String>}
* @default header = []
* @example header = ['column1', 'column2', 'column3']
*/
header: [],
/**
* @description Board dataset
* @type {Array<Array>}
* @default dataset = []
*/
dataset: [],
/**
* @description Row num
* @type {Number}
* @default rowNum = 5
*/
rowNum: 5,
/**
* @description Header background color
* @type {String}
* @default headerBGC = '#00BAFF'
*/
headerBGC: '#00BAFF',
/**
* @description Odd row background color
* @type {String}
* @default oddRowBGC = '#003B51'
*/
oddRowBGC: '#003B51',
/**
* @description Even row background color
* @type {String}
* @default evenRowBGC = '#003B51'
*/
evenRowBGC: '#0A2732',
/**
* @description Scroll wait time
* @type {Number}
* @default waitTime = 2
*/
waitTime: 2,
/**
* @description Header height
* @type {Number}
* @default headerHeight = 35
*/
headerHeight: 35,
/**
* @description Column width
* @type {Array<Number>}
* @default columnWidth = []
*/
columnWidth: [],
/**
* @description Column align
* @type {Array<String>}
* @default align = []
* @example align = ['left', 'center', 'right']
*/
align: [],
/**
* @description Show index
* @type {Boolean}
* @default index = false
*/
index: false,
/**
* @description index Header
* @type {String}
* @default indexHeader = '#'
*/
indexHeader: '#',
/**
* @description Carousel type
* @type {String}
* @default carousel = 'single'
* @example carousel = 'single' | 'page'
*/
carousel: 'single',
/**
* @description Pause scroll when mouse hovered
* @type {Boolean}
* @default hoverPause = true
* @example hoverPause = true | false
*/
hoverPause: true
},
mergedConfig: props.chartConfig.option,
header: [],
rowsData: [],
rows: [{
ceils: [],
rowIndex: 0,
scroll: 0
}],
widths: [],
heights: [0],
avgHeight: 0,
aligns: [],
animationIndex: 0,
animationHandler: 0,
updater: 0,
needCalc: false
})
const calcData = () => {
mergeConfig()
calcHeaderData()
calcRowsData()
calcWidths()
calcHeights()
calcAligns()
animation(true)
}
onMounted(()=> {
calcData()
})
const mergeConfig = () => {
status.mergedConfig = merge(cloneDeep(status.defaultConfig), props.chartConfig.option)
}
const calcHeaderData = () => {
let { header, index, indexHeader } = status.mergedConfig
if (!header.length) {
status.header = []
return
}
header = [...header]
if (index) header.unshift(indexHeader)
status.header = header
}
const calcRowsData = () => {
let { dataset, index, headerBGC, rowNum } = status.mergedConfig
if (index) {
dataset = dataset.map((row:any, i:number) => {
row = [...row]
const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${i + 1}</span>`
row.unshift(indexTag)
return row
})
}
dataset = dataset.map((ceils:any, i:number) => ({ ceils, rowIndex: i }))
const rowLength = dataset.length
if (rowLength > rowNum && rowLength < 2 * rowNum) {
dataset = [...dataset, ...dataset]
}
dataset = dataset.map((d:any, i:number) => ({ ...d, scroll: i }))
status.rowsData = dataset
status.rows = dataset
}
const calcWidths = () => {
const { mergedConfig, rowsData } = status
const { columnWidth, header } = mergedConfig
const usedWidth = columnWidth.reduce((all:any, ws:number) => all + ws, 0)
let columnNum = 0
if (rowsData[0]) {
columnNum = (rowsData[0] as any).ceils.length
} else if (header.length) {
columnNum = header.length
}
const avgWidth = (w.value - usedWidth) / (columnNum - columnWidth.length)
const widths = new Array(columnNum).fill(avgWidth)
status.widths = merge(widths, columnWidth)
}
const calcHeights = (onresize = false) => {
const { mergedConfig, header } = status
const { headerHeight, rowNum, dataset } = mergedConfig
let allHeight = h.value
if (header.length) allHeight -= headerHeight
const avgHeight = allHeight / rowNum
status.avgHeight = avgHeight
if (!onresize) status.heights = new Array(dataset.length).fill(avgHeight)
}
const calcAligns = () => {
const { header, mergedConfig } = status
const columnNum = header.length
let aligns = new Array(columnNum).fill('left')
const { align } = mergedConfig
status.aligns = merge(aligns, align)
}
const animation = async (start = false) => {
const { needCalc } = status
if (needCalc) {
calcRowsData()
calcHeights()
status.needCalc = false
}
let { avgHeight, animationIndex, mergedConfig, rowsData, updater } = status
const { waitTime, carousel, rowNum } = mergedConfig
const rowLength = rowsData.length
if (rowNum >= rowLength) return
if (start) {
await new Promise(resolve => setTimeout(resolve, waitTime*1000))
if (updater !== status.updater) return
}
const animationNum = carousel === 'single' ? 1 : rowNum
let rows = rowsData.slice(animationIndex)
rows.push(...rowsData.slice(0, animationIndex))
status.rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1)
status.heights = new Array(rowLength).fill(avgHeight)
await new Promise(resolve => setTimeout(resolve, 300))
if (updater !== status.updater) return
status.heights.splice(0, animationNum, ...new Array(animationNum).fill(0))
animationIndex += animationNum
const back = animationIndex - rowLength
if (back >= 0) animationIndex = back
status.animationIndex = animationIndex
status.animationHandler = setTimeout(animation, waitTime*1000 - 300) as any
}
const stopAnimation = () => {
status.updater = (status.updater + 1) % 999999
if (!status.animationHandler) return
clearTimeout(status.animationHandler)
}
const onRestart = async () => {
if (!status.mergedConfig) return
stopAnimation()
calcData()
}
watch(
() => w.value,
() => {
onRestart()
}
)
watch(
() => h.value,
() => {
onRestart()
}
)
// 数据更新
watch(
() => props.chartConfig.option,
() => {
onRestart()
},
{deep:true}
)
// 数据更新 (默认更新 dataset若更新之后有其它操作可添加回调函数)
useChartDataFetch(props.chartConfig, useChartEditStore)
onUnmounted(() => {
stopAnimation()
})
</script>
<style lang="scss" scoped>
.dv-scroll-board {
position: relative;
width: 100%;
height: 100%;
color: #fff;
.text {
padding: 0 10px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header {
display: flex;
flex-direction: row;
font-size: 15px;
.header-item {
transition: all 0.3s;
}
}
.rows {
overflow: hidden;
.row-item {
display: flex;
font-size: 14px;
transition: all 0.3s;
overflow: hidden;
}
}
}
</style>

View File

@@ -1,5 +1,6 @@
import { TableListConfig } from './TableList'
import { TableCommonConfig } from './TableCommon'
import { TableCategoryConfig } from './TableCategory'
import { TableScrollBoardConfig } from './TableScrollBoard'
export default [TableListConfig, TableCommonConfig, TableCategoryConfig]
export default [TableListConfig, TableScrollBoardConfig, TableCommonConfig, TableCategoryConfig]

View File

@@ -6,7 +6,8 @@ import { chartInitConfig } from '@/settings/designSetting'
const requestConfig: RequestConfigType = {
requestDataType: RequestDataTypeEnum.STATIC,
requestHttpType: RequestHttpEnum.GET
requestHttpType: RequestHttpEnum.GET,
requestUrl: ''
}
export class publicConfig implements PublicConfigType {

View File

@@ -8,7 +8,10 @@ import { SketchRule } from 'vue3-sketch-ruler'
* @param app
*/
export function setupCustomComponents(app: App) {
// 骨架屏
app.component('GoSkeleton', GoSkeleton)
// 加载
app.component('GoLoading', GoLoading)
// 标尺
app.component('SketchRule', SketchRule)
}

View File

@@ -52,7 +52,8 @@ import {
ArrowBack as ArrowBackIcon,
ArrowForward as ArrowForwardIcon,
Planet as PawIcon,
Search as SearchIcon
Search as SearchIcon,
Reload as ReloadIcon
} from '@vicons/ionicons5'
import {
@@ -190,7 +191,9 @@ const ionicons5 = {
// 狗爪
PawIcon,
// 搜索(放大镜)
SearchIcon
SearchIcon,
// 加载
ReloadIcon
}
const carbon = {

View File

@@ -1,13 +1,13 @@
import { RouteRecordRaw } from 'vue-router'
import type { AppRouteRecordRaw } from '@/router/types';
import { ErrorPage404, ErrorPage403, ErrorPage500, Layout } from '@/router/constant';
import { ErrorPage404, ErrorPage403, ErrorPage500, Layout, RedirectHome, RedirectUnPublish } from '@/router/constant';
import { PageEnum } from '@/enums/pageEnum'
import { GoReload } from '@/components/GoReload'
export const LoginRoute: RouteRecordRaw = {
path: '/login',
name: 'Login',
path: PageEnum.BASE_LOGIN,
name: PageEnum.BASE_LOGIN_NAME,
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
@@ -60,22 +60,21 @@ export const ReloadRoute: AppRouteRecordRaw = {
},
}
export const RedirectRoute: AppRouteRecordRaw = {
path: PageEnum.REDIRECT,
name: PageEnum.REDIRECT_NAME,
component: Layout,
meta: {
title: PageEnum.REDIRECT_NAME,
},
children: [
{
path: '/redirect/:path(.*)',
name: PageEnum.REDIRECT_NAME,
component: () => import('@/views/redirect/index.vue'),
meta: {
title: PageEnum.REDIRECT_NAME,
hideBreadcrumb: true,
},
export const RedirectRoute: RouteRecordRaw[] = [
{
path: PageEnum.REDIRECT,
name: PageEnum.REDIRECT_NAME,
component: RedirectHome,
meta: {
title: PageEnum.REDIRECT_NAME,
},
],
};
},
{
path: PageEnum.REDIRECT_UN_PUBLISH,
name: PageEnum.REDIRECT_UN_PUBLISH_NAME,
component: RedirectUnPublish,
meta: {
title: PageEnum.REDIRECT_UN_PUBLISH_NAME,
},
},
]

View File

@@ -4,6 +4,10 @@ export const ErrorPage403 = () => import('@/views/exception/403.vue');
export const ErrorPage500 = () => import('@/views/exception/500.vue');
export const RedirectHome = () => import('@/views/redirect/index.vue');
export const RedirectUnPublish = () => import('@/views/redirect/UnPublish.vue');
export const Layout = () => import('@/layout/index.vue');
export const ParentLayout = () => import('@/layout/parentLayout.vue');

View File

@@ -1,9 +1,8 @@
import type { App } from 'vue'
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import { RedirectRoute } from '@/router/base'
import { createRouterGuards } from './router-guards'
import { PageEnum } from '@/enums/pageEnum'
import { HttpErrorPage, LoginRoute, ReloadRoute } from '@/router/base'
import { HttpErrorPage, LoginRoute, ReloadRoute, RedirectRoute } from '@/router/base'
import { Layout } from '@/router/constant'
import modules from '@/router/modules'
@@ -19,6 +18,7 @@ const RootRoute: Array<RouteRecordRaw> = [
},
children: [
...HttpErrorPage,
...RedirectRoute,
modules.projectRoutes,
modules.chartRoutes,
modules.previewRoutes
@@ -27,7 +27,7 @@ const RootRoute: Array<RouteRecordRaw> = [
]
export const constantRouter: any[] = [LoginRoute, ...RootRoute, RedirectRoute, ReloadRoute];
export const constantRouter: any[] = [LoginRoute, ...RootRoute, ReloadRoute];
const router = createRouter({
history: createWebHashHistory(''),

View File

@@ -1,7 +1,15 @@
import { Router } from 'vue-router';
import { PageEnum } from '@/enums/pageEnum'
import { PageEnum, PreviewEnum } from '@/enums/pageEnum'
import { loginCheck } from '@/utils'
// 路由白名单
const routerAllowList = [
// 登录
PageEnum.BASE_LOGIN_NAME,
// 预览
PreviewEnum.CHART_PREVIEW_NAME
]
export function createRouterGuards(router: Router) {
// 前置
router.beforeEach(async (to, from, next) => {
@@ -12,10 +20,8 @@ export function createRouterGuards(router: Router) {
next({ name: PageEnum.ERROR_PAGE_NAME_404 })
}
if (!loginCheck()) {
if (to.name === PageEnum.BASE_LOGIN_NAME) {
next()
}
// @ts-ignore
if (!routerAllowList.includes(to.name) && !loginCheck()) {
next({ name: PageEnum.BASE_LOGIN_NAME })
}
next()

View File

@@ -48,8 +48,11 @@ export const backgroundImageSize = 5
// 预览展示方式
export const previewScaleType = PreviewScaleEnum.FIT
// 数据请求间隔
// 数据请求间隔s
export const requestInterval = 30
// 工作台自动保存间隔s
export const saveInterval = 30
// 工作区域历史记录存储最大数量
export const editHistoryMax = 100

View File

@@ -0,0 +1,2 @@
// 请求前缀
export const axiosPre = '/api/goview'

View File

@@ -1,12 +1,33 @@
import { CreateComponentType, FilterEnum} from '@/packages/index.d'
import { HistoryActionTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
import { RequestHttpEnum, RequestDataTypeEnum } from '@/enums/httpEnum'
import { SyncEnum } from '@/enums/editPageEnum'
import { PreviewScaleEnum } from '@/enums/styleEnum'
import type {
ChartColorsNameType,
GlobalThemeJsonType,
} from '@/settings/chartThemes/index'
// 项目数据枚举
export enum ProjectInfoEnum {
// 名称
PROJECT_NAME = 'projectName',
// 描述
REMARKS = 'remarks',
// 缩略图
THUMBNAIL= 'thumbnail',
// 是否公开发布
RELEASE = 'release'
}
// 项目数据
export type ProjectInfoType = {
[ProjectInfoEnum.PROJECT_NAME]: string,
[ProjectInfoEnum.REMARKS]: string,
[ProjectInfoEnum.THUMBNAIL]: string,
[ProjectInfoEnum.RELEASE]: boolean
}
// 编辑画布属性
export enum EditCanvasTypeEnum {
EDIT_LAYOUT_DOM = 'editLayoutDom',
@@ -17,9 +38,10 @@ export enum EditCanvasTypeEnum {
LOCK_SCALE = 'lockScale',
IS_CREATE = 'isCreate',
IS_DRAG = 'isDrag',
SAVE_STATUS = 'saveStatus'
}
// 编辑区域
// 编辑区域(临时)
export type EditCanvasType = {
// 编辑区域 DOM
[EditCanvasTypeEnum.EDIT_LAYOUT_DOM]: HTMLElement | null
@@ -36,9 +58,11 @@ export type EditCanvasType = {
[EditCanvasTypeEnum.IS_CREATE]: boolean
// 拖拽中
[EditCanvasTypeEnum.IS_DRAG]: boolean
// 保存状态
[EditCanvasTypeEnum.SAVE_STATUS]: SyncEnum
}
// 滤镜/背景色/宽高主题等
// 画布数据/滤镜/背景色/宽高主题等
export enum EditCanvasConfigEnum {
WIDTH = 'width',
HEIGHT = 'height',
@@ -50,7 +74,12 @@ export enum EditCanvasConfigEnum {
PREVIEW_SCALE_TYPE = 'previewScaleType',
}
export interface EditCanvasConfigType {
// 画布属性(需保存)
export type EditCanvasConfigType = {
// 项目名称
[EditCanvasConfigEnum.PROJECT_NAME]: string,
// 项目描述
[EditCanvasConfigEnum.REMARKS]: string,
// 滤镜-色相
[FilterEnum.HUE_ROTATE]: number
// 滤镜-饱和度
@@ -118,6 +147,7 @@ export type RecordChartType = {
// Store 枚举
export enum ChartEditStoreEnum {
PROJECT_INFO = 'projectInfo',
EDIT_RANGE = 'editRange',
EDIT_CANVAS = 'editCanvas',
RIGHT_MENU_SHOW = 'rightMenuShow',
@@ -137,6 +167,7 @@ export type RequestGlobalConfigType = {
// 轮询时间
requestInterval: number
}
// 单个图表请求配置
export type RequestConfigType = {
// 获取数据的方式
@@ -149,6 +180,7 @@ export type RequestConfigType = {
// Store 类型
export interface ChartEditStoreType {
[ChartEditStoreEnum.PROJECT_INFO]: ProjectInfoType
[ChartEditStoreEnum.EDIT_CANVAS]: EditCanvasType
[ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: EditCanvasConfigType
[ChartEditStoreEnum.RIGHT_MENU_SHOW]: boolean
@@ -159,6 +191,7 @@ export interface ChartEditStoreType {
[ChartEditStoreEnum.COMPONENT_LIST]: CreateComponentType[]
}
// 需要存储的数据内容
export interface ChartEditStorage {
[ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: EditCanvasConfigType
[ChartEditStoreEnum.REQUEST_GLOBAL_CONFIG]: RequestGlobalConfigType

View File

@@ -9,10 +9,13 @@ import { requestInterval, previewScaleType } from '@/settings/designSetting'
import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
// 全局设置
import { useSettingStore } from '@/store/modules/settingStore/settingStore'
// 历史类型
import { HistoryActionTypeEnum, HistoryItemType, HistoryTargetTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
import { MenuEnum } from '@/enums/editPageEnum'
import { PreviewScaleEnum } from '@/enums/styleEnum'
// 画布枚举
import { MenuEnum, SyncEnum } from '@/enums/editPageEnum'
import {
ProjectInfoType,
ChartEditStoreEnum,
ChartEditStorage,
ChartEditStoreType,
@@ -31,6 +34,13 @@ const settingStore = useSettingStore()
export const useChartEditStore = defineStore({
id: 'useChartEditStore',
state: (): ChartEditStoreType => ({
// 项目数据
projectInfo: {
projectName: '',
remarks: '',
thumbnail: '',
release: false
},
// 画布属性
editCanvas: {
// 编辑区域 Dom
@@ -47,7 +57,9 @@ export const useChartEditStore = defineStore({
// 初始化
isCreate: false,
// 拖拽中
isDrag: false
isDrag: false,
// 同步中
saveStatus: SyncEnum.PENDING
},
// 右键菜单
rightMenuShow: false,
@@ -109,6 +121,9 @@ export const useChartEditStore = defineStore({
componentList: []
}),
getters: {
getProjectInfo(): ProjectInfoType {
return this.projectInfo
},
getMousePosition(): MousePositionType {
return this.mousePosition
},
@@ -143,6 +158,10 @@ export const useChartEditStore = defineStore({
}
},
actions: {
// * 设置 peojectInfo 数据项
setProjectInfo<T extends keyof ProjectInfoType, K extends ProjectInfoType[T]>(key: T, value: K) {
this.projectInfo[key] = value
},
// * 设置 editCanvas 数据项
setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) {
this.editCanvas[key] = value
@@ -517,8 +536,8 @@ export const useChartEditStore = defineStore({
attr.x -= distance
break;
}
},
// ----------------
},
// * 页面缩放设置-----------------
// * 设置页面大小
setPageSize(scale: number): void {
this.setPageStyle('height', `${this.editCanvasConfig.height * scale}px`)

View File

@@ -4,10 +4,10 @@ import { asideCollapsedWidth } from '@/settings/designSetting'
import { SettingStoreType, ToolsStatusEnum } from './settingStore.d'
import { setLocalStorage, getLocalStorage } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum'
const { GO_SYSTEM_SETTING_STORE } = StorageEnum
const { GO_SETTING_STORE } = StorageEnum
const storageSetting: SettingStoreType = getLocalStorage(
GO_SYSTEM_SETTING_STORE
GO_SETTING_STORE
)
// 全局设置
@@ -45,7 +45,7 @@ export const useSettingStore = defineStore({
this.$patch(state => {
state[key] = value
})
setLocalStorage(GO_SYSTEM_SETTING_STORE, this.$state)
setLocalStorage(GO_SETTING_STORE, this.$state)
}
}
})

View File

@@ -0,0 +1,29 @@
export enum SystemStoreUserInfoEnum {
USER_TOKEN = 'userToken',
USER_ID = 'userId',
USER_NAME = 'userName',
NICK_NAME = 'nickName',
}
export interface UserInfoType {
[SystemStoreUserInfoEnum.USER_TOKEN]?: string,
[SystemStoreUserInfoEnum.USER_ID]?: string,
[SystemStoreUserInfoEnum.USER_NAME]?: string,
[SystemStoreUserInfoEnum.NICK_NAME]?: string,
}
export interface FetchInfoType {
OSSUrl?: string,
}
export enum SystemStoreEnum {
// 用户
USER_INFO = 'userInfo',
// 请求
FETCH_INFO = 'fetchInfo'
}
export interface SystemStoreType {
[SystemStoreEnum.USER_INFO]: UserInfoType
[SystemStoreEnum.FETCH_INFO]: FetchInfoType
}

View File

@@ -0,0 +1,40 @@
import { defineStore } from 'pinia'
import { SystemStoreType, UserInfoType, FetchInfoType } from './systemStore.d'
import { setLocalStorage, getLocalStorage } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum'
const { GO_SYSTEM_STORE } = StorageEnum
const storageSystem: SystemStoreType = getLocalStorage(GO_SYSTEM_STORE)
// 系统数据记录
export const useSystemStore = defineStore({
id: 'useSystemStore',
state: (): SystemStoreType => storageSystem || {
userInfo: {
userId: undefined,
userName: undefined,
userToken: undefined,
nickName: undefined
},
fetchInfo: {
OSSUrl: undefined
}
},
getters: {
getUserInfo(): UserInfoType {
return this.userInfo
},
getFetchInfo(): FetchInfoType {
return this.fetchInfo
},
},
actions: {
setItem<T extends keyof SystemStoreType, K extends SystemStoreType[T]>(key: T, value: K): void {
this.$patch(state => {
state[key] = value
});
setLocalStorage(GO_SYSTEM_STORE, this.$state)
}
}
})

View File

@@ -1,5 +1,5 @@
// 闪烁
.animation-twinkle {
.go-animation-twinkle {
animation: twinkle 2s ease;
animation-iteration-count: infinite;
opacity: 1;

View File

@@ -1,3 +1,65 @@
/**
* * base64转file
* @param dataurl
* @param fileName
* @returns
*/
export const base64toFile = (dataurl: string, fileName: string) => {
let dataArr = dataurl.split(","),
mime = (dataArr as any[])[0].match(/:(.*?);/)[1],
bstr = atob(dataArr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], fileName, { type: mime });
}
/**
* * file转url
*/
export const fileToUrl = (file: File): string => {
const Url = URL || window.URL || window.webkitURL
const ImageUrl = Url.createObjectURL(file)
return ImageUrl
}
/**
* * url转file
*/
export const urlToFile = (fileUrl: string, fileName = `${new Date().getTime()}`): File => {
const dataArr = fileUrl.split(',')
const mime = (dataArr as any[])[0].match(/:(.*);/)[1]
const originStr = atob(dataArr[1])
return new File([originStr], `${fileName}`, { type: mime })
}
/**
* * file转base64
* @param file 文件数据
* @param callback 回调函数
*/
export const fileTobase64 = (file: File, callback: Function) => {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (e: ProgressEvent<FileReader>) {
if (e.target) {
let base64 = e.target.result
callback(base64)
}
}
}
/**
* * canvas转file
* @param canvas
*/
export const canvastoFile = (canvas: HTMLCanvasElement, name?: string) => {
const dataurl = canvas.toDataURL('image/png')
return urlToFile(dataurl, name)
}
/**
* *获取上传的文件数据
* @param { File } file 文件对象
@@ -38,7 +100,7 @@ export const downloadByA = (url: string, filename = new Date().getTime(), fileSu
}
/**
* 下载数据
* * 下载数据
* @param { string } content 数据内容
* @param { ?string } filename 文件名称(默认随机字符)
* @param { ?string } fileSuffix 文件名称(默认随机字符)
@@ -51,4 +113,4 @@ export const downloadTextFile = (
// 字符内容转变成blob地址
const blob = new Blob([content])
downloadByA(URL.createObjectURL(blob), filename, fileSuffix)
}
}

6
src/utils/http.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* * 请求失败统一处理
*/
export const httpErrorHandle = () => {
window['$message'].error(window['$t']('http.error_message'))
}

View File

@@ -7,3 +7,4 @@ export * from '@/utils/plugin'
export * from '@/utils/componets'
export * from '@/utils/type'
export * from '@/utils/file'
export * from '@/utils/http'

View File

@@ -28,7 +28,6 @@ export const loadingError = () => {
/**
* * render 对话框
* @param { Object} params 配置参数, 详见 https://www.naiveui.com/zh-CN/light/components/dialog
* @param { Function } dialogFn 函数
* ```
* 最简易的 demo
* goDialog({
@@ -36,7 +35,7 @@ export const loadingError = () => {
* })
* ```
*/
export const goDialog = (
export const goDialog = (
params: {
// 基本
type?: DialogEnum
@@ -44,14 +43,12 @@ export const goDialog = (
title?: string | (() => any)
// 提示
message?: string
// 取消提示词
negativeText?: string
// 取消按钮的属性
negativeButtonProps?: object,
// 确定提示词
positiveText?: string
// 确定按钮的属性
positiveButtonProps?: object,
// 取消提示词
negativeText?: string
// 是否不展示取消按钮
closeNegativeText?: boolean,
// 点击遮罩是否关闭
isMaskClosable?: boolean
// 回调
@@ -61,17 +58,16 @@ export const goDialog = (
promise?: boolean
promiseResCallback?: Function
promiseRejCallback?: Function
},
dialogFn?: Function
[T:string]: any
}
) => {
const {
type,
title,
message,
negativeText,
negativeButtonProps,
positiveText,
positiveButtonProps,
negativeText,
closeNegativeText,
isMaskClosable,
onPositiveCallback,
onNegativeCallback,
@@ -83,7 +79,7 @@ export const goDialog = (
const typeObj = {
// 自定义
[DialogEnum.DELETE]: {
fn: dialogFn || window['$dialog'].warning,
fn: window['$dialog'].warning,
message: message || '是否删除此数据?'
},
// 原有
@@ -108,7 +104,7 @@ export const goDialog = (
icon: renderIcon(InformationCircleIcon, { size: dialogIconSize }),
content: typeObj[type || DialogEnum.WARNING]['message'],
positiveText: positiveText || '确定',
negativeText: negativeText || '取消',
negativeText: closeNegativeText ? undefined : (negativeText || '取消'),
// 是否通过遮罩关闭
maskClosable: isMaskClosable || maskClosable,
onPositiveClick: async () => {

View File

@@ -1,11 +1,12 @@
import { useRoute } from 'vue-router'
import { ResultEnum } from '@/enums/httpEnum'
import { ErrorPageNameMap, PageEnum } from '@/enums/pageEnum'
import { ResultEnum, RequestHttpHeaderEnum } from '@/enums/httpEnum'
import { ErrorPageNameMap, PageEnum, PreviewEnum } from '@/enums/pageEnum'
import { docPath, giteeSourceCodePath } from '@/settings/pathConst'
import { cryptoDecode } from './crypto'
import { SystemStoreEnum, SystemStoreUserInfoEnum } from '@/store/modules/systemStore/systemStore.d'
import { StorageEnum } from '@/enums/storageEnum'
import { clearLocalStorage, getLocalStorage } from './storage'
import { clearLocalStorage, getLocalStorage, clearCookie } from './storage'
import router from '@/router'
import { logoutApi } from '@/api/path'
/**
* * 根据名字跳转路由
@@ -101,11 +102,20 @@ export const reloadRoutePage = () => {
}
/**
* * 退出
* * 退出登录
*/
export const logout = () => {
clearLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE)
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
export const logout = async () => {
try {
const res = await logoutApi() as unknown as MyResponseType
if(res.code === ResultEnum.SUCCESS) {
window['$message'].success(window['$t']('global.logout_success'))
clearCookie(RequestHttpHeaderEnum.COOKIE)
clearLocalStorage(StorageEnum.GO_SYSTEM_STORE)
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
}
} catch (error) {
window['$message'].success(window['$t']('global.logout_failure'))
}
}
/**
@@ -153,6 +163,19 @@ export const fetchRouteParams = () => {
}
}
/**
* * 通过硬解析获取当前路由下的参数
* @returns object
*/
export const fetchRouteParamsLocation = () => {
try {
return document.location.hash.split('/').pop() || ''
} catch (error) {
window['$message'].warning('查询路由信息失败,请联系管理员!')
return ''
}
}
/**
* * 回到主页面
* @param confirm
@@ -162,19 +185,29 @@ export const goHome = () => {
}
/**
* * 判断是否登录(现阶段是有 login 数据即可)
* * 判断是否登录
* @return boolean
*/
export const loginCheck = () => {
try {
const info = getLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE)
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
if (!info) return false
const decodeInfo = cryptoDecode(info)
if (decodeInfo) {
if (info[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_TOKEN]) {
return true
}
return false
} catch (error) {
return false
}
}
}
/**
* * 预览地址
* @returns
*/
export const previewPath = () => {
const { origin, pathname } = document.location
const path = fetchPathByName(PreviewEnum.CHART_PREVIEW_NAME, 'href')
const previewPath = `${origin}${pathname}${path}/${fetchRouteParamsLocation()}`
return previewPath
}

View File

@@ -5,7 +5,7 @@
* @param v 键值无需stringiiy
* @returns RemovableRef
*/
export const setLocalStorage = <T>(k: string, v: T) => {
export const setLocalStorage = <T>(k: string, v: T) => {
try {
window.localStorage.setItem(k, JSON.stringify(v))
} catch (error) {
@@ -18,7 +18,7 @@
* @param k 键名
* @returns any
*/
export const getLocalStorage = (k: string) => {
export const getLocalStorage = (k: string) => {
const item = window.localStorage.getItem(k)
try {
return item ? JSON.parse(item) : item
@@ -31,7 +31,7 @@
* * 清除本地会话数据
* @param name
*/
export const clearLocalStorage = (name: string) => {
export const clearLocalStorage = (name: string) => {
window.localStorage.removeItem(name)
}
@@ -68,4 +68,42 @@ export const getSessionStorage: (k: string) => any = (k: string) => {
*/
export const clearSessioStorage = (name: string) => {
window.sessionStorage.removeItem(name)
}
/**
* * 设置 cookie
* @param name 键名
* @param cvalue 键值
* @param exdays 过期时间
*/
export const setCookie = (name: string, cvalue: string, exdays: number) => {
const d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
const expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + cvalue + "; " + expires;
}
/**
* * 获取 cookie
* @param cname 键名
* @returns string
*/
export const getCookie = (cname: string) => {
const name = cname + "=";
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1);
if (c.indexOf(name) != -1) return c.substring(name.length, c.length);
}
return "";
}
/**
* * 清除 cookie
* @param name 键名
* @returns string
*/
export const clearCookie = (name: string) => {
setCookie(name, "", -1);
}

View File

@@ -111,29 +111,6 @@ export const isMac = () => {
return /macintosh|mac os x/i.test(navigator.userAgent)
}
/**
* * file转url
*/
export const fileToUrl = (file: File): string => {
const Url = URL || window.URL || window.webkitURL
const ImageUrl = Url.createObjectURL(file)
return ImageUrl
}
/**
* * file转base64
*/
export const fileTobase64 = (file: File, callback: Function) => {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (e: ProgressEvent<FileReader>) {
if (e.target) {
let base64 = e.target.result
callback(base64)
}
}
}
/**
* * 挂载监听
*/
@@ -178,7 +155,9 @@ export const canvasCut = (html: HTMLElement | null, callback?: Function) => {
return
}
html2canvas(html).then((canvas: HTMLCanvasElement) => {
html2canvas(html, {
backgroundColor: null
}).then((canvas: HTMLCanvasElement) => {
window['$message'].success('导出成功!')
downloadByA(canvas.toDataURL(), undefined, 'png')
if (callback) callback()

View File

@@ -39,7 +39,7 @@
@click="selectChartHandle(item)"
>
<img class="list-item-img" v-lazy="item.image" alt="展示图" />
<n-text depth="2">{{ item.title }}</n-text>
<n-text class="list-item-fs" depth="2">{{ item.title }}</n-text>
</div>
</n-scrollbar>
<div class="popover-modal"></div>
@@ -167,13 +167,27 @@ $width: 178px;
position: relative;
cursor: pointer;
padding: 2px;
padding-left: 6px;
margin-bottom: 5px;
&-fs {
font-size: 12px;
}
&-img {
height: 30px;
height: 28px;
margin-right: 5px;
border-radius: 5px;
}
&:hover {
&::before {
content: '';
position: absolute;
width: 3px;
height: 100%;
left: 0;
top: 0;
border-radius: 2px;
background-color: v-bind('themeColor');
}
&::after {
z-index: -1;
content: '';

View File

@@ -128,16 +128,20 @@ import { backgroundImageSize } from '@/settings/designSetting'
import { FileTypeEnum } from '@/enums/fileTypeEnum'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { EditCanvasConfigEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { StylesSetting } from '@/components/Pages/ChartItemSetting'
import { UploadCustomRequestOptions } from 'naive-ui'
import { fileToUrl, loadAsyncComponent } from '@/utils'
import { fileToUrl, loadAsyncComponent, fetchRouteParamsLocation } from '@/utils'
import { PreviewScaleEnum } from '@/enums/styleEnum'
import { ResultEnum } from '@/enums/httpEnum'
import { icon } from '@/plugins'
import { uploadFile} from '@/api/path'
const { ColorPaletteIcon } = icon.ionicons5
const { ZAxisIcon, ScaleIcon, FitToScreenIcon, FitToHeightIcon, FitToWidthIcon } = icon.carbon
const chartEditStore = useChartEditStore()
const systemStore = useSystemStore()
const canvasConfig = chartEditStore.getEditCanvasConfig
const editCanvas = chartEditStore.getEditCanvas
@@ -270,17 +274,34 @@ const switchSelectColorHandle = () => {
// 自定义上传操作
const customRequest = (options: UploadCustomRequestOptions) => {
const { file } = options
nextTick(() => {
nextTick(async () => {
if(!systemStore.getFetchInfo.OSSUrl) {
window['$message'].error('添加图片失败,请刷新页面重试!')
return
}
if (file.file) {
const ImageUrl = fileToUrl(file.file)
chartEditStore.setEditCanvasConfig(
EditCanvasConfigEnum.BACKGROUND_IAMGE,
ImageUrl
)
chartEditStore.setEditCanvasConfig(
EditCanvasConfigEnum.SELECT_COLOR,
false
// 修改名称
const newNameFile = new File(
[file.file],
`${fetchRouteParamsLocation()}_index_background.png`,
{ type: file.file.type }
)
let uploadParams = new FormData()
uploadParams.append('object', newNameFile)
const uploadRes = await uploadFile(systemStore.getFetchInfo.OSSUrl ,uploadParams) as unknown as MyResponseType
if(uploadRes.code === ResultEnum.SUCCESS) {
chartEditStore.setEditCanvasConfig(
EditCanvasConfigEnum.BACKGROUND_IAMGE,
uploadRes.data.objectContent.httpRequest.uri
)
chartEditStore.setEditCanvasConfig(
EditCanvasConfigEnum.SELECT_COLOR,
false
)
return
}
window['$message'].error('添加图片失败,请稍后重试!')
} else {
window['$message'].error('添加图片失败,请稍后重试!')
}

View File

@@ -61,7 +61,7 @@
import { ref, toRefs } from 'vue'
import { icon } from '@/plugins'
import { SettingItemBox } from '@/components/Pages/ChartItemSetting'
import { RequestHttpEnum } from '@/enums/httpEnum'
import { RequestHttpEnum, ResultEnum } from '@/enums/httpEnum'
import { chartDataUrl, rankListUrl, numberUrl } from '@/api/mock'
import { http } from '@/api/http'
import { SelectHttpType } from '../../index.d'
@@ -113,7 +113,7 @@ const sendHandle = async () => {
const res = await http(requestHttpType)(completePath || '', {})
setTimeout(() => {
loading.value = false
if (res.status === 200) {
if (res.status === ResultEnum.SUCCESS) {
// @ts-ignore
targetData.value.option.dataset = res.data
showMatching.value = true

View File

@@ -1,6 +1,10 @@
<template>
<div class="go-edit-bottom">
<edit-history></edit-history>
<div class="go-flex-items-center">
<edit-history></edit-history>
<n-divider vertical />
<edit-data-sync></edit-data-sync>
</div>
<n-space class="bottom-ri">
<!-- 快捷键提示 -->
@@ -55,7 +59,8 @@
import { reactive, ref, toRefs, watchEffect } from 'vue'
import { icon } from '@/plugins'
import { EditHistory } from '../EditHistory/index'
import EditShortcutKey from '../EditShortcutKey/index.vue'
import { EditShortcutKey } from '../EditShortcutKey/index'
import { EditDataSync } from '../EditDataSync/index'
import { useDesignStore } from '@/store/modules/designStore/designStore'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
@@ -136,12 +141,13 @@ watchEffect(() => {
<style lang="scss" scoped>
$min-width: 500px;
@include go('edit-bottom') {
width: 100%;
min-width: $min-width;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
width: 100%;
min-width: $min-width;
height: 40px;
.bottom-ri {
position: relative;
top: 15px;
@@ -152,6 +158,7 @@ $min-width: 500px;
}
}
.scale-btn {
width: 90px;
font-size: 12px;
@include deep() {
.n-base-selection-label {

View File

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

View File

@@ -0,0 +1,97 @@
<template>
<div class="go-edit-data-sync go-flex-items-center">
<n-tooltip trigger="hover">
<template #trigger>
<n-text class="status-desc go-ml-2" :type="descType" depth="3">
{{ statusDesc }}
</n-text>
</template>
<span>{{saveInterval}}s 更新一次</span>
</n-tooltip>
<n-spin
v-show="statusDesc === statusDescObj[1]['text']"
class="go-ml-2"
size="small"
>
<template #icon>
<n-icon size="13">
<reload-icon />
</n-icon>
</template>
</n-spin>
</div>
</template>
<script lang="ts" setup>
import { ref, toRefs, watch } from 'vue'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useDesignStore } from '@/store/modules/designStore/designStore'
import { SyncEnum } from '@/enums/editPageEnum'
import { icon } from '@/plugins'
import { saveInterval } from '@/settings/designSetting'
const { ReloadIcon } = icon.ionicons5
const chartEditStore = useChartEditStore()
const designStore = useDesignStore()
const { saveStatus } = toRefs(chartEditStore.getEditCanvas)
const themeColor = ref(designStore.getAppTheme)
const statusDesc = ref('')
const descType = ref('')
let setTimeoutIns: NodeJS.Timeout = setTimeout(() => {})
const statusDescObj = {
[SyncEnum.PENDING]: {
text: '等待自动同步',
type: '',
},
[SyncEnum.START]: {
text: '正在同步中',
type: 'success',
},
[SyncEnum.SUCCESS]: {
text: '同步成功!',
type: 'success',
},
[SyncEnum.FAILURE]: {
text: '同步失败!',
type: 'error',
},
}
watch(
() => saveStatus.value,
newData => {
clearTimeout(setTimeoutIns)
statusDesc.value = statusDescObj[newData]['text']
descType.value = statusDescObj[newData]['type']
// 3秒重置展示
setTimeoutIns = setTimeout(() => {
statusDesc.value = statusDescObj[SyncEnum.PENDING]['text']
descType.value = statusDescObj[SyncEnum.PENDING]['type']
}, 3000)
},
{
immediate: true,
}
)
</script>
<style lang="scss" scoped>
@include go('edit-data-sync') {
@include deep() {
.n-spin {
width: 13px;
height: 13px;
}
}
.status-desc {
cursor: default;
color: v-bind('themeColor');
font-size: 12px;
opacity: 0.8;
}
}
</style>

View File

@@ -10,7 +10,7 @@
>
<template #trigger>
<n-button
class="mr-10"
class="go-mr-1"
secondary
size="small"
:disabled="options.length === 0"
@@ -140,9 +140,6 @@ const handleClick = () => {
</script>
<style lang="scss" scoped>
.mr-10 {
margin-right: 10px;
}
.edit-history-popover {
.btn-text {
font-size: 12px;

View File

@@ -71,15 +71,24 @@ const select = computed(() => {
border-radius: 5px;
background-color: #fff;
transform: translate(-40%, -30%);
&.t,
&.t {
width: 30px;
transform: translate(-50%, -50%);
}
&.b {
width: 30px;
transform: translate(-50%, -30%);
}
&.l,
&.r {
height: 30px;
}
&.r,
&.r {
transform: translate(-20%, -50%);
}
&.l {
transform: translate(-45%, -50%);
}
&.rt,
&.rb
{

View File

@@ -99,6 +99,11 @@ const shortcutKeyOptions = [
win: `${WinKeyboard.CTRL.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + Z `,
mac: `${MacKeyboard.CTRL.toUpperCase()} + ${MacKeyboard.SHIFT.toUpperCase()} + Z `,
},
{
label: '保存',
win: `${WinKeyboard.CTRL.toUpperCase()} + S `,
mac: `${MacKeyboard.CTRL.toUpperCase()} + S `,
},
]
const closeHandle = () => {
emit('update:modelShow', false)

View File

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

View File

@@ -1,61 +1,12 @@
import { ref, nextTick } from 'vue'
import { UploadCustomRequestOptions } from 'naive-ui'
import { FileTypeEnum } from '@/enums/fileTypeEnum'
import { fetchChartComponent } from '@/packages/index'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { ChartEditStoreEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
import { getUUID, readFile, goDialog } from '@/utils'
import { createComponent } from '@/packages'
// 更新函数
const updateComponent = async (fileData: any, isSplace = false) => {
const chartEditStore = useChartEditStore()
const chartHistoryStore = useChartHistoryStore()
if (isSplace) {
// 清除列表
chartEditStore.componentList = []
// 清除历史记录
chartHistoryStore.clearBackStack()
chartHistoryStore.clearForwardStack()
}
// 列表组件注册
fileData.componentList.forEach(async (e: CreateComponentType) => {
if (!window['$vue'].component(e.chartConfig.chartKey)) {
window['$vue'].component(
e.chartConfig.chartKey,
fetchChartComponent(e.chartConfig)
)
}
})
// 数据赋值
for (const key in fileData) {
// 组件
if (key === ChartEditStoreEnum.COMPONENT_LIST) {
for (const comItem of fileData[key]) {
// 补充 class 上的方法
let newComponent: CreateComponentType = await createComponent(
comItem.chartConfig
)
// 不保存到记录
chartEditStore.addComponentList(
Object.assign(newComponent, { ...comItem, id: getUUID() }),
false,
true
)
}
} else {
// 非组件(顺便排除脏数据)
if (key !== 'editCanvasConfig' && key !== 'requestGlobalConfig') return
Object.assign((chartEditStore as any)[key], fileData[key])
}
}
}
import { readFile, goDialog } from '@/utils'
import { useSync } from '@/views/chart/hooks/useSync.hook'
export const useFile = () => {
const importUploadFileListRef = ref()
const { updateComponent } = useSync()
// 上传-前置
//@ts-ignore
const importBeforeUpload = ({ file }) => {

View File

@@ -68,7 +68,7 @@
</template>
<script setup lang="ts">
import { ref, computed, h } from 'vue';
import { ref, computed } from 'vue';
import { useSettingStore } from '@/store/modules/settingStore/settingStore'
import { ToolsStatusEnum } from '@/store/modules/settingStore/settingStore.d'
import { exportHandle } from './utils'

View File

@@ -9,7 +9,7 @@ export const exportHandle = () => {
// 导出数据
downloadTextFile(
JSON.stringify(chartEditStore.getStorageInfo || [], (k, v) => {
JSON.stringify(chartEditStore.getStorageInfo || {}, (k, v) => {
return v === undefined ? null : v
}),
undefined,

View File

@@ -71,6 +71,7 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { useLayout } from './hooks/useLayout.hook'
import { useAddKeyboard } from '../hooks/useKeyboard.hook'
import { useSync } from '../hooks/useSync.hook'
import { dragHandle, dragoverHandle, useMouseHandle } from './hooks/useDrag.hook'
import { useComponentStyle, useSizeStyle } from './hooks/useStyle.hook'
@@ -82,6 +83,7 @@ import { EditTools } from './components/EditTools'
const chartEditStore = useChartEditStore()
const { handleContextMenu } = useContextMenu()
const { dataSyncFetch, intervalDataSyncUpdate } = useSync()
// 布局处理
useLayout()
@@ -121,9 +123,13 @@ const rangeStyle = computed(() => {
}
})
// 键盘事件
onMounted(() => {
// 键盘事件
useAddKeyboard()
// 获取数据
dataSyncFetch()
// 定时更新数据
intervalDataSyncUpdate()
})
</script>

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