├── .nvmrc ├── .browserslistrc ├── .stylelintignore ├── src ├── utils │ ├── crypto │ │ ├── index.ts │ │ └── rsa.ts │ ├── http │ │ ├── error-code.ts │ │ └── types.d.ts │ ├── globalPolyfills.ts │ ├── mitt.ts │ ├── progress │ │ └── index.ts │ ├── preventDefault.ts │ ├── propTypes.ts │ ├── responsive.ts │ ├── sso.ts │ └── auth.ts ├── assets │ ├── user.jpg │ ├── login │ │ ├── bg.png │ │ └── avatar.svg │ ├── iconfont │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── iconfont.css │ │ └── iconfont.json │ ├── table-bar │ │ ├── expand.svg │ │ ├── refresh.svg │ │ ├── drag.svg │ │ ├── collapse.svg │ │ └── settings.svg │ └── svg │ │ ├── dark.svg │ │ ├── full_screen.svg │ │ ├── exit_screen.svg │ │ ├── enter_outlined.svg │ │ ├── keyboard_esc.svg │ │ ├── day.svg │ │ ├── back_top.svg │ │ └── system.svg ├── components │ ├── ReCalendar │ │ └── index.ts │ ├── ReAuth │ │ ├── index.ts │ │ └── src │ │ │ └── auth.tsx │ ├── Attachment │ │ ├── upload │ │ │ └── uploadImg │ │ │ │ ├── index.ts │ │ │ │ └── type.ts │ │ └── index.ts │ ├── ReCropper │ │ ├── src │ │ │ ├── circled.css │ │ │ └── svg │ │ │ │ ├── arrow-v.svg │ │ │ │ ├── arrow-h.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── arrow-down.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── download.svg │ │ │ │ ├── upload.svg │ │ │ │ ├── search-minus.svg │ │ │ │ ├── rotate-left.svg │ │ │ │ ├── search-plus.svg │ │ │ │ ├── rotate-right.svg │ │ │ │ ├── reload.svg │ │ │ │ ├── index.ts │ │ │ │ └── change.svg │ │ └── index.ts │ ├── ReSearchForm │ │ └── index.ts │ ├── ReText │ │ ├── index.ts │ │ └── src │ │ │ └── index.vue │ ├── ReCropperPreview │ │ ├── index.ts │ │ └── src │ │ │ └── index.vue │ ├── RePureTableBar │ │ └── index.ts │ ├── ReSegmented │ │ ├── index.ts │ │ └── src │ │ │ └── type.ts │ ├── ReIcon │ │ ├── index.ts │ │ └── src │ │ │ ├── types.ts │ │ │ ├── offlineIcon.ts │ │ │ ├── iconifyIconOnline.ts │ │ │ ├── iconifyIconOffline.ts │ │ │ ├── iconfont.ts │ │ │ └── hooks.ts │ ├── ReCol │ │ └── index.ts │ ├── ReDrawer │ │ └── index.ts │ └── ReDialog │ │ └── index.ts ├── layout │ ├── components │ │ ├── notify │ │ │ ├── utils │ │ │ │ ├── index.scss │ │ │ │ └── types.ts │ │ │ ├── info.vue │ │ │ ├── index.vue │ │ │ └── noticeList.vue │ │ ├── lay-search │ │ │ ├── types.ts │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── SearchHistoryItem.vue │ │ │ │ └── SearchFooter.vue │ │ ├── lay-sidebar │ │ │ └── components │ │ │ │ ├── SidebarExtraIcon.vue │ │ │ │ ├── SidebarLinkItem.vue │ │ │ │ ├── SidebarFullScreen.vue │ │ │ │ ├── SidebarTopCollapse.vue │ │ │ │ ├── SidebarLeftCollapse.vue │ │ │ │ ├── SidebarCenterCollapse.vue │ │ │ │ └── SidebarLogo.vue │ │ ├── lay-notice │ │ │ ├── components │ │ │ │ └── NoticeList.vue │ │ │ └── data.ts │ │ └── lay-footer │ │ │ └── index.vue │ ├── hooks │ │ ├── useMultiFrame.ts │ │ ├── useBoolean.ts │ │ └── useLayout.ts │ ├── redirect.vue │ ├── types.ts │ └── frame.vue ├── views │ ├── sys │ │ ├── user │ │ │ └── utils │ │ │ │ ├── reset.css │ │ │ │ └── types.ts │ │ ├── tenant │ │ │ ├── org │ │ │ │ ├── smsConfig.vue │ │ │ │ ├── utils │ │ │ │ │ └── types.ts │ │ │ │ ├── basicConfig.vue │ │ │ │ └── config.vue │ │ │ ├── product │ │ │ │ └── utils │ │ │ │ │ ├── types.ts │ │ │ │ │ └── rules.ts │ │ │ └── permission │ │ │ │ └── utils │ │ │ │ ├── rule.ts │ │ │ │ └── types.ts │ │ ├── dict │ │ │ └── utils │ │ │ │ ├── rules.ts │ │ │ │ └── types.ts │ │ ├── role │ │ │ ├── utils │ │ │ │ ├── types.ts │ │ │ │ └── rules.ts │ │ │ └── form.vue │ │ ├── monitor │ │ │ └── task │ │ │ │ └── utils │ │ │ │ ├── types.ts │ │ │ │ └── rules.ts │ │ ├── notice │ │ │ └── utils │ │ │ │ ├── rules.ts │ │ │ │ └── types.ts │ │ ├── menu │ │ │ └── utils │ │ │ │ ├── rule.ts │ │ │ │ └── types.ts │ │ └── base │ │ │ └── area │ │ │ └── utils │ │ │ └── types.ts │ ├── welcome │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── calendar │ │ │ │ └── index.vue │ │ │ └── welcome │ │ │ │ └── index.vue │ │ └── index.vue │ ├── user-profile │ │ ├── components │ │ │ └── index.ts │ │ └── settings.vue │ ├── login │ │ └── utils │ │ │ ├── static.ts │ │ │ ├── rule.ts │ │ │ └── motion.ts │ ├── basic │ │ ├── org │ │ │ └── utils │ │ │ │ ├── enums.ts │ │ │ │ ├── types.ts │ │ │ │ └── rules.ts │ │ ├── post │ │ │ ├── utils │ │ │ │ ├── types.ts │ │ │ │ └── rules.ts │ │ │ └── form.vue │ │ ├── role │ │ │ ├── utils │ │ │ │ ├── types.ts │ │ │ │ └── rules.ts │ │ │ └── treeFooter.vue │ │ └── user │ │ │ └── utils │ │ │ └── types.ts │ └── error │ │ ├── 403.vue │ │ ├── 404.vue │ │ └── 500.vue ├── directives │ ├── index.ts │ ├── auth │ │ └── index.ts │ ├── copy │ │ └── index.ts │ ├── ripple │ │ └── index.scss │ ├── optimize │ │ └── index.ts │ └── longpress │ │ └── index.ts ├── api │ ├── sys │ │ ├── enums.ts │ │ ├── notice.ts │ │ ├── permission.ts │ │ ├── tenant │ │ │ ├── permission.ts │ │ │ ├── product.ts │ │ │ └── org.ts │ │ ├── base │ │ │ └── area.ts │ │ ├── user.ts │ │ ├── role.ts │ │ ├── taskJob.ts │ │ └── dict.ts │ ├── basic │ │ ├── permission.ts │ │ ├── notice.ts │ │ ├── org.ts │ │ ├── post.ts │ │ ├── user.ts │ │ └── role.ts │ ├── common │ │ ├── user.ts │ │ └── attachment.ts │ ├── auth.ts │ └── base.ts ├── store │ ├── index.ts │ ├── utils.ts │ ├── modules │ │ ├── settings.ts │ │ ├── epTheme.ts │ │ └── permission.ts │ └── types.ts ├── style │ ├── tailwind.css │ ├── index.scss │ ├── transition.scss │ └── login.css ├── router │ └── modules │ │ ├── home.ts │ │ ├── error.ts │ │ └── remaining.ts ├── App.vue ├── plugins │ └── echarts.ts ├── config │ └── index.ts └── main.ts ├── .npmrc ├── public ├── favicon.ico ├── logo.svg └── platform-config.json ├── docs └── screenshot │ ├── demo1.png │ ├── demo2.png │ ├── demo3.png │ ├── demo4.png │ ├── demo5.png │ ├── log-1.png │ ├── log-2.png │ ├── tracer-1.png │ └── tracer-2.png ├── .husky ├── commit-msg ├── common.sh └── pre-commit ├── .env ├── .prettierrc.js ├── .env.development ├── .markdownlint.json ├── types ├── shims-vue.d.ts ├── shims-tsx.d.ts └── index.d.ts ├── .editorconfig ├── postcss.config.js ├── .dockerignore ├── .gitignore ├── .vscode ├── vue3.2.code-snippets ├── vue3.3.code-snippets ├── vue3.0.code-snippets ├── extensions.json └── settings.json ├── Dockerfile ├── tailwind.config.ts ├── .env.production ├── .env.staging ├── .lintstagedrc ├── mock ├── refreshToken.ts ├── asyncRoutes.ts └── login.ts ├── commitlint.config.js ├── LICENSE ├── tsconfig.json ├── README.en-US.md ├── vite.config.ts ├── index.html └── stylelint.config.js /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.12.2 -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | src/style/reset.scss -------------------------------------------------------------------------------- /src/utils/crypto/index.ts: -------------------------------------------------------------------------------- 1 | import * as RSA from "./rsa"; 2 | export { RSA }; 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | shell-emulator=true -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/src/assets/user.jpg -------------------------------------------------------------------------------- /src/assets/login/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/src/assets/login/bg.png -------------------------------------------------------------------------------- /docs/screenshot/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/demo1.png -------------------------------------------------------------------------------- /docs/screenshot/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/demo2.png -------------------------------------------------------------------------------- /docs/screenshot/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/demo3.png -------------------------------------------------------------------------------- /docs/screenshot/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/demo4.png -------------------------------------------------------------------------------- /docs/screenshot/demo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/demo5.png -------------------------------------------------------------------------------- /docs/screenshot/log-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/log-1.png -------------------------------------------------------------------------------- /docs/screenshot/log-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/log-2.png -------------------------------------------------------------------------------- /docs/screenshot/tracer-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/tracer-1.png -------------------------------------------------------------------------------- /docs/screenshot/tracer-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/docs/screenshot/tracer-2.png -------------------------------------------------------------------------------- /src/components/ReCalendar/index.ts: -------------------------------------------------------------------------------- 1 | import ReCalendar from "./src/index.vue"; 2 | 3 | export default ReCalendar; 4 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/components/ReAuth/index.ts: -------------------------------------------------------------------------------- 1 | import auth from "./src/auth"; 2 | 3 | const Auth = auth; 4 | 5 | export { Auth }; 6 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hb0730/boot-admin-ui/HEAD/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/components/Attachment/upload/uploadImg/index.ts: -------------------------------------------------------------------------------- 1 | import ImageUpload from "./index.vue"; 2 | export default ImageUpload; 3 | -------------------------------------------------------------------------------- /src/layout/components/notify/utils/index.scss: -------------------------------------------------------------------------------- 1 | .notice-dialog { 2 | top: 30vh; 3 | height: 500px; 4 | margin-right: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /src/views/sys/user/utils/reset.css: -------------------------------------------------------------------------------- 1 | /** 局部重置 ElProgress 的部分样式 */ 2 | .el-progress-bar__outer, 3 | .el-progress-bar__inner { 4 | border-radius: 0; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/http/error-code.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "401": "认证失败,无法访问系统资源", 3 | "403": "当前操作没有权限", 4 | "404": "访问资源不存在", 5 | default: "系统未知错误,请反馈给管理员" 6 | }; 7 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./copy"; 3 | export * from "./longpress"; 4 | export * from "./optimize"; 5 | export * from "./ripple"; 6 | -------------------------------------------------------------------------------- /src/components/Attachment/index.ts: -------------------------------------------------------------------------------- 1 | import ImageUpload from "./upload/uploadImg"; 2 | import { useUpload } from "./upload/useUpload"; 3 | 4 | export { ImageUpload, useUpload }; 5 | -------------------------------------------------------------------------------- /src/views/welcome/components/index.ts: -------------------------------------------------------------------------------- 1 | import WelComeMessage from "./welcome/index.vue"; 2 | import Calendar from "./calendar/index.vue"; 3 | export { WelComeMessage, Calendar }; 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | PATH="/usr/local/bin:$PATH" 7 | 8 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /src/assets/table-bar/expand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/sys/tenant/org/smsConfig.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/circled.css: -------------------------------------------------------------------------------- 1 | @import "cropperjs/dist/cropper.css"; 2 | 3 | .re-circled { 4 | .cropper-view-box, 5 | .cropper-face { 6 | border-radius: 50%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/ReSearchForm/index.ts: -------------------------------------------------------------------------------- 1 | import searchForm from "./src/searchForm"; 2 | 3 | import { withInstall } from "@pureadmin/utils"; 4 | 5 | export const SearchForm = withInstall(searchForm); 6 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # 平台本地运行端口号 2 | VITE_PORT = 8848 3 | 4 | # 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置) 5 | VITE_HIDE_HOME = false 6 | # 服务端 API地址 7 | VITE_SERVER_API_URL= "http://localhost:9001/api/v6" 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import("prettier").Config} */ 4 | export default { 5 | bracketSpacing: true, 6 | singleQuote: false, 7 | arrowParens: "avoid", 8 | trailingComma: "none" 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/ReCropper/index.ts: -------------------------------------------------------------------------------- 1 | import reCropper from "./src"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 图片裁剪组件 */ 5 | export const ReCropper = withInstall(reCropper); 6 | 7 | export default ReCropper; 8 | -------------------------------------------------------------------------------- /src/api/sys/enums.ts: -------------------------------------------------------------------------------- 1 | import { type SelectOption, get } from "../base"; 2 | 3 | /** 4 | * 菜单类型 5 | * @returns . 6 | */ 7 | export function menuTypes() { 8 | return get(`/sys/enums/menuType`); 9 | } 10 | -------------------------------------------------------------------------------- /src/views/user-profile/components/index.ts: -------------------------------------------------------------------------------- 1 | import UserProfile from "./profile.vue"; 2 | import BasicInfo from "./basicInfo.vue"; 3 | import RestPasswd from "./restPasswd.vue"; 4 | 5 | export { UserProfile, BasicInfo, RestPasswd }; 6 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 平台本地运行端口号 2 | VITE_PORT = 8848 3 | 4 | # 开发环境读取配置文件路径 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") 8 | VITE_ROUTER_HISTORY = "hash" 9 | -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command_exists () { 3 | command -v "$1" >/dev/null 2>&1 4 | } 5 | 6 | # Workaround for Windows 10, Git Bash and Pnpm 7 | if command_exists winpty && test -t 1; then 8 | exec < /dev/tty 9 | fi 10 | -------------------------------------------------------------------------------- /src/components/ReText/index.ts: -------------------------------------------------------------------------------- 1 | import reText from "./src/index.vue"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 支持`Tooltip`提示的文本省略组件 */ 5 | export const ReText = withInstall(reText); 6 | 7 | export default ReText; 8 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from "vue"; 2 | import { createPinia } from "pinia"; 3 | const store = createPinia(); 4 | 5 | export function setupStore(app: App) { 6 | app.use(store); 7 | } 8 | 9 | export { store }; 10 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD003": false, 4 | "MD033": false, 5 | "MD013": false, 6 | "MD001": false, 7 | "MD025": false, 8 | "MD024": false, 9 | "MD007": { "indent": 4 }, 10 | "no-hard-tabs": false 11 | } 12 | -------------------------------------------------------------------------------- /src/views/login/utils/static.ts: -------------------------------------------------------------------------------- 1 | import bg from "@/assets/login/bg.png"; 2 | import avatar from "@/assets/login/avatar.svg?component"; 3 | import illustration from "@/assets/login/illustration.svg?component"; 4 | 5 | export { bg, avatar, illustration }; 6 | -------------------------------------------------------------------------------- /src/assets/table-bar/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/basic/permission.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 查询权限树 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function loadTreePermission(query?: any) { 10 | return http.get(`/bas/permission/tree`, query); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/ReCropperPreview/index.ts: -------------------------------------------------------------------------------- 1 | import reCropperPreview from "./src/index.vue"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 图片裁剪预览组件 */ 5 | export const ReCropperPreview = withInstall(reCropperPreview); 6 | 7 | export default ReCropperPreview; 8 | -------------------------------------------------------------------------------- /src/assets/svg/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/index.ts: -------------------------------------------------------------------------------- 1 | import pureTableBar from "./src/bar"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */ 5 | export const PureTableBar = withInstall(pureTableBar); 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | [ -n "$CI" ] && exit 0 6 | 7 | PATH="/usr/local/bin:$PATH" 8 | 9 | # Perform lint check on files in the staging area through .lintstagedrc configuration 10 | pnpm exec lint-staged -------------------------------------------------------------------------------- /src/components/ReSegmented/index.ts: -------------------------------------------------------------------------------- 1 | import reSegmented from "./src/index"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 分段控制器组件 */ 5 | export const ReSegmented = withInstall(reSegmented); 6 | 7 | export default ReSegmented; 8 | export type { OptionsType } from "./src/type"; 9 | -------------------------------------------------------------------------------- /src/layout/components/notify/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface Notice { 2 | id: string; 3 | title: string; 4 | content: string; 5 | created: string; 6 | createdBy: string; 7 | read: boolean; 8 | } 9 | interface FormProps { 10 | formInline: Notice; 11 | } 12 | export type { Notice, FormProps }; 13 | -------------------------------------------------------------------------------- /types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import type { DefineComponent } from "vue"; 3 | const component: DefineComponent<{}, {}, any>; 4 | export default component; 5 | } 6 | 7 | declare module "*.scss" { 8 | const scss: Record; 9 | export default scss; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/globalPolyfills.ts: -------------------------------------------------------------------------------- 1 | // 如果项目出现 `global is not defined` 报错,可能是您引入某个库的问题,比如 aws-sdk-js https://github.com/aws/aws-sdk-js 2 | // 解决办法就是将该文件引入 src/main.ts 即可 import "@/utils/globalPolyfills"; 3 | if (typeof (window as any).global === "undefined") { 4 | (window as any).global = window; 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/assets/svg/full_screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/basic/org/utils/enums.ts: -------------------------------------------------------------------------------- 1 | export const OrgTypeOptions = [ 2 | { 3 | label: "商户", 4 | value: 1, 5 | disabled: true 6 | }, 7 | { 8 | label: "机构", 9 | value: 2, 10 | disabled: false 11 | }, 12 | { 13 | label: "部门", 14 | value: 3, 15 | disabled: false 16 | } 17 | ]; 18 | -------------------------------------------------------------------------------- /src/views/sys/dict/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import type { FormRules } from "element-plus"; 2 | import { reactive } from "vue"; 3 | 4 | export const dictFormRules = reactive({ 5 | name: [{ required: true, message: "请输入字典名称", trigger: "blur" }], 6 | type: [{ required: true, message: "请输入字典类型", trigger: "blur" }] 7 | }); 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('postcss-load-config').Config} */ 4 | export default { 5 | plugins: { 6 | "postcss-import": {}, 7 | "tailwindcss/nesting": {}, 8 | tailwindcss: {}, 9 | autoprefixer: {}, 10 | ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}) 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache 7 | report.html 8 | 9 | yarn.lock 10 | npm-debug.log* 11 | .pnpm-error.log* 12 | .pnpm-debug.log 13 | tests/**/coverage/ 14 | 15 | # Editor directories and files 16 | .idea 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | tsconfig.tsbuildinfo 22 | -------------------------------------------------------------------------------- /src/assets/svg/exit_screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-v.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-h.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/mitt.ts: -------------------------------------------------------------------------------- 1 | import type { Emitter } from "mitt"; 2 | import mitt from "mitt"; 3 | 4 | /** 全局公共事件需要在此处添加类型 */ 5 | type Events = { 6 | openPanel: string; 7 | tagViewsChange: string; 8 | tagViewsShowModel: string; 9 | logoChange: boolean; 10 | changLayoutRoute: string; 11 | }; 12 | 13 | export const emitter: Emitter = mitt(); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache 7 | report.html 8 | vite.config.*.timestamp* 9 | 10 | yarn.lock 11 | npm-debug.log* 12 | .pnpm-error.log* 13 | .pnpm-debug.log 14 | tests/**/coverage/ 15 | 16 | # Editor directories and files 17 | .idea 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/sys/role/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | /** 角色名称 */ 4 | name: string; 5 | /** 角色编号 */ 6 | code: string; 7 | /** 是否启用 */ 8 | enabled?: boolean; 9 | /** 备注 */ 10 | description: string; 11 | } 12 | interface FormProps { 13 | formInline: FormItemProps; 14 | } 15 | 16 | export type { FormItemProps, FormProps }; 17 | -------------------------------------------------------------------------------- /src/assets/svg/enter_outlined.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/progress/index.ts: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress"; 2 | import "nprogress/nprogress.css"; 3 | 4 | NProgress.configure({ 5 | // 动画方式 6 | easing: "ease", 7 | // 递增进度条的速度 8 | speed: 500, 9 | // 是否显示加载ico 10 | showSpinner: false, 11 | // 自动递增间隔 12 | trickleSpeed: 200, 13 | // 初始化时的最小百分比 14 | minimum: 0.3 15 | }); 16 | 17 | export default NProgress; 18 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/keyboard_esc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/sys/monitor/task/utils/types.ts: -------------------------------------------------------------------------------- 1 | type FormItemProps = { 2 | id?: number; 3 | jobName?: string; 4 | jobClassName?: string; 5 | parameter?: string; 6 | cronExpression?: string; 7 | sysCode?: string; 8 | enabled?: boolean; 9 | description?: string; 10 | }; 11 | interface FormItem { 12 | formInline: FormItemProps; 13 | } 14 | 15 | export type { FormItem, FormItemProps }; 16 | -------------------------------------------------------------------------------- /src/views/sys/notice/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import type { FormRules } from "element-plus"; 2 | import { reactive } from "vue"; 3 | 4 | export const formRules = reactive({ 5 | title: [{ required: true, message: "公告标题为必填项", trigger: "blur" }], 6 | content: [{ required: true, message: "公告内存为必填项", trigger: "blur" }], 7 | noticeTime: [{ required: true, message: "公告日期为必选项", trigger: "blur" }] 8 | }); 9 | -------------------------------------------------------------------------------- /src/assets/table-bar/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/vue3.2.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Vue3.2+快速生成模板": { 3 | "scope": "vue", 4 | "prefix": "Vue3.2+", 5 | "body": [ 6 | "\n", 8 | "\n", 11 | "", 13 | "$2" 14 | ], 15 | "description": "Vue3.2+" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/views/sys/tenant/product/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | code?: string; 4 | name?: string; 5 | principal?: string; 6 | contact?: string; 7 | siteNum?: number; 8 | accountNum?: number; 9 | enabled?: boolean; 10 | description?: string; 11 | } 12 | 13 | interface FormProps { 14 | formInline: FormItemProps; 15 | } 16 | 17 | export type { FormItemProps, FormProps }; 18 | -------------------------------------------------------------------------------- /src/layout/components/lay-search/types.ts: -------------------------------------------------------------------------------- 1 | interface optionsItem { 2 | path: string; 3 | type: "history" | "collect"; 4 | meta: { 5 | icon?: string; 6 | title?: string; 7 | }; 8 | } 9 | 10 | interface dragItem { 11 | oldIndex: number; 12 | newIndex: number; 13 | } 14 | 15 | interface Props { 16 | value: string; 17 | options: Array; 18 | } 19 | 20 | export type { optionsItem, dragItem, Props }; 21 | -------------------------------------------------------------------------------- /src/views/basic/post/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | /** 主键 */ 3 | id?: string; 4 | /** 岗位编号 */ 5 | code?: string; 6 | /** 岗位名称 */ 7 | name?: string; 8 | /** 岗位描述 */ 9 | description?: string; 10 | /** 排序 */ 11 | sort?: number; 12 | /** 是否启用 */ 13 | enabled?: boolean; 14 | } 15 | 16 | interface FormProps { 17 | formInline: FormItemProps; 18 | } 19 | 20 | export type { FormItemProps, FormProps }; 21 | -------------------------------------------------------------------------------- /src/assets/svg/day.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/upload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/sys/notice/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | title?: string; 4 | content?: string; 5 | enabled?: boolean; 6 | noticeTime?: any; 7 | noticeTimeStart?: string; 8 | noticeTimeEnd?: string; 9 | orgIds?: Array; 10 | orgId?: string; 11 | } 12 | interface FormProps { 13 | formInline: FormItemProps; 14 | tenantOrgList: Array; 15 | } 16 | 17 | export type { FormItemProps, FormProps }; 18 | -------------------------------------------------------------------------------- /src/assets/table-bar/collapse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/style/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .flex-c { 7 | @apply flex justify-center items-center; 8 | } 9 | 10 | .flex-ac { 11 | @apply flex justify-around items-center; 12 | } 13 | 14 | .flex-bc { 15 | @apply flex justify-between items-center; 16 | } 17 | 18 | .navbar-bg-hover { 19 | @apply dark:text-white dark:hover:!bg-[#242424]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/views/basic/role/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | /** 角色名称 */ 4 | name: string; 5 | /** 角色编号 */ 6 | code: string; 7 | /** 数据权限 */ 8 | dataScope?: number; 9 | /**数据范围 */ 10 | orgIds?: string[]; 11 | /** 是否启用 */ 12 | enabled?: boolean; 13 | /** 备注 */ 14 | description: string; 15 | } 16 | interface FormProps { 17 | formInline: FormItemProps; 18 | } 19 | 20 | export type { FormItemProps, FormProps }; 21 | -------------------------------------------------------------------------------- /src/layout/hooks/useMultiFrame.ts: -------------------------------------------------------------------------------- 1 | const MAP = new Map(); 2 | 3 | export const useMultiFrame = () => { 4 | function setMap(path, Comp) { 5 | MAP.set(path, Comp); 6 | } 7 | 8 | function getMap(path?) { 9 | if (path) { 10 | return MAP.get(path); 11 | } 12 | return [...MAP.entries()]; 13 | } 14 | 15 | function delMap(path) { 16 | MAP.delete(path); 17 | } 18 | 19 | return { 20 | setMap, 21 | getMap, 22 | delMap, 23 | MAP 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/views/sys/menu/utils/rule.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | 4 | /** 自定义表单规则校验 */ 5 | export const formRules = reactive({ 6 | title: [{ required: true, message: "菜单名称为必填项", trigger: "blur" }], 7 | routeName: [{ required: true, message: "路由名称为必填项", trigger: "blur" }], 8 | path: [{ required: true, message: "路由路径为必填项", trigger: "blur" }], 9 | permission: [{ required: true, message: "权限标识为必填项", trigger: "blur" }] 10 | }); 11 | -------------------------------------------------------------------------------- /src/views/sys/tenant/permission/utils/rule.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | 4 | /** 自定义表单规则校验 */ 5 | export const formRules = reactive({ 6 | title: [{ required: true, message: "菜单名称为必填项", trigger: "blur" }], 7 | routeName: [{ required: true, message: "路由名称为必填项", trigger: "blur" }], 8 | path: [{ required: true, message: "路由路径为必填项", trigger: "blur" }], 9 | permission: [{ required: true, message: "权限标识为必填项", trigger: "blur" }] 10 | }); 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine as build-stage 2 | 3 | WORKDIR /app 4 | RUN corepack enable 5 | RUN corepack prepare pnpm@8.6.10 --activate 6 | 7 | RUN npm config set registry https://registry.npmmirror.com 8 | 9 | COPY .npmrc package.json pnpm-lock.yaml ./ 10 | RUN pnpm install --frozen-lockfile 11 | 12 | COPY . . 13 | RUN pnpm build 14 | 15 | FROM nginx:stable-alpine as production-stage 16 | 17 | COPY --from=build-stage /app/dist /usr/share/nginx/html 18 | EXPOSE 80 19 | 20 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /.vscode/vue3.3.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Vue3.3+defineOptions快速生成模板": { 3 | "scope": "vue", 4 | "prefix": "Vue3.3+", 5 | "body": [ 6 | "\n", 11 | "\n", 14 | "", 16 | "$2" 17 | ], 18 | "description": "Vue3.3+defineOptions快速生成模板" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/directives/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { hasAuth } from "@/router/utils"; 2 | import type { Directive, DirectiveBinding } from "vue"; 3 | 4 | export const auth: Directive = { 5 | mounted(el: HTMLElement, binding: DirectiveBinding) { 6 | const { value } = binding; 7 | if (value) { 8 | !hasAuth(value) && el.parentNode?.removeChild(el); 9 | } else { 10 | throw new Error( 11 | "[Directive: auth]: need auths! Like v-auth=\"['btn.add','btn.edit']\"" 12 | ); 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.vscode/vue3.0.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Vue3.0快速生成模板": { 3 | "scope": "vue", 4 | "prefix": "Vue3.0", 5 | "body": [ 6 | "\n", 9 | "\n", 16 | "", 18 | "$2" 19 | ], 20 | "description": "Vue3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ReAuth/src/auth.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, Fragment } from "vue"; 2 | import { hasAuth } from "@/router/utils"; 3 | 4 | export default defineComponent({ 5 | name: "Auth", 6 | props: { 7 | value: { 8 | type: undefined, 9 | default: [] 10 | } 11 | }, 12 | setup(props, { slots }) { 13 | return () => { 14 | if (!slots) return null; 15 | return hasAuth(props.value) ? ( 16 | {slots.default?.()} 17 | ) : null; 18 | }; 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /src/layout/redirect.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "christian-kohler.path-intellisense", 4 | "vscode-icons-team.vscode-icons", 5 | "davidanson.vscode-markdownlint", 6 | "ms-azuretools.vscode-docker", 7 | "stylelint.vscode-stylelint", 8 | "bradlc.vscode-tailwindcss", 9 | "dbaeumer.vscode-eslint", 10 | "esbenp.prettier-vscode", 11 | "redhat.vscode-yaml", 12 | "csstools.postcss", 13 | "mikestead.dotenv", 14 | "eamodio.gitlens", 15 | "antfu.iconify", 16 | "Vue.volar" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/svg/back_top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarExtraIcon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/layout/hooks/useBoolean.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | 3 | export function useBoolean(initValue = false) { 4 | const bool = ref(initValue); 5 | 6 | function setBool(value: boolean) { 7 | bool.value = value; 8 | } 9 | function setTrue() { 10 | setBool(true); 11 | } 12 | function setFalse() { 13 | setBool(false); 14 | } 15 | function toggle() { 16 | setBool(!bool.value); 17 | } 18 | 19 | return { 20 | bool, 21 | setBool, 22 | setTrue, 23 | setFalse, 24 | toggle 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/style/index.scss: -------------------------------------------------------------------------------- 1 | @import "./transition"; 2 | @import "./element-plus"; 3 | @import "./sidebar"; 4 | @import "./dark"; 5 | 6 | /* 自定义全局 CssVar */ 7 | :root { 8 | /* 左侧菜单展开、收起动画时长 */ 9 | --pure-transition-duration: 0.3s; 10 | 11 | /* 常用border-color 需要时可取用 */ 12 | --pure-border-color: rgb(5 5 5 / 6%); 13 | 14 | /* switch关闭状态下的color 需要时可取用 */ 15 | --pure-switch-off-color: #a6a6a6; 16 | } 17 | 18 | /* 灰色模式 */ 19 | .html-grey { 20 | filter: grayscale(100%); 21 | } 22 | 23 | /* 色弱模式 */ 24 | .html-weakness { 25 | filter: invert(80%); 26 | } 27 | -------------------------------------------------------------------------------- /types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from "vue"; 2 | 3 | declare module "*.tsx" { 4 | import Vue from "compatible-vue"; 5 | export default Vue; 6 | } 7 | 8 | declare global { 9 | namespace JSX { 10 | interface Element extends VNode {} 11 | interface ElementClass extends Vue {} 12 | interface ElementAttributesProperty { 13 | $props: any; 14 | } 15 | interface IntrinsicElements { 16 | [elem: string]: any; 17 | } 18 | interface IntrinsicAttributes { 19 | [elem: string]: any; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/search-minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | darkMode: "class", 5 | corePlugins: { 6 | preflight: false 7 | }, 8 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 9 | theme: { 10 | extend: { 11 | colors: { 12 | bg_color: "var(--el-bg-color)", 13 | primary: "var(--el-color-primary)", 14 | text_color_primary: "var(--el-text-color-primary)", 15 | text_color_regular: "var(--el-text-color-regular)" 16 | } 17 | } 18 | } 19 | } satisfies Config; 20 | -------------------------------------------------------------------------------- /src/components/ReIcon/index.ts: -------------------------------------------------------------------------------- 1 | import iconifyIconOffline from "./src/iconifyIconOffline"; 2 | import iconifyIconOnline from "./src/iconifyIconOnline"; 3 | import fontIcon from "./src/iconfont"; 4 | import iconSelect from "./src/Select.vue"; 5 | 6 | /** 本地图标组件 */ 7 | const IconifyIconOffline = iconifyIconOffline; 8 | /** 在线图标组件 */ 9 | const IconifyIconOnline = iconifyIconOnline; 10 | /** `iconfont`组件 */ 11 | const FontIcon = fontIcon; 12 | /** `IconSelect`图标选择器组件 */ 13 | const IconSelect = iconSelect; 14 | 15 | export { IconifyIconOffline, IconifyIconOnline, FontIcon, IconSelect }; 16 | -------------------------------------------------------------------------------- /src/views/basic/org/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | parentId?: string; 4 | name?: string; 5 | linkMan?: string; 6 | linkTel?: string; 7 | linkEmail?: string; 8 | address?: string; 9 | type?: number; 10 | memo?: string; 11 | system?: boolean; 12 | enabled?: boolean; 13 | saas?: boolean; 14 | } 15 | 16 | interface TableColumnsProps extends FormItemProps { 17 | hasChildren: boolean; 18 | } 19 | interface FormProps { 20 | formInline: FormItemProps; 21 | } 22 | 23 | export type { FormItemProps, TableColumnsProps, FormProps }; 24 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境平台打包路径 2 | VITE_PUBLIC_PATH = / 3 | 4 | # 线上环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") 5 | VITE_ROUTER_HISTORY = "hash" 6 | 7 | # 是否在打包时使用cdn替换本地库 替换 true 不替换 false 8 | VITE_CDN = false 9 | 10 | # 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件) 11 | # 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 12 | # 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 13 | VITE_COMPRESSION = "none" 14 | 15 | # 访问服务端 16 | VITE_SERVER_API_URL= "https://boot-admin.hb0730.com/pvs/api/v6" 17 | -------------------------------------------------------------------------------- /.env.staging: -------------------------------------------------------------------------------- 1 | # 预发布也需要生产环境的行为 2 | # https://cn.vitejs.dev/guide/env-and-mode.html#modes 3 | # NODE_ENV = development 4 | 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") 8 | VITE_ROUTER_HISTORY = "hash" 9 | 10 | # 是否在打包时使用cdn替换本地库 替换 true 不替换 false 11 | VITE_CDN = true 12 | 13 | # 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件) 14 | # 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 15 | # 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 16 | VITE_COMPRESSION = "none" 17 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface iconType { 2 | // iconify (https://docs.iconify.design/icon-components/vue/#properties) 3 | inline?: boolean; 4 | width?: string | number; 5 | height?: string | number; 6 | horizontalFlip?: boolean; 7 | verticalFlip?: boolean; 8 | flip?: string; 9 | rotate?: number | string; 10 | color?: string; 11 | horizontalAlign?: boolean; 12 | verticalAlign?: boolean; 13 | align?: string; 14 | onLoad?: Function; 15 | includes?: Function; 16 | // svg 需要什么SVG属性自行添加 17 | fill?: string; 18 | // all icon 19 | style?: object; 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/svg/system.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/basic/org/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import type { FormRules } from "element-plus"; 2 | import { reactive } from "vue"; 3 | 4 | /** 自定义表单规则校验 */ 5 | export const formRules = reactive({ 6 | parentId: [ 7 | { required: true, message: "所属机构为必选项", trigger: "change" } 8 | ], 9 | name: [{ required: true, message: "机构名称为必填项", trigger: "blur" }], 10 | linkMan: [{ required: true, message: "机构联系人为必填项", trigger: "blur" }], 11 | linkTel: [ 12 | { required: true, message: "机构联系电话为必填项", trigger: "change" } 13 | ], 14 | type: [{ required: true, message: "机构类型为必选项", trigger: "change" }] 15 | }); 16 | -------------------------------------------------------------------------------- /src/api/basic/notice.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 分页查询 5 | * 6 | * @param query . 7 | * @returns . 8 | */ 9 | export function pageNotice(query?: any) { 10 | return http.get(`/bas/notice/page`, query); 11 | } 12 | 13 | /** 14 | * 统计未读消息 15 | * 16 | * @returns 17 | */ 18 | export function countUnread() { 19 | return http.get(`/bas/notice/unread/count`); 20 | } 21 | 22 | /** 23 | * 读取消息 24 | * 25 | * @param ids . 26 | * @returns . 27 | */ 28 | export function readNotice(ids: string[]) { 29 | return http.put(`/bas/notice/read`, {}, ids); 30 | } 31 | -------------------------------------------------------------------------------- /src/layout/components/lay-search/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | -------------------------------------------------------------------------------- /src/layout/components/lay-notice/components/NoticeList.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/rotate-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/search-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": [ 3 | "prettier --cache --ignore-unknown --write", 4 | "eslint --cache --fix" 5 | ], 6 | "{!(package)*.json,*.code-snippets,.!({browserslist,nvm})*rc}": [ 7 | "prettier --cache --write--parser json" 8 | ], 9 | "package.json": ["prettier --cache --write"], 10 | "*.vue": [ 11 | "prettier --write", 12 | "eslint --cache --fix", 13 | "stylelint --fix --allow-empty-input" 14 | ], 15 | "*.{css,scss,html}": [ 16 | "prettier --cache --ignore-unknown --write", 17 | "stylelint --fix --allow-empty-input" 18 | ], 19 | "*.md": ["prettier --cache --ignore-unknown --write"] 20 | } 21 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/rotate-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/router/modules/home.ts: -------------------------------------------------------------------------------- 1 | const { VITE_HIDE_HOME } = import.meta.env; 2 | const Layout = () => import("@/layout/index.vue"); 3 | 4 | export default { 5 | path: "/", 6 | name: "Home", 7 | component: Layout, 8 | redirect: "/welcome", 9 | meta: { 10 | icon: "ep:home-filled", 11 | title: "首页", 12 | rank: 0 13 | }, 14 | children: [ 15 | { 16 | path: "/welcome", 17 | name: "Welcome", 18 | component: () => import("@/views/welcome/index.vue"), 19 | meta: { 20 | title: "首页", 21 | showLink: VITE_HIDE_HOME === "true" ? false : true 22 | } 23 | } 24 | ] 25 | } satisfies RouteConfigsTable; 26 | -------------------------------------------------------------------------------- /src/components/ReCol/index.ts: -------------------------------------------------------------------------------- 1 | import { ElCol } from "element-plus"; 2 | import { h, defineComponent } from "vue"; 3 | 4 | // 封装element-plus的el-col组件 5 | export default defineComponent({ 6 | name: "ReCol", 7 | props: { 8 | value: { 9 | type: Number, 10 | default: 24 11 | } 12 | }, 13 | render() { 14 | const attrs = this.$attrs; 15 | const val = this.value; 16 | return h( 17 | ElCol, 18 | { 19 | xs: val, 20 | sm: val, 21 | md: val, 22 | lg: val, 23 | xl: val, 24 | ...attrs 25 | }, 26 | { default: () => this.$slots.default() } 27 | ); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/offlineIcon.ts: -------------------------------------------------------------------------------- 1 | // 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载 2 | import { addIcon } from "@iconify/vue/dist/offline"; 3 | 4 | // 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标 5 | // @iconify-icons/ep 6 | import Lollipop from "@iconify-icons/ep/lollipop"; 7 | import HomeFilled from "@iconify-icons/ep/home-filled"; 8 | addIcon("ep:lollipop", Lollipop); 9 | addIcon("ep:home-filled", HomeFilled); 10 | // @iconify-icons/ri 11 | import Search from "@iconify-icons/ri/search-line"; 12 | import InformationLine from "@iconify-icons/ri/information-line"; 13 | addIcon("ri:search-line", Search); 14 | addIcon("ri:information-line", InformationLine); 15 | -------------------------------------------------------------------------------- /src/components/ReSegmented/src/type.ts: -------------------------------------------------------------------------------- 1 | import type { VNode, Component } from "vue"; 2 | import type { iconType } from "@/components/ReIcon/src/types.ts"; 3 | 4 | export interface OptionsType { 5 | /** 文字 */ 6 | label?: string | (() => VNode | Component); 7 | /** 8 | * @description 图标,采用平台内置的 `useRenderIcon` 函数渲染 9 | * @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks } 10 | */ 11 | icon?: string | Component; 12 | /** 图标属性、样式配置 */ 13 | iconAttrs?: iconType; 14 | /** 值 */ 15 | value?: any; 16 | /** 是否禁用 */ 17 | disabled?: boolean; 18 | /** `tooltip` 提示 */ 19 | tip?: string; 20 | } 21 | -------------------------------------------------------------------------------- /src/views/basic/user/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | org?: any; 4 | orgId?: string; 5 | roles?: Array; 6 | roleIds?: Array; 7 | posts?: Array; 8 | postIds?: Array; 9 | username?: string; 10 | nickname?: string; 11 | password?: string; 12 | confirmPassword?: string; 13 | phone?: string; 14 | email?: string; 15 | avatar?: string; 16 | gender?: number; 17 | system?: boolean; 18 | enabled?: boolean; 19 | } 20 | interface FormProps { 21 | formInline: FormItemProps; 22 | roleList: Array; 23 | orgList: Array; 24 | postList: Array; 25 | } 26 | 27 | export type { FormItemProps, FormProps }; 28 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/store/utils.ts: -------------------------------------------------------------------------------- 1 | export { store } from "@/store"; 2 | export { routerArrays } from "@/layout/types"; 3 | export { router, resetRouter, constantMenus } from "@/router"; 4 | export { getConfig, responsiveStorageNameSpace } from "@/config"; 5 | export { 6 | ascending, 7 | filterTree, 8 | filterNoPermissionTree, 9 | formatFlatteningRoutes 10 | } from "@/router/utils"; 11 | export { 12 | isUrl, 13 | isEqual, 14 | isNumber, 15 | debounce, 16 | isBoolean, 17 | getKeyList, 18 | storageLocal, 19 | deviceDetection 20 | } from "@pureadmin/utils"; 21 | export type { 22 | setType, 23 | appType, 24 | userType, 25 | multiType, 26 | cacheType, 27 | positionType 28 | } from "./types"; 29 | -------------------------------------------------------------------------------- /src/assets/login/avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/sys/dict/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemDictProps { 2 | id?: string; 3 | type?: string; 4 | name?: string; 5 | enabled?: boolean; 6 | description?: string; 7 | } 8 | 9 | interface FormItemDictItemProps { 10 | id?: string; 11 | dict?: FormItemDictProps; 12 | label?: string; 13 | value?: string; 14 | sort?: number; 15 | enabled?: boolean; 16 | description?: string; 17 | } 18 | 19 | interface FormDictProps { 20 | formInline: FormItemDictProps; 21 | } 22 | 23 | interface FormDictItemProps { 24 | formInline: FormItemDictItemProps; 25 | } 26 | 27 | export type { 28 | FormItemDictProps, 29 | FormItemDictItemProps, 30 | FormDictProps, 31 | FormDictItemProps 32 | }; 33 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 2208059 */ 3 | src: 4 | url("iconfont.woff2?t=1671895108120") format("woff2"), 5 | url("iconfont.woff?t=1671895108120") format("woff"), 6 | url("iconfont.ttf?t=1671895108120") format("truetype"); 7 | } 8 | 9 | .iconfont { 10 | font-family: "iconfont" !important; 11 | font-size: 16px; 12 | font-style: normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | .pure-iconfont-tabs:before { 18 | content: "\e63e"; 19 | } 20 | 21 | .pure-iconfont-logo:before { 22 | content: "\e620"; 23 | } 24 | 25 | .pure-iconfont-new:before { 26 | content: "\e615"; 27 | } 28 | -------------------------------------------------------------------------------- /src/views/welcome/components/calendar/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 30 | -------------------------------------------------------------------------------- /public/platform-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "5.5.0", 3 | "Title": "PureAdmin", 4 | "FixedHeader": true, 5 | "HiddenSideBar": false, 6 | "MultiTagsCache": false, 7 | "KeepAlive": true, 8 | "Layout": "vertical", 9 | "Theme": "light", 10 | "DarkMode": false, 11 | "OverallStyle": "light", 12 | "Grey": false, 13 | "Weak": false, 14 | "HideTabs": false, 15 | "HideFooter": false, 16 | "Stretch": false, 17 | "SidebarStatus": true, 18 | "EpThemeColor": "#409EFF", 19 | "ShowLogo": true, 20 | "ShowModel": "smart", 21 | "MenuArrowIconNoTransition": false, 22 | "CachingAsyncRoutes": false, 23 | "TooltipEffect": "light", 24 | "ResponsiveStorageNameSpace": "responsive-", 25 | "MenuSearchHistory": 6 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/crypto/rsa.ts: -------------------------------------------------------------------------------- 1 | import JSEncrypt from "jsencrypt"; 2 | // login password encrypt 3 | const publicKeyBase64 = 4 | "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzgtWnH39LSI3Imn/lXsLKjm+GKGROa+2g8R0Dr2vE46IAhNGmXPAmRwBWRCTqx+yNhR8gUGyWrM9YAmYyIt+5Ht3GOjPXpZmKqpqB735cIj0iFiHsIPYJ4OjITEbz/olDP2LWdrXYr89xJ69l+Y7uCzHD0NjaB2T3gZQaSKjgqVRo9OHOvMHnbhgP/O6MEKQg+63sdkBwYVWfVl57UDfhUCspizQfm5W2hCp0o23hIK5foVRKnV1ze4Iv+hDORvasZ2XOkP1SKaOMl2BDaQn5HawIjMTL1Kq4khR1f7EZiPakfwxi4AOFhXaSjZwJyuQpgWsIV1Ffc61n0zQIFZxTwIDAQAB"; 5 | /** 6 | * 加密 7 | * @param data . 8 | */ 9 | export function loginEncrypt(data: string) { 10 | var encryptor = new JSEncrypt(); 11 | encryptor.setPublicKey(publicKeyBase64); 12 | return encryptor.encrypt(data); 13 | } 14 | -------------------------------------------------------------------------------- /src/views/sys/menu/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | parentId?: string; 4 | children?: FormItemProps[]; 5 | path?: string; 6 | routeName?: string; 7 | redirect?: string; 8 | component?: string; 9 | title?: string; 10 | icon?: string; 11 | showLink?: boolean; 12 | rank?: number; 13 | showParent?: boolean; 14 | keepAlive?: boolean; 15 | frameSrc?: string; 16 | menuType?: number; 17 | permission?: string; 18 | enabled?: boolean; 19 | menuTypeName?: string; 20 | } 21 | 22 | interface TableColumnsProps extends FormItemProps { 23 | hasChildren: boolean; 24 | } 25 | interface FormProps { 26 | formInline: FormItemProps; 27 | } 28 | 29 | export type { FormItemProps, TableColumnsProps, FormProps }; 30 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | -------------------------------------------------------------------------------- /src/layout/components/lay-footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 32 | -------------------------------------------------------------------------------- /src/views/sys/tenant/permission/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | parentId?: string; 4 | children?: FormItemProps[]; 5 | path?: string; 6 | routeName?: string; 7 | redirect?: string; 8 | component?: string; 9 | title?: string; 10 | icon?: string; 11 | showLink?: boolean; 12 | rank?: number; 13 | showParent?: boolean; 14 | keepAlive?: boolean; 15 | frameSrc?: string; 16 | menuType?: number; 17 | permission?: string; 18 | enabled?: boolean; 19 | menuTypeName?: string; 20 | } 21 | 22 | interface TableColumnsProps extends FormItemProps { 23 | hasChildren: boolean; 24 | } 25 | interface FormProps { 26 | formInline: FormItemProps; 27 | } 28 | 29 | export type { FormItemProps, TableColumnsProps, FormProps }; 30 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "2208059", 3 | "name": "pure-admin", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "pure-iconfont-", 6 | "description": "pure-admin-iconfont", 7 | "glyphs": [ 8 | { 9 | "icon_id": "20594647", 10 | "name": "Tabs", 11 | "font_class": "tabs", 12 | "unicode": "e63e", 13 | "unicode_decimal": 58942 14 | }, 15 | { 16 | "icon_id": "22129506", 17 | "name": "PureLogo", 18 | "font_class": "logo", 19 | "unicode": "e620", 20 | "unicode_decimal": 58912 21 | }, 22 | { 23 | "icon_id": "7795615", 24 | "name": "New", 25 | "font_class": "new", 26 | "unicode": "e615", 27 | "unicode_decimal": 58901 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/views/sys/tenant/org/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | name?: string; 4 | sysCode?: string; 5 | logo?: string; 6 | linkMan?: string; 7 | linkTel?: string; 8 | linkEmail?: string; 9 | address?: string; 10 | productId?: string; 11 | usedEndTime?: string; 12 | product?: any; 13 | memo?: string; 14 | enabled?: boolean; 15 | } 16 | 17 | interface FormProps { 18 | formInline: FormItemProps; 19 | } 20 | 21 | interface BasicConfigFormIemProps { 22 | id?: string; 23 | usedEndTime?: string; 24 | } 25 | 26 | interface BasicConfigFormProps { 27 | formInline: BasicConfigFormIemProps; 28 | } 29 | 30 | export type { 31 | FormItemProps, 32 | FormProps, 33 | BasicConfigFormIemProps, 34 | BasicConfigFormProps 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/iconifyIconOnline.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | import { Icon as IconifyIcon } from "@iconify/vue"; 3 | 4 | // Iconify Icon在Vue里在线使用(用于外网环境) 5 | export default defineComponent({ 6 | name: "IconifyIconOnline", 7 | components: { IconifyIcon }, 8 | props: { 9 | icon: { 10 | type: String, 11 | default: "" 12 | } 13 | }, 14 | render() { 15 | const attrs = this.$attrs; 16 | return h( 17 | IconifyIcon, 18 | { 19 | icon: `${this.icon}`, 20 | style: attrs?.style 21 | ? Object.assign(attrs.style, { outline: "none" }) 22 | : { outline: "none" }, 23 | ...attrs 24 | }, 25 | { 26 | default: () => [] 27 | } 28 | ); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/views/sys/base/area/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | /** 3 | * 主键 4 | */ 5 | id?: string; 6 | /** 7 | * 父主键 8 | */ 9 | parentId?: string; 10 | /** 11 | * 名称 12 | */ 13 | name?: string; 14 | /** 15 | * 编码 16 | */ 17 | code?: string; 18 | /** 19 | * 状态 20 | */ 21 | enabled?: boolean; 22 | /** 23 | * 备注 24 | */ 25 | remark?: string; 26 | } 27 | /** 28 | * 树形数据 29 | */ 30 | interface TreeData extends FormItemProps { 31 | /** 32 | * 是否有子节点 33 | */ 34 | hasChildren?: boolean; 35 | /** 36 | * 子节点 37 | */ 38 | children?: TreeData[]; 39 | } 40 | 41 | /** 42 | * 表单属性 43 | */ 44 | interface FormProps { 45 | formInline: FormItemProps; 46 | } 47 | 48 | export type { FormItemProps, TreeData, FormProps }; 49 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarLinkItem.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 33 | -------------------------------------------------------------------------------- /src/api/common/user.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 获取当前用户信息 5 | * @returns 6 | */ 7 | export function getCurrentUserInfo() { 8 | return http.get("/common/user"); 9 | } 10 | /** 11 | * 修改用户信息 12 | * 13 | * @param data 14 | * @returns 15 | */ 16 | export function changeUserInfo(data: any) { 17 | return http.put("/common/user", {}, data); 18 | } 19 | 20 | /** 21 | * 修改密码 22 | * 23 | * @param data 24 | * @returns 25 | */ 26 | export function changePassword(data: any) { 27 | return http.put("/common/user/password", {}, data); 28 | } 29 | 30 | /** 31 | * 修改头像 32 | * @param url . 33 | * @returns 34 | */ 35 | export function changeAvatar(url: string) { 36 | return http.put("/common/user/avatar", { avatarUrl: url }); 37 | } 38 | -------------------------------------------------------------------------------- /src/api/sys/notice.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 分页查询-系统公告 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function pageNotices(query?: any) { 10 | return http.get(`/sys/notice`, query); 11 | } 12 | 13 | /** 14 | * 新增系统公告 15 | * 16 | * @param data 17 | * @returns 18 | */ 19 | export function saveNotice(data: any) { 20 | return http.post(`/sys/notice`, data); 21 | } 22 | /** 23 | * 更新公告信息 24 | * 25 | * @param data 26 | * @returns 27 | */ 28 | export function updateNotice(data: any) { 29 | return http.put(`/sys/notice`, {}, data); 30 | } 31 | /** 32 | * 删除公告 33 | * 34 | * @param ids 35 | * @returns 36 | */ 37 | export function deleteNotice(ids: string[]) { 38 | return http.del(`/sys/notice`, {}, ids); 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/table-bar/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarFullScreen.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/iconifyIconOffline.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline"; 3 | 4 | // Iconify Icon在Vue里本地使用(用于内网环境) 5 | export default defineComponent({ 6 | name: "IconifyIconOffline", 7 | components: { IconifyIcon }, 8 | props: { 9 | icon: { 10 | default: null 11 | } 12 | }, 13 | render() { 14 | if (typeof this.icon === "object") addIcon(this.icon, this.icon); 15 | const attrs = this.$attrs; 16 | return h( 17 | IconifyIcon, 18 | { 19 | icon: this.icon, 20 | style: attrs?.style 21 | ? Object.assign(attrs.style, { outline: "none" }) 22 | : { outline: "none" }, 23 | ...attrs 24 | }, 25 | { 26 | default: () => [] 27 | } 28 | ); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/router/modules/error.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | path: "/error", 3 | redirect: "/error/403", 4 | meta: { 5 | icon: "ri:information-line", 6 | showLink: false, 7 | title: "异常页面", 8 | rank: 9 9 | }, 10 | children: [ 11 | { 12 | path: "/error/403", 13 | name: "403", 14 | component: () => import("@/views/error/403.vue"), 15 | meta: { 16 | title: "403" 17 | } 18 | }, 19 | { 20 | path: "/error/404", 21 | name: "404", 22 | component: () => import("@/views/error/404.vue"), 23 | meta: { 24 | title: "404" 25 | } 26 | }, 27 | { 28 | path: "/error/500", 29 | name: "500", 30 | component: () => import("@/views/error/500.vue"), 31 | meta: { 32 | title: "500" 33 | } 34 | } 35 | ] 36 | } satisfies RouteConfigsTable; 37 | -------------------------------------------------------------------------------- /mock/refreshToken.ts: -------------------------------------------------------------------------------- 1 | import { defineFakeRoute } from "vite-plugin-fake-server/client"; 2 | 3 | // 模拟刷新token接口 4 | export default defineFakeRoute([ 5 | { 6 | url: "/refresh-token", 7 | method: "post", 8 | response: ({ body }) => { 9 | if (body.refreshToken) { 10 | return { 11 | success: true, 12 | data: { 13 | accessToken: "eyJhbGciOiJIUzUxMiJ9.newAdmin", 14 | refreshToken: "eyJhbGciOiJIUzUxMiJ9.newAdminRefresh", 15 | // `expires`选择这种日期格式是为了方便调试,后端直接设置时间戳或许更方便(每次都应该递增)。如果后端返回的是时间戳格式,前端开发请来到这个目录`src/utils/auth.ts`,把第`38`行的代码换成expires = data.expires即可。 16 | expires: "2030/10/30 23:59:59" 17 | } 18 | }; 19 | } else { 20 | return { 21 | success: false, 22 | data: {} 23 | }; 24 | } 25 | } 26 | } 27 | ]); 28 | -------------------------------------------------------------------------------- /src/views/login/utils/rule.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | 4 | /** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */ 5 | export const REGEXP_PWD = 6 | /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/; 7 | 8 | /** 登录校验 */ 9 | const loginRules = reactive({ 10 | password: [ 11 | { 12 | validator: (rule, value, callback) => { 13 | if (value === "") { 14 | callback(new Error("请输入密码")); 15 | } else if (!REGEXP_PWD.test(value)) { 16 | callback( 17 | new Error("密码格式应为8-18位数字、字母、符号的任意两种组合") 18 | ); 19 | } else { 20 | callback(); 21 | } 22 | }, 23 | trigger: "blur" 24 | } 25 | ] 26 | }); 27 | 28 | export { loginRules }; 29 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/reload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import("@commitlint/types").UserConfig} */ 4 | export default { 5 | ignores: [commit => commit.includes("init")], 6 | extends: ["@commitlint/config-conventional"], 7 | rules: { 8 | "body-leading-blank": [2, "always"], 9 | "footer-leading-blank": [1, "always"], 10 | "header-max-length": [2, "always", 108], 11 | "subject-empty": [2, "never"], 12 | "type-empty": [2, "never"], 13 | "type-enum": [ 14 | 2, 15 | "always", 16 | [ 17 | "feat", 18 | "fix", 19 | "perf", 20 | "style", 21 | "docs", 22 | "test", 23 | "refactor", 24 | "build", 25 | "ci", 26 | "chore", 27 | "revert", 28 | "wip", 29 | "workflow", 30 | "types", 31 | "release" 32 | ] 33 | ] 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarTopCollapse.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | -------------------------------------------------------------------------------- /src/views/sys/user/utils/types.ts: -------------------------------------------------------------------------------- 1 | interface FormItemProps { 2 | id?: string; 3 | roles?: any[]; 4 | roleList?: any[]; 5 | roleIds?: number[]; 6 | /** 7 | * 用户名 8 | */ 9 | username?: string; 10 | /** 11 | * 密码 12 | */ 13 | password?: string; 14 | /** 15 | * 确认密码 16 | */ 17 | confirmPassword?: string; 18 | /** 19 | * 昵称 20 | */ 21 | nickname?: string; 22 | /** 23 | * 邮箱 24 | */ 25 | email?: string; 26 | /** 27 | * 手机号 28 | */ 29 | phone?: string; 30 | /** 31 | * 性别 32 | */ 33 | gender?: number; 34 | /** 35 | * 头像 36 | */ 37 | avatar?: string; 38 | /** 39 | * 是否管理员 40 | */ 41 | admin?: boolean; 42 | /** 43 | * 状态 44 | *

45 | */ 46 | enabled?: boolean; 47 | } 48 | interface FormProps { 49 | formInline: FormItemProps; 50 | } 51 | 52 | export type { FormItemProps, FormProps }; 53 | -------------------------------------------------------------------------------- /src/views/login/utils/motion.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent, withDirectives, resolveDirective } from "vue"; 2 | 3 | /** 封装@vueuse/motion动画库中的自定义指令v-motion */ 4 | export default defineComponent({ 5 | name: "Motion", 6 | props: { 7 | delay: { 8 | type: Number, 9 | default: 50 10 | } 11 | }, 12 | render() { 13 | const { delay } = this; 14 | const motion = resolveDirective("motion"); 15 | return withDirectives( 16 | h( 17 | "div", 18 | {}, 19 | { 20 | default: () => [this.$slots.default()] 21 | } 22 | ), 23 | [ 24 | [ 25 | motion, 26 | { 27 | initial: { opacity: 0, y: 100 }, 28 | enter: { 29 | opacity: 1, 30 | y: 0, 31 | transition: { 32 | delay 33 | } 34 | } 35 | } 36 | ] 37 | ] 38 | ); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnType": true, 3 | "editor.formatOnSave": true, 4 | "[vue]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | }, 7 | "editor.tabSize": 2, 8 | "editor.formatOnPaste": true, 9 | "editor.guides.bracketPairs": "active", 10 | "files.autoSave": "afterDelay", 11 | "git.confirmSync": false, 12 | "workbench.startupEditor": "newUntitledFile", 13 | "editor.suggestSelection": "first", 14 | "editor.acceptSuggestionOnCommitCharacter": false, 15 | "css.lint.propertyIgnoredDueToDisplay": "ignore", 16 | "editor.quickSuggestions": { 17 | "other": true, 18 | "comments": true, 19 | "strings": true 20 | }, 21 | "files.associations": { 22 | "editor.snippetSuggestions": "top" 23 | }, 24 | "[css]": { 25 | "editor.defaultFormatter": "esbenp.prettier-vscode" 26 | }, 27 | "editor.codeActionsOnSave": { 28 | "source.fixAll.eslint": "explicit" 29 | }, 30 | "iconify.excludes": ["el"] 31 | } 32 | -------------------------------------------------------------------------------- /src/api/basic/org.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 机构树 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function treeOrg(query?: any) { 10 | return http.get(`/bas/org/tree`, query); 11 | } 12 | /** 13 | * 列表查询 14 | * @param query . 15 | * @returns . 16 | */ 17 | export function listOrg(query?: any) { 18 | return http.get(`/bas/org`, query); 19 | } 20 | 21 | /** 22 | * 新增机构 23 | * 24 | * @param data . 25 | * @returns 26 | */ 27 | export function saveOrg(data: any) { 28 | return http.post(`/bas/org`, data); 29 | } 30 | /** 31 | * 更新机构 32 | * 33 | * @param data . 34 | * @returns 35 | */ 36 | export function updateOrg(data: any) { 37 | return http.put(`/bas/org`, {}, data); 38 | } 39 | 40 | /** 41 | * 删除机构 42 | * 43 | * @param id . 44 | * @returns 45 | */ 46 | export function deleteOrg(id: string) { 47 | return http.del(`/bas/org`, { id: id }); 48 | } 49 | -------------------------------------------------------------------------------- /src/store/modules/settings.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { type setType, store, getConfig } from "../utils"; 3 | 4 | export const useSettingStore = defineStore({ 5 | id: "pure-setting", 6 | state: (): setType => ({ 7 | title: getConfig().Title, 8 | fixedHeader: getConfig().FixedHeader, 9 | hiddenSideBar: getConfig().HiddenSideBar 10 | }), 11 | getters: { 12 | getTitle(state) { 13 | return state.title; 14 | }, 15 | getFixedHeader(state) { 16 | return state.fixedHeader; 17 | }, 18 | getHiddenSideBar(state) { 19 | return state.hiddenSideBar; 20 | } 21 | }, 22 | actions: { 23 | CHANGE_SETTING({ key, value }) { 24 | if (Reflect.has(this, key)) { 25 | this[key] = value; 26 | } 27 | }, 28 | changeSetting(data) { 29 | this.CHANGE_SETTING(data); 30 | } 31 | } 32 | }); 33 | 34 | export function useSettingStoreHook() { 35 | return useSettingStore(store); 36 | } 37 | -------------------------------------------------------------------------------- /src/views/sys/role/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | import { existsByCode } from "@/api/sys/role"; 4 | 5 | const formData = ref(); 6 | 7 | /** 自定义表单规则校验 */ 8 | export const formRules = reactive({ 9 | name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }], 10 | code: [ 11 | { required: true, message: "角色标识为必填项", trigger: "blur" }, 12 | { 13 | validator: async (rule, value, callback) => { 14 | if (value) { 15 | const { success, data } = await existsByCode( 16 | value, 17 | formData.value?.id ?? "" 18 | ); 19 | if (success && data) { 20 | callback(new Error("角色标识已存在")); 21 | } 22 | } 23 | callback(); 24 | }, 25 | trigger: "blur" 26 | } 27 | ] 28 | }); 29 | 30 | export function setFormRule(data): FormRules { 31 | formData.value = data; 32 | return formRules; 33 | } 34 | -------------------------------------------------------------------------------- /src/views/basic/post/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | import { existPostCode } from "@/api/basic/post"; 4 | 5 | const formData = ref(); 6 | 7 | /** 自定义表单规则校验 */ 8 | export const formRules = reactive({ 9 | name: [{ required: true, message: "岗位名称为必填项", trigger: "blur" }], 10 | code: [ 11 | { required: true, message: "岗位编码为必填项", trigger: "blur" }, 12 | { 13 | validator: async (rule, value, callback) => { 14 | if (value) { 15 | const { success, data } = await existPostCode( 16 | value, 17 | formData.value?.id ?? "" 18 | ); 19 | if (success && data) { 20 | callback(new Error("岗位编码已存在")); 21 | } 22 | } 23 | callback(); 24 | }, 25 | trigger: "blur" 26 | } 27 | ] 28 | }); 29 | 30 | export function setFormRule(data): FormRules { 31 | formData.value = data; 32 | return formRules; 33 | } 34 | -------------------------------------------------------------------------------- /src/views/sys/tenant/product/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | import { existsByCode } from "@/api/sys/tenant/product"; 4 | 5 | const formData = ref(); 6 | 7 | /** 自定义表单规则校验 */ 8 | export const formRules = reactive({ 9 | name: [{ required: true, message: "产品名称为必填项", trigger: "blur" }], 10 | code: [ 11 | { required: true, message: "产品编码为必填项", trigger: "blur" }, 12 | { 13 | validator: async (rule, value, callback) => { 14 | if (value) { 15 | const { success, data } = await existsByCode( 16 | value, 17 | formData.value?.id ?? "" 18 | ); 19 | if (success && data) { 20 | callback(new Error("产品编码已存在")); 21 | } 22 | } 23 | callback(); 24 | }, 25 | trigger: "blur" 26 | } 27 | ] 28 | }); 29 | 30 | export function setFormRule(data): FormRules { 31 | formData.value = data; 32 | return formRules; 33 | } 34 | -------------------------------------------------------------------------------- /src/store/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordName } from "vue-router"; 2 | 3 | export type cacheType = { 4 | mode: string; 5 | name?: RouteRecordName; 6 | }; 7 | 8 | export type positionType = { 9 | startIndex?: number; 10 | length?: number; 11 | }; 12 | 13 | export type appType = { 14 | sidebar: { 15 | opened: boolean; 16 | withoutAnimation: boolean; 17 | // 判断是否手动点击Collapse 18 | isClickCollapse: boolean; 19 | }; 20 | layout: string; 21 | device: string; 22 | viewportSize: { width: number; height: number }; 23 | }; 24 | 25 | export type multiType = { 26 | path: string; 27 | name: string; 28 | meta: any; 29 | query?: object; 30 | params?: object; 31 | }; 32 | 33 | export type setType = { 34 | title: string; 35 | fixedHeader: boolean; 36 | hiddenSideBar: boolean; 37 | }; 38 | 39 | export type userType = { 40 | avatar?: string; 41 | username?: string; 42 | nickname?: string; 43 | roles?: Array; 44 | isRemembered?: boolean; 45 | loginDay?: number; 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/index.ts: -------------------------------------------------------------------------------- 1 | import Reload from "./reload.svg?component"; 2 | import Upload from "./upload.svg?component"; 3 | import ArrowH from "./arrow-h.svg?component"; 4 | import ArrowV from "./arrow-v.svg?component"; 5 | import ArrowUp from "./arrow-up.svg?component"; 6 | import ChangeIcon from "./change.svg?component"; 7 | import ArrowDown from "./arrow-down.svg?component"; 8 | import ArrowLeft from "./arrow-left.svg?component"; 9 | import DownloadIcon from "./download.svg?component"; 10 | import ArrowRight from "./arrow-right.svg?component"; 11 | import RotateLeft from "./rotate-left.svg?component"; 12 | import SearchPlus from "./search-plus.svg?component"; 13 | import RotateRight from "./rotate-right.svg?component"; 14 | import SearchMinus from "./search-minus.svg?component"; 15 | 16 | export { 17 | Reload, 18 | Upload, 19 | ArrowH, 20 | ArrowV, 21 | ArrowUp, 22 | ArrowDown, 23 | ArrowLeft, 24 | ChangeIcon, 25 | ArrowRight, 26 | RotateLeft, 27 | SearchPlus, 28 | RotateRight, 29 | SearchMinus, 30 | DownloadIcon 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/preventDefault.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from "@vueuse/core"; 2 | 3 | /** 是否为`img`标签 */ 4 | function isImgElement(element) { 5 | return typeof HTMLImageElement !== "undefined" 6 | ? element instanceof HTMLImageElement 7 | : element.tagName.toLowerCase() === "img"; 8 | } 9 | 10 | // 在 src/main.ts 引入并调用即可 import { addPreventDefault } from "@/utils/preventDefault"; addPreventDefault(); 11 | export const addPreventDefault = () => { 12 | // 阻止通过键盘F12快捷键打开浏览器开发者工具面板 13 | useEventListener( 14 | window.document, 15 | "keydown", 16 | ev => ev.key === "F12" && ev.preventDefault() 17 | ); 18 | // 阻止浏览器默认的右键菜单弹出(不会影响自定义右键事件) 19 | useEventListener(window.document, "contextmenu", ev => ev.preventDefault()); 20 | // 阻止页面元素选中 21 | useEventListener(window.document, "selectstart", ev => ev.preventDefault()); 22 | // 浏览器中图片通常默认是可拖动的,并且可以在新标签页或窗口中打开,或者将其拖动到其他应用程序中,此处将其禁用,使其默认不可拖动 23 | useEventListener( 24 | window.document, 25 | "dragstart", 26 | ev => isImgElement(ev?.target) && ev.preventDefault() 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/api/sys/permission.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | /** 3 | * 列表查询 4 | * @param query . 5 | * @returns . 6 | */ 7 | export function listMenus(query?: Q) { 8 | return http.get("/sys/permission", query); 9 | } 10 | /** 11 | * 树形菜单结构 12 | * 13 | * @param filterBtn 是否过滤按钮类型 14 | * @returns 15 | */ 16 | export function listTree(filterButton = true) { 17 | return http.get("/sys/permission/menu/tree", { filterButton }); 18 | } 19 | 20 | /** 21 | * 新增菜单与权限 22 | * 23 | * @param data . 24 | * @returns . 25 | */ 26 | export function addMenu(data: any) { 27 | return http.post("/sys/permission", data); 28 | } 29 | 30 | /** 31 | * 更新菜单与权限 32 | * 33 | * @param data . 34 | * @returns . 35 | */ 36 | export function updateMenu(data: any) { 37 | return http.put("/sys/permission", null, data); 38 | } 39 | 40 | /** 41 | * 删除菜单与权限 42 | * @param id 43 | * @returns 44 | */ 45 | export function deleteMenu(id: any) { 46 | return http.del("/sys/permission", { id: id }); 47 | } 48 | -------------------------------------------------------------------------------- /src/api/common/attachment.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 获取预签名URL 5 | * 6 | * @param filename . 7 | */ 8 | export function getPresignedUrl(filename: string, contentType?: string) { 9 | return http.get("/common/attachment/presigned-url", { 10 | filename: filename, 11 | contentType: contentType 12 | }); 13 | } 14 | 15 | /** 16 | * 创建上传的文件信息 17 | * 18 | * @param data . 19 | */ 20 | export function createFile(data: any) { 21 | return http.post("/common/attachment/create", data); 22 | } 23 | /** 24 | * 上传文件 25 | */ 26 | export function uploadFile(data: any, params?: any) { 27 | return http.upload("common/attachment/upload", data, params); 28 | } 29 | 30 | /** 31 | * 附件分页 32 | * @param data . 33 | * @returns 34 | */ 35 | export function attachmentPage(data?: any) { 36 | return http.get("/common/attachment/page", data); 37 | } 38 | 39 | /** 40 | * 删除附件 41 | * @param id . 42 | * @returns 43 | */ 44 | export function deleteAttachment(id: string) { 45 | return http.del(`/common/attachment`, { id }); 46 | } 47 | -------------------------------------------------------------------------------- /src/api/auth.ts: -------------------------------------------------------------------------------- 1 | import { RSA } from "@/utils/crypto"; 2 | import * as http from "@/api/base"; 3 | /** 4 | * 登录 5 | * 6 | * @param username 用户名 7 | * @param password 密码 8 | * @param tenantLogin 管理端登录 9 | * @returns 10 | */ 11 | export function login(username: string, password) { 12 | // password en 13 | const _pwd = RSA.loginEncrypt(password); 14 | return http.post(`/auth/login?username=${username}`, _pwd); 15 | } 16 | 17 | /** 18 | * 获取当前用户信息 19 | * @returns 20 | */ 21 | export function getUserInfo(): Promise> { 22 | return http.get("/auth/info"); 23 | } 24 | /** 25 | * 注销登出 26 | * 27 | * @returns . 28 | */ 29 | export function logout() { 30 | return http.post("/auth/logout"); 31 | } 32 | 33 | /** 34 | * 当前用户的routes 35 | */ 36 | export const currentRoutes = () => { 37 | //区分管理端与履约端 38 | // const tenantId = useAuthStoreHook().getTenantId; 39 | // if (tenantId) { 40 | // return http.get("/bas/permission/current/routes", { tenantId }); 41 | // } 42 | // return http.get("/sys/permission/current/routes"); 43 | return http.get("/auth/routes", {}); 44 | }; 45 | -------------------------------------------------------------------------------- /src/api/sys/tenant/permission.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../../base"; 2 | 3 | /** 4 | * 查询权限列表 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function loadPermission(query?: any) { 10 | return http.get("/sys/tenant/permission", query); 11 | } 12 | /** 13 | * 树形菜单结构 14 | * 15 | * @param filterBtn 是否过滤按钮类型 16 | * @returns 17 | */ 18 | export function listTree(filterButton = true) { 19 | return http.get("/sys/tenant/permission/menu/tree", { 20 | filterButton 21 | }); 22 | } 23 | /** 24 | * 新增权限 25 | * 26 | * @param data 27 | * @returns 28 | */ 29 | export function savePermission(data: any) { 30 | return http.post("/sys/tenant/permission", data); 31 | } 32 | 33 | /** 34 | * 修改权限 35 | * 36 | * @param data 37 | * @returns 38 | */ 39 | export function updatePermission(data: any) { 40 | return http.put("/sys/tenant/permission", {}, data); 41 | } 42 | 43 | /** 44 | * 删除权限 45 | * 46 | * @param id 47 | * @returns 48 | */ 49 | export function deletePermission(id: string) { 50 | return http.del(`/sys/tenant/permission`, { id: id }, null); 51 | } 52 | -------------------------------------------------------------------------------- /src/api/sys/base/area.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../../base"; 2 | 3 | /** 4 | * 获取地区树 5 | * 6 | * @returns 7 | */ 8 | export function areaTree() { 9 | return http.get("sys/area/tree"); 10 | } 11 | /** 12 | * 地区分页查询 13 | * 14 | * @param query . 15 | * @returns . 16 | */ 17 | export function areaPage(query?: any) { 18 | return http.get("sys/area/page", query); 19 | } 20 | 21 | /** 22 | * 地区列表查询 23 | * 24 | * @param query 25 | * @returns 26 | */ 27 | export function areaList(query?: any) { 28 | return http.get("sys/area/list", query); 29 | } 30 | 31 | /** 32 | * 保存地区 33 | * 34 | * @param data 35 | * @returns 36 | */ 37 | export function saveArea(data: any) { 38 | return http.post("sys/area", data); 39 | } 40 | 41 | /** 42 | * 更新地区 43 | * 44 | * @param data 45 | * @returns 46 | */ 47 | export function updateArea(data: any) { 48 | return http.put("sys/area", {}, data); 49 | } 50 | 51 | /** 52 | * 删除地区 53 | * 54 | * @param data 55 | * @returns 56 | */ 57 | export function deleteArea(id: any) { 58 | return http.del("sys/area", { id }, {}); 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, pure-admin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/views/sys/monitor/task/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import type { FormRules } from "element-plus"; 2 | import { reactive, ref } from "vue"; 3 | import { validateCron } from "@/api/sys/taskJob"; 4 | 5 | const formData = ref(); 6 | /** 自定义表单规则校验 */ 7 | export const formRules = reactive({ 8 | jobName: [{ required: true, message: "请输入任务名称", trigger: "blur" }], 9 | jobClassName: [ 10 | { required: true, message: "请输入任务类名/Bean名称", trigger: "blur" } 11 | ], 12 | sysCode: [ 13 | { required: true, message: "请输入分组/商户编码", trigger: "blur" } 14 | ], 15 | cronExpression: [ 16 | { required: true, message: "请输入cron表达式", trigger: "blur" }, 17 | { 18 | validator: async (rule, value, callback) => { 19 | if (!value) { 20 | return callback(new Error("请输入cron表达式")); 21 | } 22 | const { success, message } = await validateCron(value); 23 | if (!success) { 24 | return callback(new Error(message)); 25 | } 26 | callback(); 27 | }, 28 | trigger: "blur" 29 | } 30 | ] 31 | }); 32 | 33 | export function useFormRules(data) { 34 | formData.value = data; 35 | return formRules; 36 | } 37 | -------------------------------------------------------------------------------- /src/directives/copy/index.ts: -------------------------------------------------------------------------------- 1 | import { message } from "@/utils/message"; 2 | import { useEventListener } from "@vueuse/core"; 3 | import { copyTextToClipboard } from "@pureadmin/utils"; 4 | import type { Directive, DirectiveBinding } from "vue"; 5 | 6 | interface CopyEl extends HTMLElement { 7 | copyValue: string; 8 | } 9 | 10 | /** 文本复制指令(默认双击复制) */ 11 | export const copy: Directive = { 12 | mounted(el: CopyEl, binding: DirectiveBinding) { 13 | const { value } = binding; 14 | if (value) { 15 | el.copyValue = value; 16 | const arg = binding.arg ?? "dblclick"; 17 | // Register using addEventListener on mounted, and removeEventListener automatically on unmounted 18 | useEventListener(el, arg, () => { 19 | const success = copyTextToClipboard(el.copyValue); 20 | success 21 | ? message("复制成功", { type: "success" }) 22 | : message("复制失败", { type: "error" }); 23 | }); 24 | } else { 25 | throw new Error( 26 | '[Directive: copy]: need value! Like v-copy="modelValue"' 27 | ); 28 | } 29 | }, 30 | updated(el: CopyEl, binding: DirectiveBinding) { 31 | el.copyValue = binding.value; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /mock/asyncRoutes.ts: -------------------------------------------------------------------------------- 1 | // 模拟后端动态生成路由 2 | import { defineFakeRoute } from "vite-plugin-fake-server/client"; 3 | 4 | /** 5 | * roles:页面级别权限,这里模拟二种 "admin"、"common" 6 | * admin:管理员角色 7 | * common:普通角色 8 | */ 9 | const permissionRouter = { 10 | path: "/permission", 11 | meta: { 12 | title: "权限管理", 13 | icon: "ep:lollipop", 14 | rank: 10 15 | }, 16 | children: [ 17 | { 18 | path: "/permission/page/index", 19 | name: "PermissionPage", 20 | meta: { 21 | title: "页面权限", 22 | roles: ["admin", "common"] 23 | } 24 | }, 25 | { 26 | path: "/permission/button/index", 27 | name: "PermissionButton", 28 | meta: { 29 | title: "按钮权限", 30 | roles: ["admin", "common"], 31 | auths: [ 32 | "permission:btn:add", 33 | "permission:btn:edit", 34 | "permission:btn:delete" 35 | ] 36 | } 37 | } 38 | ] 39 | }; 40 | 41 | export default defineFakeRoute([ 42 | { 43 | url: "/get-async-routes", 44 | method: "get", 45 | response: () => { 46 | return { 47 | success: true, 48 | data: [permissionRouter] 49 | }; 50 | } 51 | } 52 | ]); 53 | -------------------------------------------------------------------------------- /src/style/transition.scss: -------------------------------------------------------------------------------- 1 | /* fade */ 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity 0.28s; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-active { 9 | opacity: 0; 10 | } 11 | 12 | /* fade-transform */ 13 | .fade-transform-leave-active, 14 | .fade-transform-enter-active { 15 | transition: all 0.5s; 16 | } 17 | 18 | .fade-transform-enter-from { 19 | opacity: 0; 20 | transform: translateX(-30px); 21 | } 22 | 23 | .fade-transform-leave-to { 24 | opacity: 0; 25 | transform: translateX(30px); 26 | } 27 | 28 | /* breadcrumb transition */ 29 | .breadcrumb-enter-active { 30 | transition: all 0.4s; 31 | } 32 | 33 | .breadcrumb-leave-active { 34 | position: absolute; 35 | transition: all 0.3s; 36 | } 37 | 38 | .breadcrumb-enter-from, 39 | .breadcrumb-leave-active { 40 | opacity: 0; 41 | transform: translateX(20px); 42 | } 43 | 44 | /** 45 | * @description 重置el-menu的展开收起动画时长 46 | */ 47 | .outer-most .el-collapse-transition-leave-active, 48 | .outer-most .el-collapse-transition-enter-active { 49 | transition: 0.2s all ease-in-out !important; 50 | } 51 | 52 | .horizontal-collapse-transition { 53 | transition: var(--pure-transition-duration) all !important; 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, VNodeChild } from "vue"; 2 | import { 3 | createTypes, 4 | toValidableType, 5 | type VueTypesInterface, 6 | type VueTypeValidableDef 7 | } from "vue-types"; 8 | 9 | export type VueNode = VNodeChild | JSX.Element; 10 | 11 | type PropTypes = VueTypesInterface & { 12 | readonly style: VueTypeValidableDef; 13 | readonly VNodeChild: VueTypeValidableDef; 14 | }; 15 | 16 | const newPropTypes = createTypes({ 17 | func: undefined, 18 | bool: undefined, 19 | string: undefined, 20 | number: undefined, 21 | object: undefined, 22 | integer: undefined 23 | }) as PropTypes; 24 | 25 | // 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method 26 | export default class propTypes extends newPropTypes { 27 | // a native-like validator that supports the `.validable` method 28 | static get style() { 29 | return toValidableType("style", { 30 | type: [String, Object] 31 | }); 32 | } 33 | 34 | static get VNodeChild() { 35 | return toValidableType("VNodeChild", { 36 | type: undefined 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/api/basic/post.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 岗位编码是否存在 5 | * 6 | * @param code 7 | * @param id 8 | * @returns 9 | */ 10 | export function existPostCode(code: string, id?: string) { 11 | return http.get("/bas/post/exists/code", { code, id }); 12 | } 13 | 14 | /** 15 | * 岗位分页 16 | * @param query . 17 | * @returns . 18 | */ 19 | export function postPage(query?: any) { 20 | return http.get("/bas/post/page", query); 21 | } 22 | 23 | /** 24 | * 岗位列表 25 | * 26 | * @param query 27 | * @returns 28 | */ 29 | export function postList(query?: any) { 30 | return http.get("/bas/post/list", query); 31 | } 32 | 33 | /** 34 | * 保存岗位 35 | * 36 | * @param data 37 | * @returns 38 | */ 39 | export function postSave(data: any) { 40 | return http.post("/bas/post", data); 41 | } 42 | 43 | /** 44 | * 更新岗位 45 | * 46 | * @param data 47 | * @returns 48 | */ 49 | export function postUpdate(data: any) { 50 | return http.put("/bas/post", {}, data); 51 | } 52 | 53 | /** 54 | * 删除岗位 55 | * 56 | * @param data 57 | * @returns 58 | */ 59 | export function postDelete(id: string) { 60 | return http.del("/bas/post", { id }); 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/http/types.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Method, 3 | AxiosError, 4 | AxiosResponse, 5 | AxiosRequestConfig 6 | } from "axios"; 7 | 8 | export type resultType = { 9 | accessToken?: string; 10 | }; 11 | 12 | export type RequestMethods = Extract< 13 | Method, 14 | "get" | "post" | "put" | "delete" | "patch" | "option" | "head" 15 | >; 16 | 17 | export interface PureHttpError extends AxiosError { 18 | isCancelRequest?: boolean; 19 | } 20 | 21 | export interface PureHttpResponse extends AxiosResponse { 22 | config: PureHttpRequestConfig; 23 | } 24 | 25 | export interface PureHttpRequestConfig extends AxiosRequestConfig { 26 | beforeRequestCallback?: (request: PureHttpRequestConfig) => void; 27 | beforeResponseCallback?: (response: PureHttpResponse) => void; 28 | } 29 | 30 | export default class PureHttp { 31 | request( 32 | method: RequestMethods, 33 | url: string, 34 | param?: AxiosRequestConfig, 35 | axiosConfig?: PureHttpRequestConfig 36 | ): Promise; 37 | post( 38 | url: string, 39 | params?: T, 40 | config?: PureHttpRequestConfig 41 | ): Promise

; 42 | get( 43 | url: string, 44 | params?: T, 45 | config?: PureHttpRequestConfig 46 | ): Promise

; 47 | } 48 | -------------------------------------------------------------------------------- /mock/login.ts: -------------------------------------------------------------------------------- 1 | // 根据角色动态生成路由 2 | import { defineFakeRoute } from "vite-plugin-fake-server/client"; 3 | 4 | export default defineFakeRoute([ 5 | { 6 | url: "/login", 7 | method: "post", 8 | response: ({ body }) => { 9 | if (body.username === "admin") { 10 | return { 11 | success: true, 12 | data: { 13 | avatar: "https://avatars.githubusercontent.com/u/44761321", 14 | username: "admin", 15 | nickname: "小铭", 16 | // 一个用户可能有多个角色 17 | roles: ["admin"], 18 | accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", 19 | refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", 20 | expires: "2030/10/30 00:00:00" 21 | } 22 | }; 23 | } else { 24 | return { 25 | success: true, 26 | data: { 27 | avatar: "https://avatars.githubusercontent.com/u/52823142", 28 | username: "common", 29 | nickname: "小林", 30 | roles: ["common"], 31 | accessToken: "eyJhbGciOiJIUzUxMiJ9.common", 32 | refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", 33 | expires: "2030/10/30 00:00:00" 34 | } 35 | }; 36 | } 37 | } 38 | } 39 | ]); 40 | -------------------------------------------------------------------------------- /src/api/basic/user.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 分页查询 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function pageUser(query?: any) { 10 | return http.get("/bas/user", query); 11 | } 12 | 13 | /** 14 | * 账户是否存在 15 | * 16 | * @param username 17 | * @param id 18 | * @returns 19 | */ 20 | export function existsByUsername(username: string, id?: string) { 21 | return http.get("/bas/user/existsByUsername", { username, id }); 22 | } 23 | 24 | /** 25 | * 保存用户 26 | * 27 | * @param data 28 | * @returns 29 | */ 30 | export function saveUser(data: any) { 31 | return http.post(`/bas/user`, data); 32 | } 33 | 34 | /** 35 | * 修改用户 36 | * 37 | * @param data 38 | * @returns 39 | */ 40 | export function updateUser(data: any) { 41 | return http.put(`/bas/user`, {}, data); 42 | } 43 | /** 44 | * 重置密码 45 | * 46 | * @param data 47 | * @returns 48 | */ 49 | export function resetPassword(data: any) { 50 | return http.put(`/bas/user/restPassword`, {}, data); 51 | } 52 | 53 | /** 54 | * 删除用户 55 | * 56 | * @param id 57 | * @returns 58 | */ 59 | export function deleteUser(id: string) { 60 | return http.del(`/bas/user`, { id }); 61 | } 62 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "strict": false, 7 | "jsx": "preserve", 8 | "importHelpers": true, 9 | "experimentalDecorators": true, 10 | "strictFunctionTypes": false, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "isolatedModules": true, 14 | "allowSyntheticDefaultImports": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "sourceMap": true, 17 | "baseUrl": ".", 18 | "allowJs": false, 19 | "resolveJsonModule": true, 20 | "lib": [ 21 | "ESNext", 22 | "DOM" 23 | ], 24 | "paths": { 25 | "@/*": [ 26 | "src/*" 27 | ], 28 | "@build/*": [ 29 | "build/*" 30 | ] 31 | }, 32 | "types": [ 33 | "node", 34 | "vite/client", 35 | "element-plus/global", 36 | "@pureadmin/table/volar", 37 | "@pureadmin/descriptions/volar" 38 | ] 39 | }, 40 | "include": [ 41 | "mock/*.ts", 42 | "src/**/*.ts", 43 | "src/**/*.tsx", 44 | "src/**/*.vue", 45 | "types/*.d.ts", 46 | "vite.config.ts" 47 | ], 48 | "exclude": [ 49 | "dist", 50 | "**/*.js", 51 | "node_modules" 52 | ] 53 | } -------------------------------------------------------------------------------- /src/components/ReIcon/src/iconfont.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | 3 | // 封装iconfont组件,默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 (https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code) 4 | export default defineComponent({ 5 | name: "FontIcon", 6 | props: { 7 | icon: { 8 | type: String, 9 | default: "" 10 | } 11 | }, 12 | render() { 13 | const attrs = this.$attrs; 14 | if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") { 15 | return h( 16 | "i", 17 | { 18 | class: "iconfont", 19 | ...attrs 20 | }, 21 | this.icon 22 | ); 23 | } else if ( 24 | Object.keys(attrs).includes("svg") || 25 | attrs?.iconType === "svg" 26 | ) { 27 | return h( 28 | "svg", 29 | { 30 | class: "icon-svg", 31 | "aria-hidden": true 32 | }, 33 | { 34 | default: () => [ 35 | h("use", { 36 | "xlink:href": `#${this.icon}` 37 | }) 38 | ] 39 | } 40 | ); 41 | } else { 42 | return h("i", { 43 | class: `iconfont ${this.icon}`, 44 | ...attrs 45 | }); 46 | } 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /src/directives/ripple/index.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable-next-line scss/dollar-variable-colon-space-after */ 2 | $ripple-animation-transition-in: 3 | transform 0.4s cubic-bezier(0, 0, 0.2, 1), 4 | opacity 0.2s cubic-bezier(0, 0, 0.2, 1) !default; 5 | $ripple-animation-transition-out: opacity 0.5s cubic-bezier(0, 0, 0.2, 1) !default; 6 | $ripple-animation-visible-opacity: 0.25 !default; 7 | 8 | .v-ripple { 9 | &__container { 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | z-index: 0; 14 | width: 100%; 15 | height: 100%; 16 | overflow: hidden; 17 | pointer-events: none; 18 | border-radius: inherit; 19 | contain: strict; 20 | } 21 | 22 | &__animation { 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | overflow: hidden; 27 | pointer-events: none; 28 | background: currentcolor; 29 | border-radius: 50%; 30 | opacity: 0; 31 | will-change: transform, opacity; 32 | 33 | &--enter { 34 | opacity: 0; 35 | transition: none; 36 | } 37 | 38 | &--in { 39 | opacity: $ripple-animation-visible-opacity; 40 | transition: $ripple-animation-transition-in; 41 | } 42 | 43 | &--out { 44 | opacity: 0; 45 | transition: $ripple-animation-transition-out; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/plugins/echarts.ts: -------------------------------------------------------------------------------- 1 | import type { App } from "vue"; 2 | import * as echarts from "echarts/core"; 3 | import { PieChart, BarChart, LineChart } from "echarts/charts"; 4 | import { CanvasRenderer, SVGRenderer } from "echarts/renderers"; 5 | import { 6 | GridComponent, 7 | TitleComponent, 8 | PolarComponent, 9 | LegendComponent, 10 | GraphicComponent, 11 | ToolboxComponent, 12 | TooltipComponent, 13 | DataZoomComponent, 14 | VisualMapComponent 15 | } from "echarts/components"; 16 | 17 | const { use } = echarts; 18 | 19 | use([ 20 | PieChart, 21 | BarChart, 22 | LineChart, 23 | CanvasRenderer, 24 | SVGRenderer, 25 | GridComponent, 26 | TitleComponent, 27 | PolarComponent, 28 | LegendComponent, 29 | GraphicComponent, 30 | ToolboxComponent, 31 | TooltipComponent, 32 | DataZoomComponent, 33 | VisualMapComponent 34 | ]); 35 | 36 | /** 37 | * @description 按需引入echarts,具体看 https://echarts.apache.org/handbook/zh/basics/import/#%E5%9C%A8-typescript-%E4%B8%AD%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5 38 | * @see 温馨提示:必须将 `$echarts` 添加到全局 `globalProperties` ,具体看 https://pure-admin-utils.netlify.app/hooks/useECharts/useECharts#%E4%BD%BF%E7%94%A8%E5%89%8D%E6%8F%90 39 | */ 40 | export function useEcharts(app: App) { 41 | app.config.globalProperties.$echarts = echarts; 42 | } 43 | 44 | export default echarts; 45 | -------------------------------------------------------------------------------- /src/views/basic/role/utils/rules.ts: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from "vue"; 2 | import type { FormRules } from "element-plus"; 3 | import { existsByCode } from "@/api/basic/role"; 4 | 5 | const formData = ref(); 6 | 7 | /** 自定义表单规则校验 */ 8 | export const formRules = reactive({ 9 | name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }], 10 | code: [ 11 | { required: true, message: "角色标识为必填项", trigger: "blur" }, 12 | { 13 | validator: async (rule, value, callback) => { 14 | if (value) { 15 | const { success, data } = await existsByCode( 16 | value, 17 | formData.value?.id ?? "" 18 | ); 19 | if (success && data) { 20 | callback(new Error("角色标识已存在")); 21 | } 22 | } 23 | callback(); 24 | }, 25 | trigger: "blur" 26 | } 27 | ], 28 | dataScope: [{ required: true, message: "数据权限为必填项", trigger: "blur" }], 29 | orgIds: [ 30 | { 31 | validator: (rule, value, callback) => { 32 | if (formData.value?.dataScope === 3 && !value?.length) { 33 | callback(new Error("数据范围为必填项")); 34 | } 35 | callback(); 36 | }, 37 | trigger: "blur" 38 | } 39 | ] 40 | }); 41 | 42 | export function setFormRule(data): FormRules { 43 | formData.value = data; 44 | return formRules; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Attachment/upload/uploadImg/type.ts: -------------------------------------------------------------------------------- 1 | type FileTypes = 2 | | "image/apng" 3 | | "image/bmp" 4 | | "image/gif" 5 | | "image/jpeg" 6 | | "image/pjpeg" 7 | | "image/png" 8 | | "image/svg+xml" 9 | | "image/tiff" 10 | | "image/webp" 11 | | "image/x-icon"; 12 | 13 | type UploadType = "frontEnd" | "backEnd"; 14 | 15 | type UploadImageProps = { 16 | /** 17 | * 图片地址 18 | */ 19 | modelValue: string; 20 | /** 21 | * 上传类型,默认 `frontEnd` 22 | */ 23 | uploadType?: UploadType; 24 | /** 25 | * 是否支持拖拽上传,默认 `false` 26 | */ 27 | drag?: boolean; 28 | /** 29 | * 是否禁用上传组件,默认 `false` 30 | */ 31 | disabled?: boolean; 32 | /** 33 | * 图片大小限制,默认 `5M` 34 | */ 35 | fileSize?: number; 36 | /** 37 | * 图片类型限制,默认 `['image/jpeg', 'image/png', 'image/gif']` 38 | * @see https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types 39 | */ 40 | fileType?: FileTypes[]; 41 | /** 42 | * 组件高度,默认 `150px` 43 | */ 44 | height?: string; 45 | /** 46 | * 组件宽度,默认 `150px` 47 | */ 48 | width?: string; 49 | /** 50 | * 图片圆角,默认 `8px` 51 | */ 52 | borderradius?: string; 53 | /** 54 | * 是否显示删除按钮,默认 `true` 55 | */ 56 | showDelete?: boolean; 57 | /** 58 | * 是否显示按钮文字,默认 `true` 59 | */ 60 | showBtnText?: boolean; 61 | }; 62 | 63 | export type { FileTypes, UploadImageProps }; 64 | -------------------------------------------------------------------------------- /src/api/sys/user.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 分页查询 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function userPage(query?: any) { 10 | return http.get(`/sys/user`, query); 11 | } 12 | 13 | /** 14 | * 用户名是否存在 15 | * 16 | * @param username 17 | * @param id 18 | * @returns 19 | */ 20 | export function existsByUsername(username: string, id?: number) { 21 | return http.get(`/sys/user/existsByUsername`, { 22 | username: username, 23 | id: id 24 | }); 25 | } 26 | /** 27 | * 保存用户 28 | * 29 | * @param data 30 | * @returns 31 | */ 32 | export function saveUser(data: any) { 33 | return http.post(`/sys/user`, data); 34 | } 35 | 36 | /** 37 | * 38 | * 更新用户 39 | * 40 | * @param data 41 | * @returns 42 | */ 43 | export function updateUser(data: any) { 44 | return http.put(`/sys/user`, {}, data); 45 | } 46 | 47 | /** 48 | * 删除用户 49 | * 50 | * @param id 51 | * @returns 52 | */ 53 | export function deleteUser(id: string) { 54 | return http.del(`/sys/user`, { id: id }); 55 | } 56 | /** 57 | * 重置密码 58 | * 59 | * @param id 60 | * @param pwd 61 | * @returns 62 | */ 63 | export function resetPassword(id: number, pwd: string) { 64 | return http.put( 65 | `/sys/user/resetPassword`, 66 | {}, 67 | { id: id, password: pwd } 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/change.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/components/lay-search/components/SearchHistoryItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /src/layout/components/notify/info.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 56 | -------------------------------------------------------------------------------- /src/store/modules/epTheme.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { 3 | store, 4 | getConfig, 5 | storageLocal, 6 | responsiveStorageNameSpace 7 | } from "../utils"; 8 | 9 | export const useEpThemeStore = defineStore({ 10 | id: "pure-epTheme", 11 | state: () => ({ 12 | epThemeColor: 13 | storageLocal().getItem( 14 | `${responsiveStorageNameSpace()}layout` 15 | )?.epThemeColor ?? getConfig().EpThemeColor, 16 | epTheme: 17 | storageLocal().getItem( 18 | `${responsiveStorageNameSpace()}layout` 19 | )?.theme ?? getConfig().Theme 20 | }), 21 | getters: { 22 | getEpThemeColor(state) { 23 | return state.epThemeColor; 24 | }, 25 | /** 用于mix导航模式下hamburger-svg的fill属性 */ 26 | fill(state) { 27 | if (state.epTheme === "light") { 28 | return "#409eff"; 29 | } else { 30 | return "#fff"; 31 | } 32 | } 33 | }, 34 | actions: { 35 | setEpThemeColor(newColor: string): void { 36 | const layout = storageLocal().getItem( 37 | `${responsiveStorageNameSpace()}layout` 38 | ); 39 | this.epTheme = layout?.theme; 40 | this.epThemeColor = newColor; 41 | if (!layout) return; 42 | layout.epThemeColor = newColor; 43 | storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout); 44 | } 45 | } 46 | }); 47 | 48 | export function useEpThemeStoreHook() { 49 | return useEpThemeStore(store); 50 | } 51 | -------------------------------------------------------------------------------- /src/api/sys/role.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 列表查询 5 | * 6 | * @param query 7 | * @returns 8 | */ 9 | export function listRole(query?: any) { 10 | return http.get(`/sys/role/list`, query); 11 | } 12 | 13 | /** 14 | * 分页查询 15 | * 16 | * @param query 17 | * @returns 18 | */ 19 | export function pageRole(query?: any) { 20 | return http.get(`/sys/role/page`, query); 21 | } 22 | 23 | /** 24 | * 编码是否存在 25 | * 26 | * @param code 27 | * @param id 28 | * @returns 29 | */ 30 | export function existsByCode(code: string, id?: number) { 31 | return http.get(`/sys/role/existsByCode`, { code, id }); 32 | } 33 | 34 | /** 35 | * 新增 36 | * @param data 37 | * @returns 38 | */ 39 | export function saveRole(data: any) { 40 | return http.post(`/sys/role`, data); 41 | } 42 | 43 | /** 44 | * 更新 45 | * 46 | * @param data 47 | * @returns 48 | */ 49 | export function updateRole(data: any) { 50 | return http.put(`/sys/role`, {}, data); 51 | } 52 | /** 53 | * 删除 54 | * 55 | * @param id 56 | * @returns 57 | */ 58 | export function deleteRole(id: string) { 59 | return http.del(`/sys/role`, { id: id }); 60 | } 61 | /** 62 | * 赋权 63 | * 64 | * @param id 65 | * @param permissionIds 66 | * @returns 67 | */ 68 | export function assignPermission(id: string, permissionIds: string[]) { 69 | return http.put( 70 | `/sys/role/assignPermission`, 71 | { roleId: id }, 72 | permissionIds 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/components/ReDrawer/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import type { DrawerOptions } from "./type"; 3 | import { useTimeoutFn } from "@vueuse/core"; 4 | import formDrawer from "./index.vue"; 5 | import { withInstall } from "@pureadmin/utils"; 6 | 7 | const drawerStore = ref>([]); 8 | 9 | const addDrawer = (options: DrawerOptions) => { 10 | const open = () => 11 | drawerStore.value.push(Object.assign(options, { visible: true })); 12 | if (options?.openDelay) { 13 | useTimeoutFn(() => { 14 | open(); 15 | }, options.openDelay); 16 | } else { 17 | open(); 18 | } 19 | }; 20 | 21 | /** 关闭`Drawer` */ 22 | const closeDrawer = (options: DrawerOptions, index: number, args?: any) => { 23 | drawerStore.value[index].visible = false; 24 | options.closeCallBack && options.closeCallBack({ options, index, args }); 25 | useTimeoutFn(() => { 26 | drawerStore.value.splice(index, 1); 27 | }, 200); 28 | }; 29 | 30 | /** 31 | * @description 更改弹框自身属性值 32 | * @param value 属性值 33 | * @param key 属性,默认`title` 34 | * @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`) 35 | */ 36 | const updateDrawer = (value: any, key = "title", index = 0) => { 37 | drawerStore.value[index][key] = value; 38 | }; 39 | 40 | /** 关闭所有弹框 */ 41 | const closeAllDrawer = () => { 42 | drawerStore.value = []; 43 | }; 44 | 45 | const ReDrawer = withInstall(formDrawer); 46 | 47 | export { 48 | addDrawer, 49 | closeDrawer, 50 | closeAllDrawer, 51 | updateDrawer, 52 | drawerStore, 53 | ReDrawer 54 | }; 55 | -------------------------------------------------------------------------------- /src/router/modules/remaining.ts: -------------------------------------------------------------------------------- 1 | const Layout = () => import("@/layout/index.vue"); 2 | 3 | export default [ 4 | { 5 | path: "/login", 6 | name: "Login", 7 | component: () => import("@/views/login/index.vue"), 8 | meta: { 9 | title: "登录", 10 | showLink: false, 11 | rank: 101 12 | } 13 | }, 14 | { 15 | path: "/admin", 16 | name: "LoginAdmin", 17 | component: () => import("@/views/login/admin.vue"), 18 | meta: { 19 | title: "管理员登录", 20 | showLink: false, 21 | rank: 101 22 | } 23 | }, 24 | { 25 | path: "/redirect", 26 | component: Layout, 27 | meta: { 28 | title: "加载中...", 29 | showLink: false, 30 | rank: 102 31 | }, 32 | children: [ 33 | { 34 | path: "/redirect/:path(.*)", 35 | name: "Redirect", 36 | component: () => import("@/layout/redirect.vue") 37 | } 38 | ] 39 | }, 40 | { 41 | path: "/user/profile", 42 | component: Layout, 43 | redirect: "/user/profile/settings", 44 | meta: { 45 | title: "个人中心", 46 | icon: "ep:profile", 47 | showLink: false, 48 | rank: 103 49 | }, 50 | children: [ 51 | { 52 | path: "/user/profile/settings", 53 | name: "UserProfileSettings", 54 | component: () => import("@/views/user-profile/settings.vue"), 55 | meta: { 56 | title: "个人设置", 57 | showLink: false, 58 | showParent: false 59 | } 60 | } 61 | ] 62 | } 63 | ] satisfies Array; 64 | -------------------------------------------------------------------------------- /src/views/welcome/components/welcome/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 38 | 39 | 62 | -------------------------------------------------------------------------------- /src/components/ReText/src/index.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 67 | -------------------------------------------------------------------------------- /src/views/error/403.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 71 | -------------------------------------------------------------------------------- /src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 71 | -------------------------------------------------------------------------------- /src/views/error/500.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 71 | -------------------------------------------------------------------------------- /src/style/login.css: -------------------------------------------------------------------------------- 1 | .wave { 2 | position: fixed; 3 | height: 100%; 4 | width: 80%; 5 | left: 0; 6 | bottom: 0; 7 | z-index: -1; 8 | } 9 | 10 | .login-container { 11 | width: 100vw; 12 | height: 100vh; 13 | max-width: 100%; 14 | display: grid; 15 | grid-template-columns: repeat(2, 1fr); 16 | grid-gap: 18rem; 17 | padding: 0 2rem; 18 | } 19 | 20 | .img { 21 | display: flex; 22 | justify-content: flex-end; 23 | align-items: center; 24 | } 25 | 26 | .img img { 27 | width: 500px; 28 | } 29 | 30 | .login-box { 31 | display: flex; 32 | align-items: center; 33 | text-align: center; 34 | overflow: hidden; 35 | } 36 | 37 | .login-form { 38 | width: 360px; 39 | } 40 | 41 | .avatar { 42 | width: 350px; 43 | height: 80px; 44 | } 45 | 46 | .login-form h2 { 47 | text-transform: uppercase; 48 | margin: 15px 0; 49 | color: #999; 50 | font: 51 | bold 200% Consolas, 52 | Monaco, 53 | monospace; 54 | } 55 | 56 | @media screen and (max-width: 1180px) { 57 | .login-container { 58 | grid-gap: 9rem; 59 | } 60 | 61 | .login-form { 62 | width: 290px; 63 | } 64 | 65 | .login-form h2 { 66 | font-size: 2.4rem; 67 | margin: 8px 0; 68 | } 69 | 70 | .img img { 71 | width: 360px; 72 | } 73 | 74 | .avatar { 75 | width: 280px; 76 | height: 80px; 77 | } 78 | } 79 | 80 | @media screen and (max-width: 968px) { 81 | .wave { 82 | display: none; 83 | } 84 | 85 | .img { 86 | display: none; 87 | } 88 | 89 | .login-container { 90 | grid-template-columns: 1fr; 91 | } 92 | 93 | .login-box { 94 | justify-content: center; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/api/sys/taskJob.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | /** 3 | * 验证cron表达式 4 | * 5 | * @param value 6 | * @returns 7 | */ 8 | export function validateCron(value: string) { 9 | return http.get(`/sys/quartz/job/validateCron`, { 10 | cronExpression: value 11 | }); 12 | } 13 | /** 14 | * 定时任务-分页查询 15 | * 16 | * @param query 17 | * @returns 18 | */ 19 | export function pageTaskJob(query?: any) { 20 | return http.get(`/sys/quartz/job`, query); 21 | } 22 | /** 23 | * 定时任务-新增 24 | * 25 | * @param data 26 | * @returns 27 | */ 28 | export function saveTaskJob(data: any) { 29 | return http.post(`/sys/quartz/job`, data); 30 | } 31 | 32 | /** 33 | * 定时任务-修改 34 | * 35 | * @param data 36 | * @returns 37 | */ 38 | export function updateTaskJob(data: any) { 39 | return http.put(`/sys/quartz/job`, {}, data); 40 | } 41 | 42 | /** 43 | * 定时任务-删除 44 | * 45 | * @param id 46 | * @returns 47 | */ 48 | export function DeleteTaskJob(id: number) { 49 | return http.del(`/sys/quartz/job`, { id: id }); 50 | } 51 | 52 | /** 53 | * 定时任务-立即执行 54 | * 55 | * @param id 56 | * @returns 57 | */ 58 | export function RunTaskJob(id: number) { 59 | return http.put(`/sys/quartz/job/run?id=${id}`); 60 | } 61 | 62 | /** 63 | * 定时任务-暂停 64 | * 65 | * @param id 66 | * @returns 67 | */ 68 | export function PauseTaskJob(id: number) { 69 | return http.put(`/sys/quartz/job/pause`, { id: id }); 70 | } 71 | 72 | /** 73 | * 定时任务-恢复 74 | * 75 | * @param id 76 | * @returns 77 | */ 78 | export function ResumeTaskJob(id: number) { 79 | return http.put(`/sys/quartz/job/resume`, { id: id }); 80 | } 81 | -------------------------------------------------------------------------------- /src/api/basic/role.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * code是否存在 5 | * 6 | * @param code 7 | * @param id 8 | * @returns 9 | */ 10 | export function existsByCode(code: string, id: string) { 11 | return http.get(`/bas/role/existsByCode`, { code, id }); 12 | } 13 | /** 14 | * 列表查询-角色 15 | * 16 | * @param query 17 | * @returns 18 | */ 19 | export function listRole(query?: any) { 20 | return http.get("/bas/role/list", query); 21 | } 22 | /** 23 | * 分页查询 24 | * 25 | * @param query 26 | * @returns 27 | */ 28 | export function pageRole(query?: any) { 29 | return http.get("/bas/role", query); 30 | } 31 | 32 | /** 33 | * 获取角色权限 34 | * 35 | * @param id 36 | * @returns 37 | */ 38 | export function getPermission(id: string) { 39 | return http.get(`/bas/role/permission`, { id }); 40 | } 41 | /** 42 | * 新增-角色 43 | * 44 | * @param data 45 | * @returns 46 | */ 47 | export function saveRole(data: any) { 48 | return http.post("/bas/role", data); 49 | } 50 | 51 | /** 52 | * 修改-角色 53 | * 54 | * @param data 55 | * @returns 56 | */ 57 | export function updateRole(data: any) { 58 | return http.put("/bas/role", {}, data); 59 | } 60 | 61 | /** 62 | * 删除-角色 63 | * 64 | * @param id 65 | * @returns 66 | */ 67 | export function deleteRole(id: string) { 68 | return http.del(`/bas/role`, { id: id }); 69 | } 70 | 71 | /** 72 | * 分配权限 73 | * 74 | * @param id 75 | * @param permissionIds 76 | * @returns 77 | */ 78 | export function assignPermission(id: string, permissionIds: any) { 79 | return http.put("/bas/role/grant", { id }, permissionIds); 80 | } 81 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import type { App } from "vue"; 3 | 4 | let config: object = {}; 5 | const { VITE_PUBLIC_PATH } = import.meta.env; 6 | 7 | const setConfig = (cfg?: unknown) => { 8 | config = Object.assign(config, cfg); 9 | }; 10 | 11 | const getConfig = (key?: string): PlatformConfigs => { 12 | if (typeof key === "string") { 13 | const arr = key.split("."); 14 | if (arr && arr.length) { 15 | let data = config; 16 | arr.forEach(v => { 17 | if (data && typeof data[v] !== "undefined") { 18 | data = data[v]; 19 | } else { 20 | data = null; 21 | } 22 | }); 23 | return data; 24 | } 25 | } 26 | return config; 27 | }; 28 | 29 | /** 获取项目动态全局配置 */ 30 | export const getPlatformConfig = async (app: App): Promise => { 31 | app.config.globalProperties.$config = getConfig(); 32 | return axios({ 33 | method: "get", 34 | url: `${VITE_PUBLIC_PATH}platform-config.json` 35 | }) 36 | .then(({ data: config }) => { 37 | let $config = app.config.globalProperties.$config; 38 | // 自动注入系统配置 39 | if (app && $config && typeof config === "object") { 40 | $config = Object.assign($config, config); 41 | app.config.globalProperties.$config = $config; 42 | // 设置全局配置 43 | setConfig($config); 44 | } 45 | return $config; 46 | }) 47 | .catch(() => { 48 | throw "请在public文件夹下添加platform-config.json配置文件"; 49 | }); 50 | }; 51 | 52 | /** 本地响应式存储的命名空间 */ 53 | const responsiveStorageNameSpace = () => getConfig().ResponsiveStorageNameSpace; 54 | 55 | export { getConfig, setConfig, responsiveStorageNameSpace }; 56 | -------------------------------------------------------------------------------- /src/utils/responsive.ts: -------------------------------------------------------------------------------- 1 | // 响应式storage 2 | import type { App } from "vue"; 3 | import Storage from "responsive-storage"; 4 | import { routerArrays } from "@/layout/types"; 5 | import { responsiveStorageNameSpace } from "@/config"; 6 | 7 | export const injectResponsiveStorage = (app: App, config: PlatformConfigs) => { 8 | const nameSpace = responsiveStorageNameSpace(); 9 | const configObj = Object.assign( 10 | { 11 | // layout模式以及主题 12 | layout: Storage.getData("layout", nameSpace) ?? { 13 | layout: config.Layout ?? "vertical", 14 | theme: config.Theme ?? "light", 15 | darkMode: config.DarkMode ?? false, 16 | sidebarStatus: config.SidebarStatus ?? true, 17 | epThemeColor: config.EpThemeColor ?? "#409EFF", 18 | themeColor: config.Theme ?? "light", // 主题色(对应系统配置中的主题色,与theme不同的是它不会受到浅色、深色整体风格切换的影响,只会在手动点击主题色时改变) 19 | overallStyle: config.OverallStyle ?? "light" // 整体风格(浅色:light、深色:dark、自动:system) 20 | }, 21 | // 系统配置-界面显示 22 | configure: Storage.getData("configure", nameSpace) ?? { 23 | grey: config.Grey ?? false, 24 | weak: config.Weak ?? false, 25 | hideTabs: config.HideTabs ?? false, 26 | hideFooter: config.HideFooter ?? true, 27 | showLogo: config.ShowLogo ?? true, 28 | showModel: config.ShowModel ?? "smart", 29 | multiTagsCache: config.MultiTagsCache ?? false, 30 | stretch: config.Stretch ?? false 31 | } 32 | }, 33 | config.MultiTagsCache 34 | ? { 35 | // 默认显示顶级菜单tag 36 | tags: Storage.getData("tags", nameSpace) ?? routerArrays 37 | } 38 | : {} 39 | ); 40 | 41 | app.use(Storage, { nameSpace, memory: configObj }); 42 | }; 43 | -------------------------------------------------------------------------------- /src/api/sys/tenant/product.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../../base"; 2 | 3 | /** 4 | * code是否存在 5 | * 6 | * @param code code 7 | * @param id 需要排除的ID 8 | * @returns 9 | */ 10 | export function existsByCode(code: string, id?: number) { 11 | return http.get("/sys/product/existsByCode", { 12 | code: code, 13 | id: id 14 | }); 15 | } 16 | /** 17 | * 产品-分页查询 18 | * 19 | * @param query 20 | * @returns 21 | */ 22 | export function pageProduct(query?: any) { 23 | return http.get("/sys/product", query); 24 | } 25 | /** 26 | * 列表查询 27 | * 28 | * @param query . 29 | * @returns 30 | */ 31 | export function listProduct(query?: any) { 32 | return http.get("/sys/product/list", query); 33 | } 34 | 35 | /** 36 | * 产品-保存 37 | * 38 | * @param data 39 | * @returns 40 | */ 41 | export function saveProduct(data: any) { 42 | return http.post("/sys/product", data); 43 | } 44 | 45 | /** 46 | * 产品-更新 47 | * 48 | * @param data 49 | * @returns 50 | */ 51 | export function updateProduct(data: any) { 52 | return http.put("/sys/product", {}, data); 53 | } 54 | 55 | /** 56 | * 产品-删除 57 | * 58 | * @param id 59 | * @returns 60 | */ 61 | export function deleteProduct(id: string) { 62 | return http.del("/sys/product", { id: id }); 63 | } 64 | /** 65 | * 获取产品权限 66 | * @param id . 67 | * @returns . 68 | */ 69 | export function getPermission(id: string) { 70 | return http.get(`/sys/product/permission`, { id: id }); 71 | } 72 | /** 73 | * 产品-授权 74 | * 75 | * @param data 76 | * @returns 77 | */ 78 | export function grantProduct(id: string, data: any) { 79 | return http.put(`/sys/product/grant`, { id: id }, data); 80 | } 81 | -------------------------------------------------------------------------------- /src/utils/sso.ts: -------------------------------------------------------------------------------- 1 | import { removeToken, setToken, type DataInfo } from "./auth"; 2 | import { subBefore, getQueryMap } from "@pureadmin/utils"; 3 | 4 | /** 5 | * 简版前端单点登录,根据实际业务自行编写,平台启动后本地可以跳后面这个链接进行测试 http://localhost:8848/#/permission/page/index?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin 6 | * 划重点: 7 | * 判断是否为单点登录,不为则直接返回不再进行任何逻辑处理,下面是单点登录后的逻辑处理 8 | * 1.清空本地旧信息; 9 | * 2.获取url中的重要参数信息,然后通过 setToken 保存在本地; 10 | * 3.删除不需要显示在 url 的参数 11 | * 4.使用 window.location.replace 跳转正确页面 12 | */ 13 | (function () { 14 | // 获取 url 中的参数 15 | const params = getQueryMap(location.href) as DataInfo; 16 | const must = ["username", "roles", "accessToken"]; 17 | const mustLength = must.length; 18 | if (Object.keys(params).length !== mustLength) return; 19 | 20 | // url 参数满足 must 里的全部值,才判定为单点登录,避免非单点登录时刷新页面无限循环 21 | let sso = []; 22 | let start = 0; 23 | 24 | while (start < mustLength) { 25 | if (Object.keys(params).includes(must[start]) && sso.length <= mustLength) { 26 | sso.push(must[start]); 27 | } else { 28 | sso = []; 29 | } 30 | start++; 31 | } 32 | 33 | if (sso.length === mustLength) { 34 | // 判定为单点登录 35 | 36 | // 清空本地旧信息 37 | removeToken(); 38 | 39 | // 保存新信息到本地 40 | setToken(params); 41 | 42 | // 删除不需要显示在 url 的参数 43 | delete params.roles; 44 | delete params.accessToken; 45 | 46 | const newUrl = `${location.origin}${location.pathname}${subBefore( 47 | location.hash, 48 | "?" 49 | )}?${JSON.stringify(params) 50 | .replace(/["{}]/g, "") 51 | .replace(/:/g, "=") 52 | .replace(/,/g, "&")}`; 53 | 54 | // 替换历史记录项 55 | window.location.replace(newUrl); 56 | } else { 57 | return; 58 | } 59 | })(); 60 | -------------------------------------------------------------------------------- /src/views/sys/tenant/org/basicConfig.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 65 | @/api/sys/tenant/org 66 | -------------------------------------------------------------------------------- /src/layout/components/lay-search/components/SearchFooter.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | 62 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarLeftCollapse.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 59 | 60 | 70 | -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import { storageSession } from "@pureadmin/utils"; 2 | import Cookies from "js-cookie"; 3 | /** 4 | * 通过`multiple-tabs`是否在`cookie`中,判断用户是否已经登录系统, 5 | * 从而支持多标签页打开已经登录的系统后无需再登录。 6 | * 浏览器完全关闭后`multiple-tabs`将自动从`cookie`中销毁, 7 | * 再次打开浏览器需要重新登录系统 8 | * */ 9 | export const multipleTabsKey = "multiple-tabs"; 10 | export const userKey = "user-info"; 11 | export const tenantKey = "tenant-info"; 12 | export const tokenKey = "user-token"; 13 | export const sysCode = "sys-code"; 14 | export const isTenantLoginKey = "tenant-login"; 15 | 16 | /** 17 | * 规范token 18 | * @param token token 19 | * @returns 格式化后的token 20 | */ 21 | export const formatToken = (token: string) => { 22 | return token; 23 | }; 24 | 25 | /** 26 | * 保存 auth 27 | * @param auth . 28 | */ 29 | export const saveAuth = (auth: any) => { 30 | Cookies.set(multipleTabsKey, "true", { 31 | expires: 7 32 | }); 33 | storageSession().setItem(userKey, auth); 34 | }; 35 | 36 | export const saveAccessToken = (token: string) => { 37 | storageSession().setItem(tokenKey, token); 38 | }; 39 | export const isTenantLogin = (tenantLogin: string) => { 40 | return storageSession().setItem(isTenantLoginKey, tenantLogin); 41 | }; 42 | export const saveSysCode = (data: string) => { 43 | return storageSession().setItem(sysCode, data); 44 | }; 45 | 46 | export const saveTenantInfo = (data: any) => { 47 | return storageSession().setItem(tenantKey, data); 48 | }; 49 | 50 | /** 51 | * 清理 auth 相关缓存 52 | */ 53 | export const clearAuth = () => { 54 | Cookies.remove(multipleTabsKey); 55 | storageSession().removeItem(tokenKey); 56 | storageSession().removeItem(userKey); 57 | storageSession().removeItem(sysCode); 58 | storageSession().removeItem(tenantKey); 59 | storageSession().removeItem(isTenantLoginKey); 60 | }; 61 | -------------------------------------------------------------------------------- /src/layout/components/notify/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 51 | 52 | 67 | -------------------------------------------------------------------------------- /src/views/user-profile/settings.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 51 | 52 | 68 | -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 |

vue-pure-admin Lite Edition(no i18n version)

2 | 3 | [![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE) 4 | 5 | **English** | [中文](./README.md) 6 | 7 | ## Introduce 8 | 9 | The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb` 10 | 11 | ## Supporting video 12 | 13 | [Click me to view UI design](https://www.bilibili.com/video/BV17g411T7rq) 14 | [Click me to view the rapid development tutorial](https://www.bilibili.com/video/BV1kg411v7QT) 15 | 16 | ## Nanny-level documents 17 | 18 | [Click me to view vue-pure-admin documentation](https://pure-admin.github.io/pure-admin-doc) 19 | [Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app) 20 | 21 | ## Quality service, software outsourcing, sponsorship support 22 | 23 | [Click me to view details](https://pure-admin.github.io/pure-admin-doc/pages/service/) 24 | 25 | ## Preview 26 | 27 | [Click me to view the preview station](https://pure-admin-thin.netlify.app/#/login) 28 | 29 | ## Maintainer 30 | 31 | [xiaoxian521](https://github.com/xiaoxian521) 32 | 33 | ## ⚠️ Attention 34 | 35 | The Lite version does not accept any issues and prs. If you have any questions, please go to the full version [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) to mention, thank you! 36 | 37 | ## License 38 | 39 | [MIT © 2020-present, pure-admin](./LICENSE) 40 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarCenterCollapse.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 52 | 53 | 71 | -------------------------------------------------------------------------------- /src/views/sys/role/form.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | -------------------------------------------------------------------------------- /src/layout/hooks/useLayout.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "vue"; 2 | import { routerArrays } from "../types"; 3 | import { useGlobal } from "@pureadmin/utils"; 4 | import { useMultiTagsStore } from "@/store/modules/multiTags"; 5 | 6 | export function useLayout() { 7 | const { $storage, $config } = useGlobal(); 8 | 9 | const initStorage = () => { 10 | /** 路由 */ 11 | if ( 12 | useMultiTagsStore().multiTagsCache && 13 | (!$storage.tags || $storage.tags.length === 0) 14 | ) { 15 | $storage.tags = routerArrays; 16 | } 17 | /** 导航 */ 18 | if (!$storage.layout) { 19 | $storage.layout = { 20 | layout: $config?.Layout ?? "vertical", 21 | theme: $config?.Theme ?? "light", 22 | darkMode: $config?.DarkMode ?? false, 23 | sidebarStatus: $config?.SidebarStatus ?? true, 24 | epThemeColor: $config?.EpThemeColor ?? "#409EFF", 25 | themeColor: $config?.Theme ?? "light", 26 | overallStyle: $config?.OverallStyle ?? "light" 27 | }; 28 | } 29 | /** 灰色模式、色弱模式、隐藏标签页 */ 30 | if (!$storage.configure) { 31 | $storage.configure = { 32 | grey: $config?.Grey ?? false, 33 | weak: $config?.Weak ?? false, 34 | hideTabs: $config?.HideTabs ?? false, 35 | hideFooter: $config.HideFooter ?? true, 36 | showLogo: $config?.ShowLogo ?? true, 37 | showModel: $config?.ShowModel ?? "smart", 38 | multiTagsCache: $config?.MultiTagsCache ?? false, 39 | stretch: $config?.Stretch ?? false 40 | }; 41 | } 42 | }; 43 | 44 | /** 清空缓存后从platform-config.json读取默认配置并赋值到storage中 */ 45 | const layout = computed(() => { 46 | return $storage?.layout.layout; 47 | }); 48 | 49 | const layoutTheme = computed(() => { 50 | return $storage.layout; 51 | }); 52 | 53 | return { 54 | layout, 55 | layoutTheme, 56 | initStorage 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/layout/components/lay-sidebar/components/SidebarLogo.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 38 | 39 | 73 | -------------------------------------------------------------------------------- /src/api/sys/dict.ts: -------------------------------------------------------------------------------- 1 | import * as http from "../base"; 2 | 3 | /** 4 | * 字典分页查询 5 | * @param query . 6 | * @returns . 7 | */ 8 | export function pageDict(query?: any) { 9 | return http.get(`/sys/dict/page`, query); 10 | } 11 | 12 | /** 13 | * 字典列表 14 | * @param query . 15 | * @returns 16 | */ 17 | export function listDict(query?: any) { 18 | return http.get(`/sys/dict`, query); 19 | } 20 | 21 | /** 22 | * 字典保存 23 | * @param data . 24 | * @returns . 25 | */ 26 | export function saveDict(data: any) { 27 | return http.post(`/sys/dict`, data); 28 | } 29 | 30 | /** 31 | * 字典更新 32 | * @param data . 33 | * @returns . 34 | */ 35 | export function updateDict(data: any) { 36 | return http.put(`/sys/dict`, {}, data); 37 | } 38 | 39 | /** 40 | * 字典删除 41 | * @param id . 42 | * @returns . 43 | */ 44 | export function delDict(id: string) { 45 | return http.del(`/sys/dict`, { id }); 46 | } 47 | 48 | /** 49 | * 字典项分页查询 50 | * 51 | * @param query . 52 | * @returns 53 | */ 54 | export function pageDictItem(query?: any) { 55 | return http.get(`/sys/dict/item/page`, query); 56 | } 57 | 58 | /** 59 | * 字典项列表 60 | * 61 | * @param query . 62 | * @returns 63 | */ 64 | export function listDictItem(query?: any) { 65 | return http.get(`/sys/dict/item`, query); 66 | } 67 | 68 | /** 69 | * 字典项保存 70 | * 71 | * @param data . 72 | * @returns 73 | */ 74 | export function saveDictItem(data: any) { 75 | return http.post(`/sys/dict/item`, data); 76 | } 77 | 78 | /** 79 | * 字典项更新 80 | * 81 | * @param data . 82 | * @returns 83 | */ 84 | export function updateDictItem(data: any) { 85 | return http.put(`/sys/dict/item`, {}, data); 86 | } 87 | 88 | /** 89 | * 字典项删除 90 | * 91 | * @param id . 92 | * @returns 93 | */ 94 | export function delDictItem(id: string) { 95 | return http.del(`/sys/dict/item`, { id }); 96 | } 97 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { iconType } from "./types"; 2 | import { h, defineComponent, type Component } from "vue"; 3 | import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index"; 4 | 5 | /** 6 | * 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标 7 | * @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/} 8 | * @param icon 必传 图标 9 | * @param attrs 可选 iconType 属性 10 | * @returns Component 11 | */ 12 | export function useRenderIcon(icon: any, attrs?: iconType): Component { 13 | // iconfont 14 | const ifReg = /^IF-/; 15 | // typeof icon === "function" 属于SVG 16 | if (ifReg.test(icon)) { 17 | // iconfont 18 | const name = icon.split(ifReg)[1]; 19 | const iconName = name.slice( 20 | 0, 21 | name.indexOf(" ") == -1 ? name.length : name.indexOf(" ") 22 | ); 23 | const iconType = name.slice(name.indexOf(" ") + 1, name.length); 24 | return defineComponent({ 25 | name: "FontIcon", 26 | render() { 27 | return h(FontIcon, { 28 | icon: iconName, 29 | iconType, 30 | ...attrs 31 | }); 32 | } 33 | }); 34 | } else if (typeof icon === "function" || typeof icon?.render === "function") { 35 | // svg 36 | return attrs ? h(icon, { ...attrs }) : icon; 37 | } else if (typeof icon === "object") { 38 | return defineComponent({ 39 | name: "OfflineIcon", 40 | render() { 41 | return h(IconifyIconOffline, { 42 | icon: icon, 43 | ...attrs 44 | }); 45 | } 46 | }); 47 | } else { 48 | // 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之 49 | return defineComponent({ 50 | name: "Icon", 51 | render() { 52 | const IconifyIcon = 53 | icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline; 54 | return h(IconifyIcon, { 55 | icon: icon, 56 | ...attrs 57 | }); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { getPluginsList } from "./build/plugins"; 2 | import { include, exclude } from "./build/optimize"; 3 | import { type UserConfigExport, type ConfigEnv, loadEnv } from "vite"; 4 | import { 5 | root, 6 | alias, 7 | wrapperEnv, 8 | pathResolve, 9 | __APP_INFO__ 10 | } from "./build/utils"; 11 | 12 | export default ({ mode }: ConfigEnv): UserConfigExport => { 13 | const { VITE_CDN, VITE_PORT, VITE_COMPRESSION, VITE_PUBLIC_PATH } = 14 | wrapperEnv(loadEnv(mode, root)); 15 | return { 16 | base: VITE_PUBLIC_PATH, 17 | root, 18 | resolve: { 19 | alias 20 | }, 21 | // 服务端渲染 22 | server: { 23 | // 端口号 24 | port: VITE_PORT, 25 | host: "0.0.0.0", 26 | // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy 27 | proxy: {}, 28 | // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布 29 | warmup: { 30 | clientFiles: ["./index.html", "./src/{views,components}/*"] 31 | } 32 | }, 33 | plugins: getPluginsList(VITE_CDN, VITE_COMPRESSION), 34 | // https://cn.vitejs.dev/config/dep-optimization-options.html#dep-optimization-options 35 | optimizeDeps: { 36 | include, 37 | exclude 38 | }, 39 | build: { 40 | // https://cn.vitejs.dev/guide/build.html#browser-compatibility 41 | target: "es2015", 42 | sourcemap: false, 43 | // 消除打包大小超过500kb警告 44 | chunkSizeWarningLimit: 4000, 45 | rollupOptions: { 46 | input: { 47 | index: pathResolve("./index.html", import.meta.url) 48 | }, 49 | // 静态资源分类打包 50 | output: { 51 | chunkFileNames: "static/js/[name]-[hash].js", 52 | entryFileNames: "static/js/[name]-[hash].js", 53 | assetFileNames: "static/[ext]/[name]-[hash].[ext]" 54 | } 55 | } 56 | }, 57 | define: { 58 | __INTLIFY_PROD_DEVTOOLS__: false, 59 | __APP_INFO__: JSON.stringify(__APP_INFO__) 60 | } 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /src/directives/optimize/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isArray, 3 | throttle, 4 | debounce, 5 | isObject, 6 | isFunction 7 | } from "@pureadmin/utils"; 8 | import { useEventListener } from "@vueuse/core"; 9 | import type { Directive, DirectiveBinding } from "vue"; 10 | 11 | /** 防抖(v-optimize或v-optimize:debounce)、节流(v-optimize:throttle)指令 */ 12 | export const optimize: Directive = { 13 | mounted(el: HTMLElement, binding: DirectiveBinding) { 14 | const { value } = binding; 15 | const optimizeType = binding.arg ?? "debounce"; 16 | const type = ["debounce", "throttle"].find(t => t === optimizeType); 17 | if (type) { 18 | if (value && value.event && isFunction(value.fn)) { 19 | let params = value?.params; 20 | if (params) { 21 | if (isArray(params) || isObject(params)) { 22 | params = isObject(params) ? Array.of(params) : params; 23 | } else { 24 | throw new Error( 25 | "[Directive: optimize]: `params` must be an array or object" 26 | ); 27 | } 28 | } 29 | // Register using addEventListener on mounted, and removeEventListener automatically on unmounted 30 | useEventListener( 31 | el, 32 | value.event, 33 | type === "debounce" 34 | ? debounce( 35 | params ? () => value.fn(...params) : value.fn, 36 | value?.timeout ?? 200, 37 | value?.immediate ?? false 38 | ) 39 | : throttle( 40 | params ? () => value.fn(...params) : value.fn, 41 | value?.timeout ?? 1000 42 | ) 43 | ); 44 | } else { 45 | throw new Error( 46 | "[Directive: optimize]: `event` and `fn` are required, and `fn` must be a function" 47 | ); 48 | } 49 | } else { 50 | throw new Error( 51 | "[Directive: optimize]: only `debounce` and `throttle` are supported" 52 | ); 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/views/basic/role/treeFooter.vue: -------------------------------------------------------------------------------- 1 | import bg from '@/assets/login/bg.png'; 2 | 33 | 34 | 73 | -------------------------------------------------------------------------------- /src/views/sys/tenant/org/config.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 75 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // 此文件跟同级目录的 global.d.ts 文件一样也是全局类型声明,只不过这里存放一些零散的全局类型,无需引入直接在 .vue 、.ts 、.tsx 文件使用即可获得类型提示 2 | 3 | type RefType = T | null; 4 | 5 | type EmitType = (event: string, ...args: any[]) => void; 6 | 7 | type TargetContext = "_self" | "_blank"; 8 | 9 | type ComponentRef = 10 | ComponentElRef | null; 11 | 12 | type ElRef = Nullable; 13 | 14 | type ForDataType = { 15 | [P in T]?: ForDataType; 16 | }; 17 | 18 | type AnyFunction = (...args: any[]) => T; 19 | 20 | type PropType = VuePropType; 21 | 22 | type Writable = { 23 | -readonly [P in keyof T]: T[P]; 24 | }; 25 | 26 | type Nullable = T | null; 27 | 28 | type NonNullable = T extends null | undefined ? never : T; 29 | 30 | type Recordable = Record; 31 | 32 | type ReadonlyRecordable = { 33 | readonly [key: string]: T; 34 | }; 35 | 36 | type Indexable = { 37 | [key: string]: T; 38 | }; 39 | 40 | type DeepPartial = { 41 | [P in keyof T]?: DeepPartial; 42 | }; 43 | 44 | type Without = { [P in Exclude]?: never }; 45 | 46 | type Exclusive = (Without & U) | (Without & T); 47 | 48 | type TimeoutHandle = ReturnType; 49 | 50 | type IntervalHandle = ReturnType; 51 | 52 | type Effect = "light" | "dark"; 53 | 54 | interface ChangeEvent extends Event { 55 | target: HTMLInputElement; 56 | } 57 | 58 | interface WheelEvent { 59 | path?: EventTarget[]; 60 | } 61 | 62 | interface ImportMetaEnv extends ViteEnv { 63 | __: unknown; 64 | } 65 | 66 | interface Fn { 67 | (...arg: T[]): R; 68 | } 69 | 70 | interface PromiseFn { 71 | (...arg: T[]): Promise; 72 | } 73 | 74 | interface ComponentElRef { 75 | $el: T; 76 | } 77 | 78 | function parseInt(s: string | number, radix?: number): number; 79 | 80 | function parseFloat(string: string | number): number; 81 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import App from "./App.vue"; 2 | import router from "./router"; 3 | import { setupStore } from "@/store"; 4 | import { getPlatformConfig } from "./config"; 5 | import { MotionPlugin } from "@vueuse/motion"; 6 | // import { useEcharts } from "@/plugins/echarts"; 7 | import { createApp, type Directive } from "vue"; 8 | import { useElementPlus } from "@/plugins/elementPlus"; 9 | import { injectResponsiveStorage } from "@/utils/responsive"; 10 | 11 | import Table from "@pureadmin/table"; 12 | // import PureDescriptions from "@pureadmin/descriptions"; 13 | 14 | // 引入重置样式 15 | import "./style/reset.scss"; 16 | // 导入公共样式 17 | import "./style/index.scss"; 18 | // 一定要在main.ts中导入tailwind.css,防止vite每次hmr都会请求src/style/index.scss整体css文件导致热更新慢的问题 19 | import "./style/tailwind.css"; 20 | import "element-plus/dist/index.css"; 21 | // 导入字体图标 22 | import "./assets/iconfont/iconfont.js"; 23 | import "./assets/iconfont/iconfont.css"; 24 | 25 | const app = createApp(App); 26 | 27 | // 自定义指令 28 | import * as directives from "@/directives"; 29 | Object.keys(directives).forEach(key => { 30 | app.directive(key, (directives as { [key: string]: Directive })[key]); 31 | }); 32 | 33 | // 全局注册@iconify/vue图标库 34 | import { 35 | IconifyIconOffline, 36 | IconifyIconOnline, 37 | FontIcon 38 | } from "./components/ReIcon"; 39 | app.component("IconifyIconOffline", IconifyIconOffline); 40 | app.component("IconifyIconOnline", IconifyIconOnline); 41 | app.component("FontIcon", FontIcon); 42 | 43 | // 全局注册按钮级别权限组件 44 | import { Auth } from "@/components/ReAuth"; 45 | app.component("Auth", Auth); 46 | 47 | // 全局注册vue-tippy 48 | import "tippy.js/dist/tippy.css"; 49 | import "tippy.js/themes/light.css"; 50 | import VueTippy from "vue-tippy"; 51 | app.use(VueTippy); 52 | 53 | getPlatformConfig(app).then(async config => { 54 | setupStore(app); 55 | app.use(router); 56 | await router.isReady(); 57 | injectResponsiveStorage(app, config); 58 | app.use(MotionPlugin).use(useElementPlus).use(Table); 59 | // .use(PureDescriptions) 60 | // .use(useEcharts); 61 | app.mount("#app"); 62 | }); 63 | -------------------------------------------------------------------------------- /src/components/ReDialog/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import reDialog from "./index.vue"; 3 | import { useTimeoutFn } from "@vueuse/core"; 4 | import { withInstall } from "@pureadmin/utils"; 5 | import type { 6 | EventType, 7 | ArgsType, 8 | DialogProps, 9 | ButtonProps, 10 | DialogOptions 11 | } from "./type"; 12 | 13 | const dialogStore = ref>([]); 14 | 15 | /** 打开弹框 */ 16 | const addDialog = (options: DialogOptions) => { 17 | const open = () => 18 | dialogStore.value.push(Object.assign(options, { visible: true })); 19 | if (options?.openDelay) { 20 | useTimeoutFn(() => { 21 | open(); 22 | }, options.openDelay); 23 | } else { 24 | open(); 25 | } 26 | }; 27 | 28 | /** 关闭弹框 */ 29 | const closeDialog = (options: DialogOptions, index: number, args?: any) => { 30 | dialogStore.value[index].visible = false; 31 | options.closeCallBack && options.closeCallBack({ options, index, args }); 32 | 33 | const closeDelay = options?.closeDelay ?? 200; 34 | useTimeoutFn(() => { 35 | dialogStore.value.splice(index, 1); 36 | }, closeDelay); 37 | }; 38 | 39 | /** 40 | * @description 更改弹框自身属性值 41 | * @param value 属性值 42 | * @param key 属性,默认`title` 43 | * @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`) 44 | */ 45 | const updateDialog = (value: any, key = "title", index = 0) => { 46 | dialogStore.value[index][key] = value; 47 | }; 48 | 49 | /** 关闭所有弹框 */ 50 | const closeAllDialog = () => { 51 | dialogStore.value = []; 52 | }; 53 | 54 | /** 千万别忘了在下面这三处引入并注册下,放心注册,不使用`addDialog`调用就不会被挂载 55 | * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L4 56 | * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L12 57 | * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L22 58 | */ 59 | const ReDialog = withInstall(reDialog); 60 | 61 | export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; 62 | export { 63 | ReDialog, 64 | dialogStore, 65 | addDialog, 66 | closeDialog, 67 | updateDialog, 68 | closeAllDialog 69 | }; 70 | -------------------------------------------------------------------------------- /src/layout/types.ts: -------------------------------------------------------------------------------- 1 | import type { IconifyIcon } from "@iconify/vue"; 2 | const { VITE_HIDE_HOME } = import.meta.env; 3 | 4 | export const routerArrays: Array = 5 | VITE_HIDE_HOME === "false" 6 | ? [ 7 | { 8 | path: "/welcome", 9 | meta: { 10 | title: "首页", 11 | icon: "ep:home-filled" 12 | } 13 | } 14 | ] 15 | : []; 16 | 17 | export type routeMetaType = { 18 | title?: string; 19 | icon?: string | IconifyIcon; 20 | showLink?: boolean; 21 | savedPosition?: boolean; 22 | auths?: Array; 23 | }; 24 | 25 | export type RouteConfigs = { 26 | path?: string; 27 | query?: object; 28 | params?: object; 29 | meta?: routeMetaType; 30 | children?: RouteConfigs[]; 31 | name?: string; 32 | }; 33 | 34 | export type multiTagsType = { 35 | tags: Array; 36 | }; 37 | 38 | export type tagsViewsType = { 39 | icon: string | IconifyIcon; 40 | text: string; 41 | divided: boolean; 42 | disabled: boolean; 43 | show: boolean; 44 | }; 45 | 46 | export interface setType { 47 | sidebar: { 48 | opened: boolean; 49 | withoutAnimation: boolean; 50 | isClickCollapse: boolean; 51 | }; 52 | device: string; 53 | fixedHeader: boolean; 54 | classes: { 55 | hideSidebar: boolean; 56 | openSidebar: boolean; 57 | withoutAnimation: boolean; 58 | mobile: boolean; 59 | }; 60 | hideTabs: boolean; 61 | } 62 | 63 | export type menuType = { 64 | id?: number; 65 | name?: string; 66 | path?: string; 67 | noShowingChildren?: boolean; 68 | children?: menuType[]; 69 | value: unknown; 70 | meta?: { 71 | icon?: string; 72 | title?: string; 73 | rank?: number; 74 | showParent?: boolean; 75 | extraIcon?: string; 76 | }; 77 | showTooltip?: boolean; 78 | parentId?: number; 79 | pathList?: number[]; 80 | redirect?: string; 81 | }; 82 | 83 | export type themeColorsType = { 84 | color: string; 85 | themeColor: string; 86 | }; 87 | 88 | export interface scrollbarDomType extends HTMLElement { 89 | wrap?: { 90 | offsetWidth: number; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /src/directives/longpress/index.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from "@vueuse/core"; 2 | import type { Directive, DirectiveBinding } from "vue"; 3 | import { subBefore, subAfter, isFunction } from "@pureadmin/utils"; 4 | 5 | export const longpress: Directive = { 6 | mounted(el: HTMLElement, binding: DirectiveBinding) { 7 | const cb = binding.value; 8 | if (cb && isFunction(cb)) { 9 | let timer = null; 10 | let interTimer = null; 11 | let num = 500; 12 | let interNum = null; 13 | const isInter = binding?.arg?.includes(":") ?? false; 14 | 15 | if (isInter) { 16 | num = Number(subBefore(binding.arg, ":")); 17 | interNum = Number(subAfter(binding.arg, ":")); 18 | } else if (binding.arg) { 19 | num = Number(binding.arg); 20 | } 21 | 22 | const clear = () => { 23 | if (timer) { 24 | clearTimeout(timer); 25 | timer = null; 26 | } 27 | if (interTimer) { 28 | clearInterval(interTimer); 29 | interTimer = null; 30 | } 31 | }; 32 | 33 | const onDownInter = (ev: PointerEvent) => { 34 | ev.preventDefault(); 35 | if (interTimer === null) { 36 | interTimer = setInterval(() => cb(), interNum); 37 | } 38 | }; 39 | 40 | const onDown = (ev: PointerEvent) => { 41 | clear(); 42 | ev.preventDefault(); 43 | if (timer === null) { 44 | timer = isInter 45 | ? setTimeout(() => { 46 | cb(); 47 | onDownInter(ev); 48 | }, num) 49 | : setTimeout(() => cb(), num); 50 | } 51 | }; 52 | 53 | // Register using addEventListener on mounted, and removeEventListener automatically on unmounted 54 | useEventListener(el, "pointerdown", onDown); 55 | useEventListener(el, "pointerup", clear); 56 | useEventListener(el, "pointerleave", clear); 57 | } else { 58 | throw new Error( 59 | '[Directive: longpress]: need callback and callback must be a function! Like v-longpress="callback"' 60 | ); 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/views/basic/post/form.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 73 | -------------------------------------------------------------------------------- /src/components/ReCropperPreview/src/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 77 | -------------------------------------------------------------------------------- /src/layout/frame.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 |