├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── .vscode
└── extensions.json
├── Dockerfile
├── Makefile
├── README.md
├── default.conf
├── docker-entrypoint.sh
├── docs
├── group
│ ├── groupdisablelist.png
│ └── grouplist.png
├── home
│ ├── dark.png
│ ├── home.png
│ └── theme.png
├── message
│ ├── prohibitwords.png
│ └── sendmsglist.png
├── report
│ ├── group.png
│ └── user.png
├── setting
│ ├── currencysetting.png
│ └── updatepwd.png
├── tool
│ └── appupdate.png
└── user
│ ├── adduser.png
│ ├── disablelist.png
│ └── userlist.png
├── index.html
├── nginx.conf.template
├── package.json
├── plop-templates
├── component
│ ├── index.hbs
│ └── prompt.js
├── page
│ ├── index.hbs
│ └── prompt.js
└── store
│ ├── index.hbs
│ └── prompt.js
├── plopfile.js
├── pnpm-lock.yaml
├── public
├── logo.png
└── tsdd-config.js
├── src
├── App.vue
├── api
│ ├── basic.ts
│ ├── file.ts
│ ├── group.ts
│ ├── message.ts
│ ├── report.ts
│ ├── setting.ts
│ ├── statistic.ts
│ ├── tool.ts
│ ├── user.ts
│ └── workplace
│ │ ├── app.ts
│ │ ├── banner.ts
│ │ └── category.ts
├── assets
│ ├── heder.png
│ ├── images
│ │ └── bg.svg
│ ├── logo.png
│ └── vue.svg
├── components
│ ├── BdAppVersion
│ │ └── index.vue
│ ├── BdMsg
│ │ └── index.vue
│ ├── BdPage
│ │ └── index.vue
│ ├── BdProhitWords
│ │ └── index.vue
│ ├── BdSandAllMsg
│ │ └── index.vue
│ ├── BdSendMsg
│ │ └── index.vue
│ └── SwitchDark
│ │ └── index.vue
├── config
│ ├── index.ts
│ └── modules
│ │ ├── dev.ts
│ │ └── prod.ts
├── directives
│ ├── index.ts
│ └── modules
│ │ ├── auth.ts
│ │ ├── copy.ts
│ │ ├── debounce.ts
│ │ ├── draggable.ts
│ │ ├── longpress.ts
│ │ ├── throttle.ts
│ │ └── waterMarker.ts
├── hooks
│ ├── interface
│ │ └── index.ts
│ ├── useEcharts.ts
│ └── useTheme.ts
├── i18n
│ ├── index.ts
│ └── modules
│ │ ├── en.ts
│ │ └── zh.ts
├── layouts
│ ├── components
│ │ ├── Footer.vue
│ │ ├── Header
│ │ │ ├── ToolBarLeft.vue
│ │ │ ├── ToolBarRight.vue
│ │ │ └── components
│ │ │ │ ├── AssemblySize.vue
│ │ │ │ ├── Avatar.vue
│ │ │ │ ├── Breadcrumb.vue
│ │ │ │ ├── Fullscreen.vue
│ │ │ │ ├── Language.vue
│ │ │ │ └── ThemeSetting.vue
│ │ ├── LayoutClassic.vue
│ │ ├── LayoutColumns.vue
│ │ ├── LayoutTransverse.vue
│ │ ├── LayoutVertical.vue
│ │ ├── Main.vue
│ │ ├── Menu
│ │ │ └── SubMenu.vue
│ │ ├── Tabs.vue
│ │ └── ThemeDrawer.vue
│ └── index.vue
├── main.ts
├── menu
│ ├── index.ts
│ └── modules
│ │ ├── group.ts
│ │ ├── home.ts
│ │ ├── message.ts
│ │ ├── redpacket.ts
│ │ ├── report.ts
│ │ ├── setting.ts
│ │ ├── tool.ts
│ │ ├── user.ts
│ │ └── workplace.ts
├── pages
│ ├── group
│ │ ├── groupblacklist.vue
│ │ ├── groupdisablelist.vue
│ │ ├── grouplist.vue
│ │ └── groupmembers.vue
│ ├── home
│ │ ├── components
│ │ │ ├── AddGroup.vue
│ │ │ ├── AddUser.vue
│ │ │ └── Statistics.vue
│ │ └── index.vue
│ ├── login
│ │ └── index.vue
│ ├── message
│ │ ├── components
│ │ │ └── Devices.vue
│ │ ├── prohibitwords.vue
│ │ ├── record.vue
│ │ ├── recordpersonal.vue
│ │ └── sendmsglist.vue
│ ├── my
│ │ └── bb.vue
│ ├── redpacket
│ │ └── list.vue
│ ├── report
│ │ ├── group.vue
│ │ └── user.vue
│ ├── setting
│ │ ├── currencysetting.vue
│ │ └── updatepwd.vue
│ ├── tool
│ │ └── appupdate.vue
│ ├── user
│ │ ├── adduser.vue
│ │ ├── administrator
│ │ │ ├── components
│ │ │ │ └── AdminAdd.vue
│ │ │ └── index.vue
│ │ ├── disablelist.vue
│ │ ├── friends.vue
│ │ ├── userblacklist.vue
│ │ └── userlist.vue
│ └── workplace
│ │ ├── configuration
│ │ ├── components
│ │ │ ├── AppDialog.vue
│ │ │ ├── Banner.vue
│ │ │ ├── BannerDialog.vue
│ │ │ ├── CategoryDialog.vue
│ │ │ ├── CustomGroup.vue
│ │ │ └── Recommend.vue
│ │ └── index.vue
│ │ └── manage
│ │ ├── components
│ │ └── Apply.vue
│ │ └── index.vue
├── router
│ ├── index.ts
│ ├── routers.ts
│ └── staticRouter.ts
├── stores
│ ├── index.ts
│ ├── interface
│ │ └── index.ts
│ └── modules
│ │ ├── auth.ts
│ │ ├── global.ts
│ │ ├── keepAlive.ts
│ │ ├── tabs.ts
│ │ └── user.ts
├── styles
│ ├── element.scss
│ ├── index.scss
│ ├── reset.scss
│ ├── theme
│ │ ├── aside.ts
│ │ └── element-dark.scss
│ └── var.scss
├── types
│ ├── auto-imports.d.ts
│ ├── components.d.ts
│ ├── env.d.ts
│ └── global.d.ts
└── utils
│ ├── axios.ts
│ ├── color.ts
│ ├── index.ts
│ ├── mittBus.ts
│ ├── nprogress.ts
│ ├── piniaPersist.ts
│ └── system-copyright.ts
├── tsconfig.json
├── uno.config.ts
└── vite.config.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | # 文件
2 | node_modules
3 | test
4 | .git
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # @see: http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*] # 表示所有文件适用
6 | charset = utf-8 # 设置文件字符集为 utf-8
7 | end_of_line = lf # 控制换行类型(lf | cr | crlf)
8 | insert_final_newline = true # 始终在文件末尾插入一个新行
9 | indent_style = space # 缩进风格(tab | space)
10 | indent_size = 2 # 缩进大小
11 | max_line_length = 130 # 最大行长度
12 |
13 | [*.md] # 表示仅对 md 文件适用以下规则
14 | max_line_length = off # 关闭最大行长度限制
15 | trim_trailing_whitespace = false # 关闭末尾空格修剪
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | public
3 | dist
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true,
6 | es2021: true
7 | },
8 | parser: 'vue-eslint-parser',
9 | extends: [
10 | 'eslint:recommended',
11 | 'plugin:vue/vue3-recommended',
12 | 'plugin:@typescript-eslint/recommended',
13 | 'plugin:prettier/recommended',
14 | // eslint-config-prettier 的缩写
15 | 'prettier',
16 | 'vue-global-api'
17 | ],
18 | parserOptions: {
19 | ecmaVersion: 12,
20 | parser: '@typescript-eslint/parser',
21 | sourceType: 'module',
22 | ecmaFeatures: {
23 | jsx: true
24 | }
25 | },
26 | // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写
27 | plugins: ['vue', '@typescript-eslint', 'prettier'],
28 | rules: {
29 | '@typescript-eslint/ban-ts-ignore': 'off',
30 | '@typescript-eslint/no-unused-vars': 'off',
31 | '@typescript-eslint/explicit-function-return-type': 'off',
32 | '@typescript-eslint/no-explicit-any': 'off',
33 | '@typescript-eslint/no-var-requires': 'off',
34 | '@typescript-eslint/no-empty-function': 'off',
35 | '@typescript-eslint/no-use-before-define': 'off',
36 | '@typescript-eslint/ban-ts-comment': 'off',
37 | '@typescript-eslint/ban-types': 'off',
38 | '@typescript-eslint/no-non-null-assertion': 'off',
39 | '@typescript-eslint/explicit-module-boundary-types': 'off',
40 | 'vue/multi-word-component-names': 'off',
41 | 'vue/no-mutating-props': 'off',
42 | 'no-undef': 'off'
43 | },
44 | globals: {
45 | defineProps: 'readonly',
46 | defineEmits: 'readonly',
47 | defineExpose: 'readonly',
48 | withDefaults: 'readonly'
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 | yarn.lock
10 |
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 130, // 超过最大值换行
3 | tabWidth: 2, // 缩进字节数
4 | useTabs: false, // 缩进使用tab,不使用空格
5 | semi: true, // 句尾添加分号
6 | singleQuote: true, // 使用单引号代替双引号
7 | proseWrap: "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
8 | arrowParens: "avoid", // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号
9 | bracketSpacing: true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
10 | endOfLine: "auto", // 结尾是 \n \r \n\r auto
11 | jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
12 | trailingComma: "none", // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
13 | overrides: [
14 | {
15 | files: "*.html",
16 | options: {
17 | parser: "html",
18 | },
19 | },
20 | ],
21 | };
22 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.20.1 as builder
2 | WORKDIR /app
3 | RUN npm config set registry https://registry.npm.taobao.org
4 | RUN npm install pnpm -g
5 |
6 | COPY . .
7 |
8 | RUN pnpm install && pnpm build
9 |
10 |
11 | FROM nginx:latest
12 | COPY --from=builder /app/docker-entrypoint.sh /docker-entrypoint2.sh
13 | COPY --from=builder /app/nginx.conf.template /
14 | COPY --from=builder /app/dist /usr/share/nginx/html
15 | ENTRYPOINT ["sh", "/docker-entrypoint2.sh"]
16 | CMD ["nginx","-g","daemon off;"]
17 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t tangsengdaodaomanager .
3 | deploy:
4 | docker build -t tangsengdaodaomanager .
5 | docker tag tangsengdaodaomanager registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaomanager:latest
6 | docker push registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaomanager:latest
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 唐僧叨叨后台管理
2 |
3 | ### 介绍 📖
4 |
5 | 唐僧叨叨后台管理一款基于 Vue3.3、TypeScript、Vite5、Pinia、Element-Plus 开源的后台管理框架,使用目前最新技术栈开发;是唐僧叨叨业务管理后台。
6 |
7 | - 使用 Vue3.3 + TypeScript 开发,单文件组件<script setup>
8 | - 采用 Vite 作为项目开发、打包工具(配置 gzip/brotli 打包、tsx 语法、跨域代理…)
9 | - 使用 Pinia 替代 Vuex,轻量、简单、易用,集成 Pinia 持久化插件
10 | - 使用 TypeScript 对 Axios 整个二次封装(请求拦截、取消、常用请求封装…)
11 | - 支持 Element 组件大小切换、多主题布局、暗黑模式、i18n 国际化
12 | - 使用 VueRouter 配置动态路由权限拦截、路由懒加载,支持页面按钮权限控制
13 | - 使用 KeepAlive 对页面进行缓存,支持多级嵌套路由缓存
14 | - 常用自定义指令开发(权限、复制、水印、拖拽、节流、防抖、长按…)
15 | - 使用 Prettier 统一格式化代码,集成 ESLint、Stylelint 代码校验规范
16 | - 使用 husky、lint-staged、commitlint、czg、cz-git 规范提交信息
17 |
18 | ### 安装使用步骤 📔
19 |
20 | 环境变量
21 | - NODE_ENV node环境变量
22 | - APP_ENV 应用环境变量
23 | - dev 开发环境
24 | - pord 生产环境
25 | - IS_CONFIG 是否添加配置文件
26 |
27 | 1. 安装
28 |
29 | ```sh
30 | pnpm install
31 | ```
32 |
33 | 2. 本地开发
34 |
35 | ``` sh
36 | pnpm dev
37 | ```
38 |
39 | 3. 编译
40 |
41 | ``` sh
42 | pnpm build
43 | ```
44 |
45 | 4. 配置文件编译
46 |
47 | ``` sh
48 | pnpm build:config
49 | ```
50 |
51 | 5. 本地预览
52 | > 先执行编译再执行该命令
53 |
54 | ``` sh
55 | pnpm serve
56 | ```
57 |
58 | ### 功能特色 🔨
59 |
60 | - [x] 首页
61 | - [x] 仪表盘
62 | - [x] 主题设置
63 | - [x] 用户
64 | - [x] 新增用户
65 | - [x] 用户列表
66 | - [x] 发消息
67 | - [x] 好友列表
68 | - [x] 封禁
69 | - [x] 封禁用户列表
70 | - [x] 封禁
71 | - [x] 解禁
72 | - [x] 群组
73 | - [x] 群列表
74 | - [x] 发消息
75 | - [x] 群成员
76 | - [x] 聊天记录
77 | - [x] 黑名单成员
78 | - [x] 禁言
79 | - [x] 封禁
80 | - [x] 封禁群列表
81 | - [x] 消息
82 | - [x] 消息记录
83 | - [x] 违禁词列表
84 | - [x] 举报
85 | - [x] 举报用户
86 | - [x] 举报群聊
87 | - [x] 工具
88 | - [x] APP升级
89 | - [x] 设置
90 | - [x] 通用设置
91 | - [x] 修改登录密码
92 | ### 功能截图 📷
93 |
94 |
95 | - 首页
96 |
97 | 
98 |
99 | 
100 | 
101 |
102 | - 用户
103 |
104 | 
105 |
106 | 
107 |
108 | 
109 |
110 | - 群组
111 |
112 | 
113 |
114 | 
115 |
116 | - 消息
117 |
118 | 
119 |
120 | 
121 |
122 | - 举报
123 |
124 | 
125 |
126 | 
127 |
128 | - 工具
129 |
130 | 
131 |
132 | - 设置
133 |
134 | 
135 |
136 | 
137 |
--------------------------------------------------------------------------------
/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8084;
3 |
4 | location / {
5 | root /usr/share/nginx/html;
6 | index index.html index.htm;
7 | try_files $uri $uri/ /index.html =404;
8 | }
9 |
10 | error_page 500 502 503 504 /50x.html;
11 | location = /50x.html {
12 | root html;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -eu
4 |
5 | envsubst '${API_URL}' < /nginx.conf.template > /etc/nginx/conf.d/default.conf
6 |
7 |
8 | exec "$@"
--------------------------------------------------------------------------------
/docs/group/groupdisablelist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/group/groupdisablelist.png
--------------------------------------------------------------------------------
/docs/group/grouplist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/group/grouplist.png
--------------------------------------------------------------------------------
/docs/home/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/home/dark.png
--------------------------------------------------------------------------------
/docs/home/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/home/home.png
--------------------------------------------------------------------------------
/docs/home/theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/home/theme.png
--------------------------------------------------------------------------------
/docs/message/prohibitwords.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/message/prohibitwords.png
--------------------------------------------------------------------------------
/docs/message/sendmsglist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/message/sendmsglist.png
--------------------------------------------------------------------------------
/docs/report/group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/report/group.png
--------------------------------------------------------------------------------
/docs/report/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/report/user.png
--------------------------------------------------------------------------------
/docs/setting/currencysetting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/setting/currencysetting.png
--------------------------------------------------------------------------------
/docs/setting/updatepwd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/setting/updatepwd.png
--------------------------------------------------------------------------------
/docs/tool/appupdate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/tool/appupdate.png
--------------------------------------------------------------------------------
/docs/user/adduser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/user/adduser.png
--------------------------------------------------------------------------------
/docs/user/disablelist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/user/disablelist.png
--------------------------------------------------------------------------------
/docs/user/userlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/docs/user/userlist.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%- title %>
9 | <%- injectScript %>
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/nginx.conf.template:
--------------------------------------------------------------------------------
1 | gzip on;
2 | gzip_min_length 1k;
3 | gzip_disable msie6;
4 | gzip_vary on;
5 | gzip_proxied any;
6 | gzip_comp_level 2;
7 | gzip_buffers 16 8k;
8 | gzip_http_version 1.1;
9 | gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
10 |
11 |
12 | server {
13 | listen 80;
14 | server_name localhost;
15 |
16 | location /api/ {
17 | proxy_pass ${API_URL};
18 | client_max_body_size 1000m;
19 | client_body_buffer_size 500m;
20 | }
21 |
22 | location / {
23 | root /usr/share/nginx/html;
24 | index index.html index.htm;
25 | try_files $uri $uri/ /index.html;
26 | }
27 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tsdd-control",
3 | "private": true,
4 | "version": "1.5.0",
5 | "scripts": {
6 | "dev": "cross-env APP_ENV=dev vite",
7 | "build": "cross-env APP_ENV=prod vite build",
8 | "build:config": "cross-env APP_ENV=prod IS_CONFIG=true vite build",
9 | "preview": "vite preview",
10 | "new": "plop",
11 | "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx,",
12 | "lint:prettier": "prettier --write src"
13 | },
14 | "dependencies": {
15 | "@element-plus/icons-vue": "^2.1.0",
16 | "@fancyapps/ui": "^5.0.22",
17 | "@icon-park/vue-next": "^1.4.2",
18 | "@lottiefiles/lottie-player": "^2.0.2",
19 | "@vueuse/core": "^10.1.2",
20 | "axios": "^1.4.0",
21 | "dayjs": "^1.11.9",
22 | "echarts": "^5.4.3",
23 | "element-plus": "^2.3.7",
24 | "mitt": "^3.0.0",
25 | "nprogress": "^0.2.0",
26 | "pinia": "^2.0.36",
27 | "pinia-plugin-persistedstate": "^3.1.0",
28 | "sortablejs": "^1.15.0",
29 | "vue": "^3.3.4",
30 | "vue-color-kit": "^1.0.5",
31 | "vue-grid-layout": "^3.0.0-beta1",
32 | "vue-i18n": "^9.2.2",
33 | "vue-router": "^4.2.0",
34 | "vue3-count-to": "^1.1.2",
35 | "vue3-lottie": "^2.7.4"
36 | },
37 | "devDependencies": {
38 | "@types/sortablejs": "^1.15.1",
39 | "@typescript-eslint/eslint-plugin": "^5.59.5",
40 | "@typescript-eslint/parser": "^5.59.5",
41 | "@unocss/preset-uno": "^0.51.12",
42 | "@unocss/vite": "^0.51.12",
43 | "@vitejs/plugin-vue": "^4.2.3",
44 | "@vitejs/plugin-vue-jsx": "^3.0.1",
45 | "cross-env": "^7.0.3",
46 | "eslint": "^8.40.0",
47 | "eslint-config-prettier": "^8.8.0",
48 | "eslint-plugin-prettier": "^4.2.1",
49 | "eslint-plugin-vue": "^9.12.0",
50 | "plop": "^3.1.2",
51 | "prettier": "^2.8.8",
52 | "sass": "^1.62.1",
53 | "typescript": "^5.0.4",
54 | "unplugin-auto-import": "^0.16.4",
55 | "unplugin-vue-components": "^0.25.1",
56 | "unplugin-vue-router": "^0.6.4",
57 | "unplugin-vue-setup-extend-plus": "^1.0.0",
58 | "vite": "^4.4.1",
59 | "vite-plugin-compression": "^0.5.1",
60 | "vite-plugin-html": "^3.2.0",
61 | "vite-plugin-html-template": "^1.1.5",
62 | "vite-plugin-pages": "^0.31.0",
63 | "vite-plugin-vue-devtools": "^0.4.14",
64 | "vite-plugin-vue-meta-layouts": "^0.2.2",
65 | "vue-global-api": "^0.4.1",
66 | "vue-tsc": "^1.6.4"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/plop-templates/component/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
14 |
--------------------------------------------------------------------------------
/plop-templates/component/prompt.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | function getFolder(path) {
4 | const components = [];
5 | const files = fs.readdirSync(path);
6 | files.forEach(item => {
7 | const stat = fs.lstatSync(`${path}/${item}`);
8 | if (stat.isDirectory() === true && item !== 'components') {
9 | components.push(`${path}/${item}`);
10 | components.push(...getFolder(`${path}/${item}`));
11 | }
12 | });
13 | return components;
14 | }
15 |
16 | module.exports = {
17 | description: '创建组件',
18 | prompts: [
19 | {
20 | type: 'confirm',
21 | name: 'isGlobal',
22 | message: '是否为全局组件',
23 | default: false
24 | },
25 | {
26 | type: 'list',
27 | name: 'path',
28 | message: '请选择组件创建目录',
29 | choices: getFolder('src/pages'),
30 | when: answers => {
31 | return !answers.isGlobal;
32 | }
33 | },
34 | {
35 | type: 'input',
36 | name: 'name',
37 | message: '请输入组件名称',
38 | validate: v => {
39 | if (!v || v.trim === '') {
40 | return '组件名称不能为空';
41 | } else {
42 | return true;
43 | }
44 | }
45 | }
46 | ],
47 | actions: data => {
48 | let path = '';
49 | if (data.isGlobal) {
50 | path = 'src/components/{{properCase name}}/index.vue';
51 | } else {
52 | path = `${data.path}/components/{{properCase name}}/index.vue`;
53 | }
54 | const actions = [
55 | {
56 | type: 'add',
57 | path,
58 | templateFile: 'plop-templates/component/index.hbs'
59 | }
60 | ];
61 | return actions;
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/plop-templates/page/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{#if isFilesystem}}
8 |
9 | meta:
10 | title: 页面标题
11 |
12 | {{/if}}
13 |
14 |
17 |
18 |
21 |
--------------------------------------------------------------------------------
/plop-templates/page/prompt.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | function getFolder(path) {
5 | const components = [];
6 | const files = fs.readdirSync(path);
7 | files.forEach(item => {
8 | const stat = fs.lstatSync(`${path}/${item}`);
9 | if (stat.isDirectory() === true && item !== 'components') {
10 | components.push(`${path}/${item}`);
11 | components.push(...getFolder(`${path}/${item}`));
12 | }
13 | });
14 | return components;
15 | }
16 |
17 | module.exports = {
18 | description: '创建页面',
19 | prompts: [
20 | {
21 | type: 'list',
22 | name: 'path',
23 | message: '请选择页面创建目录',
24 | choices: getFolder('src/pages')
25 | },
26 | {
27 | type: 'input',
28 | name: 'name',
29 | message: '请输入文件名',
30 | validate: v => {
31 | if (!v || v.trim === '') {
32 | return '文件名不能为空';
33 | } else {
34 | return true;
35 | }
36 | }
37 | },
38 | {
39 | type: 'confirm',
40 | name: 'isFilesystem',
41 | message: '是否为基于文件系统的路由页面',
42 | default: false
43 | }
44 | ],
45 | actions: data => {
46 | const relativePath = path.relative('src/pages', data.path);
47 | const actions = [
48 | {
49 | type: 'add',
50 | path: `${data.path}/{{dotCase name}}.vue`,
51 | templateFile: 'plop-templates/page/index.hbs',
52 | data: {
53 | componentName: `${relativePath} ${data.name}`
54 | }
55 | }
56 | ];
57 | return actions;
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/plop-templates/store/index.hbs:
--------------------------------------------------------------------------------
1 | const use{{ properCase name }}Store = defineStore(
2 | // 唯一ID
3 | '{{ camelCase name }}',
4 | {
5 | state: () => ({}),
6 | getters: {},
7 | actions: {},
8 | },
9 | )
10 |
11 | export default use{{ properCase name }}Store
--------------------------------------------------------------------------------
/plop-templates/store/prompt.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | description: '创建全局状态',
3 | prompts: [
4 | {
5 | type: 'input',
6 | name: 'name',
7 | message: '请输入模块名称',
8 | validate: v => {
9 | if (!v || v.trim === '') {
10 | return '模块名称不能为空';
11 | } else {
12 | return true;
13 | }
14 | }
15 | }
16 | ],
17 | actions: () => {
18 | const actions = [
19 | {
20 | type: 'add',
21 | path: 'src/store/modules/{{camelCase name}}.ts',
22 | templateFile: 'plop-templates/store/index.hbs'
23 | }
24 | ];
25 | return actions;
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/plopfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (plop) {
2 | plop.setWelcomeMessage('请选择需要创建的模式:');
3 | plop.setGenerator('page', require('./plop-templates/page/prompt'));
4 | plop.setGenerator('component', require('./plop-templates/component/prompt'));
5 | plop.setGenerator('store', require('./plop-templates/store/prompt'));
6 | };
7 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/public/logo.png
--------------------------------------------------------------------------------
/public/tsdd-config.js:
--------------------------------------------------------------------------------
1 | var TSDD_CONFIG = {
2 | APP_URL: 'https://api.botgate.cn/v1/'
3 | };
4 |
5 | window.TSDD_CONFIG = TSDD_CONFIG;
6 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
39 |
40 |
54 |
--------------------------------------------------------------------------------
/src/api/basic.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 登录
4 | export function loginPost(data: any) {
5 | return request({
6 | url: '/manager/login',
7 | method: 'post',
8 | data
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/file.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 获取文件路径
4 | export function feileGet(params?: any) {
5 | return request({
6 | url: '/file/upload',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/group.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 群列表
4 | export function groupListGet(params: any) {
5 | return request({
6 | url: '/manager/group/list',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 封禁群列表
13 | export function groupDisablelistGet(params: any) {
14 | return request({
15 | url: '/manager/group/disablelist',
16 | method: 'get',
17 | params
18 | });
19 | }
20 |
21 | // 群成员
22 | export function groupGroupmembersGet(params: any, groupNo: string) {
23 | return request({
24 | url: `/manager/groups/${groupNo}/members`,
25 | method: 'get',
26 | params
27 | });
28 | }
29 |
30 | // 移除成员
31 | export function groupGroupmembersDelete(data: any, groupNo: string) {
32 | return request({
33 | url: `/manager/groups/${groupNo}/members`,
34 | method: 'delete',
35 | data
36 | });
37 | }
38 |
39 | // 黑名单列表
40 | export function groupBlacklistGet(params: any, groupNo: string) {
41 | return request({
42 | url: `/manager/groups/${groupNo}/members/blacklist`,
43 | method: 'get',
44 | params
45 | });
46 | }
47 |
48 | // 禁言/解除禁言
49 | export function groupForbiddenPut(params: any) {
50 | return request({
51 | url: `/manager/groups/${params.groupNo}/forbidden/${params.forbidden}`,
52 | method: 'put'
53 | });
54 | }
55 |
56 | // 封禁/解禁
57 | export function groupLiftbanPut(params: any) {
58 | return request({
59 | url: `/manager/group/liftban/${params.groupNo}/${params.status}`,
60 | method: 'put'
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/src/api/message.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 消息记录
4 | export function messageGet(params: any) {
5 | return request({
6 | url: '/manager/message',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 发消息
13 | export function messageSendPost(data: any) {
14 | return request({
15 | url: '/manager/message/send',
16 | method: 'post',
17 | data
18 | });
19 | }
20 |
21 | // 删除消息
22 | export function messageDelete(data: any) {
23 | return request({
24 | url: '/manager/message',
25 | method: 'delete',
26 | data
27 | });
28 | }
29 |
30 | // 发全员消息
31 | export function messageSendAllPost(data: any) {
32 | return request({
33 | url: '/manager/message/sendall',
34 | method: 'post',
35 | data
36 | });
37 | }
38 |
39 | // 违禁词列表
40 | export function messageProhibitWordsGet(params: any) {
41 | return request({
42 | url: '/manager/message/prohibit_words',
43 | method: 'get',
44 | params
45 | });
46 | }
47 | // 新增违禁词
48 | export function messageProhibitWordsPost(params: any) {
49 | return request({
50 | url: '/manager/message/prohibit_words',
51 | method: 'post',
52 | params
53 | });
54 | }
55 | // 删除违禁词
56 | export function messageProhibitWordsDelete(params: any) {
57 | return request({
58 | url: '/manager/message/prohibit_words',
59 | method: 'delete',
60 | params
61 | });
62 | }
63 |
64 | // 单聊天消息
65 | export function messageRecordpersonalGet(params: any) {
66 | return request({
67 | url: '/manager/message/recordpersonal',
68 | method: 'get',
69 | params
70 | });
71 | }
72 |
73 | // 群聊天消息
74 | export function messageRecordGet(params: any) {
75 | return request({
76 | url: '/manager/message/record',
77 | method: 'get',
78 | params
79 | });
80 | }
81 |
82 | // 查看设备
83 | export function messageUserDevices(params: any) {
84 | return request({
85 | url: '/manager/user/devices',
86 | method: 'get',
87 | params
88 | });
89 | }
90 |
--------------------------------------------------------------------------------
/src/api/report.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 举报列表
4 | export function reportListGet(params: any) {
5 | return request({
6 | url: '/manager/report/list',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/setting.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 更新密码
4 | export function userUpdatepasswordPost(data: any) {
5 | return request({
6 | url: '/manager/user/updatepassword',
7 | method: 'post',
8 | data
9 | });
10 | }
11 |
12 | // 获取通用设置
13 | export function getAppconfigGet(params?: any) {
14 | return request({
15 | url: '/manager/common/appconfig',
16 | method: 'get',
17 | params
18 | });
19 | }
20 |
21 | // 更新通用设置
22 | export function updateAppconfigPost(data: any) {
23 | return request({
24 | url: '/manager/common/appconfig',
25 | method: 'post',
26 | data
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/src/api/statistic.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 统计
4 | export function statisticsCountnumGet(params: any) {
5 | return request({
6 | url: '/statistics/countnum',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 用户注册统计
13 | export function statisticsRegisteruserGet(start: string, end: string) {
14 | return request({
15 | url: `/statistics/registeruser/${start}/${end}`,
16 | method: 'get'
17 | });
18 | }
19 |
20 | // 新建群统计
21 | export function statisticsCreatedgroupGet(start: string, end: string) {
22 | return request({
23 | url: `/statistics/createdgroup/${start}/${end}`,
24 | method: 'get'
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/src/api/tool.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // APP列表
4 | export function commonAppversionListGet(params: any) {
5 | return request({
6 | url: '/common/appversion/list',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 新增App版本
13 | export function commonAppversionPost(data: any) {
14 | return request({
15 | url: '/common/appversion',
16 | method: 'post',
17 | data
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/src/api/user.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 添加用户
4 | export function userAddPost(data: any) {
5 | return request({
6 | url: '/manager/user/add',
7 | method: 'post',
8 | data
9 | });
10 | }
11 |
12 | // 用户列表
13 | export function userListGet(params: any) {
14 | return request({
15 | url: '/manager/user/list',
16 | method: 'get',
17 | params
18 | });
19 | }
20 |
21 | // 封禁用户列表
22 | export function userDisablelistGet(params: any) {
23 | return request({
24 | url: '/manager/user/disablelist',
25 | method: 'get',
26 | params
27 | });
28 | }
29 |
30 | // 好友列表
31 | export function userFriendsGet(params: any) {
32 | return request({
33 | url: 'manager/user/friends',
34 | method: 'get',
35 | params
36 | });
37 | }
38 |
39 | // 黑名单列表
40 | export function userBlacklistGet(params: any) {
41 | return request({
42 | url: 'manager/user/blacklist',
43 | method: 'get',
44 | params
45 | });
46 | }
47 |
48 | // 用户封禁/解禁
49 | export function userLiftbanPut(params: any) {
50 | return request({
51 | url: `manager/user/liftban/${params.uid}/${params.status}`,
52 | method: 'put'
53 | });
54 | }
55 |
56 | // 管理员-列表
57 | export function adminList() {
58 | return request({
59 | url: `manager/user/admin`,
60 | method: 'get'
61 | });
62 | }
63 |
64 | // 管理员-新增
65 | export function adminAdd(data: any) {
66 | return request({
67 | url: `manager/user/admin`,
68 | method: 'post',
69 | data
70 | });
71 | }
72 |
73 | // 管理员-删除
74 | export function adminDelete(params: any) {
75 | return request({
76 | url: `manager/user/admin`,
77 | method: 'delete',
78 | params
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/src/api/workplace/app.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 获取应用
4 | export function appGet(params?: any) {
5 | return request({
6 | url: '/manager/workplace/app',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 新增应用
13 | export function appPost(data: any) {
14 | return request({
15 | url: '/manager/workplace/app',
16 | method: 'post',
17 | data
18 | });
19 | }
20 |
21 | // 编辑应用
22 | export function appPut(data: any, app_id: string) {
23 | return request({
24 | url: `/manager/workplace/apps/${app_id}`,
25 | method: 'put',
26 | data
27 | });
28 | }
29 |
30 | // 删除应用
31 | export function appDelete(app_id: string) {
32 | return request({
33 | url: `/manager/workplace/apps/${app_id}`,
34 | method: 'delete'
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/workplace/banner.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 获取轮播
4 | export function bannerGet(params?: any) {
5 | return request({
6 | url: '/manager/workplace/banner',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 新增轮播
13 | export function bannerPost(data: any) {
14 | return request({
15 | url: '/manager/workplace/banner',
16 | method: 'post',
17 | data
18 | });
19 | }
20 |
21 | // 编辑轮播
22 | export function bannerPut(data: any, banner_no: string) {
23 | return request({
24 | url: `/manager/workplace/banners/${banner_no}`,
25 | method: 'put',
26 | data
27 | });
28 | }
29 |
30 | // 删除轮播
31 | export function bannerDelete(banner_no: string) {
32 | return request({
33 | url: `/manager/workplace/banners/${banner_no}`,
34 | method: 'delete'
35 | });
36 | }
37 |
38 | // 轮播排序
39 | export function bannerReorderPut(data: any) {
40 | return request({
41 | url: `/manager/workplace/banner/reorder`,
42 | method: 'put',
43 | data
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/src/api/workplace/category.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/axios';
2 |
3 | // 获取分类
4 | export function categoryGet(params?: any) {
5 | return request({
6 | url: '/manager/workplace/category',
7 | method: 'get',
8 | params
9 | });
10 | }
11 |
12 | // 新增分类
13 | export function categoryPost(data: any) {
14 | return request({
15 | url: '/manager/workplace/category',
16 | method: 'post',
17 | data
18 | });
19 | }
20 |
21 | // 分类编辑
22 | export function categoryPut(data: any, category_no: string) {
23 | return request({
24 | url: `/manager/workplace/categorys/${category_no}`,
25 | method: 'put',
26 | data
27 | });
28 | }
29 |
30 | // 删除分类
31 | export function categoryDelete(category_no: string) {
32 | return request({
33 | url: `/manager/workplace/categorys/${category_no}`,
34 | method: 'delete'
35 | });
36 | }
37 |
38 | // 分类排序
39 | export function categoryReorderPut(data: any) {
40 | return request({
41 | url: '/manager/workplace/category/reorder',
42 | method: 'put',
43 | data
44 | });
45 | }
46 |
47 | // 分类获取应用
48 | export function categoryAppGet(category_no: string) {
49 | return request({
50 | url: `/manager/workplace/categorys/${category_no}/app`,
51 | method: 'get'
52 | });
53 | }
54 |
55 | // 分类新增应用
56 | export function categoryAppPost(data: any, category_no: string) {
57 | return request({
58 | url: `/manager/workplace/categorys/${category_no}/app`,
59 | method: 'post',
60 | data
61 | });
62 | }
63 |
64 | // 分类删除应用
65 | export function categoryAppDelete(category_no: string, app_id: string) {
66 | return request({
67 | url: `/manager/workplace/categorys/${category_no}/apps/${app_id}`,
68 | method: 'delete'
69 | });
70 | }
71 |
72 | // 分类应用排序
73 | export function categorysAppsReorderPut(data: any, category_no: string) {
74 | return request({
75 | url: `/manager/workplace/categorys/${category_no}/app/reorder`,
76 | method: 'put',
77 | data
78 | });
79 | }
80 |
--------------------------------------------------------------------------------
/src/assets/heder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/src/assets/heder.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoManager/8f123aed80561f2c11111e075b55ad539dc1af08/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/vue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/BdAppVersion/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 | Android
17 | IOS
18 | Mac
19 | Windows
20 | Linux
21 |
22 |
23 |
24 |
25 | 强制更新
26 | 非强制更新
27 |
28 |
29 |
30 |
31 |
39 |
40 | 点击上传安装包
41 |
42 |
43 |
44 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 | 取消
68 | 发送
69 |
70 |
71 |
72 |
73 |
74 |
169 |
--------------------------------------------------------------------------------
/src/components/BdMsg/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ msg['content'] }}
6 |
7 |
8 |
![]()
14 |
15 |
![]()
21 |
22 |
23 |
24 |
33 |
34 |
35 |
位置标题:{{ msg['title'] }}
36 |
地址:{{ msg['address'] }}
37 |
38 |
39 |
40 |
名片UID:{{ msg['uid'] }}
41 |
用户名:{{ msg['name'] }}
42 |
43 |
44 |
45 |
文件标题:{{ msg['title'] }}
46 |
地址:{{ msg['address'] }}
47 |
48 |
49 |
50 |
红包编号:{{ msg['redpacket_no'] }}
51 |
祝福语:{{ msg['blessing'] }}
52 |
53 |
54 |
55 |
金额:¥{{ msg['amount'] }}
56 |
备注:{{ msg['remark'] }}
57 |
58 |
59 |
[合并转发]
60 |
61 |
70 |
71 |
72 |
81 | {{ msg['content'] ? msg['content'] : `[矢量emoji]` }}
82 |
83 |
84 |
85 | [截屏消息]
86 | [系统消息]
87 |
88 |
89 |
[未知消息类型]
90 |
91 |
92 |
93 |
118 |
119 |
136 |
--------------------------------------------------------------------------------
/src/components/BdPage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/src/components/BdProhitWords/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
17 |
18 | 取消
19 | 发送
20 |
21 |
22 |
23 |
24 |
25 |
83 |
--------------------------------------------------------------------------------
/src/components/BdSandAllMsg/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
16 |
17 |
18 |
19 |
20 |
21 | 取消
22 | 发送
23 |
24 |
25 |
26 |
27 |
28 |
86 |
--------------------------------------------------------------------------------
/src/components/BdSendMsg/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
接受者信息
15 |
接受者ID:{{ receiveder }}
16 |
接受者名称:{{ receivederName }}
17 |
发送者信息
18 |
发送者ID:{{ sender }}
19 |
发送者名字:{{ senderName }}
20 |
21 |
22 |
23 |
24 | 发送
25 |
26 |
27 |
28 |
97 |
--------------------------------------------------------------------------------
/src/components/SwitchDark/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | // 首页地址(默认)
2 | export const HOME_URL = '/home';
3 |
4 | // 登录页地址(默认)
5 | export const LOGIN_URL = '/login';
6 |
7 | // 默认主题颜色
8 | export const DEFAULT_PRIMARY = '#E4633B';
9 |
10 | // 路由白名单地址(必须是本地存在的路由 staticRouter.ts 中)
11 | export const ROUTER_WHITE_LIST: string[] = ['/login'];
12 |
13 | // 自定义应用根据运行环境获取配置
14 | const modules: any = {};
15 | const moduleFiles = import.meta.glob('./modules/*.ts', { import: 'default', eager: true });
16 |
17 | Object.keys(moduleFiles).forEach(name => {
18 | const key = name.replace('./modules/', '').replace('.ts', '').trim();
19 | modules[key] = moduleFiles[name];
20 | });
21 |
22 | const TSDD_CONFIG = window.TSDD_CONFIG ? window.TSDD_CONFIG : {};
23 | // 默认应用配置
24 | export const BU_DOU_CONFIG = {
25 | APP_TITLE: '唐僧叨叨后台管理',
26 | APP_TITLE_SHORT: '唐',
27 | ...modules[process.env.APP_ENV as any],
28 | ...TSDD_CONFIG
29 | // APP_URL: '/api/v1/' // 正式环境地址 (通用打包镜像,用此相对地址)
30 | };
31 |
--------------------------------------------------------------------------------
/src/config/modules/dev.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | APP_ENV: 'dev',
3 | APP_URL: 'https://api.botgate.cn/v1/'
4 | };
5 |
--------------------------------------------------------------------------------
/src/config/modules/prod.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | APP_ENV: 'prod',
3 | APP_URL: '/api/v1/'
4 | };
5 |
--------------------------------------------------------------------------------
/src/directives/index.ts:
--------------------------------------------------------------------------------
1 | import { App, Directive } from 'vue';
2 | import auth from './modules/auth';
3 | import copy from './modules/copy';
4 | import waterMarker from './modules/waterMarker';
5 | import draggable from './modules/draggable';
6 | import debounce from './modules/debounce';
7 | import throttle from './modules/throttle';
8 | import longpress from './modules/longpress';
9 |
10 | const directivesList: { [key: string]: Directive } = {
11 | auth,
12 | copy,
13 | waterMarker,
14 | draggable,
15 | debounce,
16 | throttle,
17 | longpress
18 | };
19 |
20 | const directives = {
21 | install: function (app: App) {
22 | Object.keys(directivesList).forEach(key => {
23 | app.directive(key, directivesList[key]);
24 | });
25 | }
26 | };
27 |
28 | export default directives;
29 |
--------------------------------------------------------------------------------
/src/directives/modules/auth.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * v-auth
3 | * 按钮权限指令
4 | */
5 | import { useAuthStore } from '@/stores/modules/auth';
6 | import type { Directive, DirectiveBinding } from 'vue';
7 |
8 | const auth: Directive = {
9 | mounted(el: HTMLElement, binding: DirectiveBinding) {
10 | const { value } = binding;
11 | const authStore = useAuthStore();
12 | const currentPageRoles = authStore.authButtonListGet[authStore.routeName] ?? [];
13 | if (value instanceof Array && value.length) {
14 | const hasPermission = value.every(item => currentPageRoles.includes(item));
15 | if (!hasPermission) el.remove();
16 | } else {
17 | if (!currentPageRoles.includes(value)) el.remove();
18 | }
19 | }
20 | };
21 |
22 | export default auth;
23 |
--------------------------------------------------------------------------------
/src/directives/modules/copy.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * v-copy
3 | * 复制某个值至剪贴板
4 | * 接收参数:string类型/Ref类型/Reactive类型
5 | */
6 | import type { Directive, DirectiveBinding } from 'vue';
7 | import { ElMessage } from 'element-plus';
8 | interface ElType extends HTMLElement {
9 | copyData: string | number;
10 | __handleClick__: any;
11 | }
12 | const copy: Directive = {
13 | mounted(el: ElType, binding: DirectiveBinding) {
14 | el.copyData = binding.value;
15 | el.addEventListener('click', handleClick);
16 | },
17 | updated(el: ElType, binding: DirectiveBinding) {
18 | el.copyData = binding.value;
19 | },
20 | beforeUnmount(el: ElType) {
21 | el.removeEventListener('click', el.__handleClick__);
22 | }
23 | };
24 |
25 | function handleClick(this: any) {
26 | const input = document.createElement('input');
27 | input.value = this.copyData.toLocaleString();
28 | document.body.appendChild(input);
29 | input.select();
30 | document.execCommand('Copy');
31 | document.body.removeChild(input);
32 | ElMessage({
33 | type: 'success',
34 | message: '复制成功'
35 | });
36 | }
37 |
38 | export default copy;
39 |
--------------------------------------------------------------------------------
/src/directives/modules/debounce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * v-debounce
3 | * 按钮防抖指令,可自行扩展至input
4 | * 接收参数:function类型
5 | */
6 | import type { Directive, DirectiveBinding } from 'vue';
7 | interface ElType extends HTMLElement {
8 | __handleClick__: () => any;
9 | }
10 | const debounce: Directive = {
11 | mounted(el: ElType, binding: DirectiveBinding) {
12 | if (typeof binding.value !== 'function') {
13 | throw 'callback must be a function';
14 | }
15 | let timer: NodeJS.Timeout | null = null;
16 | el.__handleClick__ = function () {
17 | if (timer) {
18 | clearInterval(timer);
19 | }
20 | timer = setTimeout(() => {
21 | binding.value();
22 | }, 500);
23 | };
24 | el.addEventListener('click', el.__handleClick__);
25 | },
26 | beforeUnmount(el: ElType) {
27 | el.removeEventListener('click', el.__handleClick__);
28 | }
29 | };
30 |
31 | export default debounce;
32 |
--------------------------------------------------------------------------------
/src/directives/modules/draggable.ts:
--------------------------------------------------------------------------------
1 | /*
2 | 需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
3 |
4 | 思路:
5 | 1、设置需要拖拽的元素为absolute,其父元素为relative。
6 | 2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
7 | 3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
8 | 4、鼠标松开(onmouseup)时完成一次拖拽
9 |
10 | 使用:在 Dom 上加上 v-draggable 即可
11 |
12 | */
13 | import type { Directive } from 'vue';
14 | interface ElType extends HTMLElement {
15 | parentNode: any;
16 | }
17 | const draggable: Directive = {
18 | mounted: function (el: ElType) {
19 | el.style.cursor = 'move';
20 | el.style.position = 'absolute';
21 | el.onmousedown = function (e) {
22 | const disX = e.pageX - el.offsetLeft;
23 | const disY = e.pageY - el.offsetTop;
24 | document.onmousemove = function (e) {
25 | let x = e.pageX - disX;
26 | let y = e.pageY - disY;
27 | const maxX = el.parentNode.offsetWidth - el.offsetWidth;
28 | const maxY = el.parentNode.offsetHeight - el.offsetHeight;
29 | if (x < 0) {
30 | x = 0;
31 | } else if (x > maxX) {
32 | x = maxX;
33 | }
34 |
35 | if (y < 0) {
36 | y = 0;
37 | } else if (y > maxY) {
38 | y = maxY;
39 | }
40 | el.style.left = x + 'px';
41 | el.style.top = y + 'px';
42 | };
43 | document.onmouseup = function () {
44 | document.onmousemove = document.onmouseup = null;
45 | };
46 | };
47 | }
48 | };
49 | export default draggable;
50 |
--------------------------------------------------------------------------------
/src/directives/modules/longpress.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * v-longpress
3 | * 长按指令,长按时触发事件
4 | */
5 | import type { Directive, DirectiveBinding } from 'vue';
6 |
7 | const directive: Directive = {
8 | mounted(el: HTMLElement, binding: DirectiveBinding) {
9 | if (typeof binding.value !== 'function') {
10 | throw 'callback must be a function';
11 | }
12 | // 定义变量
13 | let pressTimer: any = null;
14 | // 创建计时器( 2秒后执行函数 )
15 | const start = (e: any) => {
16 | if (e.button) {
17 | if (e.type === 'click' && e.button !== 0) {
18 | return;
19 | }
20 | }
21 | if (pressTimer === null) {
22 | pressTimer = setTimeout(() => {
23 | handler(e);
24 | }, 1000);
25 | }
26 | };
27 | // 取消计时器
28 | const cancel = () => {
29 | if (pressTimer !== null) {
30 | clearTimeout(pressTimer);
31 | pressTimer = null;
32 | }
33 | };
34 | // 运行函数
35 | const handler = (e: MouseEvent | TouchEvent) => {
36 | binding.value(e);
37 | };
38 | // 添加事件监听器
39 | el.addEventListener('mousedown', start);
40 | el.addEventListener('touchstart', start);
41 | // 取消计时器
42 | el.addEventListener('click', cancel);
43 | el.addEventListener('mouseout', cancel);
44 | el.addEventListener('touchend', cancel);
45 | el.addEventListener('touchcancel', cancel);
46 | }
47 | };
48 |
49 | export default directive;
50 |
--------------------------------------------------------------------------------
/src/directives/modules/throttle.ts:
--------------------------------------------------------------------------------
1 | /*
2 | 需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
3 |
4 | 思路:
5 | 1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮
6 | 2、将需要触发的方法绑定在指令上
7 |
8 | 使用:给 Dom 加上 v-throttle 及回调函数即可
9 |
10 | */
11 | import type { Directive, DirectiveBinding } from 'vue';
12 | interface ElType extends HTMLElement {
13 | __handleClick__: () => any;
14 | disabled: boolean;
15 | }
16 | const throttle: Directive = {
17 | mounted(el: ElType, binding: DirectiveBinding) {
18 | if (typeof binding.value !== 'function') {
19 | throw 'callback must be a function';
20 | }
21 | let timer: NodeJS.Timeout | null = null;
22 | el.__handleClick__ = function () {
23 | if (timer) {
24 | clearTimeout(timer);
25 | }
26 | if (!el.disabled) {
27 | el.disabled = true;
28 | binding.value();
29 | timer = setTimeout(() => {
30 | el.disabled = false;
31 | }, 1000);
32 | }
33 | };
34 | el.addEventListener('click', el.__handleClick__);
35 | },
36 | beforeUnmount(el: ElType) {
37 | el.removeEventListener('click', el.__handleClick__);
38 | }
39 | };
40 |
41 | export default throttle;
42 |
--------------------------------------------------------------------------------
/src/directives/modules/waterMarker.ts:
--------------------------------------------------------------------------------
1 | /*
2 | 需求:给整个页面添加背景水印。
3 |
4 | 思路:
5 | 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
6 | 2、将其设置为背景图片,从而实现页面或组件水印效果
7 |
8 | 使用:设置水印文案,颜色,字体大小即可
9 |
10 | */
11 |
12 | import type { Directive, DirectiveBinding } from 'vue';
13 | const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => {
14 | // 水印文字,父元素,字体,文字颜色
15 | const can: HTMLCanvasElement = document.createElement('canvas');
16 | parentNode.appendChild(can);
17 | can.width = 205;
18 | can.height = 140;
19 | can.style.display = 'none';
20 | const cans = can.getContext('2d') as CanvasRenderingContext2D;
21 | cans.rotate((-20 * Math.PI) / 180);
22 | cans.font = font || '16px Microsoft JhengHei';
23 | cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)';
24 | cans.textAlign = 'left';
25 | cans.textBaseline = 'Middle' as CanvasTextBaseline;
26 | cans.fillText(str, can.width / 10, can.height / 2);
27 | parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')';
28 | };
29 |
30 | const waterMarker = {
31 | mounted(el: DirectiveBinding, binding: DirectiveBinding) {
32 | addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor);
33 | }
34 | };
35 |
36 | export default waterMarker;
37 |
--------------------------------------------------------------------------------
/src/hooks/interface/index.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-namespace
2 | export namespace Table {
3 | export interface Pageable {
4 | pageNum: number;
5 | pageSize: number;
6 | total: number;
7 | }
8 | export interface TableStateProps {
9 | tableData: any[];
10 | pageable: Pageable;
11 | searchParam: {
12 | [key: string]: any;
13 | };
14 | searchInitParam: {
15 | [key: string]: any;
16 | };
17 | totalParam: {
18 | [key: string]: any;
19 | };
20 | icon?: {
21 | [key: string]: any;
22 | };
23 | }
24 | }
25 |
26 | // eslint-disable-next-line @typescript-eslint/no-namespace
27 | export namespace HandleData {
28 | export type MessageType = '' | 'success' | 'warning' | 'info' | 'error';
29 | }
30 |
31 | // eslint-disable-next-line @typescript-eslint/no-namespace
32 | export namespace Theme {
33 | export type GreyOrWeakType = 'grey' | 'weak';
34 | }
35 |
--------------------------------------------------------------------------------
/src/hooks/useEcharts.ts:
--------------------------------------------------------------------------------
1 | import { onDeactivated, onBeforeUnmount } from 'vue';
2 | import * as echarts from 'echarts';
3 |
4 | /**
5 | * @description 使用 Echarts (只是为了添加图表响应式)
6 | * @param {Element} myChart Echarts实例 (必传)
7 | * @param {Object} options 绘制Echarts的参数 (必传)
8 | * */
9 | export const useEcharts = (myChart: echarts.ECharts, options: echarts.EChartsCoreOption) => {
10 | if (options && typeof options === 'object') {
11 | myChart.setOption(options);
12 | }
13 | const echartsResize = () => {
14 | myChart && myChart.resize();
15 | };
16 |
17 | window.addEventListener('resize', echartsResize);
18 |
19 | // 防止 echarts 页面 keepAlive 时,还在继续监听页面
20 | onDeactivated(() => {
21 | window.removeEventListener('resize', echartsResize);
22 | });
23 |
24 | onBeforeUnmount(() => {
25 | window.removeEventListener('resize', echartsResize);
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/src/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | import { storeToRefs } from 'pinia';
2 | import { Theme } from './interface';
3 | import { ElMessage } from 'element-plus';
4 | import { DEFAULT_PRIMARY } from '@/config';
5 | import { useGlobalStore } from '@/stores/modules/global';
6 | import { getLightColor, getDarkColor } from '@/utils/color';
7 | import { asideTheme, AsideThemeType } from '@/styles/theme/aside';
8 |
9 | /**
10 | * @description 全局主题 hooks
11 | * */
12 | export const useTheme = () => {
13 | const globalStore = useGlobalStore();
14 | const { primary, isDark, isGrey, isWeak, asideInverted, layout } = storeToRefs(globalStore);
15 |
16 | // 切换暗黑模式 ==> 并带修改主题颜色、侧边栏颜色
17 | const switchDark = () => {
18 | const html = document.documentElement as HTMLElement;
19 | if (isDark.value) html.setAttribute('class', 'dark');
20 | else html.setAttribute('class', '');
21 | changePrimary(primary.value);
22 | setAsideTheme();
23 | };
24 |
25 | // 修改主题颜色
26 | const changePrimary = (val: any | null) => {
27 | if (!val) {
28 | val = DEFAULT_PRIMARY;
29 | ElMessage({ type: 'success', message: `主题颜色已重置为 ${DEFAULT_PRIMARY}` });
30 | }
31 | val = val.hex ? val.hex : val;
32 | // 计算主题颜色变化
33 | document.documentElement.style.setProperty('--el-color-primary', val);
34 | document.documentElement.style.setProperty(
35 | '--el-color-primary-dark-2',
36 | isDark.value ? `${getLightColor(val, 0.2)}` : `${getDarkColor(val, 0.3)}`
37 | );
38 | for (let i = 1; i <= 9; i++) {
39 | const primaryColor = isDark.value ? `${getDarkColor(val, i / 10)}` : `${getLightColor(val, i / 10)}`;
40 | document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor);
41 | }
42 | globalStore.setGlobalState('primary', val);
43 | };
44 |
45 | // 灰色和弱色切换
46 | const changeGreyOrWeak = (type: Theme.GreyOrWeakType, value: boolean) => {
47 | const body = document.body as HTMLElement;
48 | if (!value) return body.removeAttribute('style');
49 | const styles: Record = {
50 | grey: 'filter: grayscale(1)',
51 | weak: 'filter: invert(80%)'
52 | };
53 | body.setAttribute('style', styles[type]);
54 | const propName = type === 'grey' ? 'isWeak' : 'isGrey';
55 | globalStore.setGlobalState(propName, false);
56 | };
57 |
58 | // 设置侧边栏样式 ==> light、inverted、dark
59 | const setAsideTheme = () => {
60 | // 默认所有侧边栏为 light 模式
61 | let type: AsideThemeType = 'light';
62 | // transverse 布局下菜单栏为 inverted 模式
63 | if (layout.value == 'transverse') type = 'inverted';
64 | // 侧边栏反转色目前只支持在 vertical 布局模式下生效
65 | if (layout.value == 'vertical' && asideInverted.value) type = 'inverted';
66 | // 侧边栏 dark 模式
67 | if (isDark.value) type = 'dark';
68 | const theme = asideTheme[type!];
69 | for (const [key, value] of Object.entries(theme)) {
70 | document.documentElement.style.setProperty(key, value);
71 | }
72 | };
73 |
74 | // init theme
75 | const initTheme = () => {
76 | switchDark();
77 | if (isGrey.value) changeGreyOrWeak('grey', true);
78 | if (isWeak.value) changeGreyOrWeak('weak', true);
79 | };
80 |
81 | return {
82 | initTheme,
83 | switchDark,
84 | changePrimary,
85 | changeGreyOrWeak,
86 | setAsideTheme
87 | };
88 | };
89 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n';
2 | import { getBrowserLang } from '@/utils';
3 |
4 | import zh from './modules/zh';
5 | import en from './modules/en';
6 |
7 | const i18n = createI18n({
8 | allowComposition: true,
9 | legacy: false,
10 | locale: getBrowserLang(),
11 | messages: {
12 | zh,
13 | en
14 | }
15 | });
16 |
17 | export default i18n;
18 |
--------------------------------------------------------------------------------
/src/i18n/modules/en.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | home: {
3 | welcome: 'Welcome'
4 | },
5 | tabs: {
6 | more: 'More',
7 | refresh: 'Refresh',
8 | maximize: 'Maximize',
9 | closeCurrent: 'Close current',
10 | closeOther: 'Close other',
11 | closeAll: 'Close All'
12 | },
13 | header: {
14 | componentSize: 'Component size',
15 | language: 'Language',
16 | theme: 'theme',
17 | layoutConfig: 'Layout config',
18 | primary: 'primary',
19 | darkMode: 'Dark Mode',
20 | greyMode: 'Grey mode',
21 | weakMode: 'Weak mode',
22 | fullScreen: 'Full Screen',
23 | exitFullScreen: 'Exit Full Screen',
24 | personalData: 'Personal Data',
25 | changePassword: 'Change Password',
26 | logout: 'Logout'
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/i18n/modules/zh.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | home: {
3 | welcome: '欢迎使用'
4 | },
5 | tabs: {
6 | more: '更多',
7 | refresh: '刷新',
8 | maximize: '最大化',
9 | closeCurrent: '关闭当前',
10 | closeOther: '关闭其它',
11 | closeAll: '关闭所有'
12 | },
13 | header: {
14 | componentSize: '组件大小',
15 | language: '国际化',
16 | theme: '全局主题',
17 | layoutConfig: '布局设置',
18 | primary: 'primary',
19 | darkMode: '暗黑模式',
20 | greyMode: '灰色模式',
21 | weakMode: '色弱模式',
22 | fullScreen: '全屏',
23 | exitFullScreen: '退出全屏',
24 | personalData: '个人信息',
25 | changePassword: '修改密码',
26 | logout: '退出登录'
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/layouts/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
20 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/ToolBarLeft.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
22 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/ToolBarRight.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
20 |
35 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/components/AssemblySize.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 | {{ item.label }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
37 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/components/Avatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ username }}
5 |
6 |
![avatar]()
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 修改密码
17 |
18 |
19 |
20 |
21 |
22 | 安全退出
23 |
24 |
25 |
26 |
27 |
28 |
59 |
78 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/components/Breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ item.meta.title }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
45 |
46 |
88 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/components/Fullscreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/components/Language.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 | {{ item.label }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
39 |
--------------------------------------------------------------------------------
/src/layouts/components/Header/components/ThemeSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
--------------------------------------------------------------------------------
/src/layouts/components/LayoutClassic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
58 |
59 |
142 |
--------------------------------------------------------------------------------
/src/layouts/components/LayoutTransverse.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |

7 |
{{ APP_TITLE }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ subItem.meta.title }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {{ subItem.meta.title }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
58 |
59 |
130 |
--------------------------------------------------------------------------------
/src/layouts/components/LayoutVertical.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 |
{{ APP_TITLE }}
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
52 |
53 |
109 |
--------------------------------------------------------------------------------
/src/layouts/components/Main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
95 |
96 |
132 |
--------------------------------------------------------------------------------
/src/layouts/components/Menu/SubMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ subItem.meta.title }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ subItem.meta.title }}
18 |
19 |
20 |
21 |
22 |
23 |
34 |
35 |
92 |
--------------------------------------------------------------------------------
/src/layouts/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from '@/App.vue';
3 | import ElementPlus from 'element-plus';
4 | import VueGridLayout from 'vue-grid-layout';
5 | // icon-park
6 | import { install } from '@icon-park/vue-next/es/all';
7 | // element icons
8 | import * as Icons from '@element-plus/icons-vue';
9 | // custom directives
10 | import directives from '@/directives';
11 | // vue Router
12 | import router from '@/router';
13 | // pinia store
14 | import pinia from '@/stores';
15 | // vue i18n
16 | import I18n from '@/i18n';
17 | import 'vue-global-api';
18 | // 额外引入图标库
19 | import 'element-plus/dist/index.css';
20 | import 'element-plus/theme-chalk/dark/css-vars.css';
21 | import '@icon-park/vue-next/styles/index.css';
22 | import '@fancyapps/ui/dist/fancybox/fancybox.css';
23 | import '@/styles/index.scss';
24 | import 'uno.css';
25 |
26 | import '@/utils/system-copyright';
27 |
28 | const app = createApp(App);
29 | install(app, 'i-bd');
30 | // register the element Icons component
31 | Object.keys(Icons).forEach(key => {
32 | app.component(key, Icons[key as keyof typeof Icons]);
33 | });
34 | app.use(ElementPlus);
35 | app.use(VueGridLayout);
36 | app.use(directives);
37 | app.use(router);
38 | app.use(I18n);
39 | app.use(pinia);
40 | app.mount('#app');
41 |
--------------------------------------------------------------------------------
/src/menu/index.ts:
--------------------------------------------------------------------------------
1 | const menuModuleList: any[] = [];
2 | const modules = import.meta.glob('./modules/**/*.ts', { import: 'default', eager: true });
3 | Object.keys(modules).forEach(key => {
4 | const mod = modules[key] || {};
5 | const modList = Array.isArray(mod) ? [...mod] : [mod];
6 | menuModuleList.push(...modList);
7 | });
8 | // 按照index 升序
9 | menuModuleList.sort((a, b) => {
10 | return a.meta.index - b.meta.index;
11 | });
12 | export default [...menuModuleList];
13 |
--------------------------------------------------------------------------------
/src/menu/modules/group.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/group/adduser',
3 | name: 'group',
4 | path: '/group',
5 | meta: {
6 | icon: 'i-bd-peoples-two',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 3,
13 | title: '群组'
14 | },
15 | children: [
16 | {
17 | component: '/group/grouplist',
18 | name: 'groupGrouplist',
19 | path: '/group/grouplist',
20 | meta: {
21 | icon: 'i-bd-group',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: '群列表'
28 | }
29 | },
30 | {
31 | component: '/group/groupdisablelist',
32 | name: 'groupGroupdisablelist',
33 | path: '/group/groupdisablelist',
34 | meta: {
35 | icon: 'i-bd-ungroup',
36 | isAffix: false,
37 | isFull: false,
38 | isHide: false,
39 | isKeepAlive: true,
40 | isLink: '',
41 | title: '封禁群列表'
42 | }
43 | }
44 | ]
45 | };
46 | export default home;
47 |
--------------------------------------------------------------------------------
/src/menu/modules/home.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/home/index',
3 | name: 'homeIndex',
4 | path: '/home',
5 | meta: {
6 | icon: 'i-bd-home',
7 | isAffix: true,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 1,
13 | title: '首页'
14 | }
15 | };
16 | export default home;
17 |
--------------------------------------------------------------------------------
/src/menu/modules/message.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/message/sendmsglist',
3 | name: 'message',
4 | path: '/message',
5 | meta: {
6 | icon: 'i-bd-message',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 4,
13 | title: '通知'
14 | },
15 | children: [
16 | {
17 | component: '/message/sendmsglist',
18 | name: 'messageSendmsglist',
19 | path: '/message/sendmsglist',
20 | meta: {
21 | icon: 'i-bd-communication',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: '通知记录'
28 | }
29 | },
30 | {
31 | component: '/message/prohibitwords',
32 | name: 'messageProhibitwords',
33 | path: '/message/prohibitwords',
34 | meta: {
35 | icon: 'i-bd-message-security',
36 | isAffix: false,
37 | isFull: false,
38 | isHide: false,
39 | isKeepAlive: true,
40 | isLink: '',
41 | title: '违禁词列表'
42 | }
43 | }
44 | ]
45 | };
46 | export default home;
47 |
--------------------------------------------------------------------------------
/src/menu/modules/redpacket.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/redpacket/list',
3 | name: 'redpacket',
4 | path: '/redpacket',
5 | meta: {
6 | icon: 'i-bd-red-envelopes',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: true,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 5,
13 | title: '红包'
14 | },
15 | children: [
16 | {
17 | component: '/redpacket/list',
18 | name: 'redpacketList',
19 | path: '/redpacket/list',
20 | meta: {
21 | icon: 'i-bd-doc-search',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: '红包记录'
28 | }
29 | }
30 | ]
31 | };
32 | export default home;
33 |
--------------------------------------------------------------------------------
/src/menu/modules/report.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/report/user',
3 | name: 'report',
4 | path: '/report',
5 | meta: {
6 | icon: 'i-bd-report',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 6,
13 | title: '举报'
14 | },
15 | children: [
16 | {
17 | component: '/report/user',
18 | name: 'reportUser',
19 | path: '/report/user',
20 | meta: {
21 | icon: 'i-bd-wrong-user',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: '举报用户'
28 | }
29 | },
30 | {
31 | component: '/report/group',
32 | name: 'reportGroup',
33 | path: '/report/group',
34 | meta: {
35 | icon: 'i-bd-user-to-user-transmission',
36 | isAffix: false,
37 | isFull: false,
38 | isHide: false,
39 | isKeepAlive: true,
40 | isLink: '',
41 | title: '举报群聊'
42 | }
43 | }
44 | ]
45 | };
46 | export default home;
47 |
--------------------------------------------------------------------------------
/src/menu/modules/setting.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/setting/currencysetting',
3 | name: 'setting',
4 | path: '/setting',
5 | meta: {
6 | icon: 'i-bd-setting',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 9,
13 | title: '设置'
14 | },
15 | children: [
16 | {
17 | component: '/setting/currencysetting',
18 | name: 'settingCurrencysetting',
19 | path: '/setting/currencysetting',
20 | meta: {
21 | icon: 'i-bd-setting-config',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: '通用设置'
28 | }
29 | },
30 | {
31 | component: '/setting/updatepwd',
32 | name: 'settingUpdatepwd',
33 | path: '/setting/updatepwd',
34 | meta: {
35 | icon: 'i-bd-shield',
36 | isAffix: false,
37 | isFull: false,
38 | isHide: false,
39 | isKeepAlive: true,
40 | isLink: '',
41 | title: '修改登录密码'
42 | }
43 | }
44 | ]
45 | };
46 | export default home;
47 |
--------------------------------------------------------------------------------
/src/menu/modules/tool.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/tool/appupdate',
3 | name: 'tool',
4 | path: '/tool',
5 | meta: {
6 | icon: 'i-bd-tool',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 8,
13 | title: '工具'
14 | },
15 | children: [
16 | {
17 | component: '/tool/appupdate',
18 | name: 'toolAppupdate',
19 | path: '/tool/appupdate',
20 | meta: {
21 | icon: 'i-bd-application-one',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: 'APP升级'
28 | }
29 | }
30 | ]
31 | };
32 | export default home;
33 |
--------------------------------------------------------------------------------
/src/menu/modules/user.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | component: '/user/adduser',
3 | name: 'use',
4 | path: '/user',
5 | meta: {
6 | icon: 'i-bd-user',
7 | isAffix: false,
8 | isFull: false,
9 | isHide: false,
10 | isKeepAlive: true,
11 | isLink: '',
12 | index: 2,
13 | title: '用户'
14 | },
15 | children: [
16 | {
17 | component: '/user/adduser',
18 | name: 'userAdduser',
19 | path: '/user/adduser',
20 | meta: {
21 | icon: 'i-bd-add-user',
22 | isAffix: false,
23 | isFull: false,
24 | isHide: false,
25 | isKeepAlive: true,
26 | isLink: '',
27 | title: '新增用户'
28 | }
29 | },
30 | {
31 | component: '/user/userlist',
32 | name: 'userUserlist',
33 | path: '/user/userlist',
34 | meta: {
35 | icon: 'i-bd-user',
36 | isAffix: false,
37 | isFull: false,
38 | isHide: false,
39 | isKeepAlive: true,
40 | isLink: '',
41 | title: '用户列表'
42 | }
43 | },
44 | {
45 | component: '/user/disablelist',
46 | name: 'userDisablelist',
47 | path: '/user/disablelist',
48 | meta: {
49 | icon: 'i-bd-wrong-user',
50 | isAffix: false,
51 | isFull: false,
52 | isHide: false,
53 | isKeepAlive: true,
54 | isLink: '',
55 | title: '封禁用户列表'
56 | }
57 | },
58 | {
59 | component: '/user/administrator',
60 | name: 'userAdministrator',
61 | path: '/user/administrator',
62 | meta: {
63 | icon: 'i-bd-user-business',
64 | isAffix: false,
65 | isFull: false,
66 | isHide: false,
67 | isKeepAlive: true,
68 | isLink: '',
69 | auth: ['superAdmin'],
70 | title: '管理员'
71 | }
72 | }
73 | ]
74 | };
75 | export default home;
76 |
--------------------------------------------------------------------------------
/src/menu/modules/workplace.ts:
--------------------------------------------------------------------------------
1 | const home: Menu.MenuOptions = {
2 | name: 'tool',
3 | path: '/workplace',
4 | meta: {
5 | icon: 'i-bd-all-application',
6 | isAffix: false,
7 | isFull: false,
8 | isHide: false,
9 | isKeepAlive: true,
10 | isLink: '',
11 | index: 7,
12 | title: '工作台'
13 | },
14 | children: [
15 | {
16 | name: 'workplaceManage',
17 | path: '/workplace/manage',
18 | meta: {
19 | icon: 'i-bd-application',
20 | isAffix: false,
21 | isFull: false,
22 | isHide: false,
23 | isKeepAlive: true,
24 | isLink: '',
25 | title: '应用管理'
26 | }
27 | },
28 | {
29 | name: 'workplaceConfiguration',
30 | path: '/workplace/configuration',
31 | meta: {
32 | icon: 'i-bd-setting-config',
33 | isAffix: false,
34 | isFull: false,
35 | isHide: false,
36 | isKeepAlive: true,
37 | isLink: '',
38 | title: '工作台设置'
39 | }
40 | }
41 | ]
42 | };
43 | export default home;
44 |
--------------------------------------------------------------------------------
/src/pages/home/components/AddGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
24 |
25 |
新建群排名
26 |
27 |
28 |
29 |
{{ index + 1 }}
30 |
{{ item.time }}
31 |
{{ item.value }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
152 |
153 |
184 |
--------------------------------------------------------------------------------
/src/pages/home/components/AddUser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
24 |
25 |
注册量排名
26 |
27 |
28 |
29 |
{{ index + 1 }}
30 |
{{ item.time }}
31 |
{{ item.value }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
130 |
131 |
160 |
--------------------------------------------------------------------------------
/src/pages/home/components/Statistics.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
44 |
--------------------------------------------------------------------------------
/src/pages/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | meta:
22 | title: 首页
23 | isAffix: true
24 |
25 |
26 |
123 |
124 |
129 |
--------------------------------------------------------------------------------
/src/pages/message/components/Devices.vue:
--------------------------------------------------------------------------------
1 |
66 |
67 |
68 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | {{ item.formatter(scope.row) }}
88 |
89 |
90 | {{ scope.row[item.prop!] }}
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/src/pages/my/bb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | meta:
9 | title: 页面标题
10 |
11 |
12 |
15 |
16 |
19 |
--------------------------------------------------------------------------------
/src/pages/report/group.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ item.formatter(scope.row) }}
25 |
26 |
27 | {{ scope.row[item.prop!] }}
28 |
29 |
30 |
31 |
32 |
33 |
46 |
47 |
48 |
49 |
50 |
51 | meta:
52 | title: 举报群聊
53 | isAffix: false
54 |
55 |
56 |
161 |
162 |
168 |
--------------------------------------------------------------------------------
/src/pages/report/user.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ item.formatter(scope.row) }}
25 |
26 |
27 | {{ scope.row[item.prop!] }}
28 |
29 |
30 |
31 |
32 |
33 |
46 |
47 |
48 |
49 |
50 |
51 | meta:
52 | title: 举报用户
53 | isAffix: false
54 |
55 |
56 |
161 |
162 |
168 |
--------------------------------------------------------------------------------
/src/pages/setting/updatepwd.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 提交
25 | 重置
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | meta:
36 | title: 修改登录密码
37 |
38 |
39 |
110 |
111 |
116 |
--------------------------------------------------------------------------------
/src/pages/tool/appupdate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 | 新增版本
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ item.formatter(scope.row) }}
31 |
32 |
33 | {{ scope.row[item.prop!] }}
34 |
35 |
36 |
37 |
38 |
39 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | meta:
60 | title: APP升级
61 | isAffix: false
62 |
63 |
64 |
146 |
147 |
153 |
--------------------------------------------------------------------------------
/src/pages/user/adduser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 男
25 | 女
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 保存
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | meta:
46 | title: 添加用户
47 |
48 |
49 |
79 |
80 |
85 |
--------------------------------------------------------------------------------
/src/pages/user/administrator/components/AdminAdd.vue:
--------------------------------------------------------------------------------
1 |
79 |
80 |
81 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | 取消
106 | 确定
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/pages/user/administrator/index.vue:
--------------------------------------------------------------------------------
1 |
115 |
116 |
117 |
118 |
119 |
120 |
123 |
124 |
125 |
126 | 新增管理员
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | {{ item.formatter(scope.row) }}
146 |
147 |
148 | {{ scope.row[item.prop!] }}
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
167 |
168 |
169 | meta:
170 | title: 管理员
171 | isAffix: false
172 |
173 |
--------------------------------------------------------------------------------
/src/pages/user/userblacklist.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ $route.query.name }}
9 | 的黑名单
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 查询
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{ item.formatter(scope.row) }}
37 |
38 |
39 | {{ scope.row[item.prop!] }}
40 |
41 |
42 |
43 |
44 |
45 |
58 |
59 |
60 |
61 |
62 |
63 | meta:
64 | title: 黑名单列表
65 | isAffix: false
66 |
67 |
68 |
157 |
158 |
164 |
--------------------------------------------------------------------------------
/src/pages/workplace/configuration/components/CategoryDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
17 |
18 | 取消
19 | 保存
20 |
21 |
22 |
23 |
24 |
25 |
134 |
--------------------------------------------------------------------------------
/src/pages/workplace/configuration/components/Recommend.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 新增推荐应用
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{ item.formatter(scope.row) }}
35 |
36 |
37 | {{ scope.row[item.prop!] }}
38 |
39 |
40 |
41 |
42 |
43 |
56 |
57 |
58 |
59 |
60 |
155 |
156 |
162 |
--------------------------------------------------------------------------------
/src/pages/workplace/configuration/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | meta:
15 | title: 工作台设置
16 | isAffix: false
17 |
18 |
19 |
42 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router';
2 | import { useUserStore } from '@/stores/modules/user';
3 | import { useAuthStore } from '@/stores/modules/auth';
4 | import { LOGIN_URL, ROUTER_WHITE_LIST } from '@/config';
5 | import routes from './routers';
6 | import NProgress from '@/utils/nprogress';
7 | /**
8 | * @description 📚 路由参数配置简介
9 | * @param path ==> 菜单路径
10 | * @param name ==> 菜单别名
11 | * @param redirect ==> 重定向地址
12 | * @param component ==> 视图文件路径
13 | * @param meta ==> 菜单信息
14 | * @param meta.icon ==> 菜单图标
15 | * @param meta.title ==> 菜单标题
16 | * @param meta.activeMenu ==> 当前路由为详情页时,需要高亮的菜单
17 | * @param meta.isLink ==> 是否外链
18 | * @param meta.isHide ==> 是否隐藏
19 | * @param meta.isFull ==> 是否全屏(示例:数据大屏页面)
20 | * @param meta.isAffix ==> 是否固定在 tabs nav
21 | * @param meta.isKeepAlive ==> 是否缓存
22 | * */
23 | const router = createRouter({
24 | history: createWebHistory(),
25 | routes,
26 | strict: false,
27 | scrollBehavior: () => ({ left: 0, top: 0 })
28 | });
29 |
30 | /**
31 | * @description 路由拦截 beforeEach
32 | * */
33 | router.beforeEach(async (to, from, next) => {
34 | const authStore = useAuthStore();
35 | const userStore = useUserStore();
36 | // NProgress 开始
37 | NProgress.start();
38 |
39 | /** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */
40 | function toCorrectRoute() {
41 | ROUTER_WHITE_LIST.includes(to.fullPath) ? next(from.fullPath) : next();
42 | }
43 |
44 | if (userStore.token) {
45 | // 正常访问页面
46 | if (!authStore.authMenuListGet.length) {
47 | await authStore.getAuthMenuList();
48 | }
49 | toCorrectRoute();
50 | } else {
51 | if (to.path !== LOGIN_URL) {
52 | if (ROUTER_WHITE_LIST.indexOf(to.path) !== -1) {
53 | next();
54 | } else {
55 | next({ path: LOGIN_URL, replace: true });
56 | }
57 | } else {
58 | next();
59 | }
60 | }
61 | });
62 | /**
63 | * @description 路由跳转错误
64 | * */
65 | router.onError(error => {
66 | NProgress.done();
67 | console.warn('路由错误', error.message);
68 | });
69 | /**
70 | * @description 路由跳转结束
71 | * */
72 | router.afterEach(() => {
73 | NProgress.done();
74 | });
75 |
76 | /**
77 | * @description 重置路由
78 | * */
79 | export const resetRouter = () => {
80 | const authStore = useAuthStore();
81 | authStore.flatMenuListGet.forEach(route => {
82 | const { name } = route;
83 | if (name && router.hasRoute(name)) router.removeRoute(name);
84 | });
85 | };
86 |
87 | export default router;
88 |
--------------------------------------------------------------------------------
/src/router/routers.ts:
--------------------------------------------------------------------------------
1 | import type { RouteRecordRaw } from 'vue-router';
2 | import { setupLayouts } from 'virtual:meta-layouts';
3 | import generatedRoutes from 'virtual:generated-pages';
4 | import { staticRouter } from '@/router/staticRouter';
5 |
6 | const routes: RouteRecordRaw[] = setupLayouts(
7 | generatedRoutes.filter(item => {
8 | return item.meta?.enabled !== false && item.meta?.constant !== true && item.meta?.layout !== false;
9 | })
10 | );
11 |
12 | export default [...staticRouter, ...routes];
13 |
--------------------------------------------------------------------------------
/src/router/staticRouter.ts:
--------------------------------------------------------------------------------
1 | import { RouteRecordRaw } from 'vue-router';
2 | import { HOME_URL, LOGIN_URL } from '@/config';
3 |
4 | /**
5 | * staticRouter (静态路由)
6 | */
7 | export const staticRouter: RouteRecordRaw[] = [
8 | {
9 | path: '/',
10 | redirect: HOME_URL
11 | },
12 | {
13 | path: LOGIN_URL,
14 | name: 'login',
15 | component: () => import('@/pages/login/index.vue'),
16 | meta: {
17 | title: '登录'
18 | }
19 | }
20 | ];
21 |
--------------------------------------------------------------------------------
/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia';
2 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
3 |
4 | const pinia = createPinia();
5 | pinia.use(piniaPluginPersistedstate);
6 |
7 | export default pinia;
8 |
--------------------------------------------------------------------------------
/src/stores/interface/index.ts:
--------------------------------------------------------------------------------
1 | export type LayoutType = 'vertical' | 'classic' | 'transverse' | 'columns';
2 |
3 | export type AssemblySizeType = 'large' | 'default' | 'small';
4 |
5 | export type LanguageType = 'zh' | 'en' | null;
6 |
7 | /* GlobalState */
8 | export interface GlobalState {
9 | layout: LayoutType;
10 | assemblySize: AssemblySizeType;
11 | language: LanguageType;
12 | maximize: boolean;
13 | primary: string;
14 | isDark: boolean;
15 | isGrey: boolean;
16 | isWeak: boolean;
17 | asideInverted: boolean;
18 | isCollapse: boolean;
19 | breadcrumb: boolean;
20 | breadcrumbIcon: boolean;
21 | tabs: boolean;
22 | tabsIcon: boolean;
23 | footer: boolean;
24 | }
25 |
26 | /* UserState */
27 | export interface UserState {
28 | token: string;
29 | userInfo: { name: string; uid: string; role?: Menu.IRole };
30 | }
31 |
32 | /* tabsMenuProps */
33 | export interface TabsMenuProps {
34 | icon: string;
35 | title: string;
36 | path: string;
37 | name: string;
38 | close: boolean;
39 | isKeepAlive: boolean;
40 | }
41 |
42 | /* TabsState */
43 | export interface TabsState {
44 | tabsMenuList: TabsMenuProps[];
45 | }
46 |
47 | /* AuthState */
48 | export interface AuthState {
49 | routeName: string;
50 | authButtonList: {
51 | [key: string]: string[];
52 | };
53 | authMenuList: Menu.MenuOptions[];
54 | }
55 |
56 | /* KeepAliveState */
57 | export interface KeepAliveState {
58 | keepAliveName: string[];
59 | }
60 |
--------------------------------------------------------------------------------
/src/stores/modules/auth.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import { AuthState } from '@/stores/interface';
3 | import menu from '@/menu';
4 | import { getFlatMenuList, getShowMenuList, getAllBreadcrumbList } from '@/utils';
5 |
6 | import { useUserStore } from './user';
7 |
8 | /**
9 | * 菜单权限处理
10 | * @param menuList
11 | */
12 | function getAuthMenu(menuList: Menu.MenuOptions[]) {
13 | const newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList));
14 | const userStore = useUserStore();
15 | const role = userStore.userInfo?.role;
16 |
17 | return newMenuList.filter(item => {
18 | item.children?.length && (item.children = getAuthMenu(item.children));
19 | if (item.meta?.auth && role) {
20 | return item.meta?.auth.indexOf(role) !== -1;
21 | }
22 | return item;
23 | });
24 | }
25 |
26 | export const useAuthStore = defineStore({
27 | id: 'budou-auth',
28 | state: (): AuthState => ({
29 | // 按钮权限列表
30 | authButtonList: {},
31 | // 菜单权限列表
32 | authMenuList: [],
33 | // 当前页面的 router name,用来做按钮权限筛选
34 | routeName: ''
35 | }),
36 | getters: {
37 | // 按钮权限列表
38 | authButtonListGet: state => state.authButtonList,
39 | // 菜单权限列表 ==> 这里的菜单没有经过任何处理
40 | authMenuListGet: state => state.authMenuList,
41 | // 菜单权限列表 ==> 左侧菜单栏渲染,需要剔除 isHide == true
42 | showMenuListGet: state => getShowMenuList(state.authMenuList),
43 | // 菜单权限列表 ==> 扁平化之后的一维数组菜单,主要用来添加动态路由
44 | flatMenuListGet: state => getFlatMenuList(state.authMenuList),
45 | // 递归处理后的所有面包屑导航列表
46 | breadcrumbListGet: state => getAllBreadcrumbList(state.authMenuList)
47 | },
48 | actions: {
49 | // Get AuthButtonList
50 | async getAuthButtonList() {
51 | this.authButtonList = {};
52 | },
53 | // Get AuthMenuList
54 | async getAuthMenuList() {
55 | this.authMenuList = getAuthMenu(menu);
56 | },
57 |
58 | // Set RouteName
59 | async setRouteName(name: string) {
60 | this.routeName = name;
61 | }
62 | }
63 | });
64 |
--------------------------------------------------------------------------------
/src/stores/modules/global.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import { GlobalState } from '@/stores/interface';
3 | import { DEFAULT_PRIMARY } from '@/config';
4 | import piniaPersistConfig from '@/utils/piniaPersist';
5 |
6 | export const useGlobalStore = defineStore({
7 | id: 'budou-global',
8 | // 修改默认值之后,需清除 localStorage 数据
9 | state: (): GlobalState => ({
10 | // 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns)
11 | layout: 'columns',
12 | // element 组件大小
13 | assemblySize: 'default',
14 | // 当前系统语言
15 | language: null,
16 | // 当前页面是否全屏
17 | maximize: false,
18 | // 主题颜色
19 | primary: DEFAULT_PRIMARY,
20 | // 深色模式
21 | isDark: false,
22 | // 灰色模式
23 | isGrey: false,
24 | // 色弱模式
25 | isWeak: false,
26 | // 侧边栏反转 (目前仅支持 'vertical' 模式)
27 | asideInverted: false,
28 | // 折叠菜单
29 | isCollapse: false,
30 | // 面包屑导航
31 | breadcrumb: true,
32 | // 面包屑导航图标
33 | breadcrumbIcon: true,
34 | // 标签页
35 | tabs: true,
36 | // 标签页图标
37 | tabsIcon: true,
38 | // 页脚
39 | footer: true
40 | }),
41 | getters: {},
42 | actions: {
43 | // Set GlobalState
44 | setGlobalState(...args: ObjToKeyValArray) {
45 | this.$patch({ [args[0]]: args[1] });
46 | }
47 | },
48 | persist: piniaPersistConfig('budou-global')
49 | });
50 |
--------------------------------------------------------------------------------
/src/stores/modules/keepAlive.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import { KeepAliveState } from '@/stores/interface';
3 |
4 | export const useKeepAliveStore = defineStore({
5 | id: 'budou-keepAlive',
6 | state: (): KeepAliveState => ({
7 | keepAliveName: []
8 | }),
9 | actions: {
10 | // Add KeepAliveName
11 | async addKeepAliveName(name: string) {
12 | !this.keepAliveName.includes(name) && this.keepAliveName.push(name);
13 | },
14 | // Remove KeepAliveName
15 | async removeKeepAliveName(name: string) {
16 | this.keepAliveName = this.keepAliveName.filter(item => item !== name);
17 | },
18 | // Set KeepAliveName
19 | async setKeepAliveName(keepAliveName: string[] = []) {
20 | this.keepAliveName = keepAliveName;
21 | }
22 | }
23 | });
24 |
--------------------------------------------------------------------------------
/src/stores/modules/tabs.ts:
--------------------------------------------------------------------------------
1 | import router from '@/router';
2 | import { defineStore } from 'pinia';
3 | import { TabsState, TabsMenuProps } from '@/stores/interface';
4 | import piniaPersistConfig from '@/utils/piniaPersist';
5 |
6 | import { useKeepAliveStore } from './keepAlive';
7 | const keepAliveStore = useKeepAliveStore();
8 |
9 | export const useTabsStore = defineStore({
10 | id: 'budou-tabs',
11 | state: (): TabsState => ({
12 | tabsMenuList: []
13 | }),
14 | actions: {
15 | // Add Tabs
16 | async addTabs(tabItem: TabsMenuProps) {
17 | if (this.tabsMenuList.every(item => item.path !== tabItem.path)) {
18 | this.tabsMenuList.push(tabItem);
19 | }
20 |
21 | if (!keepAliveStore.keepAliveName.includes(tabItem.name) && tabItem.isKeepAlive) {
22 | await keepAliveStore.addKeepAliveName(tabItem.path);
23 | }
24 | },
25 | // Remove Tabs
26 | async removeTabs(tabPath: string, isCurrent = true) {
27 | const tabsMenuList = this.tabsMenuList;
28 | if (isCurrent) {
29 | tabsMenuList.forEach((item, index) => {
30 | if (item.path !== tabPath) return;
31 | const nextTab = tabsMenuList[index + 1] || tabsMenuList[index - 1];
32 | if (!nextTab) return;
33 | router.push(nextTab.path);
34 | });
35 | }
36 | this.tabsMenuList = tabsMenuList.filter(item => item.path !== tabPath);
37 | },
38 | // Close MultipleTab
39 | async closeMultipleTab(tabsMenuValue?: string) {
40 | this.tabsMenuList = this.tabsMenuList.filter(item => {
41 | return item.path === tabsMenuValue || !item.close;
42 | });
43 |
44 | const KeepAliveList = this.tabsMenuList.filter(item => item.isKeepAlive);
45 | await keepAliveStore.setKeepAliveName(KeepAliveList.map(item => item.path));
46 | },
47 | // Set Tabs
48 | async setTabs(tabsMenuList: TabsMenuProps[]) {
49 | this.tabsMenuList = tabsMenuList;
50 | },
51 | // Set Tabs Title
52 | async setTabsTitle(title: string) {
53 | const nowFullPath = location.hash.substring(1);
54 | this.tabsMenuList.forEach(item => {
55 | if (item.path == nowFullPath) item.title = title;
56 | });
57 | }
58 | },
59 | persist: piniaPersistConfig('budou-tabs')
60 | });
61 |
--------------------------------------------------------------------------------
/src/stores/modules/user.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import { UserState } from '@/stores/interface';
3 | import piniaPersistConfig from '@/utils/piniaPersist';
4 |
5 | export const useUserStore = defineStore({
6 | id: 'budou-user',
7 | state: (): UserState => ({
8 | token: '',
9 | userInfo: { name: '您好,超管', uid: '' }
10 | }),
11 | getters: {},
12 | actions: {
13 | // Set Token
14 | setToken(token: string) {
15 | this.token = token;
16 | },
17 | // Set setUserInfo
18 | setUserInfo(userInfo: UserState['userInfo']) {
19 | this.userInfo = userInfo;
20 | }
21 | },
22 | persist: piniaPersistConfig('budou-user')
23 | });
24 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './reset.scss';
2 | @import './theme/element-dark.scss';
3 | @import './element.scss';
4 |
5 |
--------------------------------------------------------------------------------
/src/styles/reset.scss:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-size: 14px;
4 | font-family: v-sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
5 | 'Segoe UI Symbol';
6 | line-height: 1.6;
7 | -webkit-text-size-adjust: 100%;
8 | -webkit-tap-highlight-color: transparent;
9 | }
10 |
11 | html,
12 | body,
13 | #app {
14 | width: 100%;
15 | height: 100%;
16 | padding: 0;
17 | margin: 0;
18 | }
19 |
20 | /* fade-transform */
21 | .fade-transform-leave-active,
22 | .fade-transform-enter-active {
23 | transition: all 0.2s;
24 | }
25 | .fade-transform-enter-from {
26 | opacity: 0;
27 | transition: all 0.2s;
28 | transform: translateX(-30px);
29 | }
30 | .fade-transform-leave-to {
31 | opacity: 0;
32 | transition: all 0.2s;
33 | transform: translateX(30px);
34 | }
35 |
36 | /* 解决 h1 标签在 webkit 内核浏览器中文字大小失效问题 */
37 | :-webkit-any(article, aside, nav, section) h1 {
38 | font-size: 2em;
39 | }
40 |
41 | /*
42 | 滚动条美化
43 | */
44 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
45 | // ::-webkit-scrollbar {
46 | // width: 7px;
47 | // height: 7px;
48 | // background-color: #78767628;
49 | // }
50 |
51 | // // /*定义滑块 内阴影+圆角*/
52 | // ::-webkit-scrollbar-thumb {
53 | // border-radius: 10px;
54 | // box-shadow: inset 0 0 6px #868687;
55 | // -webkit-box-shadow: inset 0 0 6px #868687;
56 | // background-color: #2080f0;
57 | // }
58 |
59 | /* scroll bar */
60 | ::-webkit-scrollbar {
61 | width: 6px;
62 | height: 6px;
63 | }
64 | ::-webkit-scrollbar-thumb {
65 | background-color: var(--el-border-color-darker);
66 | border-radius: 20px;
67 | }
68 |
69 | /* nprogress */
70 | #nprogress .bar {
71 | background: var(--el-color-primary) !important;
72 | }
73 | #nprogress .spinner-icon {
74 | border-top-color: var(--el-color-primary) !important;
75 | border-left-color: var(--el-color-primary) !important;
76 | }
77 | #nprogress .peg {
78 | box-shadow: 0 0 10px var(--el-color-primary), 0 0 5px var(--el-color-primary) !important;
79 | }
80 |
--------------------------------------------------------------------------------
/src/styles/theme/aside.ts:
--------------------------------------------------------------------------------
1 | export type AsideThemeType = 'light' | 'inverted' | 'dark';
2 |
3 | export const asideTheme: Record = {
4 | light: {
5 | '--el-logo-text-color': '#303133',
6 | '--el-menu-bg-color': '#ffffff',
7 | '--el-menu-hover-bg-color': '#cccccc',
8 | '--el-menu-active-bg-color': 'var(--el-color-primary-light-9)',
9 | '--el-menu-text-color': '#333333',
10 | '--el-menu-active-color': 'var(--el-color-primary)',
11 | '--el-menu-hover-text-color': '#333333',
12 | '--el-menu-horizontal-sub-item-height': '55px'
13 | },
14 | inverted: {
15 | '--el-logo-text-color': '#dadada',
16 | '--el-menu-bg-color': '#191a20',
17 | '--el-menu-hover-bg-color': '#000000',
18 | '--el-menu-active-bg-color': '#000000',
19 | '--el-menu-text-color': '#bdbdc0',
20 | '--el-menu-active-color': '#ffffff',
21 | '--el-menu-hover-text-color': '#ffffff',
22 | '--el-menu-horizontal-sub-item-height': '55px'
23 | },
24 | dark: {
25 | '--el-logo-text-color': '#dadada',
26 | '--el-menu-bg-color': '#141414',
27 | '--el-menu-hover-bg-color': '#000000',
28 | '--el-menu-active-bg-color': '#000000',
29 | '--el-menu-text-color': '#bdbdc0',
30 | '--el-menu-active-color': '#ffffff',
31 | '--el-menu-hover-text-color': '#ffffff',
32 | '--el-menu-horizontal-sub-item-height': '55px'
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/src/styles/theme/element-dark.scss:
--------------------------------------------------------------------------------
1 | /* 自定义 element 暗黑模式 */
2 | html.dark {
3 | /* wangEditor */
4 | --w-e-toolbar-color: #eeeeee;
5 | --w-e-toolbar-bg-color: #141414;
6 | --w-e-textarea-bg-color: #141414;
7 | --w-e-textarea-color: #eeeeee;
8 |
9 | /* login */
10 | .login-container {
11 | background-color: #191919 !important;
12 | .login-box {
13 | background-color: rgb(0 0 0 / 80%) !important;
14 | .login-form {
15 | box-shadow: rgb(255 255 255 / 12%) 0 2px 10px 2px !important;
16 | .logo-text {
17 | color: var(--el-text-color-primary) !important;
18 | }
19 | }
20 | }
21 | }
22 |
23 | /* layout */
24 | .el-container {
25 | // columns layout
26 | .aside-split {
27 | background-color: var(--el-bg-color) !important;
28 | }
29 | .el-header {
30 | background-color: var(--el-bg-color) !important;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/var.scss:
--------------------------------------------------------------------------------
1 | $primary-color: var(--el-color-primary);
2 |
--------------------------------------------------------------------------------
/src/types/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // Generated by unplugin-auto-import
5 | export {}
6 | declare global {
7 | const EffectScope: typeof import('vue')['EffectScope']
8 | const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
9 | const computed: typeof import('vue')['computed']
10 | const createApp: typeof import('vue')['createApp']
11 | const createPinia: typeof import('pinia')['createPinia']
12 | const customRef: typeof import('vue')['customRef']
13 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
14 | const defineComponent: typeof import('vue')['defineComponent']
15 | const defineStore: typeof import('pinia')['defineStore']
16 | const effectScope: typeof import('vue')['effectScope']
17 | const getActivePinia: typeof import('pinia')['getActivePinia']
18 | const getCurrentInstance: typeof import('vue')['getCurrentInstance']
19 | const getCurrentScope: typeof import('vue')['getCurrentScope']
20 | const h: typeof import('vue')['h']
21 | const inject: typeof import('vue')['inject']
22 | const isProxy: typeof import('vue')['isProxy']
23 | const isReactive: typeof import('vue')['isReactive']
24 | const isReadonly: typeof import('vue')['isReadonly']
25 | const isRef: typeof import('vue')['isRef']
26 | const mapActions: typeof import('pinia')['mapActions']
27 | const mapGetters: typeof import('pinia')['mapGetters']
28 | const mapState: typeof import('pinia')['mapState']
29 | const mapStores: typeof import('pinia')['mapStores']
30 | const mapWritableState: typeof import('pinia')['mapWritableState']
31 | const markRaw: typeof import('vue')['markRaw']
32 | const nextTick: typeof import('vue')['nextTick']
33 | const onActivated: typeof import('vue')['onActivated']
34 | const onBeforeMount: typeof import('vue')['onBeforeMount']
35 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
36 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
37 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
38 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
39 | const onDeactivated: typeof import('vue')['onDeactivated']
40 | const onErrorCaptured: typeof import('vue')['onErrorCaptured']
41 | const onMounted: typeof import('vue')['onMounted']
42 | const onRenderTracked: typeof import('vue')['onRenderTracked']
43 | const onRenderTriggered: typeof import('vue')['onRenderTriggered']
44 | const onScopeDispose: typeof import('vue')['onScopeDispose']
45 | const onServerPrefetch: typeof import('vue')['onServerPrefetch']
46 | const onUnmounted: typeof import('vue')['onUnmounted']
47 | const onUpdated: typeof import('vue')['onUpdated']
48 | const provide: typeof import('vue')['provide']
49 | const reactive: typeof import('vue')['reactive']
50 | const readonly: typeof import('vue')['readonly']
51 | const ref: typeof import('vue')['ref']
52 | const resolveComponent: typeof import('vue')['resolveComponent']
53 | const setActivePinia: typeof import('pinia')['setActivePinia']
54 | const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
55 | const shallowReactive: typeof import('vue')['shallowReactive']
56 | const shallowReadonly: typeof import('vue')['shallowReadonly']
57 | const shallowRef: typeof import('vue')['shallowRef']
58 | const storeToRefs: typeof import('pinia')['storeToRefs']
59 | const toRaw: typeof import('vue')['toRaw']
60 | const toRef: typeof import('vue')['toRef']
61 | const toRefs: typeof import('vue')['toRefs']
62 | const toValue: typeof import('vue')['toValue']
63 | const triggerRef: typeof import('vue')['triggerRef']
64 | const unref: typeof import('vue')['unref']
65 | const useAttrs: typeof import('vue')['useAttrs']
66 | const useCssModule: typeof import('vue')['useCssModule']
67 | const useCssVars: typeof import('vue')['useCssVars']
68 | const useLink: typeof import('vue-router')['useLink']
69 | const useRoute: typeof import('vue-router')['useRoute']
70 | const useRouter: typeof import('vue-router')['useRouter']
71 | const useSlots: typeof import('vue')['useSlots']
72 | const watch: typeof import('vue')['watch']
73 | const watchEffect: typeof import('vue')['watchEffect']
74 | const watchPostEffect: typeof import('vue')['watchPostEffect']
75 | const watchSyncEffect: typeof import('vue')['watchSyncEffect']
76 | }
77 | // for type re-export
78 | declare global {
79 | // @ts-ignore
80 | export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
81 | }
82 |
--------------------------------------------------------------------------------
/src/types/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // Generated by unplugin-vue-components
5 | // Read more: https://github.com/vuejs/core/pull/3399
6 | export {}
7 |
8 | declare module 'vue' {
9 | export interface GlobalComponents {
10 | BdAppVersion: typeof import('./../components/BdAppVersion/index.vue')['default']
11 | BdMsg: typeof import('./../components/BdMsg/index.vue')['default']
12 | BdPage: typeof import('./../components/BdPage/index.vue')['default']
13 | BdProhitWords: typeof import('./../components/BdProhitWords/index.vue')['default']
14 | BdSandAllMsg: typeof import('./../components/BdSandAllMsg/index.vue')['default']
15 | BdSendMsg: typeof import('./../components/BdSendMsg/index.vue')['default']
16 | RouterLink: typeof import('vue-router')['RouterLink']
17 | RouterView: typeof import('vue-router')['RouterView']
18 | SwitchDark: typeof import('./../components/SwitchDark/index.vue')['default']
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/types/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | declare module '*.vue' {
6 | import type { DefineComponent } from 'vue';
7 | const component: DefineComponent<{}, {}, any>;
8 | export default component;
9 | }
10 |
11 | // vue-grid-layout
12 | declare module 'vue-grid-layout';
13 |
--------------------------------------------------------------------------------
/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 | /* Menu */
2 | declare namespace Menu {
3 | type IRole = 'superAdmin' | 'admin';
4 | interface MenuOptions {
5 | path: string;
6 | name: string;
7 | component?: string | (() => Promise);
8 | redirect?: string;
9 | meta: MetaProps;
10 | children?: MenuOptions[];
11 | }
12 | interface MetaProps {
13 | icon: string;
14 | title: string;
15 | activeMenu?: string;
16 | auth?: IRole[];
17 | isLink?: string;
18 | index?: number;
19 | isHide?: boolean;
20 | isFull?: boolean;
21 | isAffix?: boolean;
22 | isKeepAlive?: boolean;
23 | }
24 | }
25 |
26 | /* FileType */
27 | declare namespace File {
28 | type ImageMimeType =
29 | | 'image/apng'
30 | | 'image/bmp'
31 | | 'image/gif'
32 | | 'image/jpeg'
33 | | 'image/pjpeg'
34 | | 'image/png'
35 | | 'image/svg+xml'
36 | | 'image/tiff'
37 | | 'image/webp'
38 | | 'image/x-icon';
39 |
40 | type ExcelMimeType = 'application/vnd.ms-excel' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
41 | }
42 |
43 | /* Vite */
44 | declare type Recordable = Record;
45 |
46 | declare interface ViteEnv {
47 | VITE_USER_NODE_ENV: 'development' | 'production' | 'test';
48 | VITE_GLOB_APP_TITLE: string;
49 | VITE_PORT: number;
50 | VITE_OPEN: boolean;
51 | VITE_REPORT: boolean;
52 | VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'gzip,brotli' | 'none';
53 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean;
54 | VITE_DROP_CONSOLE: boolean;
55 | VITE_PUBLIC_PATH: string;
56 | VITE_API_URL: string;
57 | VITE_PROXY: [string, string][];
58 | }
59 |
60 | interface ImportMetaEnv extends ViteEnv {
61 | __: unknown;
62 | }
63 |
64 | /* __APP_INFO__ */
65 | declare const __APP_INFO__: {
66 | pkg: {
67 | name: string;
68 | version: string;
69 | dependencies: Recordable;
70 | devDependencies: Recordable;
71 | };
72 | lastBuildTime: string;
73 | };
74 |
75 | /* Generic Tools */
76 | type ObjToKeyValUnion = {
77 | [K in keyof T]: { key: K; value: T[K] };
78 | }[keyof T];
79 |
80 | type ObjToKeyValArray = {
81 | [K in keyof T]: [K, T[K]];
82 | }[keyof T];
83 | declare namespace Column {
84 | interface ColumnOptions {
85 | prop?: string;
86 | label?: string;
87 | type?: 'selection' | 'index' | 'expand';
88 | fixed?: true | 'left' | 'right';
89 | width?: string | number;
90 | minWidth?: string | number;
91 | align?: 'left' | 'center' | 'right';
92 | formatter?: (scope: any) => void;
93 | render?: (scope?: any) => void;
94 | }
95 | }
96 |
97 | interface Window {
98 | TSDD_CONFIG: {
99 | APP_TITLE: string;
100 | APP_URL: string;
101 | };
102 | }
103 |
104 | declare const windos: Window & typeof globalThis;
105 |
--------------------------------------------------------------------------------
/src/utils/axios.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance, AxiosResponse } from 'axios';
2 | import { BU_DOU_CONFIG } from '@/config';
3 | import { useUserStore } from '@/stores/modules/user';
4 | import router from '@/router';
5 | const userStore = useUserStore();
6 |
7 | const axiosInstance: AxiosInstance = axios.create({
8 | baseURL: BU_DOU_CONFIG.APP_URL, // BASE_MAIN_URL
9 | withCredentials: false // 跨域请求时是否需要使用凭证
10 | });
11 |
12 | // request 拦截器
13 | axiosInstance.interceptors.request.use(
14 | config => {
15 | // 添加token
16 | if (userStore.token) {
17 | (config as any).headers['token'] = userStore.token;
18 | }
19 | return config;
20 | },
21 | (error: any) => {
22 | return Promise.reject(error);
23 | }
24 | );
25 |
26 | // respone 拦截器
27 | axiosInstance.interceptors.response.use(
28 | (response: AxiosResponse) => {
29 | return Promise.resolve(response.data);
30 | },
31 | (error: any) => {
32 | const code = error.response.status;
33 | if (code == 401) {
34 | userStore.setToken('');
35 | userStore.setUserInfo({ name: '您好,超管', uid: '' });
36 | router.replace('/login');
37 | }
38 | if (code == 400) {
39 | return Promise.reject(error.response.data);
40 | }
41 | // 响应失败
42 | return Promise.reject(error);
43 | }
44 | );
45 |
46 | export default axiosInstance;
47 |
--------------------------------------------------------------------------------
/src/utils/color.ts:
--------------------------------------------------------------------------------
1 | import { ElMessage } from 'element-plus';
2 |
3 | /**
4 | * @description hex颜色转rgb颜色
5 | * @param {String} str 颜色值字符串
6 | * @returns {String} 返回处理后的颜色值
7 | */
8 | export function hexToRgb(str: any) {
9 | let hexs: any = '';
10 | const reg = /^#?[0-9A-Fa-f]{6}$/;
11 | if (!reg.test(str)) return ElMessage.warning('输入错误的hex');
12 | str = str.replace('#', '');
13 | hexs = str.match(/../g);
14 | for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
15 | return hexs;
16 | }
17 |
18 | /**
19 | * @description rgb颜色转Hex颜色
20 | * @param {*} r 代表红色
21 | * @param {*} g 代表绿色
22 | * @param {*} b 代表蓝色
23 | * @returns {String} 返回处理后的颜色值
24 | */
25 | export function rgbToHex(r: any, g: any, b: any) {
26 | const reg = /^\d{1,3}$/;
27 | if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning('输入错误的rgb颜色值');
28 | const hexs = [r.toString(16), g.toString(16), b.toString(16)];
29 | for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
30 | return `#${hexs.join('')}`;
31 | }
32 |
33 | /**
34 | * @description 加深颜色值
35 | * @param {String} color 颜色值字符串
36 | * @param {Number} level 加深的程度,限0-1之间
37 | * @returns {String} 返回处理后的颜色值
38 | */
39 | export function getDarkColor(color: string, level: number) {
40 | const reg = /^#?[0-9A-Fa-f]{6}$/;
41 | if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值');
42 | const rgb = hexToRgb(color);
43 | for (let i = 0; i < 3; i++) rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level));
44 | return rgbToHex(rgb[0], rgb[1], rgb[2]);
45 | }
46 |
47 | /**
48 | * @description 变浅颜色值
49 | * @param {String} color 颜色值字符串
50 | * @param {Number} level 加深的程度,限0-1之间
51 | * @returns {String} 返回处理后的颜色值
52 | */
53 | export function getLightColor(color: string, level: number) {
54 | const reg = /^#?[0-9A-Fa-f]{6}$/;
55 | if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值');
56 | const rgb = hexToRgb(color);
57 | for (let i = 0; i < 3; i++) rgb[i] = Math.round(255 * level + rgb[i] * (1 - level));
58 | return rgbToHex(rgb[0], rgb[1], rgb[2]);
59 | }
60 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 获取localStorage
3 | * @param {String} key Storage名称
4 | * @returns {String}
5 | */
6 | export function localGet(key: string) {
7 | const value = window.localStorage.getItem(key);
8 | try {
9 | return JSON.parse(window.localStorage.getItem(key) as string);
10 | } catch (error) {
11 | return value;
12 | }
13 | }
14 |
15 | /**
16 | * @description 存储localStorage
17 | * @param {String} key Storage名称
18 | * @param {*} value Storage值
19 | * @returns {void}
20 | */
21 | export function localSet(key: string, value: any) {
22 | window.localStorage.setItem(key, JSON.stringify(value));
23 | }
24 |
25 | /**
26 | * @description 清除localStorage
27 | * @param {String} key Storage名称
28 | * @returns {void}
29 | */
30 | export function localRemove(key: string) {
31 | window.localStorage.removeItem(key);
32 | }
33 |
34 | /**
35 | * @description 清除所有localStorage
36 | * @returns {void}
37 | */
38 | export function localClear() {
39 | window.localStorage.clear();
40 | }
41 |
42 | /**
43 | * @description 判断数据类型
44 | * @param {*} val 需要判断类型的数据
45 | * @returns {String}
46 | */
47 | export function isType(val: any) {
48 | if (val === null) return 'null';
49 | if (typeof val !== 'object') return typeof val;
50 | else return Object.prototype.toString.call(val).slice(8, -1).toLocaleLowerCase();
51 | }
52 |
53 | /**
54 | * @description 生成唯一 uuid
55 | * @returns {String}
56 | */
57 | export function generateUUID() {
58 | let uuid = '';
59 | for (let i = 0; i < 32; i++) {
60 | const random = (Math.random() * 16) | 0;
61 | if (i === 8 || i === 12 || i === 16 || i === 20) uuid += '-';
62 | uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16);
63 | }
64 | return uuid;
65 | }
66 |
67 | /**
68 | * @description 获取浏览器默认语言
69 | * @returns {String}
70 | */
71 | export function getBrowserLang() {
72 | const browserLang = navigator.language ? navigator.language : (navigator as any).browserLanguage;
73 | let defaultBrowserLang = '';
74 | if (['cn', 'zh', 'zh-cn'].includes(browserLang.toLowerCase())) {
75 | defaultBrowserLang = 'zh';
76 | } else {
77 | defaultBrowserLang = 'en';
78 | }
79 | return defaultBrowserLang;
80 | }
81 |
82 | /**
83 | * @description 使用递归扁平化菜单,方便添加动态路由
84 | * @param {Array} menuList 菜单列表
85 | * @returns {Array}
86 | */
87 | export function getFlatMenuList(menuList: Menu.MenuOptions[]): Menu.MenuOptions[] {
88 | const newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList));
89 | return newMenuList.flatMap(item => [item, ...(item.children ? getFlatMenuList(item.children) : [])]);
90 | }
91 |
92 | /**
93 | * @description 使用递归过滤出需要渲染在左侧菜单的列表 (需剔除 isHide == true 的菜单)
94 | * @param {Array} menuList 菜单列表
95 | * @returns {Array}
96 | * */
97 | export function getShowMenuList(menuList: Menu.MenuOptions[]) {
98 | const newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList));
99 | return newMenuList.filter(item => {
100 | item.children?.length && (item.children = getShowMenuList(item.children));
101 | return !item.meta?.isHide;
102 | });
103 | }
104 |
105 | /**
106 | * @description 使用递归找出所有面包屑存储到 pinia/vuex 中
107 | * @param {Array} menuList 菜单列表
108 | * @param {Array} parent 父级菜单
109 | * @param {Object} result 处理后的结果
110 | * @returns {Object}
111 | */
112 | export const getAllBreadcrumbList = (menuList: Menu.MenuOptions[], parent = [], result: { [key: string]: any } = {}) => {
113 | for (const item of menuList) {
114 | result[item.path] = [...parent, item];
115 | if (item.children) getAllBreadcrumbList(item.children, result[item.path], result);
116 | }
117 | return result;
118 | };
119 |
120 | /**
121 | * @description 使用递归处理路由菜单 path,生成一维数组 (第一版本地路由鉴权会用到,该函数暂未使用)
122 | * @param {Array} menuList 所有菜单列表
123 | * @param {Array} menuPathArr 菜单地址的一维数组 ['**','**']
124 | * @returns {Array}
125 | */
126 | export function getMenuListPath(menuList: Menu.MenuOptions[], menuPathArr: string[] = []): string[] {
127 | for (const item of menuList) {
128 | if (typeof item === 'object' && item.path) menuPathArr.push(item.path);
129 | if (item.children?.length) getMenuListPath(item.children, menuPathArr);
130 | }
131 | return menuPathArr;
132 | }
133 |
134 | /**
135 | * @description 递归查询当前 path 所对应的菜单对象 (该函数暂未使用)
136 | * @param {Array} menuList 菜单列表
137 | * @param {String} path 当前访问地址
138 | * @returns {Object | null}
139 | */
140 | export function findMenuByPath(menuList: Menu.MenuOptions[], path: string): Menu.MenuOptions | null {
141 | for (const item of menuList) {
142 | if (item.path === path) return item;
143 | if (item.children) {
144 | const res = findMenuByPath(item.children, path);
145 | if (res) return res;
146 | }
147 | }
148 | return null;
149 | }
150 |
151 | /**
152 | * @description 使用递归过滤需要缓存的菜单 name (该函数暂未使用)
153 | * @param {Array} menuList 所有菜单列表
154 | * @param {Array} keepAliveNameArr 缓存的菜单 name ['**','**']
155 | * @returns {Array}
156 | * */
157 | export function getKeepAliveRouterName(menuList: Menu.MenuOptions[], keepAliveNameArr: string[] = []) {
158 | menuList.forEach(item => {
159 | item.meta.isKeepAlive && item.name && keepAliveNameArr.push(item.name);
160 | item.children?.length && getKeepAliveRouterName(item.children, keepAliveNameArr);
161 | });
162 | return keepAliveNameArr;
163 | }
164 |
--------------------------------------------------------------------------------
/src/utils/mittBus.ts:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt';
2 |
3 | const mittBus = mitt();
4 |
5 | export default mittBus;
6 |
--------------------------------------------------------------------------------
/src/utils/nprogress.ts:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress';
2 | import 'nprogress/nprogress.css';
3 |
4 | NProgress.configure({
5 | easing: 'ease', // 动画方式
6 | speed: 500, // 递增进度条的速度
7 | showSpinner: true, // 是否显示加载ico
8 | trickleSpeed: 200, // 自动递增间隔
9 | minimum: 0.3 // 初始化时的最小百分比
10 | });
11 |
12 | export default NProgress;
13 |
--------------------------------------------------------------------------------
/src/utils/piniaPersist.ts:
--------------------------------------------------------------------------------
1 | import { PersistedStateOptions } from 'pinia-plugin-persistedstate';
2 |
3 | /**
4 | * @description pinia 持久化参数配置
5 | * @param {String} key 存储到持久化的 name
6 | * @param {Array} paths 需要持久化的 state name
7 | * @return persist
8 | * */
9 | const piniaPersistConfig = (key: string, paths?: string[]) => {
10 | const persist: PersistedStateOptions = {
11 | key,
12 | storage: localStorage,
13 | paths
14 | };
15 | return persist;
16 | };
17 |
18 | export default piniaPersistConfig;
19 |
--------------------------------------------------------------------------------
/src/utils/system-copyright.ts:
--------------------------------------------------------------------------------
1 | if (import.meta.env.PROD) {
2 | const copyright_common_style = 'font-size: 14px; margin-bottom: 2px; padding: 6px 8px; color: #fff;';
3 | const copyright_main_style = `${copyright_common_style} background: #e24329;`;
4 | const copyright_sub_style = `${copyright_common_style} background: #707070;`;
5 | console.info(
6 | '%c由%c唐僧叨叨%c驱动',
7 | copyright_sub_style,
8 | copyright_main_style,
9 | copyright_sub_style,
10 | '\nhttps://tangsengdaodao.com/'
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "Node",
11 | "allowImportingTsExtensions": true,
12 | "allowSyntheticDefaultImports": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "preserve",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "baseUrl": "./",
24 | "paths": {
25 | "@/*": ["src/*"],
26 | "~/*": ["./*"]
27 | },
28 | },
29 | "include": [
30 | "vite.config.*",
31 | "uno.config.*",
32 | "src/**/*.ts",
33 | "src/**/*.d.ts",
34 | "src/**/*.tsx",
35 | "src/**/*.vue"
36 | ],
37 | "exclude": ["/dist/**", "node_modules"]
38 | }
39 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from '@unocss/vite';
2 | import presetUno from '@unocss/preset-uno';
3 |
4 | export default defineConfig({
5 | exclude: ['node_modules', 'dist', '.git', '.husky', '.vscode', 'public', 'build', 'mock', './stats.html'],
6 | presets: [presetUno({ dark: 'class' })],
7 | shortcuts: {
8 | 'wh-full': 'w-full h-full',
9 | 'flex-center': 'flex justify-center items-center',
10 | 'flex-col-center': 'flex-center flex-col',
11 | 'flex-x-center': 'flex justify-center',
12 | 'flex-y-center': 'flex items-center',
13 | 'i-flex-center': 'inline-flex justify-center items-center',
14 | 'i-flex-x-center': 'inline-flex justify-center',
15 | 'i-flex-y-center': 'inline-flex items-center',
16 | 'flex-col': 'flex flex-col',
17 | 'flex-col-stretch': 'flex-col items-stretch',
18 | 'i-flex-col': 'inline-flex flex-col',
19 | 'i-flex-col-stretch': 'i-flex-col items-stretch',
20 | 'flex-1-hidden': 'flex-1 overflow-hidden',
21 | 'absolute-lt': 'absolute left-0 top-0',
22 | 'absolute-lb': 'absolute left-0 bottom-0',
23 | 'absolute-rt': 'absolute right-0 top-0',
24 | 'absolute-rb': 'absolute right-0 bottom-0',
25 | 'absolute-tl': 'absolute-lt',
26 | 'absolute-tr': 'absolute-rt',
27 | 'absolute-bl': 'absolute-lb',
28 | 'absolute-br': 'absolute-rb',
29 | 'absolute-center': 'absolute-lt flex-center wh-full',
30 | 'fixed-lt': 'fixed left-0 top-0',
31 | 'fixed-lb': 'fixed left-0 bottom-0',
32 | 'fixed-rt': 'fixed right-0 top-0',
33 | 'fixed-rb': 'fixed right-0 bottom-0',
34 | 'fixed-tl': 'fixed-lt',
35 | 'fixed-tr': 'fixed-rt',
36 | 'fixed-bl': 'fixed-lb',
37 | 'fixed-br': 'fixed-rb',
38 | 'fixed-center': 'fixed-lt flex-center wh-full',
39 | 'nowrap-hidden': 'whitespace-nowrap overflow-hidden',
40 | 'ellipsis-text': 'nowrap-hidden overflow-ellipsis',
41 | 'transition-base': 'transition-all duration-300 ease-in-out'
42 | },
43 | theme: {}
44 | });
45 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, ConfigEnv, UserConfig } from 'vite';
2 | import { resolve } from 'path';
3 | // vite插件
4 | import VueDevTools from 'vite-plugin-vue-devtools';
5 | import vue from '@vitejs/plugin-vue';
6 | import vueJsx from '@vitejs/plugin-vue-jsx';
7 | import unocss from '@unocss/vite';
8 | import { createHtmlPlugin } from 'vite-plugin-html';
9 | import AutoImport from 'unplugin-auto-import/vite';
10 | import Components from 'unplugin-vue-components/vite';
11 | import setupExtend from 'unplugin-vue-setup-extend-plus/vite';
12 | import Layouts from 'vite-plugin-vue-meta-layouts';
13 | import Pages from 'vite-plugin-pages';
14 | import compression from 'vite-plugin-compression';
15 |
16 | const getPlugins = (_command?: string) => {
17 | return [
18 | AutoImport({
19 | include: [/\.[tj]sx?$/, /\.vue\?vue/, /\.md$/],
20 | imports: ['vue', 'vue-router', 'pinia'],
21 | resolvers: [],
22 | dts: 'src/types/auto-imports.d.ts'
23 | }),
24 | Components({
25 | include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
26 | resolvers: [],
27 | dts: 'src/types/components.d.ts'
28 | }),
29 | VueDevTools(),
30 | vue({
31 | template: {
32 | compilerOptions: {
33 | isCustomElement: tag => /^tgs-player/.test(tag)
34 | }
35 | }
36 | }),
37 | vueJsx(),
38 | createHtmlPlugin({
39 | inject: {
40 | data: {
41 | title: '唐僧叨叨后台管理',
42 | injectScript: process.env.IS_CONFIG ? `` : null
43 | }
44 | }
45 | }),
46 | unocss(),
47 | setupExtend({}),
48 | Layouts({
49 | defaultLayout: 'index'
50 | }),
51 | Pages({
52 | dirs: 'src/pages',
53 | exclude: ['**/components/*.vue']
54 | }),
55 | compression({
56 | ext: '.gz',
57 | deleteOriginFile: false
58 | })
59 | ];
60 | };
61 |
62 | export default defineConfig(({ command }: ConfigEnv): UserConfig => {
63 | return {
64 | resolve: {
65 | alias: {
66 | '@': resolve(__dirname, 'src'),
67 | 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
68 | }
69 | },
70 | define: {
71 | 'process.env': {
72 | APP_ENV: process.env.APP_ENV
73 | }
74 | },
75 | plugins: getPlugins(command),
76 | css: {
77 | postcss: {
78 | plugins: [
79 | {
80 | postcssPlugin: 'internal:charset-removal',
81 | AtRule: {
82 | charset: atRule => {
83 | if (atRule.name === 'charset') {
84 | atRule.remove();
85 | }
86 | }
87 | }
88 | }
89 | ]
90 | },
91 | preprocessorOptions: {
92 | scss: {
93 | additionalData: `@import "@/styles/var.scss";`
94 | }
95 | }
96 | },
97 | server: {
98 | host: '0.0.0.0'
99 | },
100 | build: {
101 | cssCodeSplit: false,
102 | sourcemap: false,
103 | emptyOutDir: true,
104 | chunkSizeWarningLimit: 1500,
105 | rollupOptions: {
106 | output: {
107 | chunkFileNames: 'static/js/[name]-[hash].js',
108 | entryFileNames: 'static/js/[name]-[hash].js',
109 | assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
110 | manualChunks: {
111 | // 分包配置,配置完成自动按需加载
112 | vue: ['vue', 'vue-router', 'pinia', 'vue-i18n', 'element-plus'],
113 | echarts: ['echarts'],
114 | 'tgs-player': ['@lottiefiles/lottie-player/dist/tgs-player']
115 | }
116 | }
117 | }
118 | }
119 | };
120 | });
121 |
--------------------------------------------------------------------------------