├── src ├── styles │ ├── el-override.scss │ ├── index.scss │ ├── theme │ │ ├── index.scss │ │ ├── base.scss │ │ └── dark.scss │ ├── size.scss │ └── scrollbar.scss ├── directive │ ├── index.ts │ ├── permission.ts │ └── loading.ts ├── constant │ ├── key.ts │ ├── cdn.ts │ └── basic.ts ├── enums │ ├── storageEnum.ts │ ├── LayoutEnum.ts │ ├── DropMenuEnum.ts │ ├── SizeEnum.ts │ ├── httpEnum.ts │ └── loadingEnum.ts ├── components │ ├── base-dialog │ │ ├── index.ts │ │ └── src │ │ │ └── base-dialog.scss │ ├── base-form │ │ ├── index.ts │ │ └── src │ │ │ ├── type.ts │ │ │ └── props.ts │ ├── base-virtual-list │ │ ├── index.ts │ │ └── src │ │ │ └── props.ts │ ├── base-table │ │ ├── index.ts │ │ └── src │ │ │ ├── base-table.scss │ │ │ └── types.ts │ ├── base-seamscroll │ │ ├── index.ts │ │ └── src │ │ │ ├── types.ts │ │ │ └── props.ts │ ├── base-charts │ │ ├── index.ts │ │ └── src │ │ │ ├── types.ts │ │ │ ├── config │ │ │ ├── wordcloud.ts │ │ │ ├── pie.ts │ │ │ ├── graph.ts │ │ │ ├── radar.ts │ │ │ ├── bar.ts │ │ │ ├── line.ts │ │ │ ├── heatmap.ts │ │ │ └── map.ts │ │ │ └── base-charts.vue │ ├── base-column-setting │ │ ├── index.ts │ │ └── src │ │ │ └── types.ts │ ├── base-json │ │ └── base-json.vue │ ├── base-filter │ │ └── props.ts │ ├── base-loading │ │ └── spin │ │ │ ├── planeSpin.vue │ │ │ ├── pulseSpin.vue │ │ │ ├── rectSpin.vue │ │ │ ├── preloaderSpin.vue │ │ │ └── cubeSpin.vue │ ├── base-pagination │ │ ├── base-pagination.vue │ │ └── props.ts │ ├── base-icon │ │ └── icon.ts │ ├── base-input │ │ ├── props.ts │ │ └── base-input.vue │ ├── base-input-number │ │ └── props.ts │ ├── base-tinymce │ │ └── config.ts │ ├── base-date-picker │ │ └── base-date-picker.vue │ ├── base-result │ │ └── base-result.vue │ └── base-button │ │ └── index.vue ├── assets │ ├── iconfont │ │ ├── iconfont.ttf │ │ ├── iconfont.css │ │ └── iconfont.json │ └── images │ │ ├── user │ │ ├── headImg.gif │ │ ├── avatar01.png │ │ └── avatar15.png │ │ └── login │ │ ├── login-box.png │ │ ├── background.png │ │ └── login-content.png ├── plugins │ ├── index.ts │ ├── icon.ts │ ├── globalUtils.ts │ └── echarts.ts ├── views │ ├── page │ │ ├── success.vue │ │ ├── error.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ ├── 403.vue │ │ └── table.vue │ ├── nested │ │ ├── menu2 │ │ │ └── index.vue │ │ └── menu1 │ │ │ ├── menu1-2 │ │ │ └── index.vue │ │ │ └── menu1-1 │ │ │ └── menu1-1-1 │ │ │ └── index.vue │ ├── permission │ │ ├── page.vue │ │ └── button.vue │ ├── comp │ │ ├── echarts │ │ │ ├── graph │ │ │ │ └── index.vue │ │ │ ├── pie │ │ │ │ └── index.vue │ │ │ ├── bar │ │ │ │ └── index.vue │ │ │ └── line │ │ │ │ └── index.vue │ │ ├── count-to │ │ │ └── index.vue │ │ ├── markdown │ │ │ └── index.vue │ │ ├── editor │ │ │ └── index.vue │ │ ├── seamscroll │ │ │ └── index.vue │ │ ├── json │ │ │ └── index.vue │ │ ├── photograph │ │ │ └── index.vue │ │ └── button │ │ │ └── index.vue │ ├── redirect │ │ └── index.vue │ ├── dashboard │ │ ├── analysis │ │ │ ├── index.vue │ │ │ ├── column.ts │ │ │ └── components │ │ │ │ └── TurnoverAnalysis.vue │ │ └── workbench │ │ │ └── components │ │ │ └── NoticeList.vue │ ├── system │ │ ├── dictionary-data │ │ │ └── index.vue │ │ ├── dictionary-key │ │ │ └── index.vue │ │ └── api │ │ │ └── index.vue │ ├── system-log │ │ ├── login-log │ │ │ ├── column.ts │ │ │ └── index.vue │ │ ├── operation-log │ │ │ └── index.vue │ │ └── error-log │ │ │ └── column.ts │ └── func │ │ └── message │ │ └── index.vue ├── icons │ ├── dark.svg │ ├── more.svg │ ├── close.svg │ ├── exit.svg │ ├── down.svg │ ├── up.svg │ ├── sizeMini.svg │ ├── sizePlus.svg │ ├── fullOutScreen.svg │ ├── confirm.svg │ ├── fullScreen.svg │ ├── textSize.svg │ ├── esc.svg │ ├── search.svg │ ├── size.svg │ ├── fold.svg │ ├── unfold.svg │ ├── remind.svg │ ├── about.svg │ ├── clear.svg │ ├── page.svg │ ├── empty.svg │ ├── comp.svg │ ├── gitee.svg │ ├── other.svg │ ├── export.svg │ ├── filter.svg │ ├── qq.svg │ ├── column.svg │ ├── func.svg │ ├── help.svg │ ├── light.svg │ ├── icon.svg │ ├── read.svg │ ├── dashboard.svg │ ├── setting.svg │ ├── nested.svg │ ├── location.svg │ ├── zhifubao.svg │ ├── approval.svg │ ├── out.svg │ ├── permission.svg │ ├── warning.svg │ ├── weixin.svg │ ├── github.svg │ └── good.svg ├── utils │ ├── index.ts │ ├── request │ │ ├── retry.ts │ │ ├── loading.ts │ │ └── cancel.ts │ ├── is.ts │ ├── storage.ts │ ├── remoteLoad.ts │ ├── dom.ts │ ├── date.ts │ └── navigator.ts ├── api │ ├── user.ts │ ├── system │ │ ├── dict.ts │ │ ├── dept.ts │ │ ├── user.ts │ │ ├── role.ts │ │ └── api.ts │ ├── test.ts │ └── log.ts ├── hooks │ ├── useEnv.ts │ ├── index.ts │ ├── useStorage.ts │ ├── useMessage.ts │ ├── useDark.ts │ └── useLoading.ts ├── router │ ├── guard │ │ ├── progress.ts │ │ ├── index.ts │ │ └── pageTitle.ts │ ├── index.ts │ ├── types.ts │ └── modules │ │ ├── abuout.ts │ │ ├── out-link.ts │ │ ├── permission.ts │ │ └── dashboard.ts ├── mockProdServer.ts ├── App.vue ├── layouts │ ├── hooks │ │ ├── usePageSetting.ts │ │ ├── useTagViewSetting.ts │ │ ├── useMenuSetting.ts │ │ └── useNavBarSetting.ts │ ├── side-bar │ │ ├── logo.vue │ │ └── index.vue │ ├── tag-view │ │ ├── tag-fullscreen.vue │ │ └── tag-item.vue │ ├── nav-bar │ │ ├── nav-fullscreen.vue │ │ ├── breadcrumb.vue │ │ ├── nav-user.vue │ │ ├── nav-text-size.vue │ │ └── nav-switch.vue │ └── setting │ │ └── index.vue ├── stores │ ├── index.ts │ └── modules │ │ ├── errorLog.ts │ │ └── app.ts └── main.ts ├── .vscode └── extensions.json ├── .husky ├── pre-commit └── commit-msg ├── .env ├── .prettierignore ├── .env.development ├── .env.production ├── tsconfig.node.json ├── mock ├── mockProdServer.ts ├── utils.ts └── controller │ ├── test.ts │ └── user.ts ├── .gitignore ├── public └── tinymce │ └── skins │ ├── ui │ ├── oxide │ │ ├── skin.shadowdom.min.css │ │ └── skin.shadowdom.css │ ├── tinymce-5 │ │ ├── skin.shadowdom.min.css │ │ └── skin.shadowdom.css │ ├── oxide-dark │ │ ├── skin.shadowdom.min.css │ │ └── skin.shadowdom.css │ └── tinymce-5-dark │ │ ├── skin.shadowdom.min.css │ │ └── skin.shadowdom.css │ └── content │ ├── default │ └── content.min.css │ ├── tinymce-5 │ └── content.min.css │ ├── writer │ └── content.min.css │ ├── dark │ └── content.min.css │ ├── tinymce-5-dark │ └── content.min.css │ └── document │ └── content.min.css ├── types └── global.d.ts ├── tsconfig.json ├── commitlint.config.cjs ├── .prettierrc.cjs └── .eslintrc.cjs /src/styles/el-override.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /src/directive/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./loading"; 2 | export * from "./permission"; 3 | -------------------------------------------------------------------------------- /src/constant/key.ts: -------------------------------------------------------------------------------- 1 | // AMap ky 2 | export const AMapKey = "73cddabc2173e0166a622f4483d3592a"; 3 | -------------------------------------------------------------------------------- /src/enums/storageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum StorageEnum { 2 | THEME_MODE = "THEME_MODE" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # app name 2 | VITE_APP_NAME=Vue3 Basic Admin 3 | 4 | # Whether to open mock 5 | VITE_USE_MOCK = true -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit 5 | -------------------------------------------------------------------------------- /src/components/base-dialog/index.ts: -------------------------------------------------------------------------------- 1 | import BaseDialog from "./src/base-dialog.vue"; 2 | 3 | export { BaseDialog }; 4 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/images/user/headImg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/images/user/headImg.gif -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | *.svg 5 | /node_modules/** 6 | 7 | **/*.svg 8 | **/*.sh 9 | 10 | /public/* -------------------------------------------------------------------------------- /src/assets/images/login/login-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/images/login/login-box.png -------------------------------------------------------------------------------- /src/assets/images/user/avatar01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/images/user/avatar01.png -------------------------------------------------------------------------------- /src/assets/images/user/avatar15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/images/user/avatar15.png -------------------------------------------------------------------------------- /src/assets/images/login/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/images/login/background.png -------------------------------------------------------------------------------- /src/components/base-form/index.ts: -------------------------------------------------------------------------------- 1 | import BaseForm from "./src/base-form.vue"; 2 | 3 | export * from "./src/type"; 4 | export { BaseForm }; 5 | -------------------------------------------------------------------------------- /src/components/base-virtual-list/index.ts: -------------------------------------------------------------------------------- 1 | import BaseVirtualList from "./src/base-virtual-list.vue"; 2 | 3 | export { BaseVirtualList }; 4 | -------------------------------------------------------------------------------- /src/assets/images/login/login-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-admin/HEAD/src/assets/images/login/login-content.png -------------------------------------------------------------------------------- /src/components/base-table/index.ts: -------------------------------------------------------------------------------- 1 | import BaseTable from "./src/base-table.vue"; 2 | 3 | export * from "./src/types"; 4 | export { BaseTable }; 5 | -------------------------------------------------------------------------------- /src/components/base-seamscroll/index.ts: -------------------------------------------------------------------------------- 1 | import BaseSeamScroll from "./src/base-seam-scroll.vue"; 2 | 3 | export * from "./src/types"; 4 | export { BaseSeamScroll }; 5 | -------------------------------------------------------------------------------- /src/constant/cdn.ts: -------------------------------------------------------------------------------- 1 | export const AMapCDN = "https://webapi.amap.com/maps?v=1.3&plugin=AMap.DistrictSearch&key="; 2 | export const AMapUiCDN = "https://webapi.amap.com/ui/1.0/main.js"; 3 | -------------------------------------------------------------------------------- /src/enums/LayoutEnum.ts: -------------------------------------------------------------------------------- 1 | // mode 2 | export enum MenuModeEnum { 3 | HORIZONTAL = "horizontal", 4 | VERTICAL = "vertical" 5 | } 6 | 7 | export type Mode = "horizontal" | "vertical"; 8 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { default as setupIcon } from "./icon"; 2 | export { default as setupGlobalUtils } from "./globalUtils"; 3 | export { default as setupErrorHandler } from "./error"; 4 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @use "./common.scss"; 2 | @use "./el-override.scss"; 3 | @use "./scrollbar.scss"; 4 | @use "./size.scss"; 5 | @use "element-plus/theme-chalk/src/dark/css-vars.scss" as *; 6 | -------------------------------------------------------------------------------- /src/components/base-charts/index.ts: -------------------------------------------------------------------------------- 1 | import BaseCharts from "./src/base-charts.vue"; 2 | import BaseMap from "./src/base-map.vue"; 3 | 4 | export * from "./src/types"; 5 | export { BaseCharts, BaseMap }; 6 | -------------------------------------------------------------------------------- /src/views/page/success.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/base-column-setting/index.ts: -------------------------------------------------------------------------------- 1 | import BaseColumnSetting from "./src/base-column-setting.vue"; 2 | 3 | export * from "./src/types"; 4 | 5 | export { BaseColumnSetting }; 6 | 7 | export function setProps() {} 8 | -------------------------------------------------------------------------------- /src/components/base-charts/src/types.ts: -------------------------------------------------------------------------------- 1 | export type EChartsType = "line" | "bar" | "pie" | "wordcloud" | "radar" | "liquidfill" | "graph" | "map" | "scatter" | "heatmap"; 2 | 3 | export type { EChartsOption } from "echarts"; 4 | -------------------------------------------------------------------------------- /src/views/page/error.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/icons/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./date"; 3 | export * from "./is"; 4 | export * from "./message"; 5 | export * from "./navigator"; 6 | export * from "./storage"; 7 | export * from "./dom"; 8 | export * from "./remoteLoad"; 9 | -------------------------------------------------------------------------------- /src/styles/theme/index.scss: -------------------------------------------------------------------------------- 1 | @use "./base.scss"; 2 | @use "./dark.scss"; 3 | 4 | .base-app-bg { 5 | background-color: var(--base-main-bg-color); 6 | } 7 | 8 | .base-box-bg { 9 | background-color: var(--base-main-content-bg-color); 10 | } 11 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # public path 2 | VITE_PUBLIC_PATH = / 3 | 4 | # api 5 | VITE_BASE_API= /api 6 | 7 | # upload api 8 | VITE_BASE_UPLOAD_API= /upload 9 | 10 | # Whether to open mock 11 | VITE_USE_MOCK = true 12 | 13 | # Delete console 14 | VITE_DROP_CONSOLE = false -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # public path 2 | VITE_PUBLIC_PATH = ./ 3 | 4 | # api 5 | VITE_BASE_API= /api 6 | 7 | # upload api 8 | VITE_BASE_UPLOAD_API= /upload 9 | 10 | # Whether to open mock 11 | VITE_USE_MOCK = true 12 | 13 | # Delete console 14 | VITE_DROP_CONSOLE = true -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts", "build/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | LOGIN = "/login", 5 | GET_USER_INFO = "/getUserInfo" 6 | } 7 | 8 | export const login = (data?: any) => request.post(Api.LOGIN, data); 9 | 10 | export const getUserInfo = (data?: any) => request.get(Api.GET_USER_INFO, data); 11 | -------------------------------------------------------------------------------- /src/icons/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useEnv.ts: -------------------------------------------------------------------------------- 1 | export function useEnv() { 2 | const { VITE_APP_NAME, VITE_BASE_API, VITE_PUBLIC_PATH, VITE_BASE_UPLOAD_API, MODE } = import.meta.env; 3 | 4 | return { 5 | MODE, 6 | VITE_APP_NAME, 7 | VITE_BASE_API, 8 | VITE_PUBLIC_PATH, 9 | VITE_BASE_UPLOAD_API 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useMessage } from "./useMessage"; 2 | export { useEnv } from "./useEnv"; 3 | export { useLoading } from "./useLoading"; 4 | export { useDark } from "./useDark"; 5 | export { useStorage } from "./useStorage"; 6 | export { useWatermark } from "./useWatermark"; 7 | export { useOpenLayersMap } from "./useOpenLayersMap"; 8 | -------------------------------------------------------------------------------- /src/api/system/dict.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | GET_DICT_KEY = "/dict/key", 5 | GET_DICT_DATA = "/dict/data" 6 | } 7 | 8 | export const getDictKeyList = (data?: any) => request.get(Api.GET_DICT_KEY, data); 9 | 10 | export const getDictData = (data: any) => request.get(Api.GET_DICT_DATA, data); 11 | -------------------------------------------------------------------------------- /src/router/guard/progress.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import NProgress from "nprogress"; 3 | 4 | export const createProgress = (router: Router) => { 5 | router.beforeEach(() => { 6 | NProgress.start(); 7 | return true; 8 | }); 9 | 10 | router.afterEach(() => { 11 | return true; 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/base-column-setting/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Column } from "@/components/base-table"; 2 | 3 | export interface ColumnState { 4 | dialogType: number; 5 | checkAll: boolean; 6 | checkedList: string[]; 7 | checkColumnList: Column[]; 8 | isIndeterminate: boolean; 9 | } 10 | 11 | export * from "@/components/base-table"; 12 | -------------------------------------------------------------------------------- /src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 嵌套菜单2 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/page/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/page/500.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/exit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/page/403.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 嵌套菜单1-2 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/router/guard/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import { createPermission } from "./permission"; 3 | import { createProgress } from "./progress"; 4 | import { createPageTitle } from "./pageTitle"; 5 | 6 | export function setupRouterGuard(router: Router) { 7 | createProgress(router); 8 | createPermission(router); 9 | createPageTitle(router); 10 | } 11 | -------------------------------------------------------------------------------- /src/icons/down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/router/guard/pageTitle.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import { usePageSetting } from "@/layouts/hooks/usePageSetting"; 3 | 4 | export const createPageTitle = (router: Router) => { 5 | const { setPageTitle } = usePageSetting(); 6 | 7 | router.beforeEach((to) => { 8 | setPageTitle(to.meta.title as string); 9 | return true; 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-1/menu1-1-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 嵌套菜单1-1-1 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/plugins/icon.ts: -------------------------------------------------------------------------------- 1 | import * as Icons from "@element-plus/icons-vue"; 2 | import "virtual:svg-icons-register"; 3 | import "@/assets/iconfont/iconfont.css"; 4 | import type { App } from "vue"; 5 | 6 | export { Icons }; 7 | 8 | export default (app: App) => { 9 | for (const [key, component] of Object.entries(Icons)) { 10 | app.component(key, component); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /mock/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer"; 2 | import log from "./controller/log"; 3 | import system from "./controller/system"; 4 | import test from "./controller/test"; 5 | import user from "./controller/user"; 6 | 7 | export function setupProdMockServer() { 8 | createProdMockServer([...log, ...system, ...test, ...user]); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/base-json/base-json.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/components/base-form/src/type.ts: -------------------------------------------------------------------------------- 1 | import { FormTypeEnum } from "@/enums/componentEnum"; 2 | 3 | export type FormColumnType = { 4 | fieldName: string; 5 | fieldDesc: string; 6 | fieldType: FormTypeEnum; 7 | active?: boolean; 8 | required?: boolean; 9 | rules?: any; 10 | sort?: number; 11 | config?: any; 12 | colProps?: any; 13 | show?: Function | boolean; 14 | }; 15 | -------------------------------------------------------------------------------- /src/icons/sizeMini.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/sizePlus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer"; 2 | import log from "../mock/controller/log"; 3 | import system from "../mock/controller/system"; 4 | import test from "../mock/controller/test"; 5 | import user from "../mock/controller/user"; 6 | 7 | export function setupProdMockServer() { 8 | createProdMockServer([...log, ...system, ...test, ...user]); 9 | } 10 | -------------------------------------------------------------------------------- /src/views/permission/page.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 该页面仅admin可见 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/layouts/hooks/usePageSetting.ts: -------------------------------------------------------------------------------- 1 | import { useMenuSetting } from "@/layouts/hooks/useMenuSetting"; 2 | 3 | export const usePageSetting = () => { 4 | const { getSystemTitle } = useMenuSetting(); 5 | 6 | const setPageTitle = (title: string) => { 7 | document.title = title ? `${title}-${getSystemTitle.value}` : getSystemTitle.value; 8 | }; 9 | return { 10 | setPageTitle 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/icons/fullOutScreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .history 15 | report.html 16 | components.d.ts 17 | auto-imports.d.ts 18 | 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | .DS_Store 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /src/icons/confirm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/request/retry.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError, AxiosInstance } from "axios"; 2 | 3 | export class AxiosRetry { 4 | retry(service: AxiosInstance, err: AxiosError) { 5 | const config = err?.config as any; 6 | config._retryCount = config._retryCount || 0; 7 | config._retryCount += 1; 8 | delete config.headers; 9 | setTimeout(() => { 10 | service(config); 11 | }, 100); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/enums/DropMenuEnum.ts: -------------------------------------------------------------------------------- 1 | export enum DropMenuEnum { 2 | REFRESH_PAGE = "REFRESH_PAGE", 3 | CLOSE_CURRENT = "CLOSE_CURRENT", 4 | CLOSE_LEFT = "CLOSE_LEFT", 5 | CLOSE_RIGHT = "CLOSE_RIGHT", 6 | CLOSE_OTHER = "CLOSE_OTHER", 7 | CLOSE_ALL = "CLOSE_ALL" 8 | } 9 | 10 | export type DropMenuType = { 11 | text: string; 12 | icon?: string; 13 | command?: string | number; 14 | disabled?: boolean; 15 | divided?: boolean; 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/system/dept.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | DEPT_TREE_LIST = "/dept/getDeptTreeList", 5 | DEPT_LIST = "/dept/getDeptList", 6 | ADD_DEPT = "/dept/addDept" 7 | } 8 | 9 | export const getDeptTreeList = () => request.get(Api.DEPT_TREE_LIST); 10 | 11 | export const getDeptList = (data?: any) => request.get(Api.DEPT_LIST, data); 12 | 13 | export const addDept = (data?: any) => request.post(Api.ADD_DEPT, data); 14 | -------------------------------------------------------------------------------- /src/icons/fullScreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/textSize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/base-seamscroll/src/types.ts: -------------------------------------------------------------------------------- 1 | export type SeamScrollOptionType = { 2 | autoPlay?: boolean; 3 | switchDelay?: number; 4 | hoverStop?: boolean; 5 | step?: number; 6 | singleHeight?: number; 7 | waitTime?: number; 8 | }; 9 | 10 | export interface StateSeam { 11 | top: number; 12 | autoPlay: boolean; 13 | delay: number; 14 | copyHtml: string; 15 | isHover: boolean; 16 | reqFrame: any; 17 | singleWaitTime: any; 18 | } 19 | -------------------------------------------------------------------------------- /src/icons/esc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layouts/side-bar/logo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | 6 | 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import type { DefineComponent } from "vue"; 3 | const component: DefineComponent<{}, {}, any>; 4 | export default component; 5 | } 6 | 7 | interface Window { 8 | AMap: any; 9 | AMapUI: any; 10 | tinymce: any; 11 | } 12 | 13 | declare global {} 14 | 15 | declare const __APP_INFO__: { 16 | pkg: { 17 | name: string; 18 | version: string; 19 | dependencies: Recordable; 20 | devDependencies: Recordable; 21 | }; 22 | lastBuildTime: string; 23 | }; 24 | -------------------------------------------------------------------------------- /src/icons/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layouts/tag-view/tag-fullscreen.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-fullscreen.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mock/utils.ts: -------------------------------------------------------------------------------- 1 | import { ResultEnum } from "@/enums/httpEnum"; 2 | 3 | export interface RequestParams { 4 | methods: string; 5 | body: any; 6 | headers?: { authorization?: string }; 7 | query: any; 8 | } 9 | 10 | export const resultSuccess = (data: any = null, message = "请求成功") => { 11 | return { 12 | code: ResultEnum.SUCCESS, 13 | data, 14 | message 15 | }; 16 | }; 17 | 18 | export const resultError = (message = "请求失败") => { 19 | return { 20 | code: ResultEnum.ERROR, 21 | data: null, 22 | message 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/wordcloud.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const pieConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "item" 6 | }, 7 | series: [ 8 | { 9 | type: "wordCloud", 10 | sizeRange: [14, 28], 11 | rotationRange: [0, 0], 12 | width: "100%", 13 | height: "100%", 14 | shape: "pentagon ", 15 | gridSize: 25, 16 | top: 0, 17 | data: [] 18 | } 19 | ] 20 | }; 21 | 22 | export default pieConfig; 23 | -------------------------------------------------------------------------------- /src/views/comp/echarts/graph/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/base-table/src/base-table.scss: -------------------------------------------------------------------------------- 1 | .base-table { 2 | padding: 10px; 3 | box-sizing: border-box; 4 | .base-table-required { 5 | color: var(--el-color-danger); 6 | } 7 | .el-tag + .el-tag { 8 | margin-left: 8px; 9 | } 10 | } 11 | 12 | .base-table-pagination { 13 | display: flex; 14 | &.base-table-pagination-bottomLeft { 15 | justify-content: flex-start; 16 | } 17 | &.base-table-pagination-bottomCenter { 18 | justify-content: center; 19 | } 20 | &.base-table-pagination-bottomRight { 21 | justify-content: flex-end; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/icons/fold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/unfold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/icons/remind.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/pie.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const pieConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "item" 6 | }, 7 | legend: { 8 | show: true, 9 | left: "center", 10 | top: "3%", 11 | type: "scroll", 12 | itemWidth: 18, 13 | itemHeight: 11 14 | }, 15 | series: [ 16 | { 17 | name: "", 18 | type: "pie", 19 | radius: ["35%", "55%"], 20 | center: ["48%", "55%"], 21 | itemStyle: {}, 22 | data: [] 23 | } 24 | ] 25 | }; 26 | 27 | export default pieConfig; 28 | -------------------------------------------------------------------------------- /src/utils/request/loading.ts: -------------------------------------------------------------------------------- 1 | import { useLoading } from "@/hooks"; 2 | 3 | const loading = useLoading({ 4 | minTime: 500 5 | }); 6 | 7 | export class AxiosLoading { 8 | loadingCount: number; 9 | constructor() { 10 | this.loadingCount = 0; 11 | } 12 | 13 | addLoading() { 14 | if (this.loadingCount === 0) { 15 | loading.open(); 16 | } 17 | this.loadingCount++; 18 | } 19 | 20 | closeLoading() { 21 | if (this.loadingCount > 0) { 22 | if (this.loadingCount === 1) { 23 | loading.close(); 24 | } 25 | this.loadingCount--; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useStorage.ts: -------------------------------------------------------------------------------- 1 | import { local, session } from "@/utils"; 2 | 3 | export const useStorage = (type: "session" | "local" = "session") => { 4 | const storageMode = type === "session" ? session : local; 5 | 6 | const getItem = (key: string): any => { 7 | return storageMode.getItem(key); 8 | }; 9 | 10 | const setItem = (key: string, value: string) => { 11 | storageMode.setItem(key, value); 12 | }; 13 | 14 | const removeItem = (key: string) => { 15 | storageMode.removeItem(key); 16 | }; 17 | 18 | const clear = () => { 19 | storageMode.clear(); 20 | }; 21 | 22 | return { getItem, setItem, removeItem, clear }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/styles/size.scss: -------------------------------------------------------------------------------- 1 | html[page-size="large"] { 2 | --el-font-size-medium: 18px; 3 | --el-font-size-base: 16px; 4 | --el-font-size-small: 15px; 5 | --el-font-size-extra-small: 14px; 6 | 7 | --base-menu-logo: 21px; 8 | } 9 | 10 | html[page-size="default"] { 11 | --el-font-size-medium: 16px; 12 | --el-font-size-base: 14px; 13 | --el-font-size-small: 13px; 14 | --el-font-size-extra-small: 12px; 15 | 16 | --base-menu-logo: 19px; 17 | } 18 | 19 | html[page-size="small"] { 20 | --el-font-size-medium: 14px; 21 | --el-font-size-base: 12px; 22 | --el-font-size-small: 11px; 23 | --el-font-size-extra-small: 10px; 24 | 25 | --base-menu-logo: 17px; 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/useMessage.ts: -------------------------------------------------------------------------------- 1 | import { messageSuccess, messageWarning, messageInfo, messageError, MessageConfig } from "@/utils"; 2 | 3 | export function useMessage(config?: MessageConfig) { 4 | const success = (text: string, close?: () => void) => { 5 | messageSuccess(text, close!, config!); 6 | }; 7 | const warning = (text: string, close?: () => void) => { 8 | messageWarning(text, close!, config!); 9 | }; 10 | const info = (text: string, close?: () => void) => { 11 | messageInfo(text, close!, config!); 12 | }; 13 | const error = (text: string, close?: () => void) => { 14 | messageError(text, close!, config!); 15 | }; 16 | return { success, warning, info, error }; 17 | } 18 | -------------------------------------------------------------------------------- /src/icons/about.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/clear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/system/user.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | USER_LIST = "/user/getUserList", 5 | ADD_USER = "/user/addUser", 6 | EDIT_USER = "/user/editUser", 7 | DELETE_USER = "/user/deleteUser", 8 | CHANGE_USER_STATUS = "/user/changeStatus" 9 | } 10 | 11 | export const getUserList = (data?: any) => request.get(Api.USER_LIST, data); 12 | 13 | export const addUser = (data?: any) => request.post(Api.ADD_USER, data); 14 | 15 | export const deleteUser = (data?: any) => request.delete(Api.DELETE_USER, data); 16 | 17 | export const editUser = (data?: any) => request.put(Api.EDIT_USER, data); 18 | 19 | export const changeUserStatus = (data?: any) => request.put(Api.CHANGE_USER_STATUS, data); 20 | -------------------------------------------------------------------------------- /src/icons/page.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createWebHashHistory, createRouter } from "vue-router"; 2 | import { AppRouteType } from "./types"; 3 | import { basicRoutes, WHITE_LIST } from "./basic"; 4 | 5 | const router = createRouter({ 6 | history: createWebHashHistory(import.meta.env.BASE_URL), 7 | routes: [...basicRoutes] as AppRouteType[], 8 | // only history 9 | scrollBehavior: () => ({ left: 0, top: 0 }) 10 | }); 11 | 12 | export function resetRouter() { 13 | router.getRoutes().forEach((route) => { 14 | const { name } = route; 15 | if (name && !WHITE_LIST.includes(name as string)) { 16 | router.hasRoute(name) && router.removeRoute(name); 17 | } 18 | }); 19 | } 20 | 21 | export default router; 22 | -------------------------------------------------------------------------------- /src/icons/empty.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | const toString = Object.prototype.toString; 2 | export const isBoolean = (val: any): val is boolean => typeof val === "boolean"; 3 | export const isFunction = (val: any): val is T => typeof val === "function"; 4 | export const isNumber = (val: any): val is number => typeof val === "number"; 5 | export const isUndefined = (val: any): val is undefined => typeof val === "undefined"; 6 | export const isString = (val: unknown): val is string => typeof val === "string"; 7 | export const isObject = (val: any): val is object => toString.call(val) === "[object Object]"; 8 | export const isArray = (val: any): val is object => toString.call(val) === "[object Array]"; 9 | export const isEmpty = (val: any): val is boolean => !val && val !== 0; 10 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /src/icons/comp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /src/router/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw, RouteMeta } from "vue-router"; 2 | 3 | export type Component = ReturnType | (() => Promise) | (() => Promise); 4 | 5 | export interface MetaType extends RouteMeta { 6 | title: string; 7 | keepAlive?: boolean; 8 | hidden?: boolean; 9 | permission?: string; 10 | affix?: boolean; 11 | icon?: string; 12 | hideChildren?: boolean; 13 | sort?: number; 14 | } 15 | 16 | export interface AppRouteType extends Omit { 17 | name: string; 18 | component?: Component | string; 19 | components?: Component; 20 | children?: AppRouteType[]; 21 | fullPath?: string; 22 | meta?: MetaType; 23 | redirect?: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/base-seamscroll/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from "vue"; 2 | import { SeamScrollOptionType } from "./types"; 3 | 4 | export const baseProps = { 5 | data: { 6 | type: Array as PropType, 7 | default: () => [] 8 | }, 9 | options: { 10 | type: Object as PropType, 11 | default: () => {} 12 | }, 13 | width: { 14 | type: [Number, String], 15 | default: "100%" 16 | }, 17 | height: { 18 | type: [Number, String], 19 | default: "300px" 20 | } 21 | }; 22 | 23 | export const defaultOptions: SeamScrollOptionType = { 24 | autoPlay: true, 25 | switchDelay: 400, 26 | hoverStop: true, 27 | step: 1, 28 | singleHeight: 0, 29 | waitTime: 1000 30 | }; 31 | -------------------------------------------------------------------------------- /src/styles/scrollbar.scss: -------------------------------------------------------------------------------- 1 | //自定义滚动条样式 2 | $scrollbar-track-color: #f8f8f8; 3 | $scrollbar-thumb: rgba(144, 147, 153, 0.5); 4 | $scrollbar-thumb-hover: rgba(144, 147, 153, 0.8); 5 | 6 | .el-scrollbar__bar { 7 | z-index: 4; 8 | } 9 | .el-scrollbar__wrap { 10 | max-height: 100%; 11 | } 12 | 13 | ::-webkit-scrollbar { 14 | width: 7px; 15 | height: 7px; 16 | } 17 | ::-webkit-scrollbar-track-piece { 18 | background-color: $scrollbar-track-color; 19 | } 20 | // 滚动条的设置 21 | ::-webkit-scrollbar-thumb { 22 | background-color: $scrollbar-thumb; 23 | background-clip: padding-box; 24 | min-height: 28px; 25 | border-radius: 5px; 26 | transition: 0.3s background-color; 27 | } 28 | 29 | ::-webkit-scrollbar-thumb:hover { 30 | background-color: $scrollbar-thumb-hover; 31 | } 32 | -------------------------------------------------------------------------------- /src/hooks/useDark.ts: -------------------------------------------------------------------------------- 1 | import { StorageEnum } from "@/enums/storageEnum"; 2 | import { useStorage } from "./useStorage"; 3 | import { addClass, removeClass } from "@/utils"; 4 | 5 | const { getItem, setItem } = useStorage("local"); 6 | 7 | const isDark = ref(getItem(StorageEnum.THEME_MODE) === "dark"); 8 | 9 | export const useDark = () => { 10 | const htmlEle = document.documentElement; 11 | 12 | const toggle = (dark: boolean = !isDark.value) => { 13 | if (dark) { 14 | addClass(htmlEle, "dark"); 15 | } else { 16 | removeClass(htmlEle, "dark"); 17 | } 18 | isDark.value = dark; 19 | setItem(StorageEnum.THEME_MODE, dark ? "dark" : "light"); 20 | }; 21 | 22 | toggle(isDark.value); 23 | 24 | return { isDark, toggle }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/plugins/globalUtils.ts: -------------------------------------------------------------------------------- 1 | import { messageSuccess, messageWarning, messageInfo, messageError, messageBox, deepClone, getDataLabel, session, local } from "@/utils"; 2 | import type { App } from "vue"; 3 | 4 | export default (app: App) => { 5 | app.config.globalProperties.$messageBox = messageBox; 6 | app.config.globalProperties.$messageSuccess = messageSuccess; 7 | app.config.globalProperties.$messageWarning = messageWarning; 8 | app.config.globalProperties.$messageInfo = messageInfo; 9 | app.config.globalProperties.$messageError = messageError; 10 | app.config.globalProperties.$deepClone = deepClone; 11 | app.config.globalProperties.$getDataLabel = getDataLabel; 12 | app.config.globalProperties.$session = session; 13 | app.config.globalProperties.$local = local; 14 | }; 15 | -------------------------------------------------------------------------------- /src/icons/gitee.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/other.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "baseUrl": "./", 16 | "types": ["vite/client"], 17 | "paths": { 18 | "@/*": ["src/*"] 19 | } 20 | }, 21 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/mock/**/*.ts", "build/**/*.ts", "types/**/*.d.ts"], 22 | "references": [{ "path": "./tsconfig.node.json" }] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/base-filter/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from "vue"; 2 | import { FormColumnType } from "../base-form"; 3 | 4 | export default { 5 | columns: { 6 | type: Array as PropType, 7 | default: () => [] 8 | }, 9 | labelWidth: { 10 | type: [Number, String], 11 | default: "100px" 12 | }, 13 | labelPosition: { 14 | type: String as PropType<"left" | "right" | "top"> 15 | }, 16 | size: { 17 | type: String as PropType<"large" | "default" | "small"> 18 | }, 19 | showOpen: { 20 | type: Boolean, 21 | default: true 22 | }, 23 | colProps: { 24 | type: Object, 25 | default: () => ({ xs: 24, sm: 12, md: 8, lg: 6, xl: 6 }) 26 | }, 27 | searchInfo: { 28 | type: Object 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/planeSpin.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 35 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id */ 3 | src: url('iconfont.ttf?t=1671350244138') format('truetype'); 4 | } 5 | 6 | .iconfont { 7 | font-family: "iconfont" !important; 8 | font-size: 16px; 9 | font-style: normal; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | .icon-love:before { 15 | content: "\e60b"; 16 | } 17 | 18 | .icon-weibo:before { 19 | content: "\e73c"; 20 | } 21 | 22 | .icon-dianhua-yuankuang:before { 23 | content: "\e8be"; 24 | } 25 | 26 | .icon-jingdong-:before { 27 | content: "\e643"; 28 | } 29 | 30 | .icon-zhifubao:before { 31 | content: "\e636"; 32 | } 33 | 34 | .icon-shejiaotubiao-44:before { 35 | content: "\e648"; 36 | } 37 | 38 | .icon-weixin:before { 39 | content: "\e607"; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/components/base-virtual-list/src/props.ts: -------------------------------------------------------------------------------- 1 | import { StyleValue, PropType } from "vue"; 2 | 3 | const listProps = { 4 | className: { 5 | type: String, 6 | default: "" 7 | }, 8 | style: { 9 | type: [Object, String, Array] as PropType 10 | }, 11 | height: { 12 | type: Number, 13 | default: 500 14 | }, 15 | width: { 16 | type: [Number, String], 17 | default: "100%" 18 | }, 19 | itemHeight: { 20 | type: Number, 21 | default: 30 22 | }, 23 | dynamic: { 24 | type: Boolean, 25 | default: false 26 | }, 27 | cache: { 28 | type: Number, 29 | default: 2 30 | }, 31 | data: { 32 | type: Array, 33 | default: () => [] 34 | } 35 | }; 36 | 37 | export default listProps; 38 | -------------------------------------------------------------------------------- /src/enums/SizeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | DEFAULT = "default", 3 | SMALL = "small", 4 | LARGE = "large" 5 | } 6 | 7 | export enum SizeNumberEnum { 8 | DEFAULT = 16, 9 | SMALL = 14, 10 | LARGE = 18 11 | } 12 | 13 | export type TextSizeType = { 14 | icon: string; 15 | text: string; 16 | iconSize: number; 17 | type: `${SizeEnum}`; 18 | }; 19 | 20 | export const textSize: TextSizeType[] = [ 21 | { 22 | icon: "sizeMini", 23 | text: "偏小", 24 | iconSize: 20, 25 | type: SizeEnum.SMALL 26 | }, 27 | { 28 | icon: "size", 29 | text: "正常", 30 | iconSize: 18, 31 | type: SizeEnum.DEFAULT 32 | }, 33 | { 34 | icon: "sizePlus", 35 | text: "偏大", 36 | iconSize: 20, 37 | type: SizeEnum.LARGE 38 | } 39 | ]; 40 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from "pinia"; 2 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; 3 | import type { App } from "vue"; 4 | import { usePermissionStoreWithOut } from "@/stores/modules/permission"; 5 | import { useTagStoreWithOut } from "@/stores/modules/tagView"; 6 | import { useUserStoreWithOut } from "@/stores/modules/user"; 7 | 8 | const store = createPinia(); 9 | 10 | export function setupPinia(app: App) { 11 | store.use(piniaPluginPersistedstate); 12 | app.use(store); 13 | } 14 | 15 | export function storeReset() { 16 | const permissionStore = usePermissionStoreWithOut(); 17 | const tagStore = useTagStoreWithOut(); 18 | const userStore = useUserStoreWithOut(); 19 | permissionStore.$reset(); 20 | tagStore.$reset(); 21 | userStore.$reset(); 22 | } 23 | 24 | export { store }; 25 | -------------------------------------------------------------------------------- /src/api/system/role.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | ROLE_LIST = "/role/getRoleList", 5 | ROLE_ALL_LIST = "/role/getAllRoleList", 6 | ADD_ROLE = "/role/addRole", 7 | EDIT_ROLE = "/role/editRole", 8 | DELETE_ROLE = "/role/deleteRole", 9 | CHANGE_ROLE_STATUS = "/role/changeStatus" 10 | } 11 | 12 | export const getRoleList = (data?: any) => request.get(Api.ROLE_LIST, data); 13 | 14 | export const getAllRoleList = () => request.get(Api.ROLE_ALL_LIST); 15 | 16 | export const addRole = (data?: any) => request.post(Api.ADD_ROLE, data); 17 | 18 | export const deleteRole = (data?: any) => request.delete(Api.DELETE_ROLE, data); 19 | 20 | export const editRole = (data?: any) => request.put(Api.EDIT_ROLE, data); 21 | 22 | export const changeRoleStatus = (data?: any) => request.put(Api.CHANGE_ROLE_STATUS, data); 23 | -------------------------------------------------------------------------------- /src/icons/export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/router/modules/abuout.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const about: AppRouteType[] = [ 4 | { 5 | path: "/about", 6 | name: "About", 7 | component: () => import("@/layouts/index.vue"), 8 | redirect: "/about/index", 9 | meta: { 10 | title: "关于", 11 | icon: "svg-about", 12 | sort: 3, 13 | hideChildren: true 14 | }, 15 | children: [ 16 | { 17 | path: "index", 18 | name: "AboutIndex", 19 | component: () => import("@/views/about/index.vue"), 20 | meta: { 21 | title: "关于", 22 | sort: 1, 23 | icon: "" 24 | } 25 | } 26 | ] 27 | } 28 | ]; 29 | 30 | export default about; 31 | -------------------------------------------------------------------------------- /src/constant/basic.ts: -------------------------------------------------------------------------------- 1 | export type BasicType = { 2 | label: number | string; 3 | value: number | string; 4 | }; 5 | 6 | export const StatusData: BasicType[] = [ 7 | { 8 | label: "启用", 9 | value: 1 10 | }, 11 | { 12 | label: "停用", 13 | value: 0 14 | } 15 | ]; 16 | 17 | export const axiosStatus: BasicType[] = [ 18 | { 19 | label: "成功", 20 | value: 200 21 | }, 22 | { 23 | label: "失败", 24 | value: 500 25 | } 26 | ]; 27 | 28 | export const methodData: BasicType[] = [ 29 | { 30 | label: "GET", 31 | value: "GET" 32 | }, 33 | { 34 | label: "POST", 35 | value: "POST" 36 | }, 37 | { 38 | label: "PUT", 39 | value: "PUT" 40 | }, 41 | { 42 | label: "DELETE", 43 | value: "DELETE" 44 | } 45 | ]; 46 | -------------------------------------------------------------------------------- /src/directive/permission.ts: -------------------------------------------------------------------------------- 1 | import type { Directive, App, DirectiveBinding } from "vue"; 2 | import { useUserStoreWithOut } from "@/stores/modules/user"; 3 | 4 | function isAuth(el: Element, binding: any) { 5 | const userStore = useUserStoreWithOut(); 6 | const value = binding.value; 7 | if (!value) return; 8 | if (!userStore.roleIds.includes(value)) { 9 | el.parentNode?.removeChild(el); 10 | } 11 | } 12 | 13 | const permissionDirective: Directive = { 14 | mounted(el: Element, binding: DirectiveBinding) { 15 | isAuth(el, binding); 16 | }, 17 | updated(el: Element, binding: DirectiveBinding) { 18 | isAuth(el, binding); 19 | } 20 | }; 21 | 22 | export const setupPermissionDirective = (app: App) => { 23 | app.directive("permission", permissionDirective); 24 | }; 25 | 26 | export default permissionDirective; 27 | -------------------------------------------------------------------------------- /src/icons/filter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/base-pagination/base-pagination.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/views/comp/count-to/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/base-pagination/props.ts: -------------------------------------------------------------------------------- 1 | export const defaultProps = { 2 | small: { 3 | type: Boolean, 4 | default: true 5 | }, 6 | background: { 7 | type: Boolean, 8 | default: true 9 | }, 10 | total: { 11 | type: Number, 12 | default: 0 13 | }, 14 | pagerCount: { 15 | type: Number, 16 | default: 7 17 | }, 18 | layout: { 19 | type: String, 20 | default: "total, prev, pager, next, sizes" 21 | }, 22 | disabled: { 23 | type: Boolean, 24 | default: false 25 | }, 26 | hideOnSinglePage: { 27 | type: Boolean, 28 | default: false 29 | }, 30 | currentPage: { 31 | type: Number, 32 | default: 1 33 | }, 34 | pageSize: { 35 | type: Number, 36 | default: 10 37 | } 38 | }; 39 | 40 | export default defaultProps; 41 | -------------------------------------------------------------------------------- /src/styles/theme/base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | // base-column-setting 头部颜色 3 | --base-column-header-color: #f4f6fa; 4 | 5 | //navbar 图标hover颜色 6 | --base-nav-bar-box-hover: rgba(0, 0, 0, 0.025); 7 | 8 | //tag border 9 | --base-tag-border: rgba(175, 175, 175, 0.3); 10 | //tag shadow 11 | --base-tag-shadow: rgb(0 21 41 / 8%); 12 | 13 | //menu背景颜色 14 | --el-menu-bg-color: rgb(41, 51, 72) !important; 15 | //submenu 背景颜色 16 | --el-submenu-bg-color: rgb(30, 36, 48) !important; 17 | // menu 文字颜色 18 | --el-menu-text-color: hsla(0, 0%, 100%, 0.65) !important; 19 | 20 | // menu hover 背景颜色 21 | --el-menu-hover-bg-color: transparent !important; 22 | 23 | //menu active文字颜色 24 | --el-menu-active-color: #fff !important; 25 | 26 | //系统主体背景颜色 27 | --base-main-bg-color: #f0f2f5; 28 | //系统主体 内容颜色 29 | --base-main-content-bg-color: #fff; 30 | } 31 | -------------------------------------------------------------------------------- /src/icons/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/test.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | GET_TEST = "/test/getTest", 5 | POST_TEST = "/test/postTest", 6 | PUT_TEST = "/test/putTest", 7 | DELETE_TEST = "/test/deleteTest", 8 | SAME_TEST = "/test/sameTest", 9 | RETRY_TEST = "/test/retryTest" 10 | } 11 | 12 | export const getTestApi = (data?: any) => request.get(Api.GET_TEST, data); 13 | 14 | export const postTestApi = (data?: any) => request.post(Api.POST_TEST, data, { successMessage: true }); 15 | 16 | export const putTestApi = (data?: any) => request.put(Api.PUT_TEST, data); 17 | 18 | export const deleteTestApi = (data?: any) => request.delete(Api.DELETE_TEST, data, { errorMessage: false }); 19 | 20 | export const sameTestApi = (data?: any) => request.get(Api.SAME_TEST, data, { cancelSame: true }); 21 | 22 | export const retryApi = (data?: any) => request.get(Api.RETRY_TEST, data, { isRetry: true }); 23 | -------------------------------------------------------------------------------- /src/components/base-icon/icon.ts: -------------------------------------------------------------------------------- 1 | import { Icons } from "@/plugins/icon"; 2 | import iconfontJson from "@/assets/iconfont/iconfont.json"; 3 | 4 | const getSvgIconList = (): string[] => { 5 | const modules = import.meta.glob("@/icons/**/*.svg", { eager: true, import: "default" }); 6 | const re = "([^/]*)(\\.\\w+)$"; 7 | return Object.keys(modules).map((item) => item.match(re)?.[1]); 8 | }; 9 | 10 | const getElIconList = (): string[] => { 11 | return Object.keys(Icons); 12 | }; 13 | 14 | const getIconfontList = (): string[] => { 15 | return iconfontJson.glyphs.map((item) => item.font_class); 16 | }; 17 | 18 | export const elIconList = getElIconList(); 19 | 20 | export const svgIconList = getSvgIconList(); 21 | 22 | export const iconfontList = getIconfontList(); 23 | 24 | export const allIconList = [...elIconList, ...svgIconList.map((item) => `svg-${item}`), ...iconfontList.map((item) => `icon-${item}`)]; 25 | -------------------------------------------------------------------------------- /src/icons/column.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | interface ProxyStorage { 2 | getItem(key: string): any; 3 | setItem(key: string, value: string): void; 4 | removeItem(key: string): void; 5 | clear(): void; 6 | } 7 | 8 | class VStorage implements ProxyStorage { 9 | protected storage: ProxyStorage; 10 | 11 | constructor(storageModel: ProxyStorage) { 12 | this.storage = storageModel; 13 | } 14 | 15 | public getItem(key: string): any { 16 | return this.storage.getItem(key); 17 | } 18 | 19 | public setItem(key: string, value: string): void { 20 | this.storage.setItem(key, value); 21 | } 22 | 23 | public removeItem(key: string): void { 24 | this.storage.removeItem(key); 25 | } 26 | 27 | public clear(): void { 28 | this.storage.clear(); 29 | } 30 | } 31 | 32 | export const local = new VStorage(localStorage); 33 | 34 | export const session = new VStorage(sessionStorage); 35 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "type-enum": [ 5 | 2, 6 | "always", 7 | [ 8 | "build", // 编译相关修改(新版本发布) 9 | "feat", // 新功能 10 | "fix", // 修复bug 11 | "update", // 更新某功能 12 | "refactor", // 重构 13 | "docs", // 文档 14 | "chore", // 增加依赖或库 15 | "style", // 格式(不影响代码变动) 16 | "revert", // 撤销commit 回滚上一版本 17 | "perf", // 性能优化 18 | "test" // 测试单元 19 | ] 20 | ], 21 | "type-case": [0], 22 | "type-empty": [0], 23 | "scope-empty": [0], 24 | "scope-case": [0], 25 | "subject-full-stop": [0, "never"], 26 | "subject-case": [0, "never"], 27 | "header-max-length": [0, "always", 72] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/styles/theme/dark.scss: -------------------------------------------------------------------------------- 1 | //配置dark下的样式 2 | 3 | $--colors: (); 4 | 5 | @forward "element-plus/theme-chalk/src/dark/var.scss" with ( 6 | $colors: $--colors 7 | ); 8 | 9 | html.dark { 10 | //navbar 图标hover颜色 11 | --base-nav-bar-box-hover: rgba(255, 255, 255, 0.06); 12 | 13 | //tag border 14 | --base-tag-border: #4f4f4f; 15 | //tag shadow 16 | --base-tag-shadow: rgb(0 21 41 / 8%); 17 | 18 | //menu背景颜色 19 | --el-menu-bg-color: #181717 !important; 20 | //submenu 背景颜色 21 | --el-submenu-bg-color: #101010 !important; 22 | // menu 文字颜色 23 | --el-menu-text-color: hsla(0, 0%, 100%, 0.65) !important; 24 | 25 | // menu hover 背景颜色 26 | --el-menu-hover-bg-color: transparent !important; 27 | 28 | //menu active文字颜色 29 | --el-menu-active-color: #fff !important; 30 | 31 | //系统主体背景颜色 32 | --base-main-bg-color: #000; 33 | //系统主体 内容颜色 34 | --base-main-content-bg-color: #1d1e1f; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/base-input/props.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | modelValue: { 3 | type: [String, Number] 4 | }, 5 | type: { 6 | type: String, 7 | default: "text" 8 | }, 9 | disabled: { 10 | type: Boolean, 11 | default: false 12 | }, 13 | placeholder: { 14 | type: String, 15 | default: "请输入" 16 | }, 17 | showWordLimit: { 18 | type: Boolean, 19 | default: false 20 | }, 21 | maxLength: { 22 | type: Number, 23 | default: Number.POSITIVE_INFINITY 24 | }, 25 | clearable: { 26 | type: Boolean, 27 | default: true 28 | }, 29 | showPassword: { 30 | type: Boolean, 31 | default: false 32 | }, 33 | prefixIcon: { 34 | type: String 35 | }, 36 | suffixIcon: { 37 | type: String 38 | }, 39 | text: { 40 | type: Boolean, 41 | default: false 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = 500 7 | } 8 | 9 | /** 10 | * @description: contentType 11 | */ 12 | export enum ContentTypeEnum { 13 | // json 14 | JSON = "application/json;charset=UTF-8", 15 | // form-data qs 16 | FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8", 17 | // form-data upload 18 | FORM_DATA = "multipart/form-data;charset=UTF-8" 19 | } 20 | 21 | /** 22 | * @description: contentType 23 | */ 24 | export enum ErrorMsgEnum { 25 | ERROR_400 = "请求失败,参数类型不匹配", 26 | ERROR_401 = "请求失败,登录状态已过期", 27 | ERROR_403 = "请求失败,您无权访问", 28 | ERROR_404 = "请求失败,未找到该资源", 29 | ERROR_500 = "请求失败,服务器错误,请联系管理员", 30 | ERROR_503 = "请求失败,服务器异常", 31 | ERROR_504 = "请求失败,请求超时" 32 | } 33 | 34 | export enum ErrorTypeEnum { 35 | VUE = "vue", 36 | SCRIPT = "script", 37 | AJAX = "ajax" 38 | } 39 | -------------------------------------------------------------------------------- /mock/controller/test.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from "vite-plugin-mock"; 2 | import { resultSuccess } from "../utils"; 3 | 4 | export default [ 5 | { 6 | url: "/api/test/getTest", 7 | method: "get", 8 | response: () => { 9 | const data = []; 10 | for (let i = 0; i < 10000; i++) { 11 | data.push(i); 12 | } 13 | return resultSuccess(data); 14 | } 15 | }, 16 | { 17 | url: "/api/test/postTest", 18 | method: "post", 19 | response: () => { 20 | return resultSuccess(); 21 | } 22 | }, 23 | { 24 | url: "/api/test/sameTest", 25 | method: "get", 26 | response: () => { 27 | const data = []; 28 | for (let i = 0; i < 10000; i++) { 29 | data.push(i); 30 | } 31 | return resultSuccess(data); 32 | } 33 | } 34 | ] as MockMethod[]; 35 | -------------------------------------------------------------------------------- /src/views/comp/markdown/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/graph.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const graphConfig: EChartsOption = { 4 | tooltip: {}, 5 | series: [ 6 | { 7 | type: "graph", 8 | layout: "force", 9 | force: { 10 | repulsion: 120, 11 | edgeLength: [20, 70] 12 | }, 13 | roam: true, 14 | draggable: true, 15 | symbolSize: (params) => { 16 | return params; 17 | }, 18 | itemStyle: { 19 | shadowColor: "rgba(133,203,247,0.75)", 20 | shadowBlur: 15 21 | }, 22 | label: { 23 | show: true 24 | }, 25 | data: [] 26 | } 27 | ], 28 | animationDurationUpdate: (index) => { 29 | return index * 100; 30 | }, 31 | animationEasingUpdate: "bounceIn" 32 | }; 33 | 34 | export default graphConfig; 35 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 一行最多 100 字符 3 | printWidth: 150, 4 | // 使用 4 个空格缩进 5 | tabWidth: 4, 6 | // 不使用缩进符,而使用空格 7 | useTabs: false, 8 | // 行尾分号 9 | semi: true, 10 | // 使用单引号 11 | singleQuote: false, 12 | // 对象的 key 仅在必要时用引号 13 | quoteProps: "as-needed", 14 | // jsx 不使用单引号,而使用双引号 15 | jsxSingleQuote: false, 16 | // 尾随逗号 17 | trailingComma: "none", 18 | // 大括号内的首尾需要空格 19 | bracketSpacing: true, 20 | // jsx 标签的反尖括号需要换行 21 | jsxBracketSameLine: false, 22 | // 箭头函数,只有一个参数的时候,也需要括号 23 | arrowParens: "always", 24 | // 每个文件格式化的范围是文件的全部内容 25 | rangeStart: 0, 26 | rangeEnd: Infinity, 27 | // 不需要写文件开头的 @prettier 28 | requirePragma: false, 29 | // 不需要自动在文件开头插入 @prettier 30 | insertPragma: false, 31 | // 使用默认的折行标准 32 | proseWrap: "preserve", 33 | // 根据显示样式决定 html 要不要折行 34 | htmlWhitespaceSensitivity: "css", 35 | // 换行符使用 lf 36 | endOfLine: "lf" 37 | }; 38 | -------------------------------------------------------------------------------- /src/views/comp/editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/radar.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const radarConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "item" 6 | }, 7 | legend: { 8 | show: true, 9 | left: "center", 10 | top: "0%", 11 | type: "scroll", 12 | itemWidth: 18, 13 | itemHeight: 11 14 | }, 15 | radar: { 16 | radius: "65%", 17 | splitNumber: 4, 18 | center: ["48%", "55%"], 19 | startAngle: 90, 20 | indicator: [] 21 | }, 22 | series: [ 23 | { 24 | type: "radar", 25 | symbolSize: 0, 26 | areaStyle: { 27 | shadowBlur: 13, 28 | shadowColor: "rgba(0,0,0,.2)", 29 | shadowOffsetX: 0, 30 | shadowOffsetY: 10, 31 | opacity: 1 32 | }, 33 | data: [] 34 | } 35 | ] 36 | }; 37 | 38 | export default radarConfig; 39 | -------------------------------------------------------------------------------- /src/layouts/hooks/useTagViewSetting.ts: -------------------------------------------------------------------------------- 1 | import { useTagStoreWithOut } from "@/stores/modules/tagView"; 2 | import { useTagEvent } from "./useTagEvent"; 3 | import type { RouteLocationNormalizedLoaded } from "vue-router"; 4 | 5 | export const useTagViewSetting = () => { 6 | const tagStore = useTagStoreWithOut(); 7 | 8 | const { closeTag } = useTagEvent(); 9 | 10 | const getTagList = computed(() => tagStore.tagList); 11 | 12 | const getCacheTagList = computed(() => tagStore.cacheTagList); 13 | 14 | const getTagFullscreen = computed(() => tagStore.fullscreen); 15 | 16 | const addTag = (route: RouteLocationNormalizedLoaded) => { 17 | tagStore.addTag(route); 18 | }; 19 | 20 | const toggleFullscreen = () => { 21 | tagStore.toggleFullscreen(); 22 | }; 23 | 24 | return { 25 | getTagList, 26 | getCacheTagList, 27 | getTagFullscreen, 28 | 29 | closeTag, 30 | addTag, 31 | toggleFullscreen 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/icons/func.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layouts/tag-view/tag-item.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ tag?.meta?.title }} 4 | 12 | 13 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | 4 | import { setupPinia } from "./stores"; 5 | import { setupIcon, setupGlobalUtils, setupErrorHandler } from "@/plugins"; 6 | import { setupLoadingDirective, setupPermissionDirective } from "./directive"; 7 | import router from "./router/index"; 8 | import { setupRouterGuard } from "@/router/guard"; 9 | 10 | import "./styles/index.scss"; 11 | 12 | const app = createApp(App); 13 | 14 | const setupPlugins = () => { 15 | // 注册pinia 16 | setupPinia(app); 17 | // 注册el-icon图标 18 | setupIcon(app); 19 | // 注册error收集 20 | setupErrorHandler(app); 21 | // 注册全局方法 22 | setupGlobalUtils(app); 23 | }; 24 | 25 | const setupDirective = () => { 26 | // 注册loading自定义指令 27 | setupLoadingDirective(app); 28 | // 注册permission自定义指令 29 | setupPermissionDirective(app); 30 | }; 31 | 32 | setupPlugins(); 33 | setupDirective(); 34 | // 注册路由守卫 35 | setupRouterGuard(router); 36 | 37 | app.use(router).mount("#app"); 38 | -------------------------------------------------------------------------------- /src/utils/remoteLoad.ts: -------------------------------------------------------------------------------- 1 | export const remoteLoad = (url: string) => { 2 | return new Promise((resolve, reject) => { 3 | const existingScript = document.getElementById(url); 4 | // 如果script不存在 5 | if (!existingScript) { 6 | const script = document.createElement("script"); 7 | script.id = url; 8 | script.src = url; 9 | script.async = true; 10 | document.body.appendChild(script); 11 | script.onload = function () { 12 | setTimeout(() => { 13 | this.onerror = this.onload = null; 14 | resolve(true); 15 | }, 500); 16 | }; 17 | script.onerror = function () { 18 | this.onerror = this.onload = null; 19 | reject(new Error("加载失败" + url)); 20 | }; 21 | } else { 22 | setTimeout(() => { 23 | resolve(true); 24 | }, 500); 25 | } 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/base-input-number/props.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | modelValue: { 3 | type: [String, Number] 4 | }, 5 | disabled: { 6 | type: Boolean, 7 | default: false 8 | }, 9 | controls: { 10 | type: Boolean, 11 | default: false 12 | }, 13 | controlsPosition: { 14 | type: String, 15 | default: "", 16 | validator(value: string) { 17 | return ["", "right"].includes(value); 18 | } 19 | }, 20 | precision: { 21 | type: Number, 22 | default: 0, 23 | validator: (val: number) => val >= 0 && val === Number.parseInt(`${val}`, 10) 24 | }, 25 | max: { 26 | type: Number, 27 | default: Number.POSITIVE_INFINITY 28 | }, 29 | min: { 30 | type: Number, 31 | default: Number.NEGATIVE_INFINITY 32 | }, 33 | step: { 34 | type: Number, 35 | default: 1 36 | }, 37 | text: { 38 | type: Boolean, 39 | default: false 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/components/base-table/src/types.ts: -------------------------------------------------------------------------------- 1 | import { FormTypeEnum } from "@/enums/componentEnum"; 2 | 3 | export type OperationType = { 4 | icon?: string; 5 | label: string; 6 | confirm?: boolean; 7 | tip?: string; 8 | callFunction: (val?: any) => void; 9 | }; 10 | 11 | export type PaginationPosition = "bottomLeft" | "bottomCenter" | "bottomRight"; 12 | 13 | export type Column = { 14 | fieldName: string; 15 | fieldDesc: string; 16 | align?: "left" | "center" | "right"; 17 | fixed?: "true" | "left" | "right"; 18 | sortable?: boolean | "custom"; 19 | width?: number | string; 20 | minWidth?: number | string; 21 | resizable?: boolean; 22 | showOverflowTooltip?: boolean; 23 | required?: boolean; 24 | formType?: `${FormTypeEnum}` | undefined; 25 | active?: boolean; 26 | sort?: number; 27 | callFunction?: Function; 28 | config?: any; 29 | operation?: OperationType[]; 30 | formatter?: Function; 31 | tagType?: Function | string; 32 | [key: string]: any; 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/base-tinymce/config.ts: -------------------------------------------------------------------------------- 1 | export const plugins = 2 | "advlist anchor autolink autoresize autosave code lists image table wordcount fullscreen directionality insertdatetime media nonbreaking pagebreak preview save searchreplace template visualblocks visualchars"; 3 | 4 | export const styles = `*{ padding:0; margin:0; } 5 | html, body { height:100%; } 6 | img { max-width:100%; display:block;height:auto; } 7 | a { text-decoration: none; } 8 | iframe { width: 100%; } 9 | p { line-height:1.6; margin: 0px; } 10 | table { word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; } 11 | ul,ol { list-style-position:inside; }`; 12 | 13 | export const toolbar = [ 14 | "undo redo bold italic underline strikethrough alignleft aligncenter alignright blockquote formatselect fontsizeselect ", 15 | "forecolor backcolor searchreplace bullist numlist outdent indent link image media charmap table preview fullscreen code" 16 | ]; 17 | -------------------------------------------------------------------------------- /src/icons/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/bar.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const barConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "axis", 6 | axisPointer: { 7 | type: "shadow" 8 | }, 9 | padding: [5, 10] 10 | }, 11 | legend: {}, 12 | grid: { 13 | left: "4%", 14 | right: "2%", 15 | bottom: "7%", 16 | top: "2%" 17 | }, 18 | xAxis: [ 19 | { 20 | type: "category", 21 | data: [], 22 | 23 | axisTick: { 24 | show: true 25 | } 26 | } 27 | ], 28 | yAxis: [ 29 | { 30 | type: "value", 31 | axisTick: { 32 | show: false 33 | } 34 | } 35 | ], 36 | series: [ 37 | { 38 | type: "bar", 39 | name: "", 40 | data: [], 41 | barWidth: "auto", 42 | barGap: "80%" 43 | } 44 | ] 45 | }; 46 | 47 | export default barConfig; 48 | -------------------------------------------------------------------------------- /src/icons/read.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/base-date-picker/base-date-picker.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ item.meta.title }} 5 | {{ item?.meta?.title }} 6 | 7 | 8 | 9 | 10 | 30 | 31 | 42 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/NoticeList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 更多 5 | 6 | 7 | {{ item.type }} 8 | {{ item.content }} 9 | 10 | 11 | 12 | 13 | 23 | 24 | 37 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/line.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const lineConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "axis", 6 | axisPointer: { 7 | type: "cross" 8 | } 9 | }, 10 | grid: { 11 | left: "4%", 12 | right: "2%", 13 | bottom: "7%", 14 | top: "2%" 15 | }, 16 | xAxis: [ 17 | { 18 | type: "category", 19 | boundaryGap: false, 20 | data: [], 21 | 22 | axisPointer: { 23 | snap: true 24 | } 25 | } 26 | ], 27 | yAxis: [ 28 | { 29 | type: "value", 30 | axisTick: { 31 | show: false 32 | }, 33 | axisPointer: { 34 | snap: true 35 | } 36 | } 37 | ], 38 | series: [ 39 | { 40 | name: "", 41 | type: "line", 42 | data: [], 43 | smooth: true, 44 | showSymbol: false 45 | } 46 | ] 47 | }; 48 | 49 | export default lineConfig; 50 | -------------------------------------------------------------------------------- /src/components/base-form/src/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from "vue"; 2 | import { FormColumnType } from "./type"; 3 | 4 | export const defaultProps = { 5 | model: { 6 | type: Object as PropType, 7 | default: {} 8 | }, 9 | inline: { 10 | type: Boolean 11 | }, 12 | labelWidth: { 13 | type: [Number, String], 14 | default: "100px" 15 | }, 16 | labelPosition: { 17 | type: String as PropType<"left" | "right" | "top"> 18 | }, 19 | size: { 20 | type: String as PropType<"large" | "default" | "small"> 21 | }, 22 | hideRequiredAsterisk: { 23 | type: Boolean 24 | }, 25 | disabled: { 26 | type: Boolean 27 | } 28 | }; 29 | 30 | export const extraProps = { 31 | columns: { 32 | type: Array as PropType, 33 | default: () => [] 34 | }, 35 | rowProps: { 36 | type: Object, 37 | default: () => {} 38 | }, 39 | colProps: { 40 | type: Object, 41 | default: () => {} 42 | } 43 | }; 44 | 45 | export default Object.assign({}, defaultProps, extraProps); 46 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/pulseSpin.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 47 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/default/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/tinymce-5/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/layouts/hooks/useMenuSetting.ts: -------------------------------------------------------------------------------- 1 | import { MenuSetting, useAppStoreWithOut } from "@/stores/modules/app"; 2 | import { isUndefined } from "@/utils"; 3 | 4 | export const useMenuSetting = () => { 5 | const appStore = useAppStoreWithOut(); 6 | 7 | const getCollapse = computed(() => appStore?.menuConfig?.collapse); 8 | 9 | const getMode = computed(() => appStore?.menuConfig?.mode); 10 | 11 | const getSystemTitle = computed(() => appStore?.menuConfig?.systemTitle); 12 | 13 | const getSideWidth = computed(() => appStore?.menuConfig?.sideWidth); 14 | 15 | const getSideCollapsed = computed(() => appStore?.menuConfig?.sideCollapsed); 16 | 17 | const setMenuSetting = (menuConfig: MenuSetting) => { 18 | appStore.setAppConfig({ menuConfig }); 19 | }; 20 | 21 | const toggleCollapse = (flag?: boolean) => { 22 | setMenuSetting({ collapse: isUndefined(flag) ? !unref(getCollapse) : flag }); 23 | }; 24 | 25 | return { 26 | toggleCollapse, 27 | setMenuSetting, 28 | 29 | getCollapse, 30 | getMode, 31 | getSystemTitle, 32 | getSideWidth, 33 | getSideCollapsed 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/column.ts: -------------------------------------------------------------------------------- 1 | type CardDataType = { 2 | title: string; 3 | extraValue: number | string; 4 | extraTitle: string; 5 | value: number; 6 | icon: string; 7 | prefix: string; 8 | suffix: string; 9 | }; 10 | 11 | export const cardListData: CardDataType[] = [ 12 | { 13 | title: "今日收益", 14 | value: 10500, 15 | extraTitle: "总收益", 16 | extraValue: 17500, 17 | prefix: "¥", 18 | suffix: "", 19 | icon: "svg-money" 20 | }, 21 | { 22 | title: "今日访问量", 23 | value: 16800, 24 | extraTitle: "总访问量", 25 | extraValue: 130845, 26 | prefix: "", 27 | suffix: "次", 28 | icon: "svg-view" 29 | }, 30 | { 31 | title: "待发货", 32 | value: 626, 33 | extraTitle: "订单量", 34 | extraValue: 1679, 35 | prefix: "", 36 | suffix: "个", 37 | icon: "svg-deliver" 38 | }, 39 | { 40 | title: "好评率", 41 | value: 98, 42 | extraTitle: "同期对比", 43 | extraValue: "+3.25%", 44 | prefix: "", 45 | suffix: "%", 46 | icon: "svg-good" 47 | } 48 | ]; 49 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/writer/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/rectSpin.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 53 | -------------------------------------------------------------------------------- /src/views/system/dictionary-data/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-user.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ getUserInfo.username }} 6 | 7 | 8 | 9 | 项目地址 10 | 项目文档 11 | 退出登录 12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/dark/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/views/system-log/login-log/column.ts: -------------------------------------------------------------------------------- 1 | import { FormColumnType } from "@/components/base-form"; 2 | import { FormTypeEnum } from "@/enums/componentEnum"; 3 | import { Column } from "@/components/base-table/src/types"; 4 | 5 | export function useColumn() { 6 | const filterColumn: FormColumnType[] = [ 7 | { 8 | fieldName: "time", 9 | fieldDesc: "登录时间", 10 | fieldType: FormTypeEnum.DATE, 11 | config: { 12 | type: "daterange" 13 | } 14 | } 15 | ]; 16 | 17 | const tableColumn: Column[] = [ 18 | { 19 | fieldName: "username", 20 | fieldDesc: "用户名" 21 | }, 22 | { 23 | fieldName: "ip", 24 | fieldDesc: "登录ip", 25 | showOverflowTooltip: true 26 | }, 27 | { 28 | fieldName: "os", 29 | fieldDesc: "操作系统" 30 | }, 31 | { 32 | fieldName: "browser", 33 | fieldDesc: "浏览器" 34 | }, 35 | { 36 | fieldName: "time", 37 | fieldDesc: "登录时间" 38 | } 39 | ]; 40 | 41 | return { 42 | filterColumn, 43 | tableColumn 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/views/comp/echarts/pie/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/tinymce-5-dark/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/views/comp/echarts/bar/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/icons/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/document/content.min.css: -------------------------------------------------------------------------------- 1 | @media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/views/comp/echarts/line/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/enums/loadingEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from "vue"; 2 | 3 | import pulseSpin from "@/components/base-loading/spin/pulseSpin.vue"; 4 | import rectSpin from "@/components/base-loading/spin/rectSpin.vue"; 5 | import planeSpin from "@/components/base-loading/spin/planeSpin.vue"; 6 | import cubeSpin from "@/components/base-loading/spin/cubeSpin.vue"; 7 | import preloaderSpin from "@/components/base-loading/spin/preloaderSpin.vue"; 8 | import chaseSpin from "@/components/base-loading/spin/chaseSpin.vue"; 9 | import dotSpin from "@/components/base-loading/spin/dotSpin.vue"; 10 | 11 | export enum LoadingEnum { 12 | PULSE = "pulse", 13 | RECT = "rect", 14 | PLANE = "plane", 15 | CUBE = "cube", 16 | PRELOADER = "preloader", 17 | CHASE = "chase", 18 | DOT = "dot", 19 | LOADING = "loading" 20 | } 21 | 22 | const loadingMap = new Map(); 23 | 24 | loadingMap.set(LoadingEnum.PULSE, pulseSpin); 25 | loadingMap.set(LoadingEnum.RECT, rectSpin); 26 | loadingMap.set(LoadingEnum.PLANE, planeSpin); 27 | loadingMap.set(LoadingEnum.CUBE, cubeSpin); 28 | loadingMap.set(LoadingEnum.PRELOADER, preloaderSpin); 29 | loadingMap.set(LoadingEnum.CHASE, chaseSpin); 30 | loadingMap.set(LoadingEnum.DOT, dotSpin); 31 | 32 | export { loadingMap }; 33 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-text-size.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | {{ item.text }} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 34 | -------------------------------------------------------------------------------- /src/icons/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/system-log/login-log/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/base-result/base-result.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ goHome ? "返回首页" : returnText }} 9 | 10 | 11 | 12 | 13 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/icons/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/comp/seamscroll/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ index + 1 }}.{{ item }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-switch.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 47 | -------------------------------------------------------------------------------- /src/views/system-log/operation-log/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/views/system/dictionary-key/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/layouts/side-bar/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/heatmap.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const heatmapConfig: EChartsOption = { 4 | visualMap: { 5 | left: "3%", 6 | bottom: "2%", 7 | color: ["#ff4601", "#fffc00", "#87cffa"], 8 | calculable: true, 9 | textStyle: { 10 | color: "#fff", 11 | fontSize: 12 12 | } 13 | }, 14 | geo: { 15 | map: "map", 16 | roam: true, 17 | itemStyle: { 18 | areaColor: "#17439a", 19 | borderColor: "#53D9FF", 20 | borderWidth: 1.3, 21 | shadowBlur: 15, 22 | shadowColor: "rgb(58,115,192)", 23 | shadowOffsetX: 7, 24 | shadowOffsetY: 6 25 | }, 26 | label: { 27 | show: true, 28 | color: "#fff" 29 | }, 30 | emphasis: { 31 | itemStyle: { 32 | areaColor: "#17439a" 33 | } 34 | }, 35 | zoom: 1.22 36 | }, 37 | series: [ 38 | { 39 | name: "hotMap", 40 | type: "heatmap", 41 | data: [], 42 | coordinateSystem: "geo", 43 | pointSize: 13, 44 | blurSize: 40 45 | } 46 | ] 47 | }; 48 | 49 | export default heatmapConfig; 50 | -------------------------------------------------------------------------------- /src/icons/zhifubao.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stores/modules/errorLog.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { store } from "../index"; 3 | import { ErrorTypeEnum } from "@/enums/httpEnum"; 4 | import dayjs from "dayjs"; 5 | import { Api, addErrorInfo } from "@/api/log"; 6 | 7 | interface ErrorLogInfo { 8 | type: ErrorTypeEnum; 9 | method: "get" | "post" | "put" | "delete" | ""; 10 | url: string; 11 | message: string; 12 | params?: string; 13 | data?: string; 14 | detail?: string; 15 | time?: string; 16 | } 17 | 18 | interface ErrorLogState { 19 | errorLogList: ErrorLogInfo[]; 20 | } 21 | 22 | export const useErrorLog = defineStore({ 23 | id: "error-log", 24 | state: (): ErrorLogState => ({ 25 | errorLogList: [] 26 | }), 27 | getters: {}, 28 | actions: { 29 | async addErrorLog(err: ErrorLogInfo) { 30 | const errorInfo = { 31 | ...err, 32 | time: dayjs().format("YYYY-MM-DD HH:mm:ss") 33 | }; 34 | // fix:重复无限请求 35 | if (errorInfo.url === Api.ADD_ERROR_INFO) return; 36 | await addErrorInfo(errorInfo); 37 | this.errorLogList.push({ ...errorInfo }); 38 | } 39 | } 40 | }); 41 | 42 | // 便于外部使用 43 | export const useErrorLogStoreWithOut = () => { 44 | return useErrorLog(store); 45 | }; 46 | -------------------------------------------------------------------------------- /src/views/comp/json/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/views/system/api/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/utils/dom.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from "./is"; 2 | 3 | export const hasClass = (ele: HTMLElement, cls: string | string[]): boolean => { 4 | const classArr = isArray(cls) ? cls : [cls]; 5 | return classArr.every((item: string) => !!ele.className.match(new RegExp("(\\s|^)" + item + "(\\s|$)"))); 6 | }; 7 | 8 | export const addClass = (ele: HTMLElement, cls: string | string[]) => { 9 | const classArr = isArray(cls) ? cls : [cls]; 10 | classArr.forEach((item: string) => { 11 | if (!hasClass(ele, item)) { 12 | ele.className += " " + item; 13 | } 14 | }); 15 | }; 16 | 17 | export const removeClass = (ele: HTMLElement, cls: string | string[]) => { 18 | const classArr = isArray(cls) ? cls : [cls]; 19 | classArr.forEach((item: string) => { 20 | if (hasClass(ele, item)) { 21 | const reg = new RegExp("(\\s|^)" + item + "(\\s|$)"); 22 | ele.className = ele.className.replace(reg, " ").trim(); 23 | } 24 | }); 25 | }; 26 | 27 | export const toggleClass = (ele: HTMLElement, cls: string | string[]) => { 28 | const classArr = isArray(cls) ? cls : [cls]; 29 | classArr.forEach((item: string) => { 30 | if (hasClass(ele, item)) { 31 | removeClass(ele, item); 32 | } else { 33 | addClass(ele, item); 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/stores/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import type { Mode } from "@/enums/layoutEnum"; 3 | import type { SizeEnum } from "@/enums/sizeEnum"; 4 | import { store } from "../index"; 5 | import { deepMerge } from "@/utils"; 6 | 7 | export interface MenuSetting { 8 | mode?: Mode; 9 | collapse?: boolean; 10 | sideWidth?: number; 11 | sideCollapsed?: number; 12 | systemTitle?: string; 13 | } 14 | 15 | export interface HeaderConfig { 16 | pageSize?: `${SizeEnum}`; 17 | } 18 | 19 | interface AppState { 20 | menuConfig?: MenuSetting; 21 | headerConfig?: HeaderConfig; 22 | } 23 | 24 | export const useAppStore = defineStore({ 25 | id: "app", 26 | persist: true, 27 | state: (): AppState => ({ 28 | menuConfig: { 29 | mode: "vertical", 30 | collapse: false, 31 | sideWidth: 240, 32 | sideCollapsed: 64, 33 | systemTitle: "Vue3 Basic admin" 34 | }, 35 | headerConfig: { 36 | pageSize: "default" 37 | } 38 | }), 39 | getters: {}, 40 | actions: { 41 | setAppConfig(config: AppState): void { 42 | this.$state = deepMerge(this.$state, config); 43 | } 44 | } 45 | }); 46 | 47 | // 便于外部使用 48 | export const useAppStoreWithOut = () => { 49 | return useAppStore(store); 50 | }; 51 | -------------------------------------------------------------------------------- /src/views/comp/photograph/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 点击拍照 5 | 6 | 7 | 8 | 9 | 10 | 11 | 录制 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/icons/approval.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/plugins/echarts.ts: -------------------------------------------------------------------------------- 1 | import * as echarts from "echarts/core"; 2 | import "echarts-wordcloud"; 3 | import "echarts-liquidfill"; 4 | 5 | import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart, ScatterChart, GraphChart, HeatmapChart } from "echarts/charts"; 6 | 7 | import { 8 | TitleComponent, 9 | TooltipComponent, 10 | GridComponent, 11 | PolarComponent, 12 | AriaComponent, 13 | ParallelComponent, 14 | LegendComponent, 15 | RadarComponent, 16 | ToolboxComponent, 17 | DataZoomComponent, 18 | VisualMapComponent, 19 | TimelineComponent, 20 | CalendarComponent, 21 | GraphicComponent 22 | } from "echarts/components"; 23 | 24 | import { CanvasRenderer } from "echarts/renderers"; 25 | 26 | echarts.use([ 27 | LegendComponent, 28 | TitleComponent, 29 | TooltipComponent, 30 | GridComponent, 31 | PolarComponent, 32 | AriaComponent, 33 | ParallelComponent, 34 | BarChart, 35 | LineChart, 36 | PieChart, 37 | MapChart, 38 | RadarChart, 39 | CanvasRenderer, 40 | PictorialBarChart, 41 | RadarComponent, 42 | ToolboxComponent, 43 | DataZoomComponent, 44 | VisualMapComponent, 45 | TimelineComponent, 46 | CalendarComponent, 47 | GraphicComponent, 48 | ScatterChart, 49 | GraphChart, 50 | HeatmapChart 51 | ]); 52 | 53 | export default echarts; 54 | -------------------------------------------------------------------------------- /src/views/page/table.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 新增 6 | 7 | 8 | 9 | 10 | 11 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/router/modules/out-link.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const outLink: AppRouteType[] = [ 4 | { 5 | path: "/out-link", 6 | name: "OutLink", 7 | component: () => import("@/layouts/index.vue"), 8 | meta: { 9 | title: "其他项目", 10 | icon: "svg-out", 11 | sort: 4 12 | }, 13 | children: [ 14 | { 15 | path: "http://gist006.gitee.io/vue-visual-drag/#/", 16 | name: "OutDrag", 17 | meta: { 18 | title: "可视化拖拽", 19 | sort: 1, 20 | icon: "" 21 | } 22 | }, 23 | { 24 | path: "https://gist006.gitee.io/vue3-bigdata/#/homepage", 25 | name: "OutBigData", 26 | meta: { 27 | title: "vue3大屏", 28 | sort: 2, 29 | icon: "" 30 | } 31 | }, 32 | { 33 | path: "http://gist006.gitee.io/vue-antd-admin/#/", 34 | name: "OutAdmin", 35 | meta: { 36 | title: "vue-antd-admin", 37 | sort: 3, 38 | icon: "" 39 | } 40 | } 41 | ] 42 | } 43 | ]; 44 | 45 | export default outLink; 46 | -------------------------------------------------------------------------------- /src/router/modules/permission.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const permission: AppRouteType[] = [ 4 | { 5 | path: "/permission", 6 | name: "Permission", 7 | component: () => import("@/layouts/index.vue"), 8 | redirect: "/permission/page", 9 | meta: { 10 | title: "权限控制", 11 | icon: "svg-permission", 12 | sort: 2, 13 | permission: "admin_permission" 14 | }, 15 | children: [ 16 | { 17 | path: "page", 18 | name: "PermissionPage", 19 | component: () => import("@/views/permission/page.vue"), 20 | meta: { 21 | title: "页面权限", 22 | sort: 1, 23 | permission: "admin_permission_page", 24 | icon: "" 25 | } 26 | }, 27 | { 28 | path: "button", 29 | name: "PermissionButton", 30 | component: () => import("@/views/permission/button.vue"), 31 | meta: { 32 | title: "按钮权限", 33 | sort: 2, 34 | permission: "admin_permission_button", 35 | icon: "" 36 | } 37 | } 38 | ] 39 | } 40 | ]; 41 | 42 | export default permission; 43 | -------------------------------------------------------------------------------- /src/icons/out.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/api/system/api.ts: -------------------------------------------------------------------------------- 1 | const getAllApi = () => { 2 | const modules = import.meta.glob("../**/*.ts", { eager: true }); 3 | const apiList: any[] = []; 4 | Object.values(modules) 5 | .map((item: any) => item.Api) 6 | .forEach((item: any) => { 7 | if (!item) return; 8 | Object.keys(item).forEach((key) => { 9 | apiList.push({ 10 | apiName: key, 11 | apiUrl: item[key], 12 | status: 1, 13 | createTime: "2023-02-01 12:00:00", 14 | remark: "" 15 | }); 16 | }); 17 | }); 18 | return apiList; 19 | }; 20 | 21 | export const getApiList = (data?: any) => { 22 | const { currentPage, pageSize, apiName, status } = data; 23 | let apiData = getAllApi(); 24 | if (apiName) { 25 | apiData = apiData.filter((item) => item.apiName.includes(apiName.toLocaleUpperCase())); 26 | } 27 | if (status) { 28 | apiData = apiData.filter((item) => item.status === status); 29 | } 30 | 31 | return new Promise((resolve) => { 32 | resolve({ 33 | code: 200, 34 | data: { 35 | list: apiData.slice((currentPage - 1) * pageSize, currentPage * pageSize), 36 | total: apiData.length 37 | }, 38 | message: "" 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | type FormatO = { 2 | "M+": number; 3 | "D+": number; 4 | "H+": number; 5 | "m+": number; 6 | "s+": number; 7 | [key: string]: any; 8 | }; 9 | 10 | /** 11 | * 格式化日期 12 | * @param {Date|undefined} date 13 | * @param {String} format 14 | * @param {String} fill 15 | * @returns {String} 16 | */ 17 | export const formatTime = (date: any, format = "YYYY-MM-DD", fill = true): string => { 18 | if (!date && !fill) return ""; 19 | const cDate = date ? new Date(date) : new Date(); 20 | if (!cDate) return ""; 21 | const o: FormatO = { 22 | "M+": cDate.getMonth() + 1, // 月份 23 | "D+": cDate.getDate(), // 日 24 | "H+": cDate.getHours(), // 小时 25 | "m+": cDate.getMinutes(), // 分 26 | "s+": cDate.getSeconds() // 秒 27 | }; 28 | if (/(Y+)/.test(format)) { 29 | format = format.replace(RegExp.$1, (cDate.getFullYear() + "").substr(4 - RegExp.$1.length)); 30 | } 31 | for (const k in o) { 32 | if (new RegExp("(" + k + ")").test(format)) { 33 | format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : addZero(o[k])); 34 | } 35 | } 36 | return format; 37 | }; 38 | 39 | /** 40 | * Add Zero 41 | * @param {String} num 42 | * @returns {String} 43 | */ 44 | const addZero = (num: string): string => { 45 | if (parseFloat(num) < 10) { 46 | return "0" + num; 47 | } 48 | return num; 49 | }; 50 | -------------------------------------------------------------------------------- /src/router/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const dashboard: AppRouteType[] = [ 4 | { 5 | path: "/dashboard", 6 | name: "Dashboard", 7 | component: () => import("@/layouts/index.vue"), 8 | redirect: "/dashboard/analysis", 9 | meta: { 10 | title: "首页", 11 | icon: "svg-dashboard", 12 | sort: 1, 13 | permission: "admin_dashboard" 14 | }, 15 | children: [ 16 | { 17 | path: "analysis", 18 | name: "Analysis", 19 | component: () => import("@/views/dashboard/analysis/index.vue"), 20 | meta: { 21 | title: "分析页", 22 | sort: 1, 23 | permission: "admin_dashboard_analysis", 24 | icon: "", 25 | affix: true 26 | } 27 | }, 28 | { 29 | path: "workbench", 30 | name: "Workbench", 31 | component: () => import("@/views/dashboard/workbench/index.vue"), 32 | meta: { 33 | title: "工作台", 34 | sort: 2, 35 | permission: "admin_dashboard_workbench", 36 | icon: "" 37 | } 38 | } 39 | ] 40 | } 41 | ]; 42 | 43 | export default dashboard; 44 | -------------------------------------------------------------------------------- /src/icons/permission.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/weixin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "name": "", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "1327489", 10 | "name": "love", 11 | "font_class": "love", 12 | "unicode": "e60b", 13 | "unicode_decimal": 58891 14 | }, 15 | { 16 | "icon_id": "1330571", 17 | "name": "微博", 18 | "font_class": "weibo", 19 | "unicode": "e73c", 20 | "unicode_decimal": 59196 21 | }, 22 | { 23 | "icon_id": "1727433", 24 | "name": "209电话-圆框", 25 | "font_class": "dianhua-yuankuang", 26 | "unicode": "e8be", 27 | "unicode_decimal": 59582 28 | }, 29 | { 30 | "icon_id": "6078867", 31 | "name": "京东-01", 32 | "font_class": "jingdong-", 33 | "unicode": "e643", 34 | "unicode_decimal": 58947 35 | }, 36 | { 37 | "icon_id": "10905645", 38 | "name": "支付宝", 39 | "font_class": "zhifubao", 40 | "unicode": "e636", 41 | "unicode_decimal": 58934 42 | }, 43 | { 44 | "icon_id": "15559700", 45 | "name": "淘宝", 46 | "font_class": "shejiaotubiao-44", 47 | "unicode": "e648", 48 | "unicode_decimal": 58952 49 | }, 50 | { 51 | "icon_id": "15933094", 52 | "name": "微信", 53 | "font_class": "weixin", 54 | "unicode": "e607", 55 | "unicode_decimal": 58887 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/layouts/hooks/useNavBarSetting.ts: -------------------------------------------------------------------------------- 1 | import { HeaderConfig, useAppStoreWithOut } from "@/stores/modules/app"; 2 | import { useUserStoreWithOut } from "@/stores/modules/user"; 3 | import { useColorMode } from "@vueuse/core"; 4 | import { SizeEnum } from "@/enums/sizeEnum"; 5 | 6 | export const useNavBarSetting = () => { 7 | const appStore = useAppStoreWithOut(); 8 | const userStore = useUserStoreWithOut(); 9 | 10 | const router = useRouter(); 11 | 12 | const getPageSize = computed(() => appStore?.headerConfig?.pageSize as `${SizeEnum}`); 13 | 14 | const getUserInfo = computed(() => userStore.userInfo); 15 | 16 | const sizeMode = useColorMode({ 17 | attribute: "page-size", 18 | modes: { 19 | default: SizeEnum.DEFAULT, 20 | large: SizeEnum.LARGE, 21 | small: SizeEnum.SMALL 22 | } 23 | }); 24 | sizeMode.value = unref(getPageSize); 25 | 26 | const setHeaderSetting = (headerConfig: HeaderConfig) => { 27 | appStore.setAppConfig({ headerConfig }); 28 | }; 29 | 30 | const togglePageSize = (pageSize: `${SizeEnum}`) => { 31 | sizeMode.value = pageSize; 32 | setHeaderSetting({ pageSize }); 33 | }; 34 | 35 | const logout = () => { 36 | userStore.logout(router); 37 | }; 38 | 39 | return { 40 | getPageSize, 41 | getUserInfo, 42 | 43 | togglePageSize, 44 | setHeaderSetting, 45 | logout 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/base-button/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true 6 | }, 7 | extends: ["standard", "plugin:vue/vue3-essential", "eslint-config-prettier"], 8 | parserOptions: { 9 | ecmaVersion: "latest", 10 | parser: "@typescript-eslint/parser", 11 | sourceType: "module" 12 | }, 13 | rules: { 14 | "no-undef": "off", 15 | semi: ["error", "always"], 16 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 17 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 18 | "space-before-function-paren": 0, 19 | "vue/array-bracket-spacing": "error", 20 | "vue/arrow-spacing": "error", 21 | "vue/block-spacing": "error", 22 | "vue/brace-style": "error", 23 | "vue/camelcase": "off", 24 | "vue/comma-dangle": "error", 25 | "vue/component-name-in-template-casing": "error", 26 | "vue/eqeqeq": "error", 27 | "vue/key-spacing": "error", 28 | "vue/match-component-file-name": "error", 29 | "vue/object-curly-spacing": "off", 30 | "no-useless-escape": "off", 31 | "no-unused-vars": "off", 32 | "vue/attribute-hyphenation": "off", 33 | "vue/custom-event-name-casing": "off", 34 | "vue/multi-word-component-names": "off", 35 | "vue/comment-directive": "off" 36 | }, 37 | plugins: ["vue", "@typescript-eslint"] 38 | }; 39 | -------------------------------------------------------------------------------- /src/views/comp/button/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ item }} 5 | 6 | 7 | {{ item }} 8 | 9 | 10 | {{ item }} 11 | 12 | 13 | {{ item }} 14 | 15 | 16 | {{ item }} 17 | 18 | 19 | {{ item }} 20 | 21 | 22 | {{ item }} 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/map.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const mapConfig: EChartsOption = { 4 | tooltip: {}, 5 | visualMap: { 6 | left: "3%", 7 | bottom: "2%", 8 | calculable: true, 9 | inRange: { 10 | color: ["#24CFF4", "#2E98CA", "#1E62AC"] 11 | }, 12 | textStyle: { 13 | color: "#24CFF4" 14 | } 15 | }, 16 | series: [ 17 | { 18 | name: "地图", 19 | type: "map", 20 | map: "map", 21 | roam: true, 22 | zoom: 1.22, 23 | data: [], 24 | label: { 25 | show: true, 26 | color: "rgb(249, 249, 249)" 27 | }, 28 | itemStyle: { 29 | areaColor: "#24CFF4", 30 | borderColor: "#53D9FF", 31 | borderWidth: 1.3, 32 | shadowBlur: 15, 33 | shadowColor: "rgb(58,115,192)", 34 | shadowOffsetX: 7, 35 | shadowOffsetY: 6 36 | }, 37 | emphasis: { 38 | label: { 39 | show: true, 40 | color: "#f75a00" 41 | }, 42 | itemStyle: { 43 | areaColor: "#8dd7fc", 44 | borderWidth: 1.6, 45 | shadowBlur: 25 46 | } 47 | } 48 | } 49 | ] 50 | }; 51 | 52 | export default mapConfig; 53 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/TurnoverAnalysis.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/func/message/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 默认 5 | 成功 6 | 警告 7 | 失败 8 | 9 | 10 | 默认 11 | 成功 12 | 警告 13 | 失败 14 | 15 | 16 | 点击弹出确认框 17 | 18 | 19 | 20 | 21 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/base-input/base-input.vue: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{ value }} 31 | 32 | 33 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/base-dialog/src/base-dialog.scss: -------------------------------------------------------------------------------- 1 | .dialog-fade-enter-active .el-dialog.base-dialog { 2 | animation: cocoFadeIn 0.3s; 3 | } 4 | .dialog-fade-leave-active .el-dialog.base-dialog { 5 | animation: cocoFadeOut 0.3s; 6 | } 7 | 8 | .el-overlay.blur { 9 | backdrop-filter: blur(5px); 10 | } 11 | 12 | .base-dialog { 13 | text-align: left; 14 | .el-dialog__header { 15 | padding-top: 6px; 16 | padding-bottom: 0; 17 | margin-right: 0; 18 | .base-dialog-header { 19 | height: 52px; 20 | line-height: 50px; 21 | .base-header-icon { 22 | position: absolute; 23 | right: 10px; 24 | top: 22px; 25 | } 26 | } 27 | } 28 | 29 | .el-dialog__footer { 30 | padding-bottom: 10px; 31 | } 32 | .el-dialog__body { 33 | padding: 14px !important; 34 | } 35 | .el-scrollbar__view { 36 | height: 100%; 37 | } 38 | &.el-dialog.is-fullscreen { 39 | .el-dialog__body { 40 | height: calc(100% - 112px); 41 | } 42 | } 43 | } 44 | 45 | @keyframes cocoFadeIn { 46 | 0% { 47 | transform: scale(0); 48 | opacity: 0; 49 | } 50 | to { 51 | transform: scale(1); 52 | opacity: 1; 53 | } 54 | } 55 | @keyframes cocoFadeOut { 56 | 0% { 57 | transform: scale3d(1, 1, 1); 58 | opacity: 1; 59 | } 60 | to { 61 | transform: scale3d(0.1, 0.1, 0.1); 62 | opacity: 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/layouts/setting/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 主题配置: 6 | 7 | 8 | 9 | 10 | 11 | 39 | 40 | 55 | -------------------------------------------------------------------------------- /src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { LoadingEnum } from "@/enums/loadingEnum"; 3 | import baseLoading from "@/components/base-loading/base-loading.vue"; 4 | 5 | type LoadingType = { 6 | text?: string; 7 | textColor?: string; 8 | background?: string; 9 | spin?: LoadingEnum; 10 | minTime?: number; 11 | modal?: boolean; 12 | }; 13 | 14 | export function useLoading(config: LoadingType = {}) { 15 | const loadingConstructor = createApp(baseLoading, { ...config }); 16 | let instance: any = null; 17 | let startTime: number = 0; 18 | let endTime: number = 0; 19 | const minTime = config.minTime || 0; 20 | 21 | const open = (target: HTMLElement = document.body) => { 22 | if (!instance) { 23 | instance = loadingConstructor.mount(document.createElement("div")); 24 | } 25 | if (!instance || !instance.$el) return; 26 | target?.appendChild?.(instance.$el); 27 | startTime = performance.now(); 28 | }; 29 | 30 | const close = () => { 31 | if (!instance || !instance.$el) return; 32 | endTime = performance.now(); 33 | if (endTime - startTime < minTime) { 34 | setTimeout(() => { 35 | instance.$el.parentNode?.removeChild(instance.$el); 36 | }, Math.floor(minTime - (endTime - startTime))); 37 | } else { 38 | instance.$el.parentNode?.removeChild(instance.$el); 39 | } 40 | }; 41 | 42 | return { 43 | open, 44 | close 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/directive/loading.ts: -------------------------------------------------------------------------------- 1 | import type { Directive, App } from "vue"; 2 | import { useLoading } from "@/hooks"; 3 | import BaseLoading from "@/components/base-loading/base-loading.vue"; 4 | 5 | const loadingDirective: Directive = { 6 | mounted(el, bind) { 7 | const { props } = BaseLoading; 8 | 9 | const full = el.getAttribute("loading-full"); 10 | const text = el.getAttribute("loading-text") || props.text.default; 11 | const textColor = el.getAttribute("loading-text-color") || props.textColor.default; 12 | const background = el.getAttribute("loading-background") || props.background.default; 13 | const spin = el.getAttribute("loading-spin") || props.spin.default; 14 | 15 | const instance = useLoading({ 16 | text, 17 | textColor, 18 | background, 19 | spin 20 | }); 21 | el.instance = instance; 22 | if (bind.value) { 23 | instance.open(full ? document.body : el); 24 | } 25 | }, 26 | updated(el, bind) { 27 | const instance = el.instance; 28 | if (!instance) return; 29 | if (bind.value) { 30 | instance.open(el.getAttribute("loading-full") === "true" ? document.body : el); 31 | } else { 32 | instance.close(); 33 | } 34 | }, 35 | unmounted(el) { 36 | el?.instance?.close(); 37 | } 38 | }; 39 | 40 | export const setupLoadingDirective = (app: App) => { 41 | app.directive("custom-loading", loadingDirective); 42 | }; 43 | 44 | export default loadingDirective; 45 | -------------------------------------------------------------------------------- /src/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mock/controller/user.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from "vite-plugin-mock"; 2 | import { RequestParams, resultSuccess, resultError } from "../utils"; 3 | import { userData, roleData } from "../constant"; 4 | import { Random } from "mockjs"; 5 | 6 | export default [ 7 | { 8 | url: "/api/login", 9 | method: "post", 10 | response: (request: RequestParams) => { 11 | const { username, password } = request.body; 12 | const userItem = userData.find((item) => item.username === username); 13 | if (!userItem) { 14 | return resultError("该用户不存在"); 15 | } 16 | return resultSuccess({ 17 | token: userItem.role + Random.string("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-.123456789", 180) 18 | }); 19 | } 20 | }, 21 | { 22 | url: "/api/getUserInfo", 23 | method: "get", 24 | response: (request: RequestParams) => { 25 | const { authorization } = request.headers as any; 26 | if (!authorization) { 27 | return resultError("获取用户信息失败"); 28 | } 29 | const userItem = userData.find((item) => authorization.includes(item.role)); 30 | if (!userItem) { 31 | return resultError("获取用户信息失败:未找到该用户"); 32 | } 33 | const roleIds = roleData.find((item) => item.role === userItem.role)?.menuIds; 34 | return resultSuccess({ 35 | ...userItem, 36 | roleIds 37 | }); 38 | } 39 | } 40 | ] as MockMethod[]; 41 | -------------------------------------------------------------------------------- /src/api/log.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | import dayjs from "dayjs"; 3 | import { getOS, getBrowse } from "@/utils"; 4 | 5 | export enum Api { 6 | ADD_OPERATION_INFO = "/log/addOperationInfo", 7 | GET_OPERATION_LIST = "/log/getOperationList", 8 | 9 | ADD_ERROR_INFO = "/error/addErrorInfo", 10 | GET_ERROR_LIST = "/error/getErrorList", 11 | 12 | ADD_LOGIN_INFO = "/login/addLoginInfo", 13 | GET_LOGON_LIST = "/login/getLoginList", 14 | 15 | ERROR_TEST = "/error/test" 16 | } 17 | 18 | export const addOperationInfo = (data?: any) => request.post(Api.ADD_OPERATION_INFO, data, { loading: false }); 19 | 20 | export const getOperationList = (data?: any) => request.get(Api.GET_OPERATION_LIST, data, { loading: false }); 21 | 22 | export const addErrorInfo = (data?: any) => request.post(Api.ADD_ERROR_INFO, data, { loading: false }); 23 | 24 | export const getErrorList = (data?: any) => request.get(Api.GET_ERROR_LIST, data, { loading: false }); 25 | 26 | export const addLoginInfo = (data?: any) => { 27 | const params = Object.assign( 28 | { 29 | username: "admin", 30 | ip: "127.0.0.1", 31 | os: getOS(), 32 | browser: getBrowse(), 33 | time: dayjs().format("YYYY-MM-DD HH:mm:ss") 34 | }, 35 | data 36 | ); 37 | return request.post(Api.ADD_LOGIN_INFO, params, { loading: false }); 38 | }; 39 | 40 | export const getLoginList = (data?: any) => request.get(Api.GET_LOGON_LIST, data, { loading: false }); 41 | 42 | // form errorlog test 43 | export const testErrorApi = () => request.get(Api.ERROR_TEST); 44 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/preloaderSpin.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 63 | -------------------------------------------------------------------------------- /src/utils/request/cancel.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig } from "axios"; 2 | 3 | export class AxiosCancel { 4 | pendingMap: Map; 5 | constructor() { 6 | this.pendingMap = new Map(); 7 | } 8 | 9 | generateReqKey(config: AxiosRequestConfig): string { 10 | const { method, url } = config; 11 | return [url || "", method || "", JSON.stringify(config.params), JSON.stringify(config.data)].join("&"); 12 | } 13 | 14 | addPending(config: AxiosRequestConfig) { 15 | this.removePending(config); 16 | const requestKey: string = this.generateReqKey(config); 17 | if (!this.pendingMap.has(requestKey)) { 18 | const controller = new AbortController(); 19 | config.signal = controller.signal; 20 | this.pendingMap.set(requestKey, controller); 21 | } else { 22 | config.signal = (this.pendingMap.get(requestKey) as AbortController).signal; 23 | } 24 | } 25 | 26 | removePending(config: AxiosRequestConfig) { 27 | const requestKey: string = this.generateReqKey(config); 28 | if (this.pendingMap.has(requestKey)) { 29 | (this.pendingMap.get(requestKey) as AbortController).abort(); 30 | this.pendingMap.delete(requestKey); 31 | } 32 | } 33 | 34 | removeAllPending() { 35 | this.pendingMap.forEach((cancel: AbortController) => { 36 | cancel.abort(); 37 | }); 38 | this.reset(); 39 | } 40 | 41 | reset() { 42 | this.pendingMap = new Map(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/views/permission/button.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 当前角色:{{ userInfo.role }} 5 | 6 | 切换角色: 7 | 8 | 9 | 10 | 11 | 12 | 13 | 仅admin权限可见 14 | 15 | 16 | 17 | 当前角色:{{ userInfo.role }} 18 | 19 | 切换角色: 20 | 21 | 22 | 23 | 24 | 25 | 26 | 仅admin权限可见 27 | 28 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/utils/navigator.ts: -------------------------------------------------------------------------------- 1 | export const getOS = () => { 2 | const userAgent = navigator.userAgent; 3 | 4 | if (userAgent.indexOf("Windows NT 10.0") !== -1) return "Windows 10"; 5 | if (userAgent.indexOf("Windows NT 6.2") !== -1) return "Windows 8"; 6 | if (userAgent.indexOf("Windows NT 6.1") !== -1) return "Windows 7"; 7 | if (userAgent.indexOf("Windows NT 6.0") !== -1) return "Windows Vista"; 8 | if (userAgent.indexOf("Windows NT 5.1") !== -1) return "Windows XP"; 9 | if (userAgent.indexOf("Windows NT 5.0") !== -1) return "Windows 2000"; 10 | if (userAgent.indexOf("Mac") !== -1) return "Mac/iOS"; 11 | if (userAgent.indexOf("X11") !== -1) return "UNIX"; 12 | if (userAgent.indexOf("Linux") !== -1) return "Linux"; 13 | return "Other"; 14 | }; 15 | 16 | export const getBrowse = () => { 17 | const userAgent = navigator.userAgent.toLowerCase(); 18 | 19 | if (userAgent.match(/msie/) != null || userAgent.match(/trident/) != null) return "IE"; 20 | 21 | if (userAgent.match(/firefox/) != null) return "firefox"; 22 | 23 | if (userAgent.match(/opera/) != null || userAgent.match(/opr/) != null) return "opera"; 24 | 25 | if (userAgent.match(/edge/) != null) return "edge"; 26 | 27 | if (userAgent.match(/bidubrowser/) != null) return "baidu"; 28 | 29 | if (userAgent.match(/metasr/) != null) return "sougou"; 30 | 31 | if (userAgent.match(/tencenttraveler/) != null || userAgent.match(/qqbrowse/) != null) return "QQ"; 32 | 33 | if (userAgent.match(/chrome/) != null) return "chrome"; 34 | 35 | if (userAgent.match(/safari/) != null) return "Safari"; 36 | 37 | return "others"; 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/base-charts/src/base-charts.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/cubeSpin.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 74 | -------------------------------------------------------------------------------- /src/views/system-log/error-log/column.ts: -------------------------------------------------------------------------------- 1 | import { FormColumnType } from "@/components/base-form"; 2 | import { FormTypeEnum } from "@/enums/componentEnum"; 3 | import { Column } from "@/components/base-table/src/types"; 4 | 5 | export function useColumn() { 6 | const filterColumn: FormColumnType[] = [ 7 | { 8 | fieldName: "time", 9 | fieldDesc: "操作时间", 10 | fieldType: FormTypeEnum.DATE, 11 | config: { 12 | type: "daterange" 13 | } 14 | } 15 | ]; 16 | 17 | const tableColumn: Column[] = [ 18 | { 19 | fieldName: "type", 20 | fieldDesc: "错误类型" 21 | }, 22 | { 23 | fieldName: "method", 24 | fieldDesc: "请求方式(来源)" 25 | }, 26 | { 27 | fieldName: "url", 28 | fieldDesc: "请求地址", 29 | showOverflowTooltip: true 30 | }, 31 | { 32 | fieldName: "message", 33 | fieldDesc: "错误提示" 34 | }, 35 | { 36 | fieldName: "params", 37 | fieldDesc: "请求参数", 38 | showOverflowTooltip: true 39 | }, 40 | { 41 | fieldName: "data", 42 | fieldDesc: "后台错误信息", 43 | showOverflowTooltip: true 44 | }, 45 | { 46 | fieldName: "detail", 47 | fieldDesc: "详细错误", 48 | showOverflowTooltip: true 49 | }, 50 | { 51 | fieldName: "time", 52 | fieldDesc: "报错时间" 53 | } 54 | ]; 55 | 56 | return { 57 | filterColumn, 58 | tableColumn 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/icons/good.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------