Pre Merge pull request !45 from 潘潘/fix

This commit is contained in:
潘潘 2022-09-16 16:32:35 +00:00 committed by Gitee
commit 8e263ab0f0
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
84 changed files with 1751 additions and 632 deletions

12
.env
View File

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

194
README.md
View File

@ -2,21 +2,17 @@
![logo](readme/logo-t-y.png) ![logo](readme/logo-t-y.png)
GoView 是一个高效的拖拽式低代码数据可视化开发平台,将图表或页面元素封装为基础组件,无需编写代码即可制作数据大屏,减少心智负担。 **`master-fetch` 分支是带有后端接口请求的分支**
### 😶 纯 **前端** 分支: **`master`** **后端项目地址:[https://gitee.com/MTrun/go-view-serve](https://gitee.com/MTrun/go-view-serve)**
### 👻 携带 **后端** 请求分支: **`master-fetch`** **接口说明地址:[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)**
### 📚 GoView **文档** 地址:[http://www.mtruning.club:81/](http://www.mtruning.club:81/) ## 使用
项目纯前端-Demo 地址:[https://www.mtruning.club](https://www.mtruning.club) 所有的接口地址位置:`src\api\path\*`
项目带后端-Demo 地址:[后端 Demo 地址](http://1.117.240.165:8080/goview/#/login) 接口地址修改:`.env`
文档-在线地址:[http://www.mtruning.club:81/](http://www.mtruning.club:81/)
文档-源码地址:[https://gitee.com/MTrun/go-view-doc](https://gitee.com/MTrun/go-view-doc)
### 🤯 后端项目 ### 🤯 后端项目
@ -24,123 +20,103 @@ GoView 是一个高效的拖拽式低代码数据可视化开发平台,将图
接口说明地址:[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://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)
技术点:
- 框架:基于 `Vue3` 框架编写,使用 `hooks` 写法抽离部分逻辑,使代码结构更加清晰;
- 类型:使用 `TypeScript` 进行类型约束,减少未知错误发生概率,可以大胆修改逻辑内容;
- 性能:多处性能优化,使用页面懒加载、组件动态注册、数据滚动加载等方式,提升页面渲染速度;
- 存储:拥有本地记忆,部分配置项采用 `storage` 存储本地,提升使用体验;
- 封装:项目进行了详细的工具类封装如:路由、存储、加/解密、文件处理、主题、NaiveUI 全局方法、组件等
工作台:
![项目截图](readme/go-view-canvas.png)
请求配置:
![项目截图](readme/go-view-fetch.png)
数据过滤:
![项目截图](readme/go-view-filter.png)
主题色:
![项目截图](readme/go-view-color.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 | 7.1.x | windows | 11 |
已完成图表:
| 分类 | 名称 | 名称 | 名称 |
| ------ | ---------------- | ---------------- | -------- |
| 图表 | 柱状图 | 横向柱状图 | 折线图 |
| \* | 单/多 折线面积图 | 饼图 | 水球图 |
| \* | 环形图 | NaiveUI 多种进度 | 🤠 |
| 信息 | 文字 | 图片 | 😶 |
| 列表 | 滚动排名列表 | 滚动表格 | 🤓 |
| 小组件 | 边框-01~13 | 装饰-01~05 | 数字翻牌 |
## 浏览器支持
开发和测试平台均在 `Google` 和最新版 `EDGE` 上完成,暂未测试 `IE11` 等其它浏览器,如有需求请自行测试与兼容。
## 安装
本项目采用` pnpm` 进行包管理
```shell ```shell
#建议使用 nrm 切换到淘宝源 https://registry.npmmirror.com/ # port
#pnpm VITE_DEV_PORT = '8080'
pnpm install
#yarn # development path
yarn install VITE_DEV_PATH = 'http://127.0.0.1:8080'
#npm # production path
npm install VITE_PRO_PATH = 'http://127.0.0.1:8080'
``` ```
## 启动 公共前缀修改:`src\settings\httpSetting.ts`
```shell ```shell
#pnpm // 请求前缀
pnpm dev export const axiosPre = '/api/goview'
# npm
npm run dev
#yarn
yarn dev
#Makefile
make dev
``` ```
## 编译 接口封装:`src\api\http.ts`
```shell ```ts
#pnpm import axiosInstance from './axios'
pnpm run build import { RequestHttpEnum, ContentTypeEnum } from '@/enums/httpEnum'
# npm export const get = (url: string, params?: object) => {
npm run build return axiosInstance({
url: url,
method: RequestHttpEnum.GET,
params: params,
})
}
#yarn export const post = (url: string, data?: object, headersType?: string) => {
yarn run build return axiosInstance({
url: url,
method: RequestHttpEnum.POST,
data: data,
headers: {
'Content-Type': headersType || ContentTypeEnum.JSON
}
})
}
#Makefile export const put = (url: string, data?: object, headersType?: string) => {
make dist 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: 新功能 * feat: 新功能
- fix: 修复 Bug * fix: 修复 Bug
- docs: 文档修改 * docs: 文档修改
- perf: 性能优化 * perf: 性能优化
- revert: 版本回退 * revert: 版本回退
- ci: CICD 集成相关 * ci: CICD集成相关
- test: 添加测试代码 * test: 添加测试代码
- refactor: 代码重构 * refactor: 代码重构
- build: 影响项目构建或依赖修改 * build: 影响项目构建或依赖修改
- style: 不影响程序逻辑的代码修改 * style: 不影响程序逻辑的代码修改
- chore: 不属于以上类型的其他类型(日常事务) * chore: 不属于以上类型的其他类型(日常事务)
## 交流 ## 交流

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,6 +1,6 @@
{ {
"name": "go-view", "name": "go-view",
"version": "1.0.9", "version": "2.0.6",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
@ -27,7 +27,7 @@
"screenfull": "^6.0.1", "screenfull": "^6.0.1",
"vue": "^3.2.31", "vue": "^3.2.31",
"vue-demi": "^0.13.1", "vue-demi": "^0.13.1",
"vue-i18n": "9.1.9", "vue-i18n": "9.1.10",
"vue-router": "4.0.12", "vue-router": "4.0.12",
"vue3-lazyload": "^0.2.5-beta", "vue3-lazyload": "^0.2.5-beta",
"vue3-sketch-ruler": "^1.3.3", "vue3-sketch-ruler": "^1.3.3",

72
pnpm-lock.yaml generated
View File

@ -52,7 +52,7 @@ specifiers:
vue: ^3.2.31 vue: ^3.2.31
vue-demi: ^0.13.1 vue-demi: ^0.13.1
vue-echarts: ^6.0.2 vue-echarts: ^6.0.2
vue-i18n: 9.1.9 vue-i18n: 9.1.10
vue-router: 4.0.12 vue-router: 4.0.12
vue-tsc: ^0.28.10 vue-tsc: ^0.28.10
vue3-lazyload: ^0.2.5-beta vue3-lazyload: ^0.2.5-beta
@ -78,7 +78,7 @@ dependencies:
screenfull: 6.0.1 screenfull: 6.0.1
vue: 3.2.37 vue: 3.2.37
vue-demi: 0.13.1_vue@3.2.37 vue-demi: 0.13.1_vue@3.2.37
vue-i18n: 9.1.9_vue@3.2.37 vue-i18n: 9.1.10_vue@3.2.37
vue-router: 4.0.12_vue@3.2.37 vue-router: 4.0.12_vue@3.2.37
vue3-lazyload: 0.2.5-beta_2yymnzrok6eda47acnj2yjm3ae vue3-lazyload: 0.2.5-beta_2yymnzrok6eda47acnj2yjm3ae
vue3-sketch-ruler: 1.3.4_vue@3.2.37 vue3-sketch-ruler: 1.3.4_vue@3.2.37
@ -649,60 +649,60 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true dev: true
/@intlify/core-base/9.1.9: /@intlify/core-base/9.1.10:
resolution: {integrity: sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==} resolution: {integrity: sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dependencies: dependencies:
'@intlify/devtools-if': 9.1.9 '@intlify/devtools-if': 9.1.10
'@intlify/message-compiler': 9.1.9 '@intlify/message-compiler': 9.1.10
'@intlify/message-resolver': 9.1.9 '@intlify/message-resolver': 9.1.10
'@intlify/runtime': 9.1.9 '@intlify/runtime': 9.1.10
'@intlify/shared': 9.1.9 '@intlify/shared': 9.1.10
'@intlify/vue-devtools': 9.1.9 '@intlify/vue-devtools': 9.1.10
dev: false dev: false
/@intlify/devtools-if/9.1.9: /@intlify/devtools-if/9.1.10:
resolution: {integrity: sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==} resolution: {integrity: sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dependencies: dependencies:
'@intlify/shared': 9.1.9 '@intlify/shared': 9.1.10
dev: false dev: false
/@intlify/message-compiler/9.1.9: /@intlify/message-compiler/9.1.10:
resolution: {integrity: sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==} resolution: {integrity: sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dependencies: dependencies:
'@intlify/message-resolver': 9.1.9 '@intlify/message-resolver': 9.1.10
'@intlify/shared': 9.1.9 '@intlify/shared': 9.1.10
source-map: 0.6.1 source-map: 0.6.1
dev: false dev: false
/@intlify/message-resolver/9.1.9: /@intlify/message-resolver/9.1.10:
resolution: {integrity: sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA==} resolution: {integrity: sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dev: false dev: false
/@intlify/runtime/9.1.9: /@intlify/runtime/9.1.10:
resolution: {integrity: sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==} resolution: {integrity: sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dependencies: dependencies:
'@intlify/message-compiler': 9.1.9 '@intlify/message-compiler': 9.1.10
'@intlify/message-resolver': 9.1.9 '@intlify/message-resolver': 9.1.10
'@intlify/shared': 9.1.9 '@intlify/shared': 9.1.10
dev: false dev: false
/@intlify/shared/9.1.9: /@intlify/shared/9.1.10:
resolution: {integrity: sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw==} resolution: {integrity: sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dev: false dev: false
/@intlify/vue-devtools/9.1.9: /@intlify/vue-devtools/9.1.10:
resolution: {integrity: sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==} resolution: {integrity: sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
dependencies: dependencies:
'@intlify/message-resolver': 9.1.9 '@intlify/message-resolver': 9.1.10
'@intlify/runtime': 9.1.9 '@intlify/runtime': 9.1.10
'@intlify/shared': 9.1.9 '@intlify/shared': 9.1.10
dev: false dev: false
/@jridgewell/gen-mapping/0.1.1: /@jridgewell/gen-mapping/0.1.1:
@ -5385,15 +5385,15 @@ packages:
- supports-color - supports-color
dev: true dev: true
/vue-i18n/9.1.9_vue@3.2.37: /vue-i18n/9.1.10_vue@3.2.37:
resolution: {integrity: sha512-JeRdNVxS2OGp1E+pye5XB6+M6BBkHwAv9C80Q7+kzoMdUDGRna06tjC0vCB/jDX9aWrl5swxOMFcyAr7or8XTA==} resolution: {integrity: sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
dependencies: dependencies:
'@intlify/core-base': 9.1.9 '@intlify/core-base': 9.1.10
'@intlify/shared': 9.1.9 '@intlify/shared': 9.1.10
'@intlify/vue-devtools': 9.1.9 '@intlify/vue-devtools': 9.1.10
'@vue/devtools-api': 6.1.4 '@vue/devtools-api': 6.1.4
vue: 3.2.37 vue: 3.2.37
dev: false dev: false

View File

@ -18,7 +18,7 @@ import { zhCN, dateZhCN, NConfigProvider } from 'naive-ui'
import { GoAppProvider } from '@/components/GoAppProvider' import { GoAppProvider } from '@/components/GoAppProvider'
import { I18n } from '@/components/I18n' import { I18n } from '@/components/I18n'
import { useDarkThemeHook, useThemeOverridesHook, useCode } from '@/hooks' import { useSystemInit, useDarkThemeHook, useThemeOverridesHook, useCode } from '@/hooks'
// //
const darkTheme = useDarkThemeHook() const darkTheme = useDarkThemeHook()
@ -28,4 +28,7 @@ const overridesTheme = useThemeOverridesHook()
// //
const hljsTheme = useCode() const hljsTheme = useCode()
//
useSystemInit()
</script> </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,19 +1,37 @@
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios' import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
import { ResultEnum } from "@/enums/httpEnum" import { ResultEnum, RequestHttpHeaderEnum } from "@/enums/httpEnum"
import { ErrorPageNameMap } from "@/enums/pageEnum" import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum"
import { redirectErrorPage } from '@/utils' 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({ 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, timeout: ResultEnum.TIMEOUT,
}) })
axiosInstance.interceptors.request.use( axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => { (config: AxiosRequestConfig) => {
// 白名单校验
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 = {
...config.headers,
[RequestHttpHeaderEnum.TOKEN]: info[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_TOKEN] || ''
}
return config return config
}, },
(error: AxiosRequestConfig) => { (err: AxiosRequestConfig) => {
Promise.reject(error) Promise.reject(err)
} }
) )
@ -21,13 +39,31 @@ axiosInstance.interceptors.request.use(
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
(res: AxiosResponse) => { (res: AxiosResponse) => {
const { code } = res.data as { code: number } 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) return Promise.resolve(res.data)
}, },
(err: AxiosResponse) => { (err: AxiosResponse) => {
window['$message'].error('接口异常,请检查!') httpErrorHandle()
Promise.reject(err) Promise.reject(err)
} }
) )

View File

@ -13,7 +13,7 @@ export const get = (url: string, params?: object) => {
return axiosInstance({ return axiosInstance({
url: url, url: url,
method: RequestHttpEnum.GET, method: RequestHttpEnum.GET,
params: params params: params,
}) })
} }

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,33 @@
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 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.

Before

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -40,8 +40,8 @@ export enum MenuEnum {
UN_GROUP = 'unGroup', UN_GROUP = 'unGroup',
// 后退 // 后退
BACK = 'back', BACK = 'back',
// 前进 FORWORD = 'forward',
FORWORD = 'forward' SAVE = 'save'
} }
// Win 键盘枚举 // Win 键盘枚举
@ -64,3 +64,15 @@ export enum MacKeyboard {
SHIFT_SOURCE_KEY = "⇧", SHIFT_SOURCE_KEY = "⇧",
ALT_SOURCE_KEY = "⌥" ALT_SOURCE_KEY = "⌥"
} }
// 同步状态枚举
export enum SyncEnum {
// 等待
PENDING,
// 开始
START,
// 成功
SUCCESS,
// 失败
FAILURE
}

View File

@ -1,13 +1,18 @@
/** // 模块 Path 前缀分类
* @description: export enum ModuleTypeEnum {
*/ SYSTEM = 'sys',
PROJECT = 'project',
}
// 请求结果集
export enum ResultEnum { export enum ResultEnum {
DATA_SUCCESS = 0, DATA_SUCCESS = 0,
SUCCESS = 200, SUCCESS = 200,
SERVER_ERROR = 500, SERVER_ERROR = 500,
SERVER_FORBIDDEN = 403, SERVER_FORBIDDEN = 403,
NOT_FOUND = 404, NOT_FOUND = 404,
TIMEOUT = 10042 TOKEN_OVERDUE = 886,
TIMEOUT = 10042,
} }
// 数据相关 // 数据相关
@ -26,9 +31,13 @@ export enum RequestContentTypeEnum {
SQL = 1 SQL = 1
} }
/** // 头部
* @description: export enum RequestHttpHeaderEnum {
*/ TOKEN = 'Token',
COOKIE = 'Cookie'
}
// 请求方法
export enum RequestHttpEnum { export enum RequestHttpEnum {
GET = 'get', GET = 'get',
POST = 'post', POST = 'post',
@ -109,9 +118,7 @@ export type RequestParams = {
} }
} }
/** // 常用的contentTyp类型
* @description: contentTyp类型
*/
export enum ContentTypeEnum { export enum ContentTypeEnum {
// json // json
JSON = 'application/json;charset=UTF-8', JSON = 'application/json;charset=UTF-8',

View File

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

View File

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

View File

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

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', help: 'Help',
contact: 'About Software', contact: 'About Software',
logout: 'Logout', logout: 'Logout',
logout_success: 'Logout success',
logout_failure: 'Logout Failed',
// system setting // system setting
sys_set: 'System Setting', sys_set: 'System Setting',
lang_set: 'Language Setting', lang_set: 'Language Setting',
@ -26,8 +28,14 @@ const global = {
r_more: 'More', 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 { export default {
global, global,
http,
login, login,
project project
} }

View File

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

View File

@ -1,6 +1,8 @@
export default { export default {
create_btn: 'Creat', 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', project: 'Project',
my: 'My', my: 'My',
new_project: 'New Project', new_project: 'New Project',

View File

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

View File

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

View File

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

View File

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

View File

@ -53,6 +53,7 @@ import {
ArrowForward as ArrowForwardIcon, ArrowForward as ArrowForwardIcon,
Planet as PawIcon, Planet as PawIcon,
Search as SearchIcon, Search as SearchIcon,
Reload as ReloadIcon,
ChevronUpOutline as ChevronUpOutlineIcon, ChevronUpOutline as ChevronUpOutlineIcon,
ChevronDownOutline as ChevronDownOutlineIcon, ChevronDownOutline as ChevronDownOutlineIcon,
Pulse as PulseIcon, Pulse as PulseIcon,
@ -86,6 +87,7 @@ import {
FitToScreen as FitToScreenIcon, FitToScreen as FitToScreenIcon,
FitToHeight as FitToHeightIcon, FitToHeight as FitToHeightIcon,
FitToWidth as FitToWidthIcon, FitToWidth as FitToWidthIcon,
Save as SaveIcon,
Carbon3DCursor as Carbon3DCursorIcon, Carbon3DCursor as Carbon3DCursorIcon,
Carbon3DSoftware as Carbon3DSoftwareIcon, Carbon3DSoftware as Carbon3DSoftwareIcon,
Filter as FilterIcon, Filter as FilterIcon,
@ -200,6 +202,8 @@ const ionicons5 = {
PawIcon, PawIcon,
// 搜索(放大镜) // 搜索(放大镜)
SearchIcon, SearchIcon,
// 加载
ReloadIcon,
// 过滤器 // 过滤器
FilterIcon, FilterIcon,
// 向上 // 向上
@ -256,6 +260,8 @@ const carbon = {
FitToScreenIcon, FitToScreenIcon,
FitToHeightIcon, FitToHeightIcon,
FitToWidthIcon, FitToWidthIcon,
// 保存
SaveIcon,
// 成组 // 成组
Carbon3DCursorIcon, Carbon3DCursorIcon,
// 解组 // 解组

View File

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

View File

@ -4,6 +4,10 @@ export const ErrorPage403 = () => import('@/views/exception/403.vue');
export const ErrorPage500 = () => import('@/views/exception/500.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 Layout = () => import('@/layout/index.vue');
export const ParentLayout = () => import('@/layout/parentLayout.vue'); export const ParentLayout = () => import('@/layout/parentLayout.vue');

View File

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

View File

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

View File

@ -55,9 +55,12 @@ export const backgroundImageSize = 5
// 预览展示方式 // 预览展示方式
export const previewScaleType = PreviewScaleEnum.FIT export const previewScaleType = PreviewScaleEnum.FIT
// 数据请求间隔 // 数据请求间隔s
export const requestInterval = 30 export const requestInterval = 30
// 工作台自动保存间隔s
export const saveInterval = 30
// 数据请求间隔单位 // 数据请求间隔单位
export const requestIntervalUnit = RequestHttpIntervalEnum.SECOND export const requestIntervalUnit = RequestHttpIntervalEnum.SECOND

View File

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

View File

@ -1,5 +1,6 @@
import { CreateComponentType, CreateComponentGroupType, FilterEnum } from '@/packages/index.d' import { CreateComponentType, CreateComponentGroupType, FilterEnum } from '@/packages/index.d'
import { HistoryActionTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d' import { HistoryActionTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
import { SyncEnum } from '@/enums/editPageEnum'
import { import {
RequestHttpEnum, RequestHttpEnum,
RequestContentTypeEnum, RequestContentTypeEnum,
@ -12,6 +13,26 @@ import {
import { PreviewScaleEnum } from '@/enums/styleEnum' import { PreviewScaleEnum } from '@/enums/styleEnum'
import type { ChartColorsNameType, GlobalThemeJsonType } from '@/settings/chartThemes/index' 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 { export enum EditCanvasTypeEnum {
EDIT_LAYOUT_DOM = 'editLayoutDom', EDIT_LAYOUT_DOM = 'editLayoutDom',
@ -20,12 +41,13 @@ export enum EditCanvasTypeEnum {
SCALE = 'scale', SCALE = 'scale',
USER_SCALE = 'userScale', USER_SCALE = 'userScale',
LOCK_SCALE = 'lockScale', LOCK_SCALE = 'lockScale',
SAVE_STATUS = 'saveStatus',
IS_CREATE = 'isCreate', IS_CREATE = 'isCreate',
IS_DRAG = 'isDrag', IS_DRAG = 'isDrag',
IS_SELECT = 'isSelect' IS_SELECT = 'isSelect'
} }
// 编辑区域 // 编辑区域(临时)
export type EditCanvasType = { export type EditCanvasType = {
// 编辑区域 DOM // 编辑区域 DOM
[EditCanvasTypeEnum.EDIT_LAYOUT_DOM]: HTMLElement | null [EditCanvasTypeEnum.EDIT_LAYOUT_DOM]: HTMLElement | null
@ -42,11 +64,13 @@ export type EditCanvasType = {
[EditCanvasTypeEnum.IS_CREATE]: boolean [EditCanvasTypeEnum.IS_CREATE]: boolean
// 拖拽中 // 拖拽中
[EditCanvasTypeEnum.IS_DRAG]: boolean [EditCanvasTypeEnum.IS_DRAG]: boolean
// 保存状态
[EditCanvasTypeEnum.SAVE_STATUS]: SyncEnum
// 框选中 // 框选中
[EditCanvasTypeEnum.IS_SELECT]: boolean [EditCanvasTypeEnum.IS_SELECT]: boolean
} }
// 滤镜/背景色/宽高主题等 // 画布数据/滤镜/背景色/宽高主题等
export enum EditCanvasConfigEnum { export enum EditCanvasConfigEnum {
WIDTH = 'width', WIDTH = 'width',
HEIGHT = 'height', HEIGHT = 'height',
@ -58,7 +82,12 @@ export enum EditCanvasConfigEnum {
PREVIEW_SCALE_TYPE = 'previewScaleType' PREVIEW_SCALE_TYPE = 'previewScaleType'
} }
export interface EditCanvasConfigType { // 画布属性(需保存)
export type EditCanvasConfigType = {
// 项目名称
[EditCanvasConfigEnum.PROJECT_NAME]: string,
// 项目描述
[EditCanvasConfigEnum.REMARKS]: string,
// 滤镜-色相 // 滤镜-色相
[FilterEnum.HUE_ROTATE]: number [FilterEnum.HUE_ROTATE]: number
// 滤镜-饱和度 // 滤镜-饱和度
@ -126,6 +155,7 @@ export type RecordChartType = {
// Store 枚举 // Store 枚举
export enum ChartEditStoreEnum { export enum ChartEditStoreEnum {
PROJECT_INFO = 'projectInfo',
EDIT_RANGE = 'editRange', EDIT_RANGE = 'editRange',
EDIT_CANVAS = 'editCanvas', EDIT_CANVAS = 'editCanvas',
RIGHT_MENU_SHOW = 'rightMenuShow', RIGHT_MENU_SHOW = 'rightMenuShow',
@ -176,6 +206,7 @@ export interface RequestConfigType extends RequestPublicConfigType {
// Store 类型 // Store 类型
export interface ChartEditStoreType { export interface ChartEditStoreType {
[ChartEditStoreEnum.PROJECT_INFO]: ProjectInfoType
[ChartEditStoreEnum.EDIT_CANVAS]: EditCanvasType [ChartEditStoreEnum.EDIT_CANVAS]: EditCanvasType
[ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: EditCanvasConfigType [ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: EditCanvasConfigType
[ChartEditStoreEnum.RIGHT_MENU_SHOW]: boolean [ChartEditStoreEnum.RIGHT_MENU_SHOW]: boolean

View File

@ -10,14 +10,22 @@ import { requestInterval, previewScaleType, requestIntervalUnit } from '@/settin
import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore' import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
// 全局设置 // 全局设置
import { useSettingStore } from '@/store/modules/settingStore/settingStore' import { useSettingStore } from '@/store/modules/settingStore/settingStore'
// 历史类型
import { HistoryActionTypeEnum, HistoryItemType, HistoryTargetTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
// 画布枚举
import { MenuEnum, SyncEnum } from '@/enums/editPageEnum'
import { import {
HistoryActionTypeEnum, getUUID,
HistoryItemType, loadingStart,
HistoryTargetTypeEnum loadingFinish,
} from '@/store/modules/chartHistoryStore/chartHistoryStore.d' loadingError,
import { MenuEnum } from '@/enums/editPageEnum' isString,
import { getUUID, loadingStart, loadingFinish, loadingError, isString, isArray } from '@/utils' isArray
} from '@/utils'
import { import {
ProjectInfoType,
ChartEditStoreEnum, ChartEditStoreEnum,
ChartEditStorage, ChartEditStorage,
ChartEditStoreType, ChartEditStoreType,
@ -36,6 +44,13 @@ const settingStore = useSettingStore()
export const useChartEditStore = defineStore({ export const useChartEditStore = defineStore({
id: 'useChartEditStore', id: 'useChartEditStore',
state: (): ChartEditStoreType => ({ state: (): ChartEditStoreType => ({
// 项目数据
projectInfo: {
projectName: '',
remarks: '',
thumbnail: '',
release: false
},
// 画布属性 // 画布属性
editCanvas: { editCanvas: {
// 编辑区域 Dom // 编辑区域 Dom
@ -54,7 +69,9 @@ export const useChartEditStore = defineStore({
// 拖拽中 // 拖拽中
isDrag: false, isDrag: false,
// 框选中 // 框选中
isSelect: false isSelect: false,
// 同步中
saveStatus: SyncEnum.PENDING
}, },
// 右键菜单 // 右键菜单
rightMenuShow: false, rightMenuShow: false,
@ -127,6 +144,9 @@ export const useChartEditStore = defineStore({
componentList: [] componentList: []
}), }),
getters: { getters: {
getProjectInfo(): ProjectInfoType {
return this.projectInfo
},
getMousePosition(): MousePositionType { getMousePosition(): MousePositionType {
return this.mousePosition return this.mousePosition
}, },
@ -161,6 +181,10 @@ export const useChartEditStore = defineStore({
} }
}, },
actions: { actions: {
// * 设置 peojectInfo 数据项
setProjectInfo<T extends keyof ProjectInfoType, K extends ProjectInfoType[T]>(key: T, value: K) {
this.projectInfo[key] = value
},
// * 设置 editCanvas 数据项 // * 设置 editCanvas 数据项
setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) { setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) {
this.editCanvas[key] = value this.editCanvas[key] = value
@ -793,7 +817,7 @@ export const useChartEditStore = defineStore({
loadingFinish() loadingFinish()
} }
}, },
// ---------------- // * 页面缩放设置-----------------
// * 设置页面大小 // * 设置页面大小
setPageSize(scale: number): void { setPageSize(scale: number): void {
this.setPageStyle('height', `${this.editCanvasConfig.height * scale}px`) 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 { SettingStoreType, ToolsStatusEnum } from './settingStore.d'
import { setLocalStorage, getLocalStorage } from '@/utils' import { setLocalStorage, getLocalStorage } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum' import { StorageEnum } from '@/enums/storageEnum'
const { GO_SYSTEM_SETTING_STORE } = StorageEnum const { GO_SETTING_STORE } = StorageEnum
const storageSetting: SettingStoreType = getLocalStorage( const storageSetting: SettingStoreType = getLocalStorage(
GO_SYSTEM_SETTING_STORE GO_SETTING_STORE
) )
// 全局设置 // 全局设置
@ -45,7 +45,7 @@ export const useSettingStore = defineStore({
this.$patch(state => { this.$patch(state => {
state[key] = value 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,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 * @param { File } file

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/componets'
export * from '@/utils/type' export * from '@/utils/type'
export * from '@/utils/file' export * from '@/utils/file'
export * from '@/utils/http'

View File

@ -35,7 +35,7 @@ export const loadingError = () => {
* }) * })
* ``` * ```
*/ */
export const goDialog = ( export const goDialog = (
params: { params: {
// 基本 // 基本
type?: DialogEnum type?: DialogEnum

View File

@ -1,11 +1,12 @@
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ResultEnum } from '@/enums/httpEnum' import { ResultEnum, RequestHttpHeaderEnum } from '@/enums/httpEnum'
import { ErrorPageNameMap, PageEnum } from '@/enums/pageEnum' import { ErrorPageNameMap, PageEnum, PreviewEnum } from '@/enums/pageEnum'
import { docPath, giteeSourceCodePath } from '@/settings/pathConst' 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 { StorageEnum } from '@/enums/storageEnum'
import { clearLocalStorage, getLocalStorage } from './storage' import { clearLocalStorage, getLocalStorage, clearCookie } from './storage'
import router from '@/router' import router from '@/router'
import { logoutApi } from '@/api/path'
/** /**
* * * *
@ -101,11 +102,20 @@ export const reloadRoutePage = () => {
} }
/** /**
* * 退 * * 退
*/ */
export const logout = () => { export const logout = async () => {
clearLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE) 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) 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() || '').split('?').shift()
} catch (error) {
window['$message'].warning('查询路由信息失败,请联系管理员!')
return ''
}
}
/** /**
* * * *
* @param confirm * @param confirm
@ -162,15 +185,14 @@ export const goHome = () => {
} }
/** /**
* * login * *
* @return boolean * @return boolean
*/ */
export const loginCheck = () => { export const loginCheck = () => {
try { try {
const info = getLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE) const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
if (!info) return false if (!info) return false
const decodeInfo = cryptoDecode(info) if (info[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_TOKEN]) {
if (decodeInfo) {
return true return true
} }
return false return false
@ -178,3 +200,14 @@ export const loginCheck = () => {
return false return false
} }
} }
/**
* *
* @returns
*/
export const previewPath = (id?: string | number) => {
const { origin, pathname } = document.location
const path = fetchPathByName(PreviewEnum.CHART_PREVIEW_NAME, 'href')
const previewPath = `${origin}${pathname}${path}/${id || fetchRouteParamsLocation()}`
return previewPath
}

View File

@ -5,7 +5,7 @@
* @param v stringiiy * @param v stringiiy
* @returns RemovableRef * @returns RemovableRef
*/ */
export const setLocalStorage = <T>(k: string, v: T) => { export const setLocalStorage = <T>(k: string, v: T) => {
try { try {
window.localStorage.setItem(k, JSON.stringify(v)) window.localStorage.setItem(k, JSON.stringify(v))
} catch (error) { } catch (error) {
@ -18,7 +18,7 @@
* @param k * @param k
* @returns any * @returns any
*/ */
export const getLocalStorage = (k: string) => { export const getLocalStorage = (k: string) => {
const item = window.localStorage.getItem(k) const item = window.localStorage.getItem(k)
try { try {
return item ? JSON.parse(item) : item return item ? JSON.parse(item) : item
@ -31,7 +31,7 @@
* * * *
* @param name * @param name
*/ */
export const clearLocalStorage = (name: string) => { export const clearLocalStorage = (name: string) => {
window.localStorage.removeItem(name) window.localStorage.removeItem(name)
} }
@ -69,3 +69,41 @@ export const getSessionStorage: (k: string) => any = (k: string) => {
export const clearSessioStorage = (name: string) => { export const clearSessioStorage = (name: string) => {
window.sessionStorage.removeItem(name) 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

@ -109,29 +109,6 @@ export const isMac = () => {
return /macintosh|mac os x/i.test(navigator.userAgent) 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)
}
}
}
/** /**
* * * *
*/ */

View File

@ -128,16 +128,20 @@ import { backgroundImageSize } from '@/settings/designSetting'
import { FileTypeEnum } from '@/enums/fileTypeEnum' import { FileTypeEnum } from '@/enums/fileTypeEnum'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { EditCanvasConfigEnum } from '@/store/modules/chartEditStore/chartEditStore.d' import { EditCanvasConfigEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { StylesSetting } from '@/components/Pages/ChartItemSetting' import { StylesSetting } from '@/components/Pages/ChartItemSetting'
import { UploadCustomRequestOptions } from 'naive-ui' import { UploadCustomRequestOptions } from 'naive-ui'
import { fileToUrl, loadAsyncComponent } from '@/utils' import { fileToUrl, loadAsyncComponent, fetchRouteParamsLocation } from '@/utils'
import { PreviewScaleEnum } from '@/enums/styleEnum' import { PreviewScaleEnum } from '@/enums/styleEnum'
import { ResultEnum } from '@/enums/httpEnum'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { uploadFile} from '@/api/path'
const { ColorPaletteIcon } = icon.ionicons5 const { ColorPaletteIcon } = icon.ionicons5
const { ScaleIcon, FitToScreenIcon, FitToHeightIcon, FitToWidthIcon } = icon.carbon const { ScaleIcon, FitToScreenIcon, FitToHeightIcon, FitToWidthIcon } = icon.carbon
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const systemStore = useSystemStore()
const canvasConfig = chartEditStore.getEditCanvasConfig const canvasConfig = chartEditStore.getEditCanvasConfig
const editCanvas = chartEditStore.getEditCanvas const editCanvas = chartEditStore.getEditCanvas
@ -261,17 +265,34 @@ const switchSelectColorHandle = () => {
// //
const customRequest = (options: UploadCustomRequestOptions) => { const customRequest = (options: UploadCustomRequestOptions) => {
const { file } = options const { file } = options
nextTick(() => { nextTick(async () => {
if(!systemStore.getFetchInfo.OSSUrl) {
window['$message'].error('添加图片失败,请刷新页面重试!')
return
}
if (file.file) { if (file.file) {
const ImageUrl = fileToUrl(file.file) //
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( chartEditStore.setEditCanvasConfig(
EditCanvasConfigEnum.BACKGROUND_IMAGE, EditCanvasConfigEnum.BACKGROUND_IMAGE,
ImageUrl uploadRes.data.objectContent.httpRequest.uri
) )
chartEditStore.setEditCanvasConfig( chartEditStore.setEditCanvasConfig(
EditCanvasConfigEnum.SELECT_COLOR, EditCanvasConfigEnum.SELECT_COLOR,
false false
) )
return
}
window['$message'].error('添加图片失败,请稍后重试!')
} else { } else {
window['$message'].error('添加图片失败,请稍后重试!') window['$message'].error('添加图片失败,请稍后重试!')
} }

View File

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

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

@ -116,9 +116,6 @@ const options = computed(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mr-10 {
margin-right: 10px;
}
.edit-history-popover { .edit-history-popover {
.btn-text { .btn-text {
font-size: 12px; font-size: 12px;

View File

@ -93,20 +93,25 @@ const shortcutKeyOptions = [
win: `${WinKeyboard.CTRL.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + Z `, win: `${WinKeyboard.CTRL.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + Z `,
mac: `${MacKeyboard.CTRL.toUpperCase()} + ${MacKeyboard.SHIFT.toUpperCase()} + Z ` mac: `${MacKeyboard.CTRL.toUpperCase()} + ${MacKeyboard.SHIFT.toUpperCase()} + Z `
}, },
{
label: '保存',
win: `${WinKeyboard.CTRL.toUpperCase()} + S `,
mac: `${MacKeyboard.CTRL.toUpperCase()} + S `,
},
{ {
label: '多选', label: '多选',
win: `${WinKeyboard.CTRL.toUpperCase()} + 🖱️ `, win: `${WinKeyboard.CTRL.toUpperCase()} + 🖱️ `,
mac: `${MacKeyboard.CTRL_SOURCE_KEY.toUpperCase()} + 🖱️ ` mac: `${MacKeyboard.CTRL.toUpperCase()} + 🖱️ `
}, },
{ {
label: '创建分组', label: '创建分组',
win: `${WinKeyboard.CTRL.toUpperCase()} + G / 🖱️ `, win: `${WinKeyboard.CTRL.toUpperCase()} + G / 🖱️ `,
mac: `${MacKeyboard.CTRL_SOURCE_KEY.toUpperCase()} + G / 🖱️` mac: `${MacKeyboard.CTRL.toUpperCase()} + G / 🖱️`
}, },
{ {
label: '解除分组', label: '解除分组',
win: `${WinKeyboard.CTRL.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + G `, win: `${WinKeyboard.CTRL.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + G `,
mac: `${MacKeyboard.CTRL_SOURCE_KEY.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + G ` mac: `${MacKeyboard.CTRL.toUpperCase()} + ${WinKeyboard.SHIFT.toUpperCase()} + G `
} }
] ]
const closeHandle = () => { const closeHandle = () => {

View File

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

View File

@ -7,7 +7,6 @@ import { useSync } from '@/views/chart/hooks/useSync.hook'
export const useFile = () => { export const useFile = () => {
const importUploadFileListRef = ref() const importUploadFileListRef = ref()
const { updateComponent } = useSync() const { updateComponent } = useSync()
// 上传-前置 // 上传-前置
//@ts-ignore //@ts-ignore
const importBeforeUpload = ({ file }) => { const importBeforeUpload = ({ file }) => {

View File

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

View File

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

View File

@ -88,6 +88,7 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { useLayout } from './hooks/useLayout.hook' import { useLayout } from './hooks/useLayout.hook'
import { useAddKeyboard } from '../hooks/useKeyboard.hook' import { useAddKeyboard } from '../hooks/useKeyboard.hook'
import { useSync } from '../hooks/useSync.hook'
import { dragHandle, dragoverHandle, mousedownHandleUnStop, useMouseHandle } from './hooks/useDrag.hook' import { dragHandle, dragoverHandle, mousedownHandleUnStop, useMouseHandle } from './hooks/useDrag.hook'
import { useComponentStyle, useSizeStyle } from './hooks/useStyle.hook' import { useComponentStyle, useSizeStyle } from './hooks/useStyle.hook'
@ -101,6 +102,7 @@ import { EditTools } from './components/EditTools'
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const { handleContextMenu } = useContextMenu() const { handleContextMenu } = useContextMenu()
const { dataSyncFetch, intervalDataSyncUpdate } = useSync()
// //
useLayout() useLayout()
@ -166,9 +168,13 @@ const rangeStyle = computed(() => {
} }
}) })
//
onMounted(() => { onMounted(() => {
//
useAddKeyboard() useAddKeyboard()
//
dataSyncFetch()
//
intervalDataSyncUpdate()
}) })
</script> </script>

View File

@ -29,26 +29,44 @@
</template> </template>
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
</n-tooltip> </n-tooltip>
<n-divider vertical />
<!-- 保存 -->
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<div class="save-btn" >
<n-button size="small" type="primary" ghost @click="dataSyncUpdate()">
<template #icon>
<n-icon>
<SaveIcon></SaveIcon>
</n-icon>
</template>
</n-button>
</div>
</template>
<span>保存</span>
</n-tooltip>
</n-space> </n-space>
</n-space> </n-space>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { toRefs, Ref, reactive, computed } from 'vue' import { toRefs, ref, Ref, reactive, computed } from 'vue'
import { renderIcon, goDialog, goHome } from '@/utils' import { renderIcon, goDialog, goHome } from '@/utils'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { useRemoveKeyboard } from '../../hooks/useKeyboard.hook' import { useRemoveKeyboard } from '../../hooks/useKeyboard.hook'
import { useSync } from '../../hooks/useSync.hook'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore' import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
import { HistoryStackEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d' import { HistoryStackEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore' import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d' import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
const { LayersIcon, BarChartIcon, PrismIcon, HomeIcon, ArrowBackIcon, ArrowForwardIcon } = icon.ionicons5 const { LayersIcon, BarChartIcon, PrismIcon, HomeIcon, ArrowBackIcon, ArrowForwardIcon } = icon.ionicons5
const { SaveIcon } = icon.carbon
const { setItem } = useChartLayoutStore() const { setItem } = useChartLayoutStore()
const { dataSyncUpdate } = useSync()
const { getLayers, getCharts, getDetails } = toRefs(useChartLayoutStore()) const { getLayers, getCharts, getDetails } = toRefs(useChartLayoutStore())
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const chartHistoryStore = useChartHistoryStore() const chartHistoryStore = useChartHistoryStore()
@ -130,7 +148,7 @@ const clickHistoryHandle = (item: ItemType<HistoryStackEnum>) => {
// //
const goHomeHandle = () => { const goHomeHandle = () => {
goDialog({ goDialog({
message: '返回将不会保存任何操作', message: '确定已保存了数据Ctrl / ⌘ + S并返回到首页吗',
isMaskClosable: true, isMaskClosable: true,
onPositiveCallback: () => { onPositiveCallback: () => {
goHome() goHome()

View File

@ -1,29 +1,98 @@
<template> <template>
<n-space class="go-mt-0"> <n-space>
<n-button v-for="item in btnList" :key="item.title" ghost @click="item.event"> <n-button
v-for="item in btnList"
:key="item.key"
:type="item.type()"
ghost
@click="item.event"
>
<template #icon> <template #icon>
<component :is="item.icon"></component> <component :is="item.icon"></component>
</template> </template>
<span>{{ item.title }}</span> <span>{{ item.title() }}</span>
</n-button> </n-button>
</n-space> </n-space>
<!-- 发布管理弹窗 -->
<n-modal v-model:show="modelShow" @afterLeave="closeHandle">
<n-list bordered class="go-system-setting">
<template #header>
<n-space justify="space-between">
<n-h3 class="go-mb-0">发布管理</n-h3>
<n-icon size="20" class="go-cursor-pointer" @click="closeHandle">
<close-icon></close-icon>
</n-icon>
</n-space>
</template>
<n-list-item>
<n-space :size="10">
<n-alert :show-icon="false" title="预览地址:" type="success">
{{ previewPath() }}
</n-alert>
<n-space vertical>
<n-button tertiary type="primary" @click="copyPreviewPath()">
复制地址
</n-button>
<n-button :type="release ? 'warning' : 'primary'" @click="sendHandle">
{{ release ? '取消发布' : '发布大屏' }}
</n-button>
</n-space>
</n-space>
</n-list-item>
<n-list-item>
<n-space :size="10">
<n-button @click="modelShowHandle">关闭弹窗</n-button>
</n-space>
</n-list-item>
</n-list>
</n-modal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { shallowReactive } from 'vue' import { ref, shallowReactive, watchEffect } from 'vue'
import { renderIcon, goDialog, fetchPathByName, routerTurnByPath, setSessionStorage, getLocalStorage } from '@/utils' import { useRoute } from 'vue-router'
import { useClipboard } from '@vueuse/core'
import { PreviewEnum } from '@/enums/pageEnum' import { PreviewEnum } from '@/enums/pageEnum'
import { StorageEnum } from '@/enums/storageEnum' import { StorageEnum } from '@/enums/storageEnum'
import { useRoute } from 'vue-router' import { ResultEnum } from '@/enums/httpEnum'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d' import { ProjectInfoEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { updateProjectApi } from '@/api/path'
import {
previewPath,
renderIcon,
fetchPathByName,
routerTurnByPath,
setSessionStorage,
getLocalStorage,
httpErrorHandle,
fetchRouteParamsLocation,
} from '@/utils'
import { icon } from '@/plugins' import { icon } from '@/plugins'
const { BrowsersOutlineIcon, SendIcon } = icon.ionicons5 const { BrowsersOutlineIcon, SendIcon, CloseIcon } = icon.ionicons5
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const previewPathRef = ref(previewPath())
const { copy, isSupported } = useClipboard({ source: previewPathRef })
const routerParamsInfo = useRoute() const routerParamsInfo = useRoute()
const modelShow = ref<boolean>(false)
const release = ref<boolean>(false)
watchEffect(() => {
release.value = chartEditStore.getProjectInfo.release || false
})
//
const closeHandle = () => {
modelShow.value = false
}
// //
const previewHandle = () => { const previewHandle = () => {
const path = fetchPathByName(PreviewEnum.CHART_PREVIEW_NAME, 'href') const path = fetchPathByName(PreviewEnum.CHART_PREVIEW_NAME, 'href')
@ -32,55 +101,100 @@ const previewHandle = () => {
// id // id
const previewId = typeof id === 'string' ? id : id[0] const previewId = typeof id === 'string' ? id : id[0]
const storageInfo = chartEditStore.getStorageInfo const storageInfo = chartEditStore.getStorageInfo
const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] const sessionStorageInfo =
getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || []
if (sessionStorageInfo?.length) { if (sessionStorageInfo?.length) {
const repeateIndex = sessionStorageInfo.findIndex((e: { id: string }) => e.id === previewId) const repeateIndex = sessionStorageInfo.findIndex(
(e: { id: string }) => e.id === previewId
)
// //
if (repeateIndex !== -1) { if (repeateIndex !== -1) {
sessionStorageInfo.splice(repeateIndex, 1, { id: previewId, ...storageInfo }) sessionStorageInfo.splice(repeateIndex, 1, {
id: previewId,
...storageInfo,
})
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo) setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
} else { } else {
sessionStorageInfo.push({ sessionStorageInfo.push({
id: previewId, ...storageInfo id: previewId,
...storageInfo,
}) })
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo) setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
} }
} else { } else {
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ id: previewId, ...storageInfo }]) setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [
{ id: previewId, ...storageInfo },
])
} }
// //
routerTurnByPath(path, [previewId], undefined, true) routerTurnByPath(path, [previewId], undefined, true)
} }
//
const modelShowHandle = () => {
modelShow.value = !modelShow.value
}
//
const copyPreviewPath = (successText?: string, failureText?: string) => {
if (isSupported) {
copy()
window['$message'].success(successText || '复制成功!')
} else {
window['$message'].error(failureText || '复制失败!')
}
}
// //
const sendHandle = () => { const sendHandle = async () => {
goDialog({ const res = (await updateProjectApi({
message: '想体验发布功能,请前往 master-fetch 分支查看: https://gitee.com/MTrun/go-view/tree/master-fetch', id: fetchRouteParamsLocation(),
positiveText: '了然', //
closeNegativeText: true, state: release.value ? -1 : 1,
onPositiveCallback: () => {} })) as unknown as MyResponseType
})
if (res.code === ResultEnum.SUCCESS) {
modelShowHandle()
if (!release.value) {
copyPreviewPath('发布成功!已复制地址到剪贴板~', '发布成功!')
} else {
window['$message'].success(`已取消发布`)
}
chartEditStore.setProjectInfo(ProjectInfoEnum.RELEASE, !release.value)
} else {
httpErrorHandle()
}
} }
const btnList = shallowReactive([ const btnList = shallowReactive([
{ {
select: true, key: 'preview',
title: '预览', title: () => '预览',
type: () => 'default',
icon: renderIcon(BrowsersOutlineIcon), icon: renderIcon(BrowsersOutlineIcon),
event: previewHandle event: previewHandle,
}, },
{ {
select: true, key: 'release',
title: '发布', title: () => (release.value ? '已发布' : '发布'),
icon: renderIcon(SendIcon), icon: renderIcon(SendIcon),
event: sendHandle type: () => (release.value ? 'primary' : 'default'),
} event: modelShowHandle,
},
]) ])
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.align-center { @include go('system-setting') {
margin-top: -4px; @extend .go-background-filter;
min-width: 100px;
max-width: 60vw;
padding-bottom: 20px;
@include deep() {
.n-list-item:not(:last-child) {
border-bottom: 0;
}
}
} }
</style> </style>

View File

@ -6,9 +6,7 @@
<n-text @click="handleFocus"> <n-text @click="handleFocus">
工作空间 - 工作空间 -
<n-button v-show="!focus" secondary round size="tiny"> <n-button v-show="!focus" secondary round size="tiny">
<span class="title"> <span class="title">{{ comTitle }}</span>
{{ comTitle }}
</span>
</n-button> </n-button>
</n-text> </n-text>
@ -29,31 +27,31 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, nextTick, computed } from 'vue' import { ref, nextTick, computed, watchEffect } from 'vue'
import { fetchRouteParams } from '@/utils' import { ResultEnum } from '@/enums/httpEnum'
import { fetchRouteParamsLocation, httpErrorHandle } from '@/utils'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { ProjectInfoEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { updateProjectApi } from '@/api/path'
import { useSync } from '../../hooks/useSync.hook'
import { icon } from '@/plugins' import { icon } from '@/plugins'
const chartEditStore = useChartEditStore()
const { dataSyncUpdate } = useSync()
const { FishIcon } = icon.ionicons5 const { FishIcon } = icon.ionicons5
const focus = ref<boolean>(false) const focus = ref<boolean>(false)
const inputInstRef = ref(null) const inputInstRef = ref(null)
// id const title = ref<string>(fetchRouteParamsLocation())
const fetchProhectInfoById = () => {
const routeParamsRes = fetchRouteParams()
if (!routeParamsRes) return
const { id } = routeParamsRes
if (id.length) {
return id[0]
}
return ''
}
const title = ref<string>(fetchProhectInfoById() || '')
watchEffect(() => {
title.value = chartEditStore.getProjectInfo.projectName || ''
})
const comTitle = computed(() => { const comTitle = computed(() => {
title.value = title.value.replace(/\s/g, ""); title.value = title.value && title.value.replace(/\s/g, "")
return title.value.length ? title.value : '新项目' return title.value.length ? title.value : fetchRouteParamsLocation()
}) })
const handleFocus = () => { const handleFocus = () => {
@ -63,8 +61,18 @@ const handleFocus = () => {
}) })
} }
const handleBlur = () => { const handleBlur = async () => {
focus.value = false focus.value = false
chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_NAME, title.value || '')
const res = await updateProjectApi({
id: fetchRouteParamsLocation(),
projectName: title.value,
}) as unknown as MyResponseType
if(res.code === ResultEnum.SUCCESS) {
dataSyncUpdate()
} else {
httpErrorHandle()
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,4 +1,5 @@
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useSync } from './useSync.hook'
import { WinKeyboard, MacKeyboard, MenuEnum } from '@/enums/editPageEnum' import { WinKeyboard, MacKeyboard, MenuEnum } from '@/enums/editPageEnum'
import throttle from 'lodash/throttle' import throttle from 'lodash/throttle'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
@ -6,7 +7,7 @@ import debounce from 'lodash/debounce'
import keymaster from 'keymaster' import keymaster from 'keymaster'
// Keymaster可以支持识别以下组合键shiftoptionaltctrlcontrolcommand和⌘ // Keymaster可以支持识别以下组合键shiftoptionaltctrlcontrolcommand和⌘
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const useSyncIns = useSync()
const winCtrlMerge = (e: string) => `${WinKeyboard.CTRL}+${e}` const winCtrlMerge = (e: string) => `${WinKeyboard.CTRL}+${e}`
const winShiftMerge = (e: string) => `${WinKeyboard.SHIFT}+${e}` const winShiftMerge = (e: string) => `${WinKeyboard.SHIFT}+${e}`
const winAltMerge = (e: string) => `${WinKeyboard.ALT}+${e}` const winAltMerge = (e: string) => `${WinKeyboard.ALT}+${e}`
@ -22,6 +23,7 @@ export const winKeyboardValue = {
[MenuEnum.DELETE]: 'delete', [MenuEnum.DELETE]: 'delete',
[MenuEnum.BACK]: winCtrlMerge('z'), [MenuEnum.BACK]: winCtrlMerge('z'),
[MenuEnum.FORWORD]: winCtrlMerge(winShiftMerge('z')), [MenuEnum.FORWORD]: winCtrlMerge(winShiftMerge('z')),
[MenuEnum.SAVE]: winCtrlMerge('s'),
[MenuEnum.GROUP]: winCtrlMerge('g'), [MenuEnum.GROUP]: winCtrlMerge('g'),
[MenuEnum.UN_GROUP]: winCtrlMerge(winShiftMerge('g')), [MenuEnum.UN_GROUP]: winCtrlMerge(winShiftMerge('g')),
} }
@ -43,6 +45,7 @@ export const macKeyboardValue = {
[MenuEnum.DELETE]: macCtrlMerge('backspace'), [MenuEnum.DELETE]: macCtrlMerge('backspace'),
[MenuEnum.BACK]: macCtrlMerge('z'), [MenuEnum.BACK]: macCtrlMerge('z'),
[MenuEnum.FORWORD]: macCtrlMerge(macShiftMerge('z')), [MenuEnum.FORWORD]: macCtrlMerge(macShiftMerge('z')),
[MenuEnum.SAVE]: macCtrlMerge('s'),
[MenuEnum.GROUP]: macCtrlMerge('g'), [MenuEnum.GROUP]: macCtrlMerge('g'),
[MenuEnum.UN_GROUP]: macCtrlMerge(macShiftMerge('g')), [MenuEnum.UN_GROUP]: macCtrlMerge(macShiftMerge('g')),
} }
@ -62,6 +65,7 @@ const winKeyList: Array<string> = [
winKeyboardValue.back, winKeyboardValue.back,
winKeyboardValue.forward, winKeyboardValue.forward,
winKeyboardValue.save,
winKeyboardValue.group, winKeyboardValue.group,
winKeyboardValue.unGroup, winKeyboardValue.unGroup,
] ]
@ -81,6 +85,7 @@ const macKeyList: Array<string> = [
macKeyboardValue.back, macKeyboardValue.back,
macKeyboardValue.forward, macKeyboardValue.forward,
macKeyboardValue.save,
macKeyboardValue.group, macKeyboardValue.group,
macKeyboardValue.unGroup, macKeyboardValue.unGroup,
] ]
@ -156,6 +161,11 @@ export const useAddKeyboard = () => {
case keyboardValue.unGroup: case keyboardValue.unGroup:
keymaster(e, throttle(() => { chartEditStore.setUnGroup(); return false }, throttleTime)) keymaster(e, throttle(() => { chartEditStore.setUnGroup(); return false }, throttleTime))
break; break;
// 保存 ct+s
case keyboardValue.save:
keymaster(e, throttle(() => { useSyncIns.dataSyncUpdate(); return false }, 200))
break;
} }
} }
winKeyList.forEach((key: string) => { winKeyList.forEach((key: string) => {

View File

@ -1,8 +1,19 @@
import { getUUID } from '@/utils' import { onUnmounted } from 'vue';
import html2canvas from 'html2canvas'
import { getUUID, httpErrorHandle, fetchRouteParamsLocation, base64toFile } from '@/utils'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { ChartEditStoreEnum, ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' import { EditCanvasTypeEnum, ChartEditStoreEnum, ProjectInfoEnum, ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d'
import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore' import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { fetchChartComponent, fetchConfigComponent, createComponent } from '@/packages/index' import { fetchChartComponent, fetchConfigComponent, createComponent } from '@/packages/index'
import { saveInterval } from '@/settings/designSetting'
import throttle from 'lodash/throttle'
// 接口状态
import { ResultEnum } from '@/enums/httpEnum'
// 接口
import { saveProjectApi, fetchProjectApi, uploadFile, updateProjectApi } from '@/api/path'
// 画布枚举
import { SyncEnum } from '@/enums/editPageEnum'
import { CreateComponentType, CreateComponentGroupType, ConfigType } from '@/packages/index.d' import { CreateComponentType, CreateComponentGroupType, ConfigType } from '@/packages/index.d'
import { PublicGroupConfigClass } from '@/packages/public/publicConfig' import { PublicGroupConfigClass } from '@/packages/public/publicConfig'
@ -10,6 +21,7 @@ import { PublicGroupConfigClass } from '@/packages/public/publicConfig'
export const useSync = () => { export const useSync = () => {
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
const chartHistoryStore = useChartHistoryStore() const chartHistoryStore = useChartHistoryStore()
const systemStore = useSystemStore()
/** /**
* * * *
@ -105,7 +117,120 @@ export const useSync = () => {
} }
} }
/**
* *
* @param projectData
* @returns
*/
const updateStoreInfo = (projectData: {
id: string,
projectName: string,
indexImage: string,
remarks: string,
state: number
}) => {
const { projectName, remarks, indexImage, state } = projectData
// 名称
chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_NAME, projectName)
// 描述
chartEditStore.setProjectInfo(ProjectInfoEnum.REMARKS, remarks)
// 缩略图
chartEditStore.setProjectInfo(ProjectInfoEnum.THUMBNAIL, indexImage)
// 发布
chartEditStore.setProjectInfo(ProjectInfoEnum.RELEASE, state === 1)
}
// * 数据获取
const dataSyncFetch = async () => {
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.START)
try {
const res = await fetchProjectApi({ projectId: fetchRouteParamsLocation() }) as unknown as MyResponseType
if (res.code === ResultEnum.SUCCESS) {
if (res.data) {
updateStoreInfo(res.data)
// 更新全局数据
await updateComponent(JSON.parse(res.data.content))
return
}
setTimeout(() => {
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.SUCCESS)
}, 1000)
return
}
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE)
} catch (error) {
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE)
httpErrorHandle()
}
}
// * 数据保存
const dataSyncUpdate = throttle(async () => {
if(!fetchRouteParamsLocation()) return
if(!systemStore.getFetchInfo.OSSUrl) {
window['$message'].error('数据保存失败,请刷新页面重试!')
return
}
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.START)
// 获取缩略图片
const range = document.querySelector('.go-edit-range') as HTMLElement
// 生成图片
const canvasImage: HTMLCanvasElement = await html2canvas(range, {
backgroundColor: null,
allowTaint: true,
useCORS: true
})
// 上传预览图
let uploadParams = new FormData()
uploadParams.append('object', base64toFile(canvasImage.toDataURL(), `${fetchRouteParamsLocation()}_index_preview.png`))
const uploadRes = await uploadFile(systemStore.getFetchInfo.OSSUrl, uploadParams) as unknown as MyResponseType
// 保存预览图
if(uploadRes.code === ResultEnum.SUCCESS) {
await updateProjectApi({
id: fetchRouteParamsLocation(),
indexImage: uploadRes.data.objectContent.httpRequest.uri
})
}
// 保存数据
let params = new FormData()
params.append('projectId', fetchRouteParamsLocation())
params.append('content', JSON.stringify(chartEditStore.getStorageInfo || {}))
const res= await saveProjectApi(params) as unknown as MyResponseType
if (res.code === ResultEnum.SUCCESS) {
// 成功状态
setTimeout(() => {
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.SUCCESS)
}, 1000)
return
}
// 失败状态
chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE)
}, 3000)
// * 定时处理
const intervalDataSyncUpdate = () => {
// 定时获取数据
const syncTiming = setInterval(() => {
dataSyncUpdate()
}, saveInterval * 1000)
// 销毁
onUnmounted(() => {
clearInterval(syncTiming)
})
}
return { return {
updateComponent updateComponent,
updateStoreInfo,
dataSyncFetch,
dataSyncUpdate,
intervalDataSyncUpdate
} }
} }

View File

@ -4,7 +4,7 @@
<img src="~@/assets/images/exception/403.svg" alt="" /> <img src="~@/assets/images/exception/403.svg" alt="" />
</div> </div>
<div class="text-center"> <div class="text-center">
<h1 class="text-base text-gray-500">抱歉你无权访问该页面</h1> <h1>抱歉你无权访问该页面</h1>
</div> </div>
<n-button type="primary" @click="goHome">回到首页</n-button> <n-button type="primary" @click="goHome">回到首页</n-button>
</div> </div>

View File

@ -4,7 +4,7 @@
<img src="~@/assets/images/exception/404.svg" alt="" /> <img src="~@/assets/images/exception/404.svg" alt="" />
</div> </div>
<div class="text-center"> <div class="text-center">
<h1 class="text-base text-gray-500">抱歉你访问的页面不存在</h1> <h1>抱歉你访问的页面不存在</h1>
</div> </div>
<n-button type="primary" @click="goHome">回到首页</n-button> <n-button type="primary" @click="goHome">回到首页</n-button>
</div> </div>

View File

@ -4,9 +4,9 @@
<img src="~@/assets/images/exception/500.svg" alt="" /> <img src="~@/assets/images/exception/500.svg" alt="" />
</div> </div>
<div class="text-center"> <div class="text-center">
<h1 class="text-base text-gray-500">抱歉服务器出错了</h1> <h1>抱歉服务器出错了</h1>
</div> </div>
<n-button type="primary" secondary @click="goHome">回到首页</n-button> <n-button type="primary" secondary @click="goLogin">重新登录</n-button>
</div> </div>
</template> </template>
@ -14,8 +14,8 @@
import { PageEnum } from '@/enums/pageEnum' import { PageEnum } from '@/enums/pageEnum'
import { routerTurnByName } from '@/utils' import { routerTurnByName } from '@/utils'
function goHome() { function goLogin() {
routerTurnByName(PageEnum.BASE_HOME_NAME) routerTurnByName(PageEnum.BASE_LOGIN_NAME)
} }
</script> </script>

View File

@ -118,45 +118,38 @@
import { reactive, ref, onMounted } from 'vue' import { reactive, ref, onMounted } from 'vue'
import shuffle from 'lodash/shuffle' import shuffle from 'lodash/shuffle'
import { carouselInterval } from '@/settings/designSetting' import { carouselInterval } from '@/settings/designSetting'
import { useDesignStore } from '@/store/modules/designStore/designStore' import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { SystemStoreEnum } from '@/store/modules/systemStore/systemStore.d'
import { GoThemeSelect } from '@/components/GoThemeSelect' import { GoThemeSelect } from '@/components/GoThemeSelect'
import { GoLangSelect } from '@/components/GoLangSelect' import { GoLangSelect } from '@/components/GoLangSelect'
import { LayoutHeader } from '@/layout/components/LayoutHeader' import { LayoutHeader } from '@/layout/components/LayoutHeader'
import { LayoutFooter } from '@/layout/components/LayoutFooter' import { LayoutFooter } from '@/layout/components/LayoutFooter'
import { PageEnum } from '@/enums/pageEnum' import { PageEnum } from '@/enums/pageEnum'
import { icon } from '@/plugins'
import { StorageEnum } from '@/enums/storageEnum' import { StorageEnum } from '@/enums/storageEnum'
import { routerTurnByName, cryptoEncode, setLocalStorage } from '@/utils' import { icon } from '@/plugins'
const { GO_LOGIN_INFO_STORE } = StorageEnum import { routerTurnByName } from '@/utils'
import { loginApi } from '@/api/path'
const { PersonOutlineIcon, LockClosedOutlineIcon } = icon.ionicons5
interface FormState { interface FormState {
username: string username: string
password: string password: string
} }
const { GO_SYSTEM_STORE } = StorageEnum
const { PersonOutlineIcon, LockClosedOutlineIcon } = icon.ionicons5
const formRef = ref() const formRef = ref()
const loading = ref(false) const loading = ref(false)
const autoLogin = ref(true) const autoLogin = ref(true)
const show = ref(false) const show = ref(false)
const showBg = ref(false) const showBg = ref(false)
const designStore = useDesignStore() const systemStore = useSystemStore()
const t = window['$t'] const t = window['$t']
onMounted(() => {
setTimeout(() => {
show.value = true
}, 300)
setTimeout(() => {
showBg.value = true
}, 100)
})
const formInline = reactive({ const formInline = reactive({
username: 'admin', username: 'admin',
password: '123456', password: 'admin',
}) })
const rules = { const rules = {
@ -196,38 +189,55 @@ const getImageUrl = (name: string, folder: string) => {
return new URL(`../../assets/images/${folder}/${name}.png`, import.meta.url).href return new URL(`../../assets/images/${folder}/${name}.png`, import.meta.url).href
} }
// //
const shuffleHandle = () => { const shuffleHandle = () => {
shuffleTimiing.value = setInterval(() => { shuffleTimiing.value = setInterval(() => {
bgList.value = shuffle(bgList.value) bgList.value = shuffle(bgList.value)
}, carouselInterval) }, carouselInterval)
} }
// //
const handleSubmit = (e: Event) => { const handleSubmit = async (e: Event) => {
e.preventDefault() e.preventDefault()
formRef.value.validate(async (errors: any) => { formRef.value.validate(async (errors: any) => {
if (!errors) { if (!errors) {
const { username, password } = formInline const { username, password } = formInline
loading.value = true loading.value = true
setLocalStorage( //
GO_LOGIN_INFO_STORE, const res = await loginApi({
cryptoEncode(
JSON.stringify({
username, username,
password, password
}) as unknown as MyResponseType
if(res.data) {
const { tokenValue } = res.data.token
const { nickname, username, id } = res.data.userinfo
// pinia
systemStore.setItem(SystemStoreEnum.USER_INFO, {
userToken: tokenValue,
userId: id,
userName: username,
nickName: nickname,
}) })
)
) window['$message'].success(t('login.login_success'))
window['$message'].success(`${t('login.login_success')}!`)
routerTurnByName(PageEnum.BASE_HOME_NAME, true) routerTurnByName(PageEnum.BASE_HOME_NAME, true)
}
} else { } else {
window['$message'].error(`${t('login.login_message')}!`) window['$message'].error(t('login.login_message'))
} }
}) })
} }
onMounted(() => { onMounted(() => {
setTimeout(() => {
show.value = true
}, 300)
setTimeout(() => {
showBg.value = true
}, 100)
shuffleHandle() shuffleHandle()
}) })
</script> </script>

View File

@ -8,7 +8,7 @@ export const useComInstall = (localStorageInfo: ChartEditStorageType) => {
// 注册组件(一开始无法获取window['$vue']) // 注册组件(一开始无法获取window['$vue'])
const intervalTiming = setInterval(() => { const intervalTiming = setInterval(() => {
if (window['$vue'].component) { if (window['$vue']?.component) {
clearInterval(intervalTiming) clearInterval(intervalTiming)
const intComponent = (target: CreateComponentType) => { const intComponent = (target: CreateComponentType) => {

View File

@ -4,6 +4,7 @@ import type { ChartEditStorageType } from '../index.d'
import { PreviewScaleEnum } from '@/enums/styleEnum' import { PreviewScaleEnum } from '@/enums/styleEnum'
export const useScale = (localStorageInfo: ChartEditStorageType) => { export const useScale = (localStorageInfo: ChartEditStorageType) => {
const entityRef = ref() const entityRef = ref()
const previewRef = ref() const previewRef = ref()
const width = ref(localStorageInfo.editCanvasConfig.width) const width = ref(localStorageInfo.editCanvasConfig.width)

View File

@ -1,5 +1,6 @@
import { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' import { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d'
export interface ChartEditStorageType extends ChartEditStorage { export interface ChartEditStorageType extends ChartEditStorage {
id: string id: string,
isRelease?: boolean
} }

View File

@ -1,91 +1,9 @@
<template> <template>
<div :class="`go-preview ${localStorageInfo.editCanvasConfig.previewScaleType}`"> <suspense>
<template v-if="showEntity"> <suspense-index></suspense-index>
<!-- 实体区域 --> </suspense>
<div ref="entityRef" class="go-preview-entity">
<!-- 缩放层 -->
<div ref="previewRef" class="go-preview-scale">
<!-- 展示层 -->
<div :style="previewRefStyle" v-if="show">
<!-- 渲染层 -->
<preview-render-list :localStorageInfo="localStorageInfo"></preview-render-list>
</div>
</div>
</div>
</template>
<template v-else>
<!-- 缩放层 -->
<div ref="previewRef" class="go-preview-scale">
<!-- 展示层 -->
<div :style="previewRefStyle" v-if="show">
<!-- 渲染层 -->
<preview-render-list :localStorageInfo="localStorageInfo"></preview-render-list>
</div>
</div>
</template>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import suspenseIndex from './suspenseIndex.vue'
import { PreviewRenderList } from './components/PreviewRenderList'
import { getFilterStyle } from '@/utils'
import { getEditCanvasConfigStyle, getSessionStorageInfo } from './utils'
import { useComInstall } from './hooks/useComInstall.hook'
import { useScale } from './hooks/useScale.hook'
import { useStore } from './hooks/useStore.hook'
import { PreviewScaleEnum } from '@/enums/styleEnum'
import type { ChartEditStorageType } from './index.d'
const localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
const previewRefStyle = computed(() => {
return {
...getEditCanvasConfigStyle(localStorageInfo.editCanvasConfig),
...getFilterStyle(localStorageInfo.editCanvasConfig)
}
})
const showEntity = computed(() => {
const type = localStorageInfo.editCanvasConfig.previewScaleType
return type === PreviewScaleEnum.SCROLL_Y || type === PreviewScaleEnum.SCROLL_X
})
useStore(localStorageInfo)
const { entityRef, previewRef } = useScale(localStorageInfo)
const { show } = useComInstall(localStorageInfo)
</script> </script>
<style lang="scss" scoped>
@include go('preview') {
position: relative;
height: 100vh;
width: 100vw;
@include background-image('background-image');
&.fit,
&.full {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.go-preview-scale {
transform-origin: center center;
}
}
&.scrollY {
overflow-x: hidden;
.go-preview-scale {
transform-origin: left top;
}
}
&.scrollX {
overflow-y: hidden;
.go-preview-scale {
transform-origin: left top;
}
}
.go-preview-entity {
overflow: hidden;
}
}
</style>

View File

@ -0,0 +1,110 @@
<template>
<div
:class="`go-preview ${localStorageInfo.editCanvasConfig.previewScaleType}`"
>
<template v-if="showEntity">
<!-- 实体区域 -->
<div ref="entityRef" class="go-preview-entity">
<!-- 缩放层 -->
<div ref="previewRef" class="go-preview-scale">
<!-- 展示层 -->
<div :style="previewRefStyle" v-if="show">
<!-- 渲染层 -->
<preview-render-list
:localStorageInfo="localStorageInfo"
></preview-render-list>
</div>
</div>
</div>
</template>
<template v-else>
<!-- 缩放层 -->
<div ref="previewRef" class="go-preview-scale">
<!-- 展示层 -->
<div :style="previewRefStyle" v-if="show">
<!-- 渲染层 -->
<preview-render-list
:localStorageInfo="localStorageInfo"
></preview-render-list>
</div>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { PreviewRenderList } from './components/PreviewRenderList'
import { getFilterStyle, routerTurnByName, getSessionStorage } from '@/utils'
import { getEditCanvasConfigStyle, getSessionStorageInfo } from './utils'
import { PageEnum } from '@/enums/pageEnum'
import { StorageEnum } from '@/enums/storageEnum'
import { useScale } from './hooks/useScale.hook'
import { useStore } from './hooks/useStore.hook'
import { PreviewScaleEnum } from '@/enums/styleEnum'
import { useComInstall } from './hooks/useComInstall.hook'
import type { ChartEditStorageType } from './index.d'
const storageList: ChartEditStorageType[] = getSessionStorage(
StorageEnum.GO_CHART_STORAGE_LIST
)
const localStorageInfo = await getSessionStorageInfo() as ChartEditStorageType
// @ts-ignore
if(localStorageInfo.isRelease === false) {
routerTurnByName(PageEnum.REDIRECT_UN_PUBLISH_NAME, true, false)
}
const previewRefStyle = computed(() => {
return {
...getEditCanvasConfigStyle(localStorageInfo.editCanvasConfig),
...getFilterStyle(localStorageInfo.editCanvasConfig),
}
})
const showEntity = computed(() => {
const type = localStorageInfo.editCanvasConfig.previewScaleType
return (
type === PreviewScaleEnum.SCROLL_Y || type === PreviewScaleEnum.SCROLL_X
)
})
useStore(localStorageInfo)
const { entityRef, previewRef } = useScale(localStorageInfo)
const { show } = useComInstall(localStorageInfo)
</script>
<style lang="scss" scoped>
@include go('preview') {
position: relative;
height: 100vh;
width: 100vw;
@include background-image('background-image');
&.fit,
&.full {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.go-preview-scale {
transform-origin: center center;
}
}
&.scrollY {
overflow-x: hidden;
.go-preview-scale {
transform-origin: left top;
}
}
&.scrollX {
overflow-y: hidden;
.go-preview-scale {
transform-origin: left top;
}
}
.go-preview-entity {
overflow: hidden;
}
}
</style>

View File

@ -1,26 +1,40 @@
import { getSessionStorage } from '@/utils' import { getSessionStorage, fetchRouteParamsLocation, httpErrorHandle } from '@/utils'
import { ResultEnum } from '@/enums/httpEnum'
import { StorageEnum } from '@/enums/storageEnum' import { StorageEnum } from '@/enums/storageEnum'
import { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' import { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d'
import { fetchProjectApi } from '@/api/path'
export interface ChartEditStorageType extends ChartEditStorage { export interface ChartEditStorageType extends ChartEditStorage {
id: string id: string
} }
// 根据路由 id 获取存储数据的信息 // 根据路由 id 获取存储数据的信息
export const getSessionStorageInfo = () => { export const getSessionStorageInfo = async () => {
const urlHash = document.location.hash const id = fetchRouteParamsLocation()
const toPathArray = urlHash.split('/')
const id = toPathArray && toPathArray[toPathArray.length - 1]
const storageList: ChartEditStorageType[] = getSessionStorage( const storageList: ChartEditStorageType[] = getSessionStorage(
StorageEnum.GO_CHART_STORAGE_LIST StorageEnum.GO_CHART_STORAGE_LIST
) )
if(!storageList) return // 是否本地预览
if (!storageList || storageList.findIndex(e => e.id === id.toString()) === -1) {
// 接口调用
const res = await fetchProjectApi({ projectId: id }) as unknown as MyResponseType
if (res.code === ResultEnum.SUCCESS) {
const { content, state } = res.data
if (state === -1) {
// 跳转未发布页
return { isRelease: false }
}
return { ...JSON.parse(content), id }
} else {
httpErrorHandle()
}
} else {
// 本地读取
for (let i = 0; i < storageList.length; i++) { for (let i = 0; i < storageList.length; i++) {
if (id.toString() === storageList[i]['id']) { if (id.toString() === storageList[i]['id']) {
return storageList[i] return storageList[i]
} }
} }
}
} }

View File

@ -7,7 +7,7 @@
<mac-os-control-btn <mac-os-control-btn
class="top-btn" class="top-btn"
:hidden="['remove']" :hidden="['remove']"
@close="deleteHanlde" @close="deleteHandle"
@resize="resizeHandle" @resize="resizeHandle"
></mac-os-control-btn> ></mac-os-control-btn>
</div> </div>
@ -17,9 +17,7 @@
object-fit="contain" object-fit="contain"
height="180" height="180"
preview-disabled preview-disabled
:src=" :src="`${cardData.image}?time=${new Date().getTime()}`"
requireUrl('project/moke-20211219181327.png')
"
:alt="cardData.title" :alt="cardData.title"
:fallback-src="requireErrorImg()" :fallback-src="requireErrorImg()"
></n-image> ></n-image>
@ -27,8 +25,8 @@
</div> </div>
<template #action> <template #action>
<div class="go-flex-items-center list-footer" justify="space-between"> <div class="go-flex-items-center list-footer" justify="space-between">
<n-text class="go-ellipsis-1" :title="cardData.title"> <n-text class="go-ellipsis-1">
{{ cardData.title || '' }} {{ cardData.title || cardData.id || '未命名' }}
</n-text> </n-text>
<!-- 工具 --> <!-- 工具 -->
<div class="go-flex-items-center list-footer-ri"> <div class="go-flex-items-center list-footer-ri">
@ -75,9 +73,9 @@
</n-tooltip> </n-tooltip>
</template> </template>
</n-space> </n-space>
</div>
<!-- end --> <!-- end -->
</div> </div>
</div>
</template> </template>
</n-card> </n-card>
</div> </div>
@ -100,17 +98,12 @@ const {
SendIcon SendIcon
} = icon.ionicons5 } = icon.ionicons5
const emit = defineEmits(['delete', 'resize', 'edit']) const emit = defineEmits(['preview', 'delete', 'resize', 'edit', 'release'])
const props = defineProps({ const props = defineProps({
cardData: Object as PropType<Chartype> cardData: Object as PropType<Chartype>
}) })
// url
const requireUrl = (name: string) => {
return new URL(`../../../../../assets/images/${name}`, import.meta.url).href
}
const fnBtnList = reactive([ const fnBtnList = reactive([
{ {
label: renderLang('global.r_edit'), label: renderLang('global.r_edit'),
@ -133,12 +126,14 @@ const selectOptions = ref([
{ {
label: renderLang('global.r_copy'), label: renderLang('global.r_copy'),
key: 'copy', key: 'copy',
icon: renderIcon(CopyIcon) icon: renderIcon(CopyIcon),
disabled: true
}, },
{ {
label: renderLang('global.r_rename'), label: renderLang('global.r_rename'),
key: 'rename', key: 'rename',
icon: renderIcon(PencilIcon) icon: renderIcon(PencilIcon),
disabled: true
}, },
{ {
type: 'divider', type: 'divider',
@ -148,13 +143,14 @@ const selectOptions = ref([
label: props.cardData?.release label: props.cardData?.release
? renderLang('global.r_unpublish') ? renderLang('global.r_unpublish')
: renderLang('global.r_publish'), : renderLang('global.r_publish'),
key: 'send', key: 'release',
icon: renderIcon(SendIcon) icon: renderIcon(SendIcon)
}, },
{ {
label: renderLang('global.r_download'), label: renderLang('global.r_download'),
key: 'download', key: 'download',
icon: renderIcon(DownloadIcon) icon: renderIcon(DownloadIcon),
disabled: true
}, },
{ {
type: 'divider', type: 'divider',
@ -169,8 +165,14 @@ const selectOptions = ref([
const handleSelect = (key: string) => { const handleSelect = (key: string) => {
switch (key) { switch (key) {
case 'preview':
previewHandle()
break
case 'delete': case 'delete':
deleteHanlde() deleteHandle()
break
case 'release':
releaseHandle()
break break
case 'edit': case 'edit':
editHandle() editHandle()
@ -178,8 +180,13 @@ const handleSelect = (key: string) => {
} }
} }
//
const previewHandle = () => {
emit('preview', props.cardData)
}
// //
const deleteHanlde = () => { const deleteHandle = () => {
emit('delete', props.cardData) emit('delete', props.cardData)
} }
@ -188,6 +195,11 @@ const editHandle = () => {
emit('edit', props.cardData) emit('edit', props.cardData)
} }
//
const releaseHandle = () => {
emit('release', props.cardData)
}
// //
const resizeHandle = () => { const resizeHandle = () => {
emit('resize', props.cardData) emit('resize', props.cardData)

View File

@ -1,58 +1,122 @@
import { ref } from 'vue' import { ref, reactive } from 'vue';
import { goDialog } from '@/utils' import { goDialog, httpErrorHandle } from '@/utils'
import { DialogEnum } from '@/enums/pluginEnum' import { DialogEnum } from '@/enums/pluginEnum'
import { ChartList } from '../../..' import { projectListApi, deleteProjectApi, changeProjectReleaseApi } from '@/api/path'
import { Chartype, ChartList } from '../../../index.d'
import { ResultEnum } from '@/enums/httpEnum'
// 数据初始化 // 数据初始化
export const useDataListInit = () => { export const useDataListInit = () => {
const list = ref<ChartList>([
{
id: 1,
title: '物料1-假数据不可用',
release: true,
label: '官方案例'
},
{
id: 2,
title: '物料2-假数据不可用',
release: false,
label: '官方案例'
},
{
id: 3,
title: '物料3-假数据不可用',
release: false,
label: '官方案例'
},
{
id: 4,
title: '物料4-假数据不可用',
release: false,
label: '官方案例'
},
{
id: 5,
title: '物料5-假数据不可用',
release: false,
label: '官方案例'
}
])
// 删除 const loading = ref(true)
const deleteHandle = (cardData: object, index: number) => {
const paginat = reactive({
// 当前页数
page: 1,
// 每页值
limit: 12,
// 总数
count: 10,
})
const list = ref<ChartList>([])
// 数据请求
const fetchList = async () => {
loading.value = true
const res = await projectListApi({
page: paginat.page,
limit: paginat.limit
}) as any
if (res.data) {
const { count } = res
paginat.count = count
list.value = res.data.map((e: any) => {
const { id, projectName, state, createTime, indexImage, createUserId } = e
return {
id: id,
title: projectName,
createId: createUserId,
time: createTime,
image: indexImage,
release: state !== -1
}
})
setTimeout(() => {
loading.value = false
}, 500)
return
}
httpErrorHandle()
}
// 修改页数
const changePage = (_page: number) => {
paginat.page = _page
fetchList()
}
// 修改大小
const changeSize = (_size: number) => {
paginat.limit = _size
fetchList()
}
// 删除处理
const deleteHandle = (cardData: Chartype) => {
goDialog({ goDialog({
type: DialogEnum.DELETE, type: DialogEnum.DELETE,
promise: true, promise: true,
onPositiveCallback: () => onPositiveCallback: () => new Promise(res => {
new Promise(res => setTimeout(() => res(1), 1000)), res(deleteProjectApi({
promiseResCallback: (e: any) => { ids: cardData.id
window.$message.success('删除成功') }))
list.value.splice(index, 1) }),
promiseResCallback: (res: any) => {
if (res.code === ResultEnum.SUCCESS) {
window['$message'].success(window['$t']('global.r_delete_success'))
fetchList()
return
}
httpErrorHandle()
} }
}) })
} }
// 发布处理
const releaseHandle = async (cardData: Chartype, index: number) => {
const { id, release } = cardData
const res = await changeProjectReleaseApi({
id: id,
// [-1未发布, 1发布]
state: !release ? 1 : -1
}) as unknown as MyResponseType
if (res.code === ResultEnum.SUCCESS) {
list.value = []
fetchList()
// 发布 -> 未发布
if (release) {
window['$message'].success(window['$t']('global.r_unpublish_success'))
return
}
// 未发布 -> 发布
window['$message'].success(window['$t']('global.r_publish_success'))
return
}
httpErrorHandle()
}
// 立即请求
fetchList()
return { return {
loading,
paginat,
list, list,
fetchList,
releaseHandle,
changeSize,
changePage,
deleteHandle deleteHandle
} }
} }

View File

@ -1,7 +1,7 @@
import { ref, Ref } from 'vue' import { ref } from 'vue'
import { ChartEnum } from '@/enums/pageEnum' import { ChartEnum } from '@/enums/pageEnum'
import { fetchPathByName, routerTurnByPath } from '@/utils' import { fetchPathByName, routerTurnByPath, openNewWindow, previewPath } from '@/utils'
import { Chartype } from '../../..' import { Chartype } from '../../../index.d'
export const useModalDataInit = () => { export const useModalDataInit = () => {
const modalShow = ref<boolean>(false) const modalShow = ref<boolean>(false)
const modalData = ref<Chartype | null>(null) const modalData = ref<Chartype | null>(null)
@ -12,25 +12,31 @@ export const useModalDataInit = () => {
modalData.value = null modalData.value = null
} }
// 打开 modal // 缩放处理
const resizeHandle = (cardData: Chartype) => { const resizeHandle = (cardData: Chartype) => {
if(!cardData) return if (!cardData) return
modalShow.value = true modalShow.value = true
modalData.value = cardData modalData.value = cardData
} }
// 打开 modal // 编辑处理
const editHandle = (cardData: Chartype) => { const editHandle = (cardData: Chartype) => {
if(!cardData) return if (!cardData) return
const path = fetchPathByName(ChartEnum.CHART_HOME_NAME, 'href') const path = fetchPathByName(ChartEnum.CHART_HOME_NAME, 'href')
routerTurnByPath(path, [cardData.id], undefined, true) routerTurnByPath(path, [cardData.id], undefined, true)
} }
// 预览处理
const previewHandle = (cardData: Chartype) => {
openNewWindow(previewPath(cardData.id))
}
return { return {
modalData, modalData,
modalShow, modalShow,
closeModal, closeModal,
resizeHandle, resizeHandle,
editHandle editHandle,
previewHandle
} }
} }

View File

@ -1,28 +1,40 @@
<template> <template>
<div class="go-items-list"> <div class="go-items-list">
<n-grid <!-- 加载 -->
:x-gap="20" <div v-show="loading">
:y-gap="20" <go-loading></go-loading>
cols="2 s:2 m:3 l:4 xl:4 xxl:4" </div>
responsive="screen" <!-- 列表 -->
> <div v-show="!loading">
<n-grid :x-gap="20" :y-gap="20" cols="2 s:2 m:3 l:4 xl:4 xxl:4" responsive="screen">
<n-grid-item v-for="(item, index) in list" :key="item.id"> <n-grid-item v-for="(item, index) in list" :key="item.id">
<project-items-card <project-items-card
:cardData="item" :cardData="item"
@preview="previewHandle"
@resize="resizeHandle" @resize="resizeHandle"
@delete="deleteHandle($event, index)" @delete="deleteHandle(item)"
@release="releaseHandle(item, index)"
@edit="editHandle" @edit="editHandle"
></project-items-card> ></project-items-card>
</n-grid-item> </n-grid-item>
</n-grid> </n-grid>
</div>
<!-- 分页 -->
<div class="list-pagination"> <div class="list-pagination">
<n-pagination <n-pagination
:item-count="10" :page="paginat.page"
:page-sizes="[10, 20, 30, 40]" :page-size="paginat.limit"
:item-count="paginat.count"
:page-sizes="[12, 24, 36, 48]"
@update:page="changePage"
@update:page-size="changeSize"
show-size-picker show-size-picker
/> />
</div> </div>
</div> </div>
<!-- model -->
<project-items-modal-card <project-items-modal-card
v-if="modalData" v-if="modalData"
v-model:modalShow="modalShow" v-model:modalShow="modalShow"
@ -40,9 +52,8 @@ import { useModalDataInit } from './hooks/useModal.hook'
import { useDataListInit } from './hooks/useData.hook' import { useDataListInit } from './hooks/useData.hook'
const { CopyIcon, EllipsisHorizontalCircleSharpIcon } = icon.ionicons5 const { CopyIcon, EllipsisHorizontalCircleSharpIcon } = icon.ionicons5
const { list, deleteHandle } = useDataListInit() const { modalData, modalShow, closeModal, previewHandle, resizeHandle, editHandle } = useModalDataInit()
const { modalData, modalShow, closeModal, resizeHandle, editHandle } = const { loading, paginat, list, changeSize, changePage, releaseHandle, deleteHandle } = useDataListInit()
useModalDataInit()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -51,7 +62,7 @@ $contentHeight: 250px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
min-height: calc(100vh - #{$--header-height} * 2 - 2px); min-height: calc(100vh - #{$--header-height} - 40px - 2px);
.list-content { .list-content {
position: relative; position: relative;
height: $contentHeight; height: $contentHeight;

View File

@ -11,7 +11,7 @@
<n-space class="list-content-top go-px-0" justify="center"> <n-space class="list-content-top go-px-0" justify="center">
<n-space> <n-space>
<n-text> <n-text>
{{ cardData?.title || '' }} {{ cardData?.title || cardData?.id || '未命名' }}
</n-text> </n-text>
</n-space> </n-space>
</n-space> </n-space>
@ -26,9 +26,7 @@
<!-- 中间 --> <!-- 中间 -->
<div class="list-content-img"> <div class="list-content-img">
<img <img
:src=" :src="cardData?.image"
requireUrl('project/moke-20211219181327.png')
"
:alt="cardData?.title" :alt="cardData?.title"
/> />
</div> </div>
@ -75,10 +73,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive, PropType } from 'vue'
import { renderIcon, renderLang } from '@/utils' import { renderIcon, renderLang } from '@/utils'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { MacOsControlBtn } from '@/components/Tips/MacOsControlBtn' import { MacOsControlBtn } from '@/components/Tips/MacOsControlBtn'
import { Chartype } from '../../index.d'
const { HammerIcon } = icon.ionicons5 const { HammerIcon } = icon.ionicons5
@ -86,14 +85,9 @@ const emit = defineEmits(['close', 'edit'])
const props = defineProps({ const props = defineProps({
modalShow: Boolean, modalShow: Boolean,
cardData: Object cardData: Object as PropType<Chartype>
}) })
// url
const requireUrl = (name: string) => {
return new URL(`../../../../../assets/images/${name}`, import.meta.url).href
}
const fnBtnList = reactive([ const fnBtnList = reactive([
{ {
label: renderLang('global.r_edit'), label: renderLang('global.r_edit'),
@ -124,12 +118,14 @@ const closeHandle = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
$padding: 30px; $padding: 30px;
$contentHeight: calc(80vh); $contentHeight: calc(80vh);
$imageHeight: calc(80vh - 110px);
$contentWidth: calc(82vw); $contentWidth: calc(82vw);
@include go('modal-box') { @include go('modal-box') {
width: $contentWidth; width: $contentWidth;
height: $contentHeight;
.list-content { .list-content {
margin-top: 28px; margin-top: 20px;
border-radius: $--border-radius-base; border-radius: $--border-radius-base;
overflow: hidden; overflow: hidden;
@include background-image('background-point'); @include background-image('background-point');
@ -144,8 +140,9 @@ $contentWidth: calc(82vw);
} }
&-img { &-img {
@extend .go-flex-center; @extend .go-flex-center;
padding: 6px 0;
img { img {
max-height: $contentHeight; height: $imageHeight;
min-height: 200px; min-height: 200px;
max-width: 100%; max-width: 100%;
@extend .go-border-radius; @extend .go-border-radius;

View File

@ -2,7 +2,10 @@ export type Chartype = {
id: number | string id: number | string
title: string // 标题 title: string // 标题
label: string // 标签 label: string // 标签
release: boolean // 0未发布 | 1已发布 time: string, // 时间
image: string, // 预览图地址
createId: string, // 创建者
release: boolean // false 未发布 | true 已发布
} }
export type ChartList = Chartype[] export type ChartList = Chartype[]

View File

@ -10,6 +10,6 @@ import { ProjectItemsList } from './components/ProjectItemsList'
<style lang="scss" scoped> <style lang="scss" scoped>
@include go(project-items) { @include go(project-items) {
padding: 30px 20px; padding: 20px 20px;
} }
</style> </style>

View File

@ -18,7 +18,7 @@
:disabled="item.disabled" :disabled="item.disabled"
v-for="item in typeList" v-for="item in typeList"
:key="item.key" :key="item.key"
@click="btnHandle" @click="btnHandle(item.key)"
> >
<component :is="item.title"></component> <component :is="item.title"></component>
<template #icon> <template #icon>
@ -35,10 +35,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch, reactive } from 'vue' import { watch } from 'vue'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { PageEnum, ChartEnum } from '@/enums/pageEnum' import { PageEnum, ChartEnum } from '@/enums/pageEnum'
import { ResultEnum } from '@/enums/httpEnum'
import { fetchPathByName, routerTurnByPath, renderLang, getUUID } from '@/utils' import { fetchPathByName, routerTurnByPath, renderLang, getUUID } from '@/utils'
import { createProjectApi } from '@/api/path'
const { FishIcon, CloseIcon } = icon.ionicons5 const { FishIcon, CloseIcon } = icon.ionicons5
const { StoreIcon, ObjectStorageIcon } = icon.carbon const { StoreIcon, ObjectStorageIcon } = icon.carbon
@ -48,7 +50,7 @@ const props = defineProps({
show: Boolean show: Boolean
}) })
const typeList = reactive([ const typeList = [
{ {
title: renderLang('project.new_project'), title: renderLang('project.new_project'),
key: ChartEnum.CHART_HOME_NAME, key: ChartEnum.CHART_HOME_NAME,
@ -67,7 +69,7 @@ const typeList = reactive([
icon: StoreIcon, icon: StoreIcon,
disabled: true disabled: true
} }
]) ]
// @on-after-leave // @on-after-leave
watch(props, newValue => { watch(props, newValue => {
@ -82,11 +84,32 @@ const closeHandle = () => {
} }
// //
const btnHandle = (key: string) => { const btnHandle = async (key: string) => {
closeHandle() switch (key) {
const id = getUUID() case ChartEnum.CHART_HOME_NAME:
try {
//
const res = await createProjectApi({
//
projectName: getUUID(),
// remarks
remarks: null,
//
indexImage: null,
}) as unknown as MyResponseType
if(res.code === ResultEnum.SUCCESS) {
window['$message'].success(window['$t']('project.create_success'))
const { id } = res.data
const path = fetchPathByName(ChartEnum.CHART_HOME_NAME, 'href') const path = fetchPathByName(ChartEnum.CHART_HOME_NAME, 'href')
routerTurnByPath(path, [id], undefined, true) routerTurnByPath(path, [id], undefined, true)
closeHandle()
}
} catch (error) {
window['$message'].error(window['$t']('project.create_failure'))
}
break;
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -0,0 +1,35 @@
<template>
<div class="go-redirect-un-publish">
<div class="text-center">
<img src="~@/assets/images/exception/nodata.svg" alt="" />
</div>
<div class="text-center">
<h1>当前项目暂未发布</h1>
</div>
</div>
</template>
<style lang="scss" scoped>
@include go(redirect-un-publish) {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
height: 100vh;
overflow: hidden;
padding: 100px 0;
@include background-image('background-image');
.text-center {
h1 {
color: #666;
padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
}
}
</style>

View File

@ -1,9 +1,11 @@
<template> <template>
<div class="go-redirect">
<n-empty description="你什么也找不到"> <n-empty description="你什么也找不到">
<template #extra> <template #extra>
<n-button size="small" @click="goHome">看看别的</n-button> <n-button size="small" @click="goHome">看看别的</n-button>
</template> </template>
</n-empty> </n-empty>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeMount } from 'vue' import { onBeforeMount } from 'vue'
@ -14,3 +16,16 @@ const goHome = () => {
router.replace({ path: '/' }) router.replace({ path: '/' })
} }
</script> </script>
<style lang="scss" scoped>
@include go(redirect) {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
height: 100vh;
overflow: hidden;
padding: 100px 0;
@include background-image('background-image');
}
</style>

7
types/global.d.ts vendored
View File

@ -9,4 +9,11 @@ interface Window {
$KeyboardActive?: Set<string> $KeyboardActive?: Set<string>
} }
declare interface MyResponseType {
code: number;
msg: string;
data: any;
}
declare type Recordable<T = any> = Record<string, T> declare type Recordable<T = any> = Record<string, T>

2
types/vite-env.d.ts vendored
View File

@ -1,8 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
// 标题
VITE_GLOB_APP_TITLE: string;
// 端口 // 端口
VITE_DEV_PORT: string; VITE_DEV_PORT: string;
// 开发地址 // 开发地址

View File

@ -1,8 +1,9 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { resolve } from 'path' import { resolve } from 'path'
import { OUTPUT_DIR, brotliSize, chunkSizeWarningLimit, terserOptions, rollupOptions } from './build/constant' import { OUTPUT_DIR, brotliSize, chunkSizeWarningLimit, terserOptions, rollupOptions } from './build/constant'
import viteCompression from 'vite-plugin-compression' import viteCompression from 'vite-plugin-compression'
import { axiosPre } from './src/settings/httpSetting'
import { viteMockServe } from 'vite-plugin-mock' import { viteMockServe } from 'vite-plugin-mock'
import monacoEditorPlugin from 'vite-plugin-monaco-editor' import monacoEditorPlugin from 'vite-plugin-monaco-editor'
@ -10,8 +11,8 @@ function pathResolve(dir: string) {
return resolve(process.cwd(), '.', dir) return resolve(process.cwd(), '.', dir)
} }
export default defineConfig({ export default ({ mode }) => defineConfig({
base: '/', base: process.env.NODE_ENV === 'production' ? './' : '/',
// 路径重定向 // 路径重定向
resolve: { resolve: {
alias: [ alias: [
@ -35,6 +36,21 @@ export default defineConfig({
} }
} }
}, },
// 开发服务器配置
server: {
host: true,
open: true,
port: 3000,
proxy: {
[axiosPre]: {
// @ts-ignore
target: loadEnv(mode, process.cwd()).VITE_DEV_PATH,
changeOrigin: true,
ws: true,
secure: true,
}
}
},
plugins: [ plugins: [
vue(), vue(),
monacoEditorPlugin({ monacoEditorPlugin({