From e16863930f8ded77688bb1de730b6aa56acad36f Mon Sep 17 00:00:00 2001 From: jinjian <151812800@qq.com> Date: Thu, 20 Oct 2022 17:15:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=90=8E=E7=AB=AF=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E6=95=B4=E7=90=86=EF=BC=8C=E9=87=87=E7=94=A8=20indexD?= =?UTF-8?q?b?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 5 +- src/backend/ibackend.ts | 145 +++++++++++++++ src/backend/indexdb/indexdb.ts | 147 +++++++++++++++ src/backend/indexdbbackend.ts | 155 ++++++++++++++++ src/backend/mockbackend.ts | 130 +++++++++++++ src/enums/editPageEnum.ts | 15 +- src/enums/httpEnum.ts | 8 +- src/enums/pageEnum.ts | 7 +- src/hooks/useLifeHandler.hook.ts | 6 + src/i18n/en/index.ts | 8 + src/i18n/en/login.ts | 4 +- src/i18n/en/project.ts | 4 +- src/i18n/zh/index.ts | 13 ++ src/i18n/zh/login.ts | 2 +- src/i18n/zh/project.ts | 2 + src/plugins/icon.ts | 6 + src/router/base.ts | 41 ++--- src/router/constant.ts | 4 + src/router/index.ts | 6 +- src/router/router-guards.ts | 18 +- src/settings/designSetting.ts | 5 +- .../chartEditStore/chartEditStore.d.ts | 42 ++++- .../modules/chartEditStore/chartEditStore.ts | 57 ++++-- src/utils/file.ts | 46 ++++- src/utils/http.ts | 27 +++ src/utils/index.ts | 1 + src/utils/router.ts | 70 +++++-- src/utils/storage.ts | 38 ++++ src/utils/utils.ts | 21 --- .../components/CanvasPage/index.vue | 25 ++- .../components/ChartDataRequest/index.vue | 8 +- .../components/EditBottom/index.vue | 32 ++-- .../components/EditDataSync/index.ts | 3 + .../components/EditDataSync/index.vue | 97 ++++++++++ .../EditShortcutKey/ShortcutKeyModal.vue | 11 +- src/views/chart/ContentEdit/index.vue | 11 +- .../ContentHeader/headerLeftBtn/index.vue | 23 ++- .../ContentHeader/headerRightBtn/index.vue | 174 +++++++++++++++--- .../chart/ContentHeader/headerTitle/index.vue | 49 +++-- src/views/chart/hooks/useKeyboard.hook.ts | 16 +- src/views/chart/hooks/useSync.hook.ts | 127 ++++++++++++- src/views/exception/500.vue | 8 +- src/views/login/index.vue | 33 ++-- src/views/preview/index.d.ts | 3 +- src/views/preview/index.vue | 90 +-------- src/views/preview/suspenseIndex.vue | 110 +++++++++++ src/views/preview/utils/storage.ts | 48 ++++- .../components/ProjectItemsCard/index.vue | 52 ++++-- .../ProjectItemsList/hooks/useData.hook.ts | 157 +++++++++++----- .../ProjectItemsList/hooks/useModal.hook.ts | 22 ++- .../components/ProjectItemsList/index.vue | 54 +++--- .../ProjectItemsModalCard/index.vue | 23 +-- src/views/project/items/index.d.ts | 5 +- .../components/CreateModal/index.vue | 41 ++++- src/views/redirect/UnPublish.vue | 35 ++++ 55 files changed, 1890 insertions(+), 400 deletions(-) create mode 100644 src/backend/ibackend.ts create mode 100644 src/backend/indexdb/indexdb.ts create mode 100644 src/backend/indexdbbackend.ts create mode 100644 src/backend/mockbackend.ts create mode 100644 src/utils/http.ts create mode 100644 src/views/chart/ContentEdit/components/EditDataSync/index.ts create mode 100644 src/views/chart/ContentEdit/components/EditDataSync/index.vue create mode 100644 src/views/preview/suspenseIndex.vue create mode 100644 src/views/redirect/UnPublish.vue diff --git a/src/App.vue b/src/App.vue index 1865e459..d2dd6e20 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/backend/ibackend.ts b/src/backend/ibackend.ts new file mode 100644 index 00000000..a622689d --- /dev/null +++ b/src/backend/ibackend.ts @@ -0,0 +1,145 @@ + +/** + * 后端接口,相关功能对应表: + * 登录 - login + * 登出 - logout + * 预览,token 注入或单点登陆 - checkToken + * 显示项目列表和分页 - projectList + * 保存、发布、修改名称 - updateProject + * 复制项目 - copyProject + * 图表内的图片上传 - uploadFile + * 上传图片显示处理 - getFileUrl + * 所有接口返回格式:MyResponseType + */ +import { IndexDbBackend } from "./indexdbbackend"; +import { MockBackend } from "./mockbackend"; + +export interface MyResponseType { + code: number; // 状态:200 表示接口调用成功,参考:HttpEnum + msg: string; // 提示信息,配合 data 和 code + data: any; // data = null 表示接口结果错误,错误原因放在 msg +} + +export class MyResponse implements MyResponseType { + code: number = 200; + msg: string = ""; + data: any = {}; +} + +/** + * 实现 IBackend 后端接口 + * 错误处理: + */ +export interface IBackend { + /** + * 初始化后端系统,测试后端连接,oss地址等 + * @param data 可选,备用 + */ + init(data:any):any + + /** + * 登陆 + * @param data {} .username .password + * @return MyResponseType + * .data 须包含: + * token:{tokenValue:"", tokenName:""}, + * userinfo:{nickname:"", username: "", id: 用户ID} + * 错误处理: + * 1 接口错误 .code 不为 200 .msg 可选,后端反馈错误信息 + * 2 登陆错误 .code=200 .data = null, msg 可选,反馈不能登陆的原因 + * 登陆信息.data 记录: + * setLocalStorage(GO_LOGIN_INFO_STORE, res.data) + */ + login(data:any):any + + /** + * 通知后端登出 + */ + logout():any + + /** + * 检查Token是否有效,配合预览页面和单点登陆,备用 + * @param data {tokenValue, tokenName} + * @return 同 login() + */ + checkToken(data:any):any + + /** + * 项目列表 + * @param data {} .page, .limit + * @return [项目],字段名称需要进行 map + * id: projectId + * title:projectName + * release, + * label:remarks + * image:indexImage 如果需要挂刷新,在这里处理。如果需要拼接 url(getFileUrl),也在这里处理好。 + */ + projectList(data:any):any + + /** + * 新增项目 + * @param data + * .projectName + * @return id 新项目 ID + */ + createProject(data: any):any + + /** + * 获取项目 + * @param data .projectId + * @return + id:projectId + projectName, + state: release, + remarks, + content + */ + fetchProject(data: any):any + + /** + * 修改项目 + * @param data + * .projectId 必须 + * .projectName 可选 + * .release 可选 + * .content 可选 + * .object File 可选 对象 + * .remarks 可选 + * @return + */ + updateProject(data: any):any + + /** + * 复制项目 + * @param data + * .copyId 需要复制的项目ID + * .projectName + * @return id 新项目ID + */ + copyProject(data: any):any + + /** + * 删除项目 + * @param data + * .projectId + * @return + */ + deleteProject(data: any):any + + /** + * 文件上传 + * @param file File 图片对象 + * @param params 备用 Todo: 上传文件可带上项目ID和其他附加信息,以便后端文件管理 + * @return .uri 文件对象 uri。建议在图表中保存相对地址,通过 getFileUrl 得到完整地址 + */ + uploadFile(file: File, params: any):any + /** + * 文件地址转换,处理 uploadFile 的返回地址。如果是绝对地址,可以不处理 + * @param uploadUri 上传返回的 uri + * @return 供 image.src 使用的地址信息 + */ + getFileUrl(uploadUri:string):string +} + +export const BackEndFactory = new IndexDbBackend(); +// export const BackEndFactory = new MockBackend(); \ No newline at end of file diff --git a/src/backend/indexdb/indexdb.ts b/src/backend/indexdb/indexdb.ts new file mode 100644 index 00000000..2e17c49b --- /dev/null +++ b/src/backend/indexdb/indexdb.ts @@ -0,0 +1,147 @@ +/** + * IndexDb 帮助类 + */ + +const win: { [k: string]: any } = window || globalThis; +const indexedDB = + win.indexedDB || win.mozIndexedDB || win.webkitIndexedDB || win.msIndexedDB; +const dbs: { [k: string]: IDBDatabase } = {}; +let databaseName: string; +let request: IDBOpenDBRequest; +interface AnyEvent { + [k: string]: any; +} + +export interface TableOption { + storeName: string; + option: { [K: string]: any }; + index: { [K: string]: any }[]; +} + +export const createDB = ( + name: string, + version?: string, + options?: TableOption[], +) => + new Promise((resolve, reject) => { + if (!indexedDB) reject('浏览器不支持indexedDB'); + databaseName = name; + if (dbs?.[name]) { + resolve(dbs[name]); + return; + } + request = indexedDB.open(name, version); + createTable(options)?.then((db: IDBDatabase) => resolve(db)); + request.onsuccess = (event: AnyEvent) => { + // IDBDatabase + const db = event.target.result; + // 缓存起来 + dbs[name] = db; + resolve(db); + }; + request.onerror = (event: AnyEvent) => reject(event); + }); + +export const createTable = (options?: TableOption[]) => { + if (!options) return; + return new Promise((resolve) => { + request.onupgradeneeded = (event: AnyEvent) => { + const db = event.target.result; + dbs[databaseName] = db; + for (const i in options) { + // 判断是否存在表 + if (!db.objectStoreNames.contains(options[i].storeName)) { + const objectStore = db.createObjectStore( + options[i].storeName, + options[i].option, + ); + for (const j of options[i].index) { + objectStore.createIndex(j.name, j.keyPath, { + unique: j.unique, + }); + } + } + } + resolve(db); + }; + }); +}; + +const getTransaction = async (name: string, version?: string) => { + let db: IDBDatabase; + // 先从缓存获取 + if (dbs[databaseName]) { + db = dbs[databaseName]; + } else { + db = await createDB(databaseName, version); + } + return db.transaction(name, 'readwrite'); +}; + +const getObjectStore = async ( + name: string, + version?: string, +): Promise => { + const transaction = await getTransaction(name, version); + return transaction.objectStore(name); +}; + +const getStore = (name: string, type: string, data: any) => + new Promise((resolve) => { + getObjectStore(name).then((objectStore: IDBObjectStore | any) => { + const request = objectStore[type](data); + request.onsuccess = (event: AnyEvent) => + resolve(event.target.result); + }); + }); + + +const findStore = ( + name: string, + start: any, + end: any, + startInclude: any, + endInclude: any, +) => + new Promise((resolve, reject) => { + getObjectStore(name).then((objectStore: IDBObjectStore) => { + const request = objectStore.openCursor( + IDBKeyRange.bound(start, end, startInclude, endInclude), + ); + request.onsuccess = (event: AnyEvent) => + resolve(event.target.result); + request.onerror = (event: AnyEvent) => reject(event); + }); + }); + +export interface DBSelect { + add: (data: any) => Promise; + get: (data: any) => Promise; + getAll: () => Promise; + del: (data: any) => Promise; + clear: (data: any) => Promise; + put: (data: any) => Promise; + find: ( + start: any, + end: any, + startInclude: any, + endInclude: any, + ) => Promise; +} +// 获取一个store +export const onDBSelect = async ( + name: string, + version: string +): Promise => { + const add = (data: any) => getStore(name, 'add', data); + const get = (data: any) => getStore(name, 'get', data); + const getAll = () => getStore(name, 'getAll', null); + const del = (data: any) => getStore(name, 'delete', data); + const clear = (data: any) => getStore(name, 'clear', data); + const put = (data: any) => getStore(name, 'put', data); + const find = (start: any, end: any, startInclude: any, endInclude: any) => + findStore(name, start, end, startInclude, endInclude); + const options: DBSelect = { add, get, getAll, clear, del, put, find }; + getObjectStore(name, version); + return options; +}; \ No newline at end of file diff --git a/src/backend/indexdbbackend.ts b/src/backend/indexdbbackend.ts new file mode 100644 index 00000000..345ba482 --- /dev/null +++ b/src/backend/indexdbbackend.ts @@ -0,0 +1,155 @@ +import { MyResponse, IBackend } from './ibackend' +import { createDB, DBSelect, onDBSelect } from './indexdb/indexdb' +import { fileToUrl, fileToBlob } from "@/utils" + +const PROJECT_TABLE = "project" +const IMAGE_TABLE = "image" // 保存图片,未实现,Todo +const DB_NAME = "goview" +const DB_VER = "1" + +export class IndexDbBackend implements IBackend { + public async init(data: any) { + let rtn:MyResponse = new MyResponse; + const db:IDBDatabase = await createDB(DB_NAME, DB_VER, [ + { + storeName: PROJECT_TABLE, + option: { + keyPath: "projectId", autoIncrement:true + }, + index: [ + {name: 'projectId', keyPath: "projectId", unique: true}, + {name: 'projectName', keyPath: "projectName", unique: false}, + {name: 'release', keyPath: "release", unique: false}, + {name: 'remarks', keyPath: "remarks", unique: false}, + {name: 'content', keyPath: "content", unique: false}, + {name: 'indexImage', keyPath: "indexImage", unique: false} + ] + } + ]) + return rtn; + } + + public async login(data:any) { + let rtn:MyResponse = new MyResponse; + if(data.password == "123456" && data.username == "admin"){ + rtn.data = { + token:{tokenValue:"mockToken", tokenName:"name"}, + userinfo:{nickname:"nickname", username:data.username, id:1} + } + }else{ + rtn.data = null + rtn.msg = "admin 和 123456" + } + return rtn; + } + + public async logout() { + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async checkToken(data: any) { + let rtn:MyResponse = new MyResponse; + console.log("CheckToken: " + data.token) + rtn.data = { + token:{tokenValue:"mockToken", tokenName:"name"}, + userinfo:{nickname:"nickname", username:data.username, id:1} + } + return rtn; + } + + public async projectList(data:any){ + let rtn:MyResponse = new MyResponse; + const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER) + const r:any = await db.getAll() + rtn.data = [] + r.map(function (item: any) { + let url = "" + if(item.indexImage){ + const Url = URL || window.URL || window.webkitURL + url = Url.createObjectURL(item.indexImage) + } + rtn.data.push({ + id: item.projectId, + title: item.projectName, + release: item.release == 1, + label:item.remarks, + image:url + }) + }) + return rtn; + } + + public async createProject(data: any){ + let rtn:MyResponse = new MyResponse; + const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER) + rtn.data.id = await db.add({ projectName:data.projectName }) + return rtn; + } + + public async fetchProject(data: any){ + let rtn:MyResponse = new MyResponse; + const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER) + const r:any = await db.get(parseInt(data.projectId)) + rtn.data = { + id:r.projectId, + projectName: r.projectName, + state: r.release, + remarks: r.remarks, + content: r.content + } + return rtn; + } + + public async updateProject(data: any){ + let rtn:MyResponse = new MyResponse; + const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER) + const row:any = await db.get(parseInt(data.projectId)) + if("content" in data) row.content = data.content + if("projectName" in data) row.projectName = data.projectName + if("release" in data) row.release = data.release + if("remarks" in data) row.remarks = data.remarks + if("object" in data) { + row.indexImage = await fileToBlob(data.object) + } + await db.put(row) + return rtn; + } + + public async copyProject(data: any){ + let rtn:MyResponse = new MyResponse; + const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER) + const row:any = await db.get(parseInt(data.copyId)) + rtn.data.id =await db.add({ + projectName:data.projectName, + content:row.content, + indexImage:row.indexImage, + remarks:row.remarks + }) + return rtn; + } + + public async deleteProject(data: any){ + let rtn:MyResponse = new MyResponse; + const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER) + await db.del(parseInt(data.projectId)) + return rtn; + } + + public async changeProjectRelease(data: any){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async uploadFile(data: File, params:any){ + // Todo: 图片可以保存在表中 + let rtn:MyResponse = new MyResponse; + rtn.data.uri = fileToUrl(data) + return rtn; + } + + public getFileUrl(uploadUri:string){ + return uploadUri; + } +} + diff --git a/src/backend/mockbackend.ts b/src/backend/mockbackend.ts new file mode 100644 index 00000000..2c1ae819 --- /dev/null +++ b/src/backend/mockbackend.ts @@ -0,0 +1,130 @@ +import { MyResponse, IBackend } from './ibackend' +import { fileToUrl } from '@/utils' + + +/** + * MockBackend + * 模拟纯前端,不会保存,也不报错。 + */ + +export class MockBackend implements IBackend { + public async init(data: any) { + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async login(data:any) { + let rtn:MyResponse = new MyResponse; + if(data.password == "123456" && data.username == "admin"){ + rtn.data = { + token:{tokenValue:"mockToken", tokenName:"name"}, + userinfo:{nickname:"nickname", username:data.username, id:1} + } + }else{ + rtn.data = null + rtn.msg = "用户名或密码错误!" + } + return rtn; + } + + public async logout() { + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async checkToken(data:any){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async projectList(data:any){ + let rtn:MyResponse = new MyResponse; + rtn.data =[ + { + id: 1, + title: '假数据不可用', + 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: '官方案例' + } + ]; + return rtn; + } + + public async createProject(data: any){ + let rtn:MyResponse = new MyResponse; + rtn.data.id = "newId" + return rtn; + } + + public async fetchProject(data: any){ + let rtn:MyResponse = new MyResponse; + rtn.data = { + id:data.projectId, + projectName: '假数据不可用', + indexImage:'', + state: 0, + remarks: '官方案例', + content: null + } + return rtn; + } + + public async saveProject(data: object){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async updateProject(data: any){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async copyProject(data: any){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async deleteProject(data: any){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async changeProjectRelease(data: any){ + let rtn:MyResponse = new MyResponse; + return rtn; + } + + public async uploadFile(data: File, params:any){ + let rtn:MyResponse = new MyResponse; + rtn.data.uri = fileToUrl(data) + return rtn; + } + + public getFileUrl(uploadUri:string){ + return uploadUri; + } +} \ No newline at end of file diff --git a/src/enums/editPageEnum.ts b/src/enums/editPageEnum.ts index df395653..faa60eff 100644 --- a/src/enums/editPageEnum.ts +++ b/src/enums/editPageEnum.ts @@ -40,8 +40,9 @@ export enum MenuEnum { UN_GROUP = 'unGroup', // 后退 BACK = 'back', - // 前进 FORWORD = 'forward', + // 保存 + SAVE = 'save', // 锁定 LOCK = 'lock', // 解除锁定 @@ -72,3 +73,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 c51d7e29..a9304ca3 100644 --- a/src/enums/httpEnum.ts +++ b/src/enums/httpEnum.ts @@ -2,11 +2,11 @@ * @description: 请求结果集 */ export enum ResultEnum { - DATA_SUCCESS = 0, SUCCESS = 200, SERVER_ERROR = 500, SERVER_FORBIDDEN = 403, NOT_FOUND = 404, + TOKEN_OVERDUE = 886, TIMEOUT = 60000 } @@ -26,6 +26,12 @@ export enum RequestContentTypeEnum { SQL = 1 } +// 头部 +export enum RequestHttpHeaderEnum { + TOKEN = 'Token', + COOKIE = 'Cookie' +} + /** * @description: 请求方法 */ 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/hooks/useLifeHandler.hook.ts b/src/hooks/useLifeHandler.hook.ts index aa0d557b..af471fd6 100644 --- a/src/hooks/useLifeHandler.hook.ts +++ b/src/hooks/useLifeHandler.hook.ts @@ -1,5 +1,11 @@ import { CreateComponentType, EventLife } from '@/packages/index.d' import * as echarts from 'echarts' +import { BackEndFactory } from '@/backend/ibackend' + +// * 初始化 +export const useSystemInit = async () => { + const res = await BackEndFactory.init({}) as any; +} // 所有图表组件集合对象 const components: { [K in string]?: any } = {} 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/icon.ts b/src/plugins/icon.ts index 48d6a0a0..80551a5a 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, @@ -91,6 +92,7 @@ import { FitToScreen as FitToScreenIcon, FitToHeight as FitToHeightIcon, FitToWidth as FitToWidthIcon, + Save as SaveIcon, Carbon3DCursor as Carbon3DCursorIcon, Carbon3DSoftware as Carbon3DSoftwareIcon, Filter as FilterIcon, @@ -205,6 +207,8 @@ const ionicons5 = { PawIcon, // 搜索(放大镜) SearchIcon, + // 加载 + ReloadIcon, // 过滤器 FilterIcon, // 向上 @@ -270,6 +274,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..d3c8858d 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) => { @@ -10,13 +18,13 @@ export function createRouterGuards(router: Router) { const isErrorPage = router.getRoutes().findIndex((item) => item.name === to.name); if (isErrorPage === -1) { next({ name: PageEnum.ERROR_PAGE_NAME_404 }) + return } - if (!loginCheck()) { - if (to.name === PageEnum.BASE_LOGIN_NAME) { - next() - } + // @ts-ignore + if (!routerAllowList.includes(to.name) && !loginCheck()) { next({ name: PageEnum.BASE_LOGIN_NAME }) + return } 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/store/modules/chartEditStore/chartEditStore.d.ts b/src/store/modules/chartEditStore/chartEditStore.d.ts index 4d0232cb..db5f2433 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,29 @@ import { import { PreviewScaleEnum } from '@/enums/styleEnum' import type { ChartColorsNameType, GlobalThemeJsonType } from '@/settings/chartThemes/index' +// 项目数据枚举 +export enum ProjectInfoEnum { + // ID + PROJECT_ID = "projectId", + // 名称 + PROJECT_NAME = 'projectName', + // 描述 + REMARKS = 'remarks', + // 缩略图 + THUMBNAIL= 'thumbnail', + // 是否公开发布 + RELEASE = 'release' +} + +// 项目数据 +export type ProjectInfoType = { + [ProjectInfoEnum.PROJECT_ID]: string, + [ProjectInfoEnum.PROJECT_NAME]: string, + [ProjectInfoEnum.REMARKS]: string, + [ProjectInfoEnum.THUMBNAIL]: string, + [ProjectInfoEnum.RELEASE]: boolean +} + // 编辑画布属性 export enum EditCanvasTypeEnum { EDIT_LAYOUT_DOM = 'editLayoutDom', @@ -20,12 +44,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 +67,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 +85,14 @@ export enum EditCanvasConfigEnum { PREVIEW_SCALE_TYPE = 'previewScaleType' } -export interface EditCanvasConfigType { +// 画布属性(需保存) +export type EditCanvasConfigType = { + // ID + [EditCanvasConfigEnum.PROJECT_ID]: string, + // 项目名称 + [EditCanvasConfigEnum.PROJECT_NAME]: string, + // 项目描述 + [EditCanvasConfigEnum.REMARKS]: string, // 滤镜-启用 [FilterEnum.FILTERS_SHOW]: boolean // 滤镜-色相 @@ -130,6 +164,7 @@ export type RecordChartType = { // Store 枚举 export enum ChartEditStoreEnum { + PROJECT_INFO = 'projectInfo', EDIT_RANGE = 'editRange', EDIT_CANVAS = 'editCanvas', RIGHT_MENU_SHOW = 'rightMenuShow', @@ -180,6 +215,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 ea1759a7..8da161b7 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 { - HistoryActionTypeEnum, - HistoryItemType, - HistoryTargetTypeEnum -} from '@/store/modules/chartHistoryStore/chartHistoryStore.d' -import { MenuEnum } from '@/enums/editPageEnum' -import { getUUID, loadingStart, loadingFinish, loadingError, isString, isArray } from '@/utils' + getUUID, + loadingStart, + loadingFinish, + loadingError, + isString, + isArray +} from '@/utils' + import { + ProjectInfoType, ChartEditStoreEnum, ChartEditStorage, ChartEditStoreType, @@ -36,6 +44,14 @@ const settingStore = useSettingStore() export const useChartEditStore = defineStore({ id: 'useChartEditStore', state: (): ChartEditStoreType => ({ + // 项目数据 + projectInfo: { + projectId: '', + projectName: '', + remarks: '', + thumbnail: '', + release: false + }, // 画布属性 editCanvas: { // 编辑区域 Dom @@ -54,7 +70,9 @@ export const useChartEditStore = defineStore({ // 拖拽中 isDrag: false, // 框选中 - isSelect: false + isSelect: false, + // 同步中 + saveStatus: SyncEnum.PENDING }, // 右键菜单 rightMenuShow: false, @@ -131,6 +149,9 @@ export const useChartEditStore = defineStore({ componentList: [] }), getters: { + getProjectInfo(): ProjectInfoType { + return this.projectInfo + }, getMousePosition(): MousePositionType { return this.mousePosition }, @@ -165,6 +186,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 @@ -503,21 +528,21 @@ export const useChartEditStore = defineStore({ item.id = getUUID() }) } - + return e } const isCut = recordCharts.type === HistoryActionTypeEnum.CUT const targetList = Array.isArray(recordCharts.charts) ? recordCharts.charts : [ recordCharts.charts ] // 多项 targetList.forEach((e: CreateComponentType | CreateComponentGroupType) => { - this.addComponentList(parseHandle(e), undefined, true) - // 剪切需删除原数据 - if (isCut) { - this.setTargetSelectChart(e.id) - this.removeComponentList(undefined, true) - } - }) - if (isCut) this.setRecordChart(undefined) + this.addComponentList(parseHandle(e), undefined, true) + // 剪切需删除原数据 + if (isCut) { + this.setTargetSelectChart(e.id) + this.removeComponentList(undefined, true) + } + }) + if (isCut) this.setRecordChart(undefined) loadingFinish() } catch (value) { loadingError() diff --git a/src/utils/file.ts b/src/utils/file.ts index 858afbad..92e507ab 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -1,3 +1,47 @@ +/** + * * 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 +} + +/** + * file转 blob + * @param { File } file 文件对象 + */ +export const fileToBlob = (file:File) =>{ + return new Promise(function (resolve, reject) { + let reader = new FileReader() + reader.readAsArrayBuffer(file) + reader.onload = function (e: ProgressEvent) { + if(e.target){ + const blob = new Blob([e.target.result], { type: file.type }); + resolve(blob); + } + } + }) +} + /** * *获取上传的文件数据 * @param { File } file 文件对象 @@ -51,4 +95,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..41a92adf --- /dev/null +++ b/src/utils/http.ts @@ -0,0 +1,27 @@ +/** + * 请求失败统一处理,allowRoute 允许跳转。 + * @param MyResponse MyResponseType,可以为空。 + * @return + */ + import { ResultEnum } from "@/enums/httpEnum" + import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum" + import { redirectErrorPage, routerTurnByName } from '@/utils' + + export const httpErrorHandle = (MyResponse?:any, allowRoute:boolean = true) => { + if(MyResponse){ + const {code, msg} = MyResponse + if (MyResponse.code === ResultEnum.TOKEN_OVERDUE) { + window['$message'].error(msg || window['$t']('http.token_overdue_message')) + if(allowRoute) routerTurnByName(PageEnum.BASE_LOGIN_NAME) + return + } + + if (MyResponse.code != ResultEnum.SUCCESS) { + // 其他错误处理 Todo + if (ErrorPageNameMap.get(code) && allowRoute) { + redirectErrorPage(code) + } + } + } + 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 8d2478e6..3f015786 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from '@/utils/plugin' export * from '@/utils/components' export * from '@/utils/type' export * from '@/utils/file' +export * from '@/utils/http' \ No newline at end of file diff --git a/src/utils/router.ts b/src/utils/router.ts index f2bf6142..a3fdd87e 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -1,11 +1,11 @@ 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 { StorageEnum } from '@/enums/storageEnum' -import { clearLocalStorage, getLocalStorage } from './storage' +import { clearLocalStorage, getLocalStorage, clearCookie } from './storage' import router from '@/router' +import { BackEndFactory } from '@/backend/ibackend' /** * * 根据名字跳转路由 @@ -101,11 +101,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 BackEndFactory.logout() as any + if(res.code === ResultEnum.SUCCESS) { + window['$message'].success(window['$t']('global.logout_success')) + clearCookie(RequestHttpHeaderEnum.COOKIE) + clearLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE) + routerTurnByName(PageEnum.BASE_LOGIN_NAME) + } + } catch (error) { + window['$message'].success(window['$t']('global.logout_failure')) + } } /** @@ -153,6 +162,28 @@ export const fetchRouteParams = () => { } } +export const fetchRouteQuery = () => { + try { + const route = useRoute() + return route.query + } catch (error) { + window['$message'].warning('查询路由信息失败,请联系管理员!') + } +} + +/** + * * 通过硬解析获取当前路由下的参数 + * @returns object + */ +export const fetchRouteParamsLocation = () => { + try { + return document.location.hash.split('/').pop() || '' + } catch (error) { + window['$message'].warning('查询路由信息失败,请联系管理员!') + return '' + } +} + /** * * 回到主页面 * @param confirm @@ -162,19 +193,28 @@ export const goHome = () => { } /** - * * 判断是否登录(现阶段是有 login 数据即可) + * * 判断是否登录 * @return boolean */ -export const loginCheck = () => { + export const loginCheck = () => { try { const info = getLocalStorage(StorageEnum.GO_LOGIN_INFO_STORE) if (!info) return false - const decodeInfo = cryptoDecode(info) - if (decodeInfo) { - return true - } + // 检查 Token ? + if(info.token && info.userinfo) 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 d55d33bd..97d288eb 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -68,3 +68,41 @@ 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); +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 6d56e05c..8e35c8ba 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -113,28 +113,7 @@ 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 5d4bcc23..a7730a53 100644 --- a/src/views/chart/ContentConfigurations/components/CanvasPage/index.vue +++ b/src/views/chart/ContentConfigurations/components/CanvasPage/index.vue @@ -30,7 +30,7 @@ :onBeforeUpload="beforeUploadHandle" > - 背景 + 背景
@@ -133,9 +133,12 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore import { EditCanvasConfigEnum } from '@/store/modules/chartEditStore/chartEditStore.d' 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 { BackEndFactory } from '@/backend/ibackend' + const { ColorPaletteIcon } = icon.ionicons5 const { ScaleIcon, FitToScreenIcon, FitToHeightIcon, FitToWidthIcon } = icon.carbon @@ -268,11 +271,21 @@ const clearColor = () => { // 自定义上传操作 const customRequest = (options: UploadCustomRequestOptions) => { const { file } = options - nextTick(() => { + nextTick(async () => { if (file.file) { - const ImageUrl = fileToUrl(file.file) - chartEditStore.setEditCanvasConfig(EditCanvasConfigEnum.BACKGROUND_IMAGE, ImageUrl) - chartEditStore.setEditCanvasConfig(EditCanvasConfigEnum.SELECT_COLOR, false) + const uploadRes = await BackEndFactory.uploadFile(file.file, null) as any + if(uploadRes.code === ResultEnum.SUCCESS) { + chartEditStore.setEditCanvasConfig( + EditCanvasConfigEnum.BACKGROUND_IMAGE, + uploadRes.data.uri + ) + chartEditStore.setEditCanvasConfig( + EditCanvasConfigEnum.SELECT_COLOR, + false + ) + return + } + window['$message'].error('添加图片失败,请稍后重试!') } else { window['$message'].error('添加图片失败,请稍后重试!') } diff --git a/src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/index.vue b/src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/index.vue index 21511961..3a016c79 100644 --- a/src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/index.vue +++ b/src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/index.vue @@ -1,5 +1,5 @@ @@ -38,7 +55,7 @@ import { toRefs, Ref, reactive, computed } from 'vue' import { renderIcon, goDialog, goHome } from '@/utils' import { icon } from '@/plugins' import { useRemoveKeyboard } from '../../hooks/useKeyboard.hook' - +import { useSync } from '../../hooks/useSync.hook' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore' @@ -48,7 +65,9 @@ import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayou import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d' const { LayersIcon, BarChartIcon, PrismIcon, HomeIcon, ArrowBackIcon, ArrowForwardIcon } = icon.ionicons5 +const { SaveIcon } = icon.carbon const { setItem } = useChartLayoutStore() +const { dataSyncUpdate } = useSync() const { getLayers, getCharts, getDetails } = toRefs(useChartLayoutStore()) const chartEditStore = useChartEditStore() const chartHistoryStore = useChartHistoryStore() @@ -130,7 +149,7 @@ const clickHistoryHandle = (item: ItemType) => { // 返回首页 const goHomeHandle = () => { goDialog({ - message: '返回将不会保存任何操作', + message: '确定已保存了数据(Ctrl / ⌘ + S),并返回到首页吗?', isMaskClosable: true, onPositiveCallback: () => { goHome() diff --git a/src/views/chart/ContentHeader/headerRightBtn/index.vue b/src/views/chart/ContentHeader/headerRightBtn/index.vue index b92a86de..513b989d 100644 --- a/src/views/chart/ContentHeader/headerRightBtn/index.vue +++ b/src/views/chart/ContentHeader/headerRightBtn/index.vue @@ -1,29 +1,98 @@ + diff --git a/src/views/chart/ContentHeader/headerTitle/index.vue b/src/views/chart/ContentHeader/headerTitle/index.vue index 5ac70dc8..d6dc7a85 100644 --- a/src/views/chart/ContentHeader/headerTitle/index.vue +++ b/src/views/chart/ContentHeader/headerTitle/index.vue @@ -6,9 +6,7 @@ 工作空间 - - - {{ comTitle }} - + {{ comTitle }} @@ -29,31 +27,32 @@ diff --git a/src/views/preview/suspenseIndex.vue b/src/views/preview/suspenseIndex.vue new file mode 100644 index 00000000..2a51dad7 --- /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..751e1e83 100644 --- a/src/views/preview/utils/storage.ts +++ b/src/views/preview/utils/storage.ts @@ -1,23 +1,55 @@ -import { getSessionStorage } from '@/utils' +import { loginCheck, getSessionStorage, fetchRouteParamsLocation, httpErrorHandle, fetchRouteParams, fetchRouteQuery, setLocalStorage } from '@/utils' +import { ResultEnum } from '@/enums/httpEnum' import { StorageEnum } from '@/enums/storageEnum' import { ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' +import { BackEndFactory } from '@/backend/ibackend' + 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 - + + // 是否本地预览 + if (!storageList || storageList.findIndex(e => e.id === id.toString()) === -1) { + // 处理 Token 注入 + const q = fetchRouteQuery(); + if(q && q.token && !loginCheck()){ + // Token 注入 + const rt = await BackEndFactory.checkToken({ token: q.token }) as any + if (rt.code === ResultEnum.SUCCESS && rt.data) { + // 记录登陆信息 + setLocalStorage( StorageEnum.GO_LOGIN_INFO_STORE, rt.data) + }else{ + httpErrorHandle() + return {} + } + } + + // 接口调用 + const res = await BackEndFactory.fetchProject({ projectId: id }) as any + if (res.code === ResultEnum.SUCCESS && res.data) { + const { content, state } = res.data + if (state === -1) { + // 跳转未发布页 + return { isRelease: false } + } + return { ...JSON.parse(content), id } + } else { + httpErrorHandle() + // 错误处理,Todo + return {} + } + } + + // 本地读取 for (let i = 0; i < storageList.length; i++) { if (id.toString() === storageList[i]['id']) { return storageList[i] diff --git a/src/views/project/items/components/ProjectItemsCard/index.vue b/src/views/project/items/components/ProjectItemsCard/index.vue index b042d229..756b10fb 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}`" :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','copy', '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,13 @@ const selectOptions = ref([ { label: renderLang('global.r_copy'), key: 'copy', - icon: renderIcon(CopyIcon) + icon: renderIcon(CopyIcon), }, { label: renderLang('global.r_rename'), key: 'rename', - icon: renderIcon(PencilIcon) + icon: renderIcon(PencilIcon), + disabled: true }, { type: 'divider', @@ -148,13 +142,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 +164,17 @@ const selectOptions = ref([ const handleSelect = (key: string) => { switch (key) { + case 'preview': + previewHandle() + break case 'delete': - deleteHanlde() + deleteHandle() + break + case 'copy': + emit('copy', props.cardData) + break; + case 'release': + releaseHandle() break case 'edit': editHandle() @@ -178,8 +182,13 @@ const handleSelect = (key: string) => { } } +// 预览处理 +const previewHandle = () => { + emit('preview', props.cardData) +} + // 删除处理 -const deleteHanlde = () => { +const deleteHandle = () => { emit('delete', props.cardData) } @@ -188,6 +197,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..20ae0994 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,131 @@ -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 { BackEndFactory } from '@/backend/ibackend' +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 BackEndFactory.projectList({ + page: paginat.page, + limit: paginat.limit + }) as any + if (res.code==ResultEnum.SUCCESS) { + const { count } = res + paginat.count = count + list.value = res.data; + 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(BackEndFactory.deleteProject({ + projectId: cardData.id + })) + }), + promiseResCallback: (res: any) => { + if (res.code === ResultEnum.SUCCESS) { + window['$message'].success(window['$t']('global.r_delete_success')) + fetchList() + return + } + httpErrorHandle() } }) } + + // 复制项目 + const copyHandle = async (cardData: Chartype) => { + const { id, title } = cardData + const res = await BackEndFactory.copyProject({ + copyId: id, + projectName: '复制-' + title + }) as any + if (res.code === ResultEnum.SUCCESS) { + list.value = [] + fetchList() + window['$message'].success("复制项目成功!") + return + } + httpErrorHandle() + } + + + // 发布处理 + const releaseHandle = async (cardData: Chartype, index: number) => { + const { id, release } = cardData + const res = await BackEndFactory.updateProject({ + projectId: id, + // [-1未发布, 1发布] + release: !release ? 1 : -1 + }) as any + 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, + copyHandle, + 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..5b6e54bb 100644 --- a/src/views/project/items/components/ProjectItemsList/index.vue +++ b/src/views/project/items/components/ProjectItemsList/index.vue @@ -1,28 +1,41 @@