diff --git a/.env b/.env index 733a8577..87882566 100644 --- a/.env +++ b/.env @@ -1,14 +1,8 @@ # port -VITE_DEV_PORT = '8001' +VITE_DEV_PORT = '8080' # development path -VITE_DEV_PATH = '/' +VITE_DEV_PATH = 'http://1.117.240.165:8080' # production path -VITE_PRO_PATH = '/' - -# spa-title -VITE_GLOB_APP_TITLE = GoView - -# spa shortname -VITE_GLOB_APP_SHORT_NAME = GoView +VITE_PRO_PATH = 'http://1.117.240.165:8080' \ No newline at end of file diff --git a/README.md b/README.md index 07d8d788..452a007d 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,17 @@ ![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) - -文档-在线地址:[http://www.mtruning.club:81/](http://www.mtruning.club:81/) - -文档-源码地址:[https://gitee.com/MTrun/go-view-doc](https://gitee.com/MTrun/go-view-doc) +接口地址修改:`.env` ### 🤯 后端项目 @@ -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) -技术点: - -- 框架:基于 `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 -#建议使用 nrm 切换到淘宝源 https://registry.npmmirror.com/ -#pnpm -pnpm install +# port +VITE_DEV_PORT = '8080' -#yarn -yarn install +# development path +VITE_DEV_PATH = 'http://127.0.0.1:8080' -#npm -npm install +# production path +VITE_PRO_PATH = 'http://127.0.0.1:8080' ``` -## 启动 +公共前缀修改:`src\settings\httpSetting.ts` ```shell -#pnpm -pnpm dev - -# npm -npm run dev - -#yarn -yarn dev - -#Makefile -make dev +// 请求前缀 +export const axiosPre = '/api/goview' ``` -## 编译 +接口封装:`src\api\http.ts` -```shell -#pnpm -pnpm run build +```ts +import axiosInstance from './axios' +import { RequestHttpEnum, ContentTypeEnum } from '@/enums/httpEnum' -# npm -npm run build +export const get = (url: string, params?: object) => { + return axiosInstance({ + url: url, + method: RequestHttpEnum.GET, + params: params, + }) +} -#yarn -yarn run build +export const post = (url: string, data?: object, headersType?: string) => { + return axiosInstance({ + url: url, + method: RequestHttpEnum.POST, + data: data, + headers: { + 'Content-Type': headersType || ContentTypeEnum.JSON + } + }) +} -#Makefile -make dist +export const put = (url: string, data?: object, headersType?: string) => { + return axiosInstance({ + url: url, + method: RequestHttpEnum.PUT, + data: data, + headers: { + 'Content-Type': headersType || ContentTypeEnum.JSON + } + }) +} + +export const del = (url: string, params?: object) => { + return axiosInstance({ + url: url, + method: RequestHttpEnum.DELETE, + params + }) +} + +// 获取请求函数,默认get +export const http = (type?: RequestHttpEnum) => { + switch (type) { + case RequestHttpEnum.GET: + return get + + case RequestHttpEnum.POST: + return post + + case RequestHttpEnum.PUT: + return put + + case RequestHttpEnum.DELETE: + return del + + default: + return get + } +} ``` ## 代码提交 -- feat: 新功能 -- fix: 修复 Bug -- docs: 文档修改 -- perf: 性能优化 -- revert: 版本回退 -- ci: CICD 集成相关 -- test: 添加测试代码 -- refactor: 代码重构 -- build: 影响项目构建或依赖修改 -- style: 不影响程序逻辑的代码修改 -- chore: 不属于以上类型的其他类型(日常事务) +* feat: 新功能 +* fix: 修复 Bug +* docs: 文档修改 +* perf: 性能优化 +* revert: 版本回退 +* ci: CICD集成相关 +* test: 添加测试代码 +* refactor: 代码重构 +* build: 影响项目构建或依赖修改 +* style: 不影响程序逻辑的代码修改 +* chore: 不属于以上类型的其他类型(日常事务) ## 交流 diff --git a/build/getConfigFileName.ts b/build/getConfigFileName.ts deleted file mode 100644 index d61cd416..00000000 --- a/build/getConfigFileName.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Get the configuration file variable name - * @param env - */ -export const getConfigFileName = (env: Record) => { - return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` - .toUpperCase() - .replace(/\s/g, ''); -}; diff --git a/package.json b/package.json index fe0f048e..913c9eda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "go-view", - "version": "1.0.9", + "version": "2.0.6", "scripts": { "dev": "vite --host", "build": "vue-tsc --noEmit && vite build", @@ -27,7 +27,7 @@ "screenfull": "^6.0.1", "vue": "^3.2.31", "vue-demi": "^0.13.1", - "vue-i18n": "9.1.9", + "vue-i18n": "9.1.10", "vue-router": "4.0.12", "vue3-lazyload": "^0.2.5-beta", "vue3-sketch-ruler": "^1.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a22b19dc..4662845d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,7 @@ specifiers: vue: ^3.2.31 vue-demi: ^0.13.1 vue-echarts: ^6.0.2 - vue-i18n: 9.1.9 + vue-i18n: 9.1.10 vue-router: 4.0.12 vue-tsc: ^0.28.10 vue3-lazyload: ^0.2.5-beta @@ -78,7 +78,7 @@ dependencies: screenfull: 6.0.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 vue3-lazyload: 0.2.5-beta_2yymnzrok6eda47acnj2yjm3ae vue3-sketch-ruler: 1.3.4_vue@3.2.37 @@ -649,60 +649,60 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@intlify/core-base/9.1.9: - resolution: {integrity: sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==} + /@intlify/core-base/9.1.10: + resolution: {integrity: sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==} engines: {node: '>= 10'} dependencies: - '@intlify/devtools-if': 9.1.9 - '@intlify/message-compiler': 9.1.9 - '@intlify/message-resolver': 9.1.9 - '@intlify/runtime': 9.1.9 - '@intlify/shared': 9.1.9 - '@intlify/vue-devtools': 9.1.9 + '@intlify/devtools-if': 9.1.10 + '@intlify/message-compiler': 9.1.10 + '@intlify/message-resolver': 9.1.10 + '@intlify/runtime': 9.1.10 + '@intlify/shared': 9.1.10 + '@intlify/vue-devtools': 9.1.10 dev: false - /@intlify/devtools-if/9.1.9: - resolution: {integrity: sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==} + /@intlify/devtools-if/9.1.10: + resolution: {integrity: sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==} engines: {node: '>= 10'} dependencies: - '@intlify/shared': 9.1.9 + '@intlify/shared': 9.1.10 dev: false - /@intlify/message-compiler/9.1.9: - resolution: {integrity: sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==} + /@intlify/message-compiler/9.1.10: + resolution: {integrity: sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==} engines: {node: '>= 10'} dependencies: - '@intlify/message-resolver': 9.1.9 - '@intlify/shared': 9.1.9 + '@intlify/message-resolver': 9.1.10 + '@intlify/shared': 9.1.10 source-map: 0.6.1 dev: false - /@intlify/message-resolver/9.1.9: - resolution: {integrity: sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA==} + /@intlify/message-resolver/9.1.10: + resolution: {integrity: sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w==} engines: {node: '>= 10'} dev: false - /@intlify/runtime/9.1.9: - resolution: {integrity: sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==} + /@intlify/runtime/9.1.10: + resolution: {integrity: sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==} engines: {node: '>= 10'} dependencies: - '@intlify/message-compiler': 9.1.9 - '@intlify/message-resolver': 9.1.9 - '@intlify/shared': 9.1.9 + '@intlify/message-compiler': 9.1.10 + '@intlify/message-resolver': 9.1.10 + '@intlify/shared': 9.1.10 dev: false - /@intlify/shared/9.1.9: - resolution: {integrity: sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw==} + /@intlify/shared/9.1.10: + resolution: {integrity: sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA==} engines: {node: '>= 10'} dev: false - /@intlify/vue-devtools/9.1.9: - resolution: {integrity: sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==} + /@intlify/vue-devtools/9.1.10: + resolution: {integrity: sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==} engines: {node: '>= 10'} dependencies: - '@intlify/message-resolver': 9.1.9 - '@intlify/runtime': 9.1.9 - '@intlify/shared': 9.1.9 + '@intlify/message-resolver': 9.1.10 + '@intlify/runtime': 9.1.10 + '@intlify/shared': 9.1.10 dev: false /@jridgewell/gen-mapping/0.1.1: @@ -5385,15 +5385,15 @@ packages: - supports-color dev: true - /vue-i18n/9.1.9_vue@3.2.37: - resolution: {integrity: sha512-JeRdNVxS2OGp1E+pye5XB6+M6BBkHwAv9C80Q7+kzoMdUDGRna06tjC0vCB/jDX9aWrl5swxOMFcyAr7or8XTA==} + /vue-i18n/9.1.10_vue@3.2.37: + resolution: {integrity: sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==} engines: {node: '>= 10'} peerDependencies: vue: ^3.0.0 dependencies: - '@intlify/core-base': 9.1.9 - '@intlify/shared': 9.1.9 - '@intlify/vue-devtools': 9.1.9 + '@intlify/core-base': 9.1.10 + '@intlify/shared': 9.1.10 + '@intlify/vue-devtools': 9.1.10 '@vue/devtools-api': 6.1.4 vue: 3.2.37 dev: false diff --git a/src/App.vue b/src/App.vue index 1865e459..29687f9a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -18,7 +18,7 @@ import { zhCN, dateZhCN, NConfigProvider } from 'naive-ui' import { GoAppProvider } from '@/components/GoAppProvider' import { I18n } from '@/components/I18n' -import { useDarkThemeHook, useThemeOverridesHook, useCode } from '@/hooks' +import { useSystemInit, useDarkThemeHook, useThemeOverridesHook, useCode } from '@/hooks' // 暗黑主题 const darkTheme = useDarkThemeHook() @@ -28,4 +28,7 @@ const overridesTheme = useThemeOverridesHook() // 代码主题 const hljsTheme = useCode() + +// 系统全局数据初始化 +useSystemInit() diff --git a/src/api/axios.config.ts b/src/api/axios.config.ts new file mode 100644 index 00000000..5c976c7c --- /dev/null +++ b/src/api/axios.config.ts @@ -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 = [] \ No newline at end of file diff --git a/src/api/axios.ts b/src/api/axios.ts index c913ea09..2b9f1aec 100644 --- a/src/api/axios.ts +++ b/src/api/axios.ts @@ -1,19 +1,37 @@ import axios, { AxiosResponse, AxiosRequestConfig } from 'axios' -import { ResultEnum } from "@/enums/httpEnum" -import { ErrorPageNameMap } from "@/enums/pageEnum" -import { redirectErrorPage } from '@/utils' +import { ResultEnum, RequestHttpHeaderEnum } from "@/enums/httpEnum" +import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum" +import { StorageEnum } from '@/enums/storageEnum' +import { axiosPre } from '@/settings/httpSetting' +import { SystemStoreEnum, SystemStoreUserInfoEnum } from '@/store/modules/systemStore/systemStore.d' +import { redirectErrorPage, getLocalStorage, routerTurnByName, httpErrorHandle } from '@/utils' +import { fetchAllowList } from './axios.config' +import includes from 'lodash/includes' const axiosInstance = axios.create({ - baseURL: import.meta.env.DEV ? import.meta.env.VITE_DEV_PATH : import.meta.env.VITE_PRO_PATH, + baseURL: `${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}${axiosPre}`, timeout: ResultEnum.TIMEOUT, }) axiosInstance.interceptors.request.use( (config: AxiosRequestConfig) => { + // 白名单校验 + 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 }, - (error: AxiosRequestConfig) => { - Promise.reject(error) + (err: AxiosRequestConfig) => { + Promise.reject(err) } ) @@ -21,13 +39,31 @@ axiosInstance.interceptors.request.use( axiosInstance.interceptors.response.use( (res: AxiosResponse) => { const { code } = res.data as { code: number } - if (code === ResultEnum.DATA_SUCCESS) return Promise.resolve(res.data) - // 重定向 - if (ErrorPageNameMap.get(code)) redirectErrorPage(code) + + // 成功 + if (code === ResultEnum.SUCCESS) { + return Promise.resolve(res.data) + } + + // 登录过期 + if (code === ResultEnum.TOKEN_OVERDUE) { + window['$message'].error(window['$t']('http.token_overdue_message')) + routerTurnByName(PageEnum.BASE_LOGIN_NAME) + return Promise.resolve(res.data) + } + + // 固定错误码重定向 + if (ErrorPageNameMap.get(code)) { + redirectErrorPage(code) + return Promise.resolve(res.data) + } + + // 提示错误 + window['$message'].error(window['$t']((res.data as any).msg)) return Promise.resolve(res.data) }, (err: AxiosResponse) => { - window['$message'].error('接口异常,请检查!') + httpErrorHandle() Promise.reject(err) } ) diff --git a/src/api/http.ts b/src/api/http.ts index f6657c65..73701e34 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -13,7 +13,7 @@ export const get = (url: string, params?: object) => { return axiosInstance({ url: url, method: RequestHttpEnum.GET, - params: params + params: params, }) } diff --git a/src/api/path/index.ts b/src/api/path/index.ts new file mode 100644 index 00000000..66594d27 --- /dev/null +++ b/src/api/path/index.ts @@ -0,0 +1,2 @@ +export * from '@/api/path/project.api' +export * from '@/api/path/system.api' \ No newline at end of file diff --git a/src/api/path/project.api.ts b/src/api/path/project.api.ts new file mode 100644 index 00000000..5123ff2f --- /dev/null +++ b/src/api/path/project.api.ts @@ -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(); + } +} \ No newline at end of file diff --git a/src/api/path/system.api.ts b/src/api/path/system.api.ts new file mode 100644 index 00000000..499194c7 --- /dev/null +++ b/src/api/path/system.api.ts @@ -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(); + } +} \ No newline at end of file diff --git a/src/assets/images/project/moke-20211219181327.png b/src/assets/images/project/moke-20211219181327.png deleted file mode 100644 index 7be19aa3..00000000 Binary files a/src/assets/images/project/moke-20211219181327.png and /dev/null differ diff --git a/src/enums/editPageEnum.ts b/src/enums/editPageEnum.ts index e2044bd8..5fb94988 100644 --- a/src/enums/editPageEnum.ts +++ b/src/enums/editPageEnum.ts @@ -40,8 +40,8 @@ export enum MenuEnum { UN_GROUP = 'unGroup', // 后退 BACK = 'back', - // 前进 - FORWORD = 'forward' + FORWORD = 'forward', + SAVE = 'save' } // Win 键盘枚举 @@ -64,3 +64,15 @@ export enum MacKeyboard { SHIFT_SOURCE_KEY = "⇧", ALT_SOURCE_KEY = "⌥" } + +// 同步状态枚举 +export enum SyncEnum { + // 等待 + PENDING, + // 开始 + START, + // 成功 + SUCCESS, + // 失败 + FAILURE +} diff --git a/src/enums/httpEnum.ts b/src/enums/httpEnum.ts index ffc98893..12211e15 100644 --- a/src/enums/httpEnum.ts +++ b/src/enums/httpEnum.ts @@ -1,13 +1,18 @@ -/** - * @description: 请求结果集 - */ +// 模块 Path 前缀分类 +export enum ModuleTypeEnum { + SYSTEM = 'sys', + PROJECT = 'project', +} + +// 请求结果集 export enum ResultEnum { DATA_SUCCESS = 0, SUCCESS = 200, SERVER_ERROR = 500, SERVER_FORBIDDEN = 403, NOT_FOUND = 404, - TIMEOUT = 10042 + TOKEN_OVERDUE = 886, + TIMEOUT = 10042, } // 数据相关 @@ -26,9 +31,13 @@ export enum RequestContentTypeEnum { SQL = 1 } -/** - * @description: 请求方法 - */ +// 头部 +export enum RequestHttpHeaderEnum { + TOKEN = 'Token', + COOKIE = 'Cookie' +} + +// 请求方法 export enum RequestHttpEnum { GET = 'get', POST = 'post', @@ -109,9 +118,7 @@ export type RequestParams = { } } -/** - * @description: 常用的contentTyp类型 - */ +// 常用的contentTyp类型 export enum ContentTypeEnum { // json JSON = 'application/json;charset=UTF-8', diff --git a/src/enums/pageEnum.ts b/src/enums/pageEnum.ts index d709043c..9612dc31 100644 --- a/src/enums/pageEnum.ts +++ b/src/enums/pageEnum.ts @@ -20,10 +20,15 @@ export enum PageEnum { //重定向 REDIRECT = '/redirect', REDIRECT_NAME = 'Redirect', + + // 未发布 + REDIRECT_UN_PUBLISH = '/redirect/unPublish', + REDIRECT_UN_PUBLISH_NAME = 'redirect-un-publish', + + // 重载 RELOAD = '/reload', RELOAD_NAME = 'Reload', - // 首页 BASE_HOME = '/project', BASE_HOME_NAME = 'Project', diff --git a/src/enums/storageEnum.ts b/src/enums/storageEnum.ts index b0818a24..a36e99e8 100644 --- a/src/enums/storageEnum.ts +++ b/src/enums/storageEnum.ts @@ -1,10 +1,8 @@ export enum StorageEnum { // 全局设置 - GO_SYSTEM_SETTING_STORE = 'GO_SYSTEM_SETTING', - // token 等信息 - GO_ACCESS_TOKEN_STORE = 'GO_ACCESS_TOKEN', + GO_SETTING_STORE = 'GO_SETTING', // 登录信息 - GO_LOGIN_INFO_STORE = 'GO_LOGIN_INFO', + GO_SYSTEM_STORE = 'GO_SYSTEM', // 语言 GO_LANG_STORE = 'GO_LANG', // 当前选择的主题 diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 16b0bb69..c1d5037b 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,5 @@ export * from '@/hooks/useTheme.hook' export * from '@/hooks/usePreviewScale.hook' export * from '@/hooks/useCode.hook' -export * from '@/hooks/useChartDataFetch.hook' \ No newline at end of file +export * from '@/hooks/useChartDataFetch.hook' +export * from '@/hooks/useSystemInit.hook' \ No newline at end of file diff --git a/src/hooks/useSystemInit.hook.ts b/src/hooks/useSystemInit.hook.ts new file mode 100644 index 00000000..72f2d0fa --- /dev/null +++ b/src/hooks/useSystemInit.hook.ts @@ -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() +} \ No newline at end of file diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index 4ce07872..b781bc53 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -11,6 +11,8 @@ const global = { help: 'Help', contact: 'About Software', logout: 'Logout', + logout_success: 'Logout success!', + logout_failure: 'Logout Failed!', // system setting sys_set: 'System Setting', lang_set: 'Language Setting', @@ -26,8 +28,14 @@ const global = { r_more: 'More', } +const http = { + error_message: 'The interface is abnormal, please check the interface!', + token_overdue_message: 'Login expired, please log in again!' +} + export default { global, + http, login, project } diff --git a/src/i18n/en/login.ts b/src/i18n/en/login.ts index d6f7b750..eabe86c0 100644 --- a/src/i18n/en/login.ts +++ b/src/i18n/en/login.ts @@ -2,6 +2,6 @@ export default { desc: "Login", form_auto: "Sign in automatically", form_button: "Login", - login_success: "Login success", - login_message: "Please complete the letter", + login_success: "Login success!", + login_message: "Please complete the letter!", } \ No newline at end of file diff --git a/src/i18n/en/project.ts b/src/i18n/en/project.ts index 3b7d367b..dae8146d 100644 --- a/src/i18n/en/project.ts +++ b/src/i18n/en/project.ts @@ -1,6 +1,8 @@ export default { create_btn: 'Creat', - create_tip: 'Please select a content for development', + create_success: 'Creat Success!', + create_failure: 'Failed to create, please try again later!', + create_tip: 'Please select a content for development!', project: 'Project', my: 'My', new_project: 'New Project', diff --git a/src/i18n/zh/index.ts b/src/i18n/zh/index.ts index c26d1567..64e54aae 100644 --- a/src/i18n/zh/index.ts +++ b/src/i18n/zh/index.ts @@ -11,6 +11,8 @@ const global = { help: '帮助中心', contact: '关于软件', logout: '退出登录', + logout_success: '退出成功!', + logout_failure: '退出失败!', // 系统设置 sys_set: '系统设置', lang_set: '语言设置', @@ -18,16 +20,27 @@ const global = { r_edit: '编辑', r_preview: '预览', r_copy: '克隆', + r_copy_success: '克隆成功!', r_rename: '重命名', + r_rename_success: '重命名成功!', r_publish: '发布', + r_publish_success: '成功发布!', r_unpublish: '取消发布', + r_unpublish_success: '取消成功!', r_download: '下载', r_delete: '删除', + r_delete_success: '删除成功!', r_more: '更多', } +const http = { + error_message: '获取数据失败,请稍后重试!', + token_overdue_message: '登录过期,请重新登录!' +} + export default { global, + http, login, project } diff --git a/src/i18n/zh/login.ts b/src/i18n/zh/login.ts index 38d53eab..96182694 100644 --- a/src/i18n/zh/login.ts +++ b/src/i18n/zh/login.ts @@ -2,6 +2,6 @@ export default { desc: "登录", form_auto: "自动登录", form_button: "登录", - login_success: "登录成功", login_message: "请填写完整信息", + login_success: "登录成功!", } \ No newline at end of file diff --git a/src/i18n/zh/project.ts b/src/i18n/zh/project.ts index 49e86567..d6f25680 100644 --- a/src/i18n/zh/project.ts +++ b/src/i18n/zh/project.ts @@ -1,6 +1,8 @@ export default { // aside create_btn: '新建', + create_success: '新建成功!', + create_failure: '新建失败,请稍后重试!', create_tip: '从哪里出发好呢?', project: '项目', my: '我的', diff --git a/src/plugins/customComponents.ts b/src/plugins/customComponents.ts index 0fad1075..81e67df1 100644 --- a/src/plugins/customComponents.ts +++ b/src/plugins/customComponents.ts @@ -8,7 +8,10 @@ import { SketchRule } from 'vue3-sketch-ruler' * @param app */ export function setupCustomComponents(app: App) { + // 骨架屏 app.component('GoSkeleton', GoSkeleton) + // 加载 app.component('GoLoading', GoLoading) + // 标尺 app.component('SketchRule', SketchRule) } diff --git a/src/plugins/icon.ts b/src/plugins/icon.ts index 90bf057c..98df80cf 100644 --- a/src/plugins/icon.ts +++ b/src/plugins/icon.ts @@ -53,6 +53,7 @@ import { ArrowForward as ArrowForwardIcon, Planet as PawIcon, Search as SearchIcon, + Reload as ReloadIcon, ChevronUpOutline as ChevronUpOutlineIcon, ChevronDownOutline as ChevronDownOutlineIcon, Pulse as PulseIcon, @@ -86,6 +87,7 @@ import { FitToScreen as FitToScreenIcon, FitToHeight as FitToHeightIcon, FitToWidth as FitToWidthIcon, + Save as SaveIcon, Carbon3DCursor as Carbon3DCursorIcon, Carbon3DSoftware as Carbon3DSoftwareIcon, Filter as FilterIcon, @@ -200,6 +202,8 @@ const ionicons5 = { PawIcon, // 搜索(放大镜) SearchIcon, + // 加载 + ReloadIcon, // 过滤器 FilterIcon, // 向上 @@ -256,6 +260,8 @@ const carbon = { FitToScreenIcon, FitToHeightIcon, FitToWidthIcon, + // 保存 + SaveIcon, // 成组 Carbon3DCursorIcon, // 解组 diff --git a/src/router/base.ts b/src/router/base.ts index 5e3a0279..8e4202b8 100644 --- a/src/router/base.ts +++ b/src/router/base.ts @@ -1,13 +1,13 @@ import { RouteRecordRaw } from 'vue-router' import type { AppRouteRecordRaw } from '@/router/types'; -import { ErrorPage404, ErrorPage403, ErrorPage500, Layout } from '@/router/constant'; +import { ErrorPage404, ErrorPage403, ErrorPage500, Layout, RedirectHome, RedirectUnPublish } from '@/router/constant'; import { PageEnum } from '@/enums/pageEnum' import { GoReload } from '@/components/GoReload' export const LoginRoute: RouteRecordRaw = { - path: '/login', - name: 'Login', + path: PageEnum.BASE_LOGIN, + name: PageEnum.BASE_LOGIN_NAME, component: () => import('@/views/login/index.vue'), meta: { title: '登录', @@ -60,22 +60,21 @@ export const ReloadRoute: AppRouteRecordRaw = { }, } -export const RedirectRoute: AppRouteRecordRaw = { - path: PageEnum.REDIRECT, - name: PageEnum.REDIRECT_NAME, - component: Layout, - meta: { - title: PageEnum.REDIRECT_NAME, - }, - children: [ - { - path: '/redirect/:path(.*)', - name: PageEnum.REDIRECT_NAME, - component: () => import('@/views/redirect/index.vue'), - meta: { - title: PageEnum.REDIRECT_NAME, - hideBreadcrumb: true, - }, +export const RedirectRoute: RouteRecordRaw[] = [ + { + path: PageEnum.REDIRECT, + name: PageEnum.REDIRECT_NAME, + component: RedirectHome, + meta: { + title: PageEnum.REDIRECT_NAME, }, - ], -}; + }, + { + path: PageEnum.REDIRECT_UN_PUBLISH, + name: PageEnum.REDIRECT_UN_PUBLISH_NAME, + component: RedirectUnPublish, + meta: { + title: PageEnum.REDIRECT_UN_PUBLISH_NAME, + }, + }, +] diff --git a/src/router/constant.ts b/src/router/constant.ts index 73f22267..d540b6f5 100644 --- a/src/router/constant.ts +++ b/src/router/constant.ts @@ -4,6 +4,10 @@ export const ErrorPage403 = () => import('@/views/exception/403.vue'); export const ErrorPage500 = () => import('@/views/exception/500.vue'); +export const RedirectHome = () => import('@/views/redirect/index.vue'); + +export const RedirectUnPublish = () => import('@/views/redirect/UnPublish.vue'); + export const Layout = () => import('@/layout/index.vue'); export const ParentLayout = () => import('@/layout/parentLayout.vue'); diff --git a/src/router/index.ts b/src/router/index.ts index 88e42395..0a7e7e04 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,9 +1,8 @@ import type { App } from 'vue' import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' -import { RedirectRoute } from '@/router/base' import { createRouterGuards } from './router-guards' import { PageEnum } from '@/enums/pageEnum' -import { HttpErrorPage, LoginRoute, ReloadRoute } from '@/router/base' +import { HttpErrorPage, LoginRoute, ReloadRoute, RedirectRoute } from '@/router/base' import { Layout } from '@/router/constant' import modules from '@/router/modules' @@ -19,6 +18,7 @@ const RootRoute: Array = [ }, children: [ ...HttpErrorPage, + ...RedirectRoute, modules.projectRoutes, modules.chartRoutes, modules.previewRoutes @@ -27,7 +27,7 @@ const RootRoute: Array = [ ] -export const constantRouter: any[] = [LoginRoute, ...RootRoute, RedirectRoute, ReloadRoute]; +export const constantRouter: any[] = [LoginRoute, ...RootRoute, ReloadRoute]; const router = createRouter({ history: createWebHashHistory(''), diff --git a/src/router/router-guards.ts b/src/router/router-guards.ts index aedb0667..bdaa7fb2 100644 --- a/src/router/router-guards.ts +++ b/src/router/router-guards.ts @@ -1,7 +1,15 @@ import { Router } from 'vue-router'; -import { PageEnum } from '@/enums/pageEnum' +import { PageEnum, PreviewEnum } from '@/enums/pageEnum' import { loginCheck } from '@/utils' +// 路由白名单 +const routerAllowList = [ + // 登录 + PageEnum.BASE_LOGIN_NAME, + // 预览 + PreviewEnum.CHART_PREVIEW_NAME +] + export function createRouterGuards(router: Router) { // 前置 router.beforeEach(async (to, from, next) => { @@ -12,10 +20,8 @@ export function createRouterGuards(router: Router) { next({ name: PageEnum.ERROR_PAGE_NAME_404 }) } - if (!loginCheck()) { - if (to.name === PageEnum.BASE_LOGIN_NAME) { - next() - } + // @ts-ignore + if (!routerAllowList.includes(to.name) && !loginCheck()) { next({ name: PageEnum.BASE_LOGIN_NAME }) } next() diff --git a/src/settings/designSetting.ts b/src/settings/designSetting.ts index 42fa7ad5..b919db14 100644 --- a/src/settings/designSetting.ts +++ b/src/settings/designSetting.ts @@ -55,9 +55,12 @@ export const backgroundImageSize = 5 // 预览展示方式 export const previewScaleType = PreviewScaleEnum.FIT -// 数据请求间隔 +// 数据请求间隔(s) export const requestInterval = 30 +// 工作台自动保存间隔(s) +export const saveInterval = 30 + // 数据请求间隔单位 export const requestIntervalUnit = RequestHttpIntervalEnum.SECOND diff --git a/src/settings/httpSetting.ts b/src/settings/httpSetting.ts new file mode 100644 index 00000000..9e9334bf --- /dev/null +++ b/src/settings/httpSetting.ts @@ -0,0 +1,2 @@ +// 请求前缀 +export const axiosPre = '/api/goview' \ No newline at end of file diff --git a/src/store/modules/chartEditStore/chartEditStore.d.ts b/src/store/modules/chartEditStore/chartEditStore.d.ts index 1defbb77..7a532e7b 100644 --- a/src/store/modules/chartEditStore/chartEditStore.d.ts +++ b/src/store/modules/chartEditStore/chartEditStore.d.ts @@ -1,5 +1,6 @@ import { CreateComponentType, CreateComponentGroupType, FilterEnum } from '@/packages/index.d' import { HistoryActionTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d' +import { SyncEnum } from '@/enums/editPageEnum' import { RequestHttpEnum, RequestContentTypeEnum, @@ -12,6 +13,26 @@ import { import { PreviewScaleEnum } from '@/enums/styleEnum' import type { ChartColorsNameType, GlobalThemeJsonType } from '@/settings/chartThemes/index' +// 项目数据枚举 +export enum ProjectInfoEnum { + // 名称 + PROJECT_NAME = 'projectName', + // 描述 + REMARKS = 'remarks', + // 缩略图 + THUMBNAIL= 'thumbnail', + // 是否公开发布 + RELEASE = 'release' +} + +// 项目数据 +export type ProjectInfoType = { + [ProjectInfoEnum.PROJECT_NAME]: string, + [ProjectInfoEnum.REMARKS]: string, + [ProjectInfoEnum.THUMBNAIL]: string, + [ProjectInfoEnum.RELEASE]: boolean +} + // 编辑画布属性 export enum EditCanvasTypeEnum { EDIT_LAYOUT_DOM = 'editLayoutDom', @@ -20,12 +41,13 @@ export enum EditCanvasTypeEnum { SCALE = 'scale', USER_SCALE = 'userScale', LOCK_SCALE = 'lockScale', + SAVE_STATUS = 'saveStatus', IS_CREATE = 'isCreate', IS_DRAG = 'isDrag', IS_SELECT = 'isSelect' } -// 编辑区域 +// 编辑区域(临时) export type EditCanvasType = { // 编辑区域 DOM [EditCanvasTypeEnum.EDIT_LAYOUT_DOM]: HTMLElement | null @@ -42,11 +64,13 @@ export type EditCanvasType = { [EditCanvasTypeEnum.IS_CREATE]: boolean // 拖拽中 [EditCanvasTypeEnum.IS_DRAG]: boolean + // 保存状态 + [EditCanvasTypeEnum.SAVE_STATUS]: SyncEnum // 框选中 [EditCanvasTypeEnum.IS_SELECT]: boolean } -// 滤镜/背景色/宽高主题等 +// 画布数据/滤镜/背景色/宽高主题等 export enum EditCanvasConfigEnum { WIDTH = 'width', HEIGHT = 'height', @@ -58,7 +82,12 @@ export enum EditCanvasConfigEnum { PREVIEW_SCALE_TYPE = 'previewScaleType' } -export interface EditCanvasConfigType { +// 画布属性(需保存) +export type EditCanvasConfigType = { + // 项目名称 + [EditCanvasConfigEnum.PROJECT_NAME]: string, + // 项目描述 + [EditCanvasConfigEnum.REMARKS]: string, // 滤镜-色相 [FilterEnum.HUE_ROTATE]: number // 滤镜-饱和度 @@ -126,6 +155,7 @@ export type RecordChartType = { // Store 枚举 export enum ChartEditStoreEnum { + PROJECT_INFO = 'projectInfo', EDIT_RANGE = 'editRange', EDIT_CANVAS = 'editCanvas', RIGHT_MENU_SHOW = 'rightMenuShow', @@ -176,6 +206,7 @@ export interface RequestConfigType extends RequestPublicConfigType { // Store 类型 export interface ChartEditStoreType { + [ChartEditStoreEnum.PROJECT_INFO]: ProjectInfoType [ChartEditStoreEnum.EDIT_CANVAS]: EditCanvasType [ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: EditCanvasConfigType [ChartEditStoreEnum.RIGHT_MENU_SHOW]: boolean diff --git a/src/store/modules/chartEditStore/chartEditStore.ts b/src/store/modules/chartEditStore/chartEditStore.ts index bd2f70ba..3c87e155 100644 --- a/src/store/modules/chartEditStore/chartEditStore.ts +++ b/src/store/modules/chartEditStore/chartEditStore.ts @@ -10,14 +10,22 @@ import { requestInterval, previewScaleType, requestIntervalUnit } from '@/settin import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore' // 全局设置 import { useSettingStore } from '@/store/modules/settingStore/settingStore' +// 历史类型 +import { HistoryActionTypeEnum, HistoryItemType, HistoryTargetTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d' +// 画布枚举 +import { MenuEnum, SyncEnum } from '@/enums/editPageEnum' + +import { + getUUID, + loadingStart, + loadingFinish, + loadingError, + isString, + isArray +} from '@/utils' + import { - HistoryActionTypeEnum, - HistoryItemType, - HistoryTargetTypeEnum -} from '@/store/modules/chartHistoryStore/chartHistoryStore.d' -import { MenuEnum } from '@/enums/editPageEnum' -import { getUUID, loadingStart, loadingFinish, loadingError, isString, isArray } from '@/utils' -import { + ProjectInfoType, ChartEditStoreEnum, ChartEditStorage, ChartEditStoreType, @@ -36,6 +44,13 @@ const settingStore = useSettingStore() export const useChartEditStore = defineStore({ id: 'useChartEditStore', state: (): ChartEditStoreType => ({ + // 项目数据 + projectInfo: { + projectName: '', + remarks: '', + thumbnail: '', + release: false + }, // 画布属性 editCanvas: { // 编辑区域 Dom @@ -54,7 +69,9 @@ export const useChartEditStore = defineStore({ // 拖拽中 isDrag: false, // 框选中 - isSelect: false + isSelect: false, + // 同步中 + saveStatus: SyncEnum.PENDING }, // 右键菜单 rightMenuShow: false, @@ -127,6 +144,9 @@ export const useChartEditStore = defineStore({ componentList: [] }), getters: { + getProjectInfo(): ProjectInfoType { + return this.projectInfo + }, getMousePosition(): MousePositionType { return this.mousePosition }, @@ -161,6 +181,10 @@ export const useChartEditStore = defineStore({ } }, actions: { + // * 设置 peojectInfo 数据项 + setProjectInfo(key: T, value: K) { + this.projectInfo[key] = value + }, // * 设置 editCanvas 数据项 setEditCanvas(key: T, value: K) { this.editCanvas[key] = value @@ -793,7 +817,7 @@ export const useChartEditStore = defineStore({ loadingFinish() } }, - // ---------------- + // * 页面缩放设置----------------- // * 设置页面大小 setPageSize(scale: number): void { this.setPageStyle('height', `${this.editCanvasConfig.height * scale}px`) diff --git a/src/store/modules/settingStore/settingStore.ts b/src/store/modules/settingStore/settingStore.ts index 775cd132..a1fd4cbe 100644 --- a/src/store/modules/settingStore/settingStore.ts +++ b/src/store/modules/settingStore/settingStore.ts @@ -4,10 +4,10 @@ import { asideCollapsedWidth } from '@/settings/designSetting' import { SettingStoreType, ToolsStatusEnum } from './settingStore.d' import { setLocalStorage, getLocalStorage } from '@/utils' import { StorageEnum } from '@/enums/storageEnum' -const { GO_SYSTEM_SETTING_STORE } = StorageEnum +const { GO_SETTING_STORE } = StorageEnum const storageSetting: SettingStoreType = getLocalStorage( - GO_SYSTEM_SETTING_STORE + GO_SETTING_STORE ) // 全局设置 @@ -45,7 +45,7 @@ export const useSettingStore = defineStore({ this.$patch(state => { state[key] = value }) - setLocalStorage(GO_SYSTEM_SETTING_STORE, this.$state) + setLocalStorage(GO_SETTING_STORE, this.$state) } } }) diff --git a/src/store/modules/systemStore/systemStore.d.ts b/src/store/modules/systemStore/systemStore.d.ts new file mode 100644 index 00000000..77f5c348 --- /dev/null +++ b/src/store/modules/systemStore/systemStore.d.ts @@ -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 +} \ No newline at end of file diff --git a/src/store/modules/systemStore/systemStore.ts b/src/store/modules/systemStore/systemStore.ts new file mode 100644 index 00000000..bb507f2a --- /dev/null +++ b/src/store/modules/systemStore/systemStore.ts @@ -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(key: T, value: K): void { + this.$patch(state => { + state[key] = value + }); + setLocalStorage(GO_SYSTEM_STORE, this.$state) + } + } +}) \ No newline at end of file diff --git a/src/utils/file.ts b/src/utils/file.ts index 858afbad..4a10bd38 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -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) { + 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 文件对象 @@ -51,4 +113,4 @@ export const downloadTextFile = ( // 字符内容转变成blob地址 const blob = new Blob([content]) downloadByA(URL.createObjectURL(blob), filename, fileSuffix) -} +} \ No newline at end of file diff --git a/src/utils/http.ts b/src/utils/http.ts new file mode 100644 index 00000000..8e92db7b --- /dev/null +++ b/src/utils/http.ts @@ -0,0 +1,6 @@ +/** + * * 请求失败统一处理 + */ +export const httpErrorHandle = () => { + window['$message'].error(window['$t']('http.error_message')) +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index fa9fa055..806d8902 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from '@/utils/plugin' export * from '@/utils/componets' export * from '@/utils/type' export * from '@/utils/file' +export * from '@/utils/http' \ No newline at end of file diff --git a/src/utils/plugin.ts b/src/utils/plugin.ts index 6b3a86ab..34e80367 100644 --- a/src/utils/plugin.ts +++ b/src/utils/plugin.ts @@ -35,7 +35,7 @@ export const loadingError = () => { * }) * ``` */ -export const goDialog = ( + export const goDialog = ( params: { // 基本 type?: DialogEnum diff --git a/src/utils/router.ts b/src/utils/router.ts index f2bf6142..c1582951 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -1,11 +1,12 @@ import { useRoute } from 'vue-router' -import { ResultEnum } from '@/enums/httpEnum' -import { ErrorPageNameMap, PageEnum } from '@/enums/pageEnum' +import { ResultEnum, RequestHttpHeaderEnum } from '@/enums/httpEnum' +import { ErrorPageNameMap, PageEnum, PreviewEnum } from '@/enums/pageEnum' import { docPath, giteeSourceCodePath } from '@/settings/pathConst' -import { cryptoDecode } from './crypto' +import { SystemStoreEnum, SystemStoreUserInfoEnum } from '@/store/modules/systemStore/systemStore.d' import { StorageEnum } from '@/enums/storageEnum' -import { clearLocalStorage, getLocalStorage } from './storage' +import { clearLocalStorage, getLocalStorage, clearCookie } from './storage' import router from '@/router' +import { logoutApi } from '@/api/path' /** * * 根据名字跳转路由 @@ -101,11 +102,20 @@ export const reloadRoutePage = () => { } /** - * * 退出 + * * 退出登录 */ -export const logout = () => { - clearLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE) - routerTurnByName(PageEnum.BASE_LOGIN_NAME) +export const logout = async () => { + try { + const res = await logoutApi() as unknown as MyResponseType + if(res.code === ResultEnum.SUCCESS) { + window['$message'].success(window['$t']('global.logout_success')) + clearCookie(RequestHttpHeaderEnum.COOKIE) + clearLocalStorage(StorageEnum.GO_SYSTEM_STORE) + routerTurnByName(PageEnum.BASE_LOGIN_NAME) + } + } catch (error) { + window['$message'].success(window['$t']('global.logout_failure')) + } } /** @@ -153,6 +163,19 @@ export const fetchRouteParams = () => { } } +/** + * * 通过硬解析获取当前路由下的参数 + * @returns object + */ +export const fetchRouteParamsLocation = () => { + try { + return (document.location.hash.split('/').pop() || '').split('?').shift() + } catch (error) { + window['$message'].warning('查询路由信息失败,请联系管理员!') + return '' + } +} + /** * * 回到主页面 * @param confirm @@ -162,19 +185,29 @@ export const goHome = () => { } /** - * * 判断是否登录(现阶段是有 login 数据即可) + * * 判断是否登录 * @return boolean */ export const loginCheck = () => { try { - const info = getLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE) + const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE) if (!info) return false - const decodeInfo = cryptoDecode(info) - if (decodeInfo) { + if (info[SystemStoreEnum.USER_INFO][SystemStoreUserInfoEnum.USER_TOKEN]) { return true } return false } catch (error) { return false } -} \ No newline at end of file +} + +/** + * * 预览地址 + * @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 +} \ No newline at end of file diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 60e7c7e8..be46d576 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -5,7 +5,7 @@ * @param v 键值(无需stringiiy) * @returns RemovableRef */ - export const setLocalStorage = (k: string, v: T) => { +export const setLocalStorage = (k: string, v: T) => { try { window.localStorage.setItem(k, JSON.stringify(v)) } catch (error) { @@ -18,7 +18,7 @@ * @param k 键名 * @returns any */ - export const getLocalStorage = (k: string) => { +export const getLocalStorage = (k: string) => { const item = window.localStorage.getItem(k) try { return item ? JSON.parse(item) : item @@ -31,7 +31,7 @@ * * 清除本地会话数据 * @param name */ - export const clearLocalStorage = (name: string) => { +export const clearLocalStorage = (name: string) => { window.localStorage.removeItem(name) } @@ -68,4 +68,42 @@ export const getSessionStorage: (k: string) => any = (k: string) => { */ export const clearSessioStorage = (name: string) => { window.sessionStorage.removeItem(name) +} + +/** + * * 设置 cookie + * @param name 键名 + * @param cvalue 键值 + * @param exdays 过期时间 + */ +export const setCookie = (name: string, cvalue: string, exdays: number) => { + const d = new Date(); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + const expires = "expires=" + d.toUTCString(); + document.cookie = name + "=" + cvalue + "; " + expires; +} + +/** + * * 获取 cookie + * @param cname 键名 + * @returns string + */ +export const getCookie = (cname: string) => { + const name = cname + "="; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1); + if (c.indexOf(name) != -1) return c.substring(name.length, c.length); + } + return ""; +} + +/** + * * 清除 cookie + * @param name 键名 + * @returns string + */ +export const clearCookie = (name: string) => { + setCookie(name, "", -1); } \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 9d7ae3ae..2604efa9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -109,29 +109,6 @@ export const isMac = () => { return /macintosh|mac os x/i.test(navigator.userAgent) } -/** - * * file转url - */ -export const fileToUrl = (file: File): string => { - const Url = URL || window.URL || window.webkitURL - const ImageUrl = Url.createObjectURL(file) - return ImageUrl -} - -/** - * * file转base64 - */ -export const fileTobase64 = (file: File, callback: Function) => { - let reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = function (e: ProgressEvent) { - if (e.target) { - let base64 = e.target.result - callback(base64) - } - } -} - /** * * 挂载监听 */ diff --git a/src/views/chart/ContentConfigurations/components/CanvasPage/index.vue b/src/views/chart/ContentConfigurations/components/CanvasPage/index.vue index 040823e1..1f8c174b 100644 --- a/src/views/chart/ContentConfigurations/components/CanvasPage/index.vue +++ b/src/views/chart/ContentConfigurations/components/CanvasPage/index.vue @@ -128,16 +128,20 @@ import { backgroundImageSize } from '@/settings/designSetting' import { FileTypeEnum } from '@/enums/fileTypeEnum' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { EditCanvasConfigEnum } from '@/store/modules/chartEditStore/chartEditStore.d' +import { useSystemStore } from '@/store/modules/systemStore/systemStore' import { StylesSetting } from '@/components/Pages/ChartItemSetting' import { UploadCustomRequestOptions } from 'naive-ui' -import { fileToUrl, loadAsyncComponent } from '@/utils' +import { fileToUrl, loadAsyncComponent, fetchRouteParamsLocation } from '@/utils' import { PreviewScaleEnum } from '@/enums/styleEnum' +import { ResultEnum } from '@/enums/httpEnum' import { icon } from '@/plugins' +import { uploadFile} from '@/api/path' const { ColorPaletteIcon } = icon.ionicons5 const { ScaleIcon, FitToScreenIcon, FitToHeightIcon, FitToWidthIcon } = icon.carbon const chartEditStore = useChartEditStore() +const systemStore = useSystemStore() const canvasConfig = chartEditStore.getEditCanvasConfig const editCanvas = chartEditStore.getEditCanvas @@ -261,17 +265,34 @@ const switchSelectColorHandle = () => { // 自定义上传操作 const customRequest = (options: UploadCustomRequestOptions) => { const { file } = options - nextTick(() => { + nextTick(async () => { + if(!systemStore.getFetchInfo.OSSUrl) { + window['$message'].error('添加图片失败,请刷新页面重试!') + return + } if (file.file) { - const ImageUrl = fileToUrl(file.file) - chartEditStore.setEditCanvasConfig( - EditCanvasConfigEnum.BACKGROUND_IMAGE, - ImageUrl - ) - chartEditStore.setEditCanvasConfig( - EditCanvasConfigEnum.SELECT_COLOR, - false + // 修改名称 + const newNameFile = new File( + [file.file], + `${fetchRouteParamsLocation()}_index_background.png`, + { type: file.file.type } ) + let uploadParams = new FormData() + uploadParams.append('object', newNameFile) + const uploadRes = await uploadFile(systemStore.getFetchInfo.OSSUrl ,uploadParams) as unknown as MyResponseType + + if(uploadRes.code === ResultEnum.SUCCESS) { + chartEditStore.setEditCanvasConfig( + EditCanvasConfigEnum.BACKGROUND_IMAGE, + uploadRes.data.objectContent.httpRequest.uri + ) + chartEditStore.setEditCanvasConfig( + EditCanvasConfigEnum.SELECT_COLOR, + false + ) + return + } + window['$message'].error('添加图片失败,请稍后重试!') } else { window['$message'].error('添加图片失败,请稍后重试!') } diff --git a/src/views/chart/ContentEdit/components/EditBottom/index.vue b/src/views/chart/ContentEdit/components/EditBottom/index.vue index 85aad898..6c9c5881 100644 --- a/src/views/chart/ContentEdit/components/EditBottom/index.vue +++ b/src/views/chart/ContentEdit/components/EditBottom/index.vue @@ -1,6 +1,10 @@ \ No newline at end of file + diff --git a/src/views/chart/hooks/useKeyboard.hook.ts b/src/views/chart/hooks/useKeyboard.hook.ts index 19e219dd..563d32ca 100644 --- a/src/views/chart/hooks/useKeyboard.hook.ts +++ b/src/views/chart/hooks/useKeyboard.hook.ts @@ -1,4 +1,5 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' +import { useSync } from './useSync.hook' import { WinKeyboard, MacKeyboard, MenuEnum } from '@/enums/editPageEnum' import throttle from 'lodash/throttle' import debounce from 'lodash/debounce' @@ -6,7 +7,7 @@ import debounce from 'lodash/debounce' import keymaster from 'keymaster' // Keymaster可以支持识别以下组合键: ⇧,shift,option,⌥,alt,ctrl,control,command,和⌘ const chartEditStore = useChartEditStore() - +const useSyncIns = useSync() const winCtrlMerge = (e: string) => `${WinKeyboard.CTRL}+${e}` const winShiftMerge = (e: string) => `${WinKeyboard.SHIFT}+${e}` const winAltMerge = (e: string) => `${WinKeyboard.ALT}+${e}` @@ -22,6 +23,7 @@ export const winKeyboardValue = { [MenuEnum.DELETE]: 'delete', [MenuEnum.BACK]: winCtrlMerge('z'), [MenuEnum.FORWORD]: winCtrlMerge(winShiftMerge('z')), + [MenuEnum.SAVE]: winCtrlMerge('s'), [MenuEnum.GROUP]: winCtrlMerge('g'), [MenuEnum.UN_GROUP]: winCtrlMerge(winShiftMerge('g')), } @@ -43,6 +45,7 @@ export const macKeyboardValue = { [MenuEnum.DELETE]: macCtrlMerge('backspace'), [MenuEnum.BACK]: macCtrlMerge('z'), [MenuEnum.FORWORD]: macCtrlMerge(macShiftMerge('z')), + [MenuEnum.SAVE]: macCtrlMerge('s'), [MenuEnum.GROUP]: macCtrlMerge('g'), [MenuEnum.UN_GROUP]: macCtrlMerge(macShiftMerge('g')), } @@ -62,6 +65,7 @@ const winKeyList: Array = [ winKeyboardValue.back, winKeyboardValue.forward, + winKeyboardValue.save, winKeyboardValue.group, winKeyboardValue.unGroup, ] @@ -81,6 +85,7 @@ const macKeyList: Array = [ macKeyboardValue.back, macKeyboardValue.forward, + macKeyboardValue.save, macKeyboardValue.group, macKeyboardValue.unGroup, ] @@ -156,6 +161,11 @@ export const useAddKeyboard = () => { case keyboardValue.unGroup: keymaster(e, throttle(() => { chartEditStore.setUnGroup(); return false }, throttleTime)) break; + + // 保存 ct+s + case keyboardValue.save: + keymaster(e, throttle(() => { useSyncIns.dataSyncUpdate(); return false }, 200)) + break; } } winKeyList.forEach((key: string) => { diff --git a/src/views/chart/hooks/useSync.hook.ts b/src/views/chart/hooks/useSync.hook.ts index 09a26aa9..943bbf57 100644 --- a/src/views/chart/hooks/useSync.hook.ts +++ b/src/views/chart/hooks/useSync.hook.ts @@ -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 { 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 { useSystemStore } from '@/store/modules/systemStore/systemStore' 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 { PublicGroupConfigClass } from '@/packages/public/publicConfig' @@ -10,6 +21,7 @@ import { PublicGroupConfigClass } from '@/packages/public/publicConfig' export const useSync = () => { const chartEditStore = useChartEditStore() 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 { - updateComponent + updateComponent, + updateStoreInfo, + dataSyncFetch, + dataSyncUpdate, + intervalDataSyncUpdate } } diff --git a/src/views/exception/403.vue b/src/views/exception/403.vue index 679462d7..8f973b23 100644 --- a/src/views/exception/403.vue +++ b/src/views/exception/403.vue @@ -4,7 +4,7 @@
-

抱歉,你无权访问该页面

+

抱歉,你无权访问该页面

回到首页 diff --git a/src/views/exception/404.vue b/src/views/exception/404.vue index 86546c3b..d6e0a82c 100644 --- a/src/views/exception/404.vue +++ b/src/views/exception/404.vue @@ -4,7 +4,7 @@
-

抱歉,你访问的页面不存在

+

抱歉,你访问的页面不存在

回到首页 diff --git a/src/views/exception/500.vue b/src/views/exception/500.vue index c0d35a6a..9494d02c 100644 --- a/src/views/exception/500.vue +++ b/src/views/exception/500.vue @@ -4,9 +4,9 @@
-

抱歉,服务器出错了

+

抱歉,服务器出错了

- 回到首页 + 重新登录 @@ -14,8 +14,8 @@ import { PageEnum } from '@/enums/pageEnum' import { routerTurnByName } from '@/utils' -function goHome() { - routerTurnByName(PageEnum.BASE_HOME_NAME) +function goLogin() { + routerTurnByName(PageEnum.BASE_LOGIN_NAME) } diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 6cd657c5..eaf1ab08 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -118,45 +118,38 @@ import { reactive, ref, onMounted } from 'vue' import shuffle from 'lodash/shuffle' 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 { GoLangSelect } from '@/components/GoLangSelect' import { LayoutHeader } from '@/layout/components/LayoutHeader' import { LayoutFooter } from '@/layout/components/LayoutFooter' import { PageEnum } from '@/enums/pageEnum' -import { icon } from '@/plugins' import { StorageEnum } from '@/enums/storageEnum' -import { routerTurnByName, cryptoEncode, setLocalStorage } from '@/utils' -const { GO_LOGIN_INFO_STORE } = StorageEnum - -const { PersonOutlineIcon, LockClosedOutlineIcon } = icon.ionicons5 +import { icon } from '@/plugins' +import { routerTurnByName } from '@/utils' +import { loginApi } from '@/api/path' interface FormState { username: string password: string } +const { GO_SYSTEM_STORE } = StorageEnum +const { PersonOutlineIcon, LockClosedOutlineIcon } = icon.ionicons5 + const formRef = ref() const loading = ref(false) const autoLogin = ref(true) const show = ref(false) const showBg = ref(false) -const designStore = useDesignStore() +const systemStore = useSystemStore() const t = window['$t'] -onMounted(() => { - setTimeout(() => { - show.value = true - }, 300) - setTimeout(() => { - showBg.value = true - }, 100) -}) - const formInline = reactive({ username: 'admin', - password: '123456', + password: 'admin', }) const rules = { @@ -196,38 +189,55 @@ const getImageUrl = (name: string, folder: string) => { return new URL(`../../assets/images/${folder}/${name}.png`, import.meta.url).href } -// 打乱 +// 打乱图片顺序 const shuffleHandle = () => { shuffleTimiing.value = setInterval(() => { bgList.value = shuffle(bgList.value) }, carouselInterval) } -// 点击事件 -const handleSubmit = (e: Event) => { +// 登录 +const handleSubmit = async (e: Event) => { e.preventDefault() formRef.value.validate(async (errors: any) => { if (!errors) { const { username, password } = formInline loading.value = true - setLocalStorage( - GO_LOGIN_INFO_STORE, - cryptoEncode( - JSON.stringify({ - username, - password, - }) - ) - ) - window['$message'].success(`${t('login.login_success')}!`) - routerTurnByName(PageEnum.BASE_HOME_NAME, true) + // 提交请求 + const res = await loginApi({ + username, + 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')) + routerTurnByName(PageEnum.BASE_HOME_NAME, true) + } } else { - window['$message'].error(`${t('login.login_message')}!`) + window['$message'].error(t('login.login_message')) } }) } onMounted(() => { + setTimeout(() => { + show.value = true + }, 300) + + setTimeout(() => { + showBg.value = true + }, 100) + shuffleHandle() }) diff --git a/src/views/preview/hooks/useComInstall.hook.ts b/src/views/preview/hooks/useComInstall.hook.ts index faf4f155..a11e8bf8 100644 --- a/src/views/preview/hooks/useComInstall.hook.ts +++ b/src/views/preview/hooks/useComInstall.hook.ts @@ -8,7 +8,7 @@ export const useComInstall = (localStorageInfo: ChartEditStorageType) => { // 注册组件(一开始无法获取window['$vue']) const intervalTiming = setInterval(() => { - if (window['$vue'].component) { + if (window['$vue']?.component) { clearInterval(intervalTiming) const intComponent = (target: CreateComponentType) => { diff --git a/src/views/preview/hooks/useScale.hook.ts b/src/views/preview/hooks/useScale.hook.ts index 0db5e48b..202aaf05 100644 --- a/src/views/preview/hooks/useScale.hook.ts +++ b/src/views/preview/hooks/useScale.hook.ts @@ -4,6 +4,7 @@ import type { ChartEditStorageType } from '../index.d' import { PreviewScaleEnum } from '@/enums/styleEnum' export const useScale = (localStorageInfo: ChartEditStorageType) => { + const entityRef = ref() const previewRef = ref() const width = ref(localStorageInfo.editCanvasConfig.width) diff --git a/src/views/preview/index.d.ts b/src/views/preview/index.d.ts index 612f037f..0dfbe7ca 100644 --- a/src/views/preview/index.d.ts +++ b/src/views/preview/index.d.ts @@ -1,5 +1,6 @@ import { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' export interface ChartEditStorageType extends ChartEditStorage { - id: string + id: string, + isRelease?: boolean } \ No newline at end of file diff --git a/src/views/preview/index.vue b/src/views/preview/index.vue index fbe74eca..4e6bc664 100644 --- a/src/views/preview/index.vue +++ b/src/views/preview/index.vue @@ -1,91 +1,9 @@ - - diff --git a/src/views/preview/suspenseIndex.vue b/src/views/preview/suspenseIndex.vue new file mode 100644 index 00000000..7036df6e --- /dev/null +++ b/src/views/preview/suspenseIndex.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/views/preview/utils/storage.ts b/src/views/preview/utils/storage.ts index 1a97af29..494315c7 100644 --- a/src/views/preview/utils/storage.ts +++ b/src/views/preview/utils/storage.ts @@ -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 { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' +import { fetchProjectApi } from '@/api/path' export interface ChartEditStorageType extends ChartEditStorage { id: string } // 根据路由 id 获取存储数据的信息 -export const getSessionStorageInfo = () => { - const urlHash = document.location.hash - const toPathArray = urlHash.split('/') - const id = toPathArray && toPathArray[toPathArray.length - 1] - +export const getSessionStorageInfo = async () => { + const id = fetchRouteParamsLocation() const storageList: ChartEditStorageType[] = getSessionStorage( StorageEnum.GO_CHART_STORAGE_LIST ) - - if(!storageList) return - - for (let i = 0; i < storageList.length; i++) { - if (id.toString() === storageList[i]['id']) { - return storageList[i] + + // 是否本地预览 + 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++) { + if (id.toString() === storageList[i]['id']) { + return storageList[i] + } } } } \ No newline at end of file diff --git a/src/views/project/items/components/ProjectItemsCard/index.vue b/src/views/project/items/components/ProjectItemsCard/index.vue index b042d229..a0f847cf 100644 --- a/src/views/project/items/components/ProjectItemsCard/index.vue +++ b/src/views/project/items/components/ProjectItemsCard/index.vue @@ -7,7 +7,7 @@ @@ -17,9 +17,7 @@ object-fit="contain" height="180" preview-disabled - :src=" - requireUrl('project/moke-20211219181327.png') - " + :src="`${cardData.image}?time=${new Date().getTime()}`" :alt="cardData.title" :fallback-src="requireErrorImg()" > @@ -27,8 +25,8 @@ - + @@ -100,17 +98,12 @@ const { SendIcon } = icon.ionicons5 -const emit = defineEmits(['delete', 'resize', 'edit']) +const emit = defineEmits(['preview', 'delete', 'resize', 'edit', 'release']) const props = defineProps({ cardData: Object as PropType }) -// 处理url获取 -const requireUrl = (name: string) => { - return new URL(`../../../../../assets/images/${name}`, import.meta.url).href -} - const fnBtnList = reactive([ { label: renderLang('global.r_edit'), @@ -133,12 +126,14 @@ const selectOptions = ref([ { label: renderLang('global.r_copy'), key: 'copy', - icon: renderIcon(CopyIcon) + icon: renderIcon(CopyIcon), + disabled: true }, { label: renderLang('global.r_rename'), key: 'rename', - icon: renderIcon(PencilIcon) + icon: renderIcon(PencilIcon), + disabled: true }, { type: 'divider', @@ -148,13 +143,14 @@ const selectOptions = ref([ label: props.cardData?.release ? renderLang('global.r_unpublish') : renderLang('global.r_publish'), - key: 'send', + key: 'release', icon: renderIcon(SendIcon) }, { label: renderLang('global.r_download'), key: 'download', - icon: renderIcon(DownloadIcon) + icon: renderIcon(DownloadIcon), + disabled: true }, { type: 'divider', @@ -169,8 +165,14 @@ const selectOptions = ref([ const handleSelect = (key: string) => { switch (key) { + case 'preview': + previewHandle() + break case 'delete': - deleteHanlde() + deleteHandle() + break + case 'release': + releaseHandle() break case 'edit': editHandle() @@ -178,8 +180,13 @@ const handleSelect = (key: string) => { } } +// 预览处理 +const previewHandle = () => { + emit('preview', props.cardData) +} + // 删除处理 -const deleteHanlde = () => { +const deleteHandle = () => { emit('delete', props.cardData) } @@ -188,6 +195,11 @@ const editHandle = () => { emit('edit', props.cardData) } +// 编辑处理 +const releaseHandle = () => { + emit('release', props.cardData) +} + // 放大处理 const resizeHandle = () => { emit('resize', props.cardData) diff --git a/src/views/project/items/components/ProjectItemsList/hooks/useData.hook.ts b/src/views/project/items/components/ProjectItemsList/hooks/useData.hook.ts index 1c38b116..591e5ff1 100644 --- a/src/views/project/items/components/ProjectItemsList/hooks/useData.hook.ts +++ b/src/views/project/items/components/ProjectItemsList/hooks/useData.hook.ts @@ -1,58 +1,122 @@ -import { ref } from 'vue' -import { goDialog } from '@/utils' +import { ref, reactive } from 'vue'; +import { goDialog, httpErrorHandle } from '@/utils' 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 = () => { - const list = ref([ - { - 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 deleteHandle = (cardData: object, index: number) => { + const loading = ref(true) + + const paginat = reactive({ + // 当前页数 + page: 1, + // 每页值 + limit: 12, + // 总数 + count: 10, + }) + + const list = ref([]) + + // 数据请求 + 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({ type: DialogEnum.DELETE, promise: true, - onPositiveCallback: () => - new Promise(res => setTimeout(() => res(1), 1000)), - promiseResCallback: (e: any) => { - window.$message.success('删除成功') - list.value.splice(index, 1) + onPositiveCallback: () => new Promise(res => { + res(deleteProjectApi({ + ids: cardData.id + })) + }), + 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 { + loading, + paginat, list, + fetchList, + releaseHandle, + changeSize, + changePage, deleteHandle } } diff --git a/src/views/project/items/components/ProjectItemsList/hooks/useModal.hook.ts b/src/views/project/items/components/ProjectItemsList/hooks/useModal.hook.ts index 89894b03..e28b1f91 100644 --- a/src/views/project/items/components/ProjectItemsList/hooks/useModal.hook.ts +++ b/src/views/project/items/components/ProjectItemsList/hooks/useModal.hook.ts @@ -1,7 +1,7 @@ -import { ref, Ref } from 'vue' +import { ref } from 'vue' import { ChartEnum } from '@/enums/pageEnum' -import { fetchPathByName, routerTurnByPath } from '@/utils' -import { Chartype } from '../../..' +import { fetchPathByName, routerTurnByPath, openNewWindow, previewPath } from '@/utils' +import { Chartype } from '../../../index.d' export const useModalDataInit = () => { const modalShow = ref(false) const modalData = ref(null) @@ -12,25 +12,31 @@ export const useModalDataInit = () => { modalData.value = null } - // 打开 modal + // 缩放处理 const resizeHandle = (cardData: Chartype) => { - if(!cardData) return + if (!cardData) return modalShow.value = true modalData.value = cardData } - // 打开 modal + // 编辑处理 const editHandle = (cardData: Chartype) => { - if(!cardData) return + if (!cardData) return const path = fetchPathByName(ChartEnum.CHART_HOME_NAME, 'href') routerTurnByPath(path, [cardData.id], undefined, true) } + // 预览处理 + const previewHandle = (cardData: Chartype) => { + openNewWindow(previewPath(cardData.id)) + } + return { modalData, modalShow, closeModal, resizeHandle, - editHandle + editHandle, + previewHandle } } diff --git a/src/views/project/items/components/ProjectItemsList/index.vue b/src/views/project/items/components/ProjectItemsList/index.vue index dc4f9206..2ce7145c 100644 --- a/src/views/project/items/components/ProjectItemsList/index.vue +++ b/src/views/project/items/components/ProjectItemsList/index.vue @@ -1,28 +1,40 @@