Browse Source

升级TS + 3.2

master
ZAN 2 years ago
parent
commit
fabc754190
  1. 3
      .env.development
  2. 3
      .env.production
  3. 2
      .eslintignore
  4. 173
      .eslintrc.js
  5. 22
      .gitignore
  6. 17
      .prettier.js
  7. 2
      .prettierignore
  8. 10
      .prettierrc.js
  9. 3
      .vscode/extensions.json
  10. 153
      README.md
  11. 54
      auto-imports.d.ts
  12. 17
      components.d.ts
  13. 25
      deploy.sh
  14. 53
      index.html
  15. 4699
      package-lock.json
  16. 64
      package.json
  17. 4241
      pnpm-lock.yaml
  18. 13
      postcss.config.js
  19. 61
      src/App.vue
  20. 175
      src/api/axios.js
  21. 15
      src/api/http.ts
  22. 37
      src/api/request.ts
  23. 62
      src/api/resource.js
  24. 59
      src/api/role.js
  25. 72
      src/api/user.js
  26. 11
      src/api/user.ts
  27. 24
      src/assets/css/font-awesome.css
  28. 2335
      src/assets/css/font-awesome.min.css
  29. 1
      src/assets/image/workbench.svg
  30. 15
      src/assets/js/dictionarie.ts
  31. 61
      src/assets/js/guide.js
  32. 70
      src/assets/js/guide.ts
  33. 201
      src/assets/js/resource.js
  34. 191
      src/assets/js/resource.ts
  35. 50
      src/assets/js/versionLog.js
  36. 41
      src/assets/js/versionLog.ts
  37. 12
      src/assets/scss/_animated.scss
  38. 38
      src/assets/scss/_bordered-pulled.scss
  39. 4
      src/assets/scss/_core.scss
  40. 2700
      src/assets/scss/_icons.scss
  41. 16
      src/assets/scss/_larger.scss
  42. 4
      src/assets/scss/_list.scss
  43. 15
      src/assets/scss/_mixins.scss
  44. 21
      src/assets/scss/_path.scss
  45. 20
      src/assets/scss/_rotated-flipped.scss
  46. 8
      src/assets/scss/_screen-reader.scss
  47. 15
      src/assets/scss/_stacked.scss
  48. 15
      src/assets/scss/_variables.scss
  49. 40
      src/assets/style/element-ui.scss
  50. 79
      src/assets/style/index.scss
  51. 8
      src/assets/style/main.scss
  52. 8
      src/common/event.ts
  53. 20
      src/common/mouse.ts
  54. 60
      src/common/wartermark.js
  55. 52
      src/common/watermark.ts
  56. 288
      src/components/Header/index.vue
  57. 71
      src/components/Pagination/index.vue
  58. 192
      src/components/Setting/baseInfo.vue
  59. 112
      src/components/Setting/checkPass.vue
  60. 178
      src/components/Tags/index.vue
  61. 21
      src/components/abnormal/403.vue
  62. 21
      src/components/abnormal/404.vue
  63. 21
      src/components/abnormal/build.vue
  64. 19
      src/components/abnormal/networkError.vue
  65. 96
      src/components/computerMonitor/index.vue
  66. 54
      src/components/copy/index.vue
  67. 309
      src/components/dashboard/analysis.vue
  68. 311
      src/components/dashboard/workbench.vue
  69. 31
      src/components/dialogDrag/index.vue
  70. 224
      src/components/dropDownItem/baseInfo.vue
  71. 139
      src/components/dropDownItem/checkPass.vue
  72. 62
      src/components/dropDownItem/versionLog.vue
  73. 16
      src/components/editor/markdown.vue
  74. 77
      src/components/editor/textEditor.vue
  75. 53
      src/components/i18n/index.vue
  76. 45
      src/components/infiniteScroll/index.vue
  77. 71
      src/components/message/feedbackCenter.vue
  78. 34
      src/components/noviceGuide/index.vue
  79. 19
      src/components/qrCode/index.vue
  80. 237
      src/components/template/cardList.vue
  81. 162
      src/components/template/easyForm.vue
  82. 234
      src/components/template/tableOperation.vue
  83. 25
      src/components/tips/errorTip.vue
  84. 20
      src/components/tips/successTip.vue
  85. 18
      src/components/tips/warningTip.vue
  86. 38
      src/components/watermark/index.vue
  87. 27
      src/directives/copy.js
  88. 27
      src/directives/copy.ts
  89. 70
      src/directives/dialogDrag.js
  90. 6
      src/directives/index.js
  91. 2
      src/directives/index.ts
  92. 14
      src/enums/index.ts
  93. 8
      src/env.d.ts
  94. 20
      src/filters/index.js
  95. 18
      src/filters/index.ts
  96. 288
      src/layouts/components/Header/index.vue
  97. 92
      src/layouts/components/SideBar/index.vue
  98. 270
      src/layouts/components/Tags/index.vue
  99. 3
      src/layouts/global/index.vue
  100. 84
      src/layouts/index.vue

3
.env.development

@ -0,0 +1,3 @@
NODE_ENV=development
VITE_APP_WEB_URL= '/api'

3
.env.production

@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_WEB_URL= '/api'

2
.eslintignore

@ -0,0 +1,2 @@
node_modules
dist

173
.eslintrc.js

@ -1,20 +1,157 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true
root: true,
env: {
browser: true,
node: true,
es2021: true,
},
parser: "vue-eslint-parser",
extends: [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
// eslint-config-prettier 的缩写
"prettier",
"vue-global-api",
],
parserOptions: {
ecmaVersion: 12,
parser: "@typescript-eslint/parser",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
extends: ['plugin:vue/essential', '@vue/prettier'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module'
},
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error'
}
}
},
// eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写
plugins: ["vue", "@typescript-eslint", "prettier"],
rules: {
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-var": "error",
"prettier/prettier": "error",
// 禁止出现console
"no-console": "warn",
// 禁用debugger
"no-debugger": "warn",
// 禁止出现重复的 case 标签
"no-duplicate-case": "warn",
// 禁止出现空语句块
"no-empty": "warn",
// 禁止不必要的括号
"no-extra-parens": "off",
// 禁止对 function 声明重新赋值
"no-func-assign": "warn",
// 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
"no-unreachable": "warn",
// 强制所有控制语句使用一致的括号风格
curly: "warn",
// 要求 switch 语句中有 default 分支
"default-case": "warn",
// 强制尽可能地使用点号
"dot-notation": "warn",
// 要求使用 === 和 !==
eqeqeq: "warn",
// 禁止 if 语句中 return 语句之后有 else 块
"no-else-return": "warn",
// 禁止出现空函数
"no-empty-function": "warn",
// 禁用不必要的嵌套块
"no-lone-blocks": "warn",
// 禁止使用多个空格
"no-multi-spaces": "warn",
// 禁止多次声明同一变量
"no-redeclare": "warn",
// 禁止在 return 语句中使用赋值语句
"no-return-assign": "warn",
// 禁用不必要的 return await
"no-return-await": "warn",
// 禁止自我赋值
"no-self-assign": "warn",
// 禁止自身比较
"no-self-compare": "warn",
// 禁止不必要的 catch 子句
"no-useless-catch": "warn",
// 禁止多余的 return 语句
"no-useless-return": "warn",
// 禁止变量声明与外层作用域的变量同名
"no-shadow": "off",
// 允许delete变量
"no-delete-var": "off",
// 强制数组方括号中使用一致的空格
"array-bracket-spacing": "warn",
// 强制在代码块中使用一致的大括号风格
"brace-style": "warn",
// 强制使用骆驼拼写法命名约定
camelcase: "warn",
// 强制使用一致的缩进
indent: "off",
// 强制在 JSX 属性中一致地使用双引号或单引号
// 'jsx-quotes': 'warn',
// 强制可嵌套的块的最大深度4
"max-depth": "warn",
// 强制最大行数 300
// "max-lines": ["warn", { "max": 1200 }],
// 强制函数最大代码行数 50
// 'max-lines-per-function': ['warn', { max: 70 }],
// 强制函数块最多允许的的语句数量20
"max-statements": ["warn", 100],
// 强制回调函数最大嵌套深度
"max-nested-callbacks": ["warn", 3],
// 强制函数定义中最多允许的参数数量
"max-params": ["warn", 3],
// 强制每一行中所允许的最大语句数量
"max-statements-per-line": ["warn", { max: 1 }],
// 要求方法链中每个调用都有一个换行符
"newline-per-chained-call": ["warn", { ignoreChainWithDepth: 3 }],
// 禁止 if 作为唯一的语句出现在 else 语句中
"no-lonely-if": "warn",
// 禁止空格和 tab 的混合缩进
"no-mixed-spaces-and-tabs": "warn",
// 禁止出现多行空行
"no-multiple-empty-lines": "warn",
// 禁止出现;
semi: ["warn", "never"],
// 强制在块之前使用一致的空格
"space-before-blocks": "warn",
// 强制在 function的左括号之前使用一致的空格
// 'space-before-function-paren': ['warn', 'never'],
// 强制在圆括号内使用一致的空格
"space-in-parens": "warn",
// 要求操作符周围有空格
"space-infix-ops": "warn",
// 强制在一元操作符前后使用一致的空格
"space-unary-ops": "warn",
// 强制在注释中 // 或 /* 使用一致的空格
// "spaced-comment": "warn",
// 强制在 switch 的冒号左右有空格
"switch-colon-spacing": "warn",
// 强制箭头函数的箭头前后使用一致的空格
"arrow-spacing": "warn",
"no-var": "warn",
"prefer-const": "warn",
"prefer-rest-params": "warn",
"no-useless-escape": "warn",
"no-irregular-whitespace": "warn",
"no-prototype-builtins": "warn",
"no-fallthrough": "warn",
"no-extra-boolean-cast": "warn",
"no-case-declarations": "warn",
"no-async-promise-executor": "warn",
},
globals: {
defineProps: "readonly",
defineEmits: "readonly",
defineExpose: "readonly",
withDefaults: "readonly",
},
};

22
.gitignore

@ -1,6 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
*.local
.idea
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

17
.prettier.js

@ -0,0 +1,17 @@
module.exports = {
tabWidth: 2,
jsxSingleQuote: true,
jsxBracketSameLine: true,
printWidth: 100,
singleQuote: true,
semi: false,
overrides: [
{
files: "*.json",
options: {
printWidth: 200,
},
},
],
arrowParens: "always",
};

2
.prettierignore

@ -0,0 +1,2 @@
node_modules
dist

10
.prettierrc.js

@ -1,10 +0,0 @@
module.exports = {
tabWidth: 2,
useTabs: false,
semi: false,
arrowParens: 'avoid',
singleQuote: true,
bracketSpacing: true,
endOfLine: 'lf',
trailingComma: 'none'
}

3
.vscode/extensions.json

@ -0,0 +1,3 @@
{
"recommendations": ["johnsoncodehk.volar"]
}

153
README.md

@ -1,152 +1,11 @@
# Vue 3 + Typescript + Vite
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
# Admin-Frame-Vue3
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
## 介绍
本项目是基于Vue3 + Element-Plus + Vite 开发的实现后台管理系统。
[访问地址](http://47.96.87.129:8001)
#### 1.1、使用技术
Vite + Vue3 + Element-Plus + Sass + Vuex + Vue-router + Vue-i8n + Echarts
#### 1.2、兼容性注意
Vite 需要 Node.js 版本 >= 12.0.0。
## 项目运行
`git clone https://github.com/ZANJIAHAO1008/Admin-Frame-Vue3.git`
`cd Admin-Frame-Vue3`
`npm install `
`npm run dev`
## 项目截图
![avatar](http://47.96.87.129:8001/denglu.png)
- ![avatar](http://47.96.87.129:8001/shouye.png)
## 目前包含功能
- 登陆
- 注册
- 首页
- 个人设置
- [x] 修改密码
- [x] 基础信息修改
- [x] 版权信息
- 头部
- [x] 全屏
- [x] 国际化
- 功能
- [x] 可拖拽弹框
- [x] 添加水印
- [x] 时间选择器
- [x] 地图
- [x] 复制
- [x] 生成二维码
- [x] 无限滚动
- [x] 监测电脑信息
- 表单模板
- [x] 基础表单
- [x] 表格表单
- [x] 卡片列表
- 编辑器
- [x] markdown
- [x] 富文本编辑器
- 消息管理
- [x] 消息中心
- 返回结果
- [x] 成功
- [x] 异常
- [x] 失败
- 异常页面
- [x] 404
- [x] 403
- [x] 暂无数据
- [x] 功能建设中
- [x] 网络不可用
- 权限管理
- [x] 用户管理
- [x] 角色管理
- [x] 资源管理
- 工作流程
- 新手引导
- 国际化
## 更新日志
**V1.0.1** 2021/11/24
​ 1、vue版本升级至3.2.22
​ 2、项目中添加vueuse库
​ 3、新增生成二维码、监测电脑信息功能。
**V1.0.2** 2021/11/26
​ 1、新增卡片列表
**V1.0.3** 2021/12/07
​ 1、新增版本日志.
2、新增工作流程(建设中).
​ 3、添加被动事件监听器来阻止touchstart事件.
**V1.0.4** 2021/12/08
​ 1、优化首屏加载等待动画.
​ 2、修复Echarts切换页面不显示问题.
**V1.0.5** 2021/12/09
​ 1、新增新手引导功能.
**V1.0.6** 2021/12/20
​ 1、配置国际化.
#### 有好的意见欢迎提issues 或者联系我 Q974813758
# 如果觉得有帮助的话 , 可以帮忙点个star吗
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.

54
auto-imports.d.ts

@ -0,0 +1,54 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const EffectScope: typeof import('vue')['EffectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
}
export {}

17
components.d.ts

@ -0,0 +1,17 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
declare module "vue" {
export interface GlobalComponents {
BaseInfo: typeof import("./src/components/Setting/baseInfo.vue")["default"];
CheckPass: typeof import("./src/components/Setting/checkPass.vue")["default"];
Header: typeof import("./src/components/Header/index.vue")["default"];
Pagination: typeof import("./src/components/Pagination/index.vue")["default"];
SideBar: typeof import("./src/components/SideBar/index.vue")["default"];
Tags: typeof import("./src/components/Tags/index.vue")["default"];
VersionLog: typeof import("./src/components/Setting/versionLog.vue")["default"];
}
}
export {};

25
deploy.sh

@ -0,0 +1,25 @@
#!/usr/bin/env sh
# 发生错误时终止
set -e
# 构建
npm run build
# 进入构建文件夹
cd dist
# 如果你要部署到自定义域名
# echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# 如果你要部署在 https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git main
# 如果你要部署在 https://<USERNAME>.github.io/<REPO>
# git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pages
cd -

53
index.html

@ -1,35 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/LG.png" rel="icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Frame</title>
</head>
<script src="https://unpkg.com/driver.js/dist/driver.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/driver.js/dist/driver.min.css">
<body>
<div id="app">
<div id="Loading">
<div class="loader-inner ball-beat">
<div></div>
<div></div>
<div></div>
<head>
<meta charset="UTF-8" />
<link href="/LG.png" rel="icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Admin Frame</title>
</head>
<!-- <script type="text/javascript"
src="https://cdn.jsdelivr.net/gh/Fuukei/Public_Repository@latest/static/js/sakura-less.js"></script> -->
<!-- <script src="https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script> -->
<body>
<div id="app">
<div id="Loading">
<div class="loader-inner ball-beat">
<div></div>
<div></div>
<div></div>
</div>
<div class="loading-hint">Loading...</div>
</div>
<div class="loading-hint">Loading...</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<style>
* {
padding: 0;
margin: 0;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
#Loading {
@ -69,8 +68,8 @@
}
}
.ball-beat>div {
background-color: #409EFF;
.ball-beat > div {
background-color: #409eff;
width: 10px;
height: 10px;
border-radius: 100% !important;
@ -82,7 +81,7 @@
animation: ball-beat 0.7s 0s infinite linear;
}
.ball-beat>div:nth-child(2n-1) {
.ball-beat > div:nth-child(2n-1) {
-webkit-animation-delay: 0.35s !important;
animation-delay: 0.35s !important;
}
@ -91,4 +90,4 @@
padding-top: 6px;
color: #909399;
}
</style>
</style>

4699
package-lock.json
File diff suppressed because it is too large
View File

64
package.json

@ -1,13 +1,20 @@
{
"name": "Admin-Frame",
"private": true,
"version": "1.0.6",
"description": "Admin-Frame 中/后台管理系统",
"description": "Admin-Frame 是一款中/后台管理系统",
"url": "https://github.com/ZANJIAHAO1008/Admin-Frame-Vue3",
"scripts": {
"dev": "vite",
"build": "vite build"
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview --port 8888",
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
"prettier": "prettier --write .",
"build:dev": "vue-tsc --noEmit && vite build --mode development",
"build:pro": "vue-tsc --noEmit && vite build --mode production"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@element-plus/icons-vue": "^1.1.4",
"@vueup/vue-quill": "^1.0.0-beta.7",
"@vueuse/core": "^7.0.3",
"@vueuse/integrations": "^7.0.3",
@ -18,29 +25,52 @@
"default-passive-events": "^2.0.0",
"driver.js": "^0.9.8",
"echarts": "^5.1.2",
"element-plus": "^1.0.2-beta.70",
"element-plus": "^2.1.7",
"js-cookie": "^2.2.1",
"jsplumb": "^2.15.6",
"md-editor-v3": "^1.4.4",
"mockjs": "^1.1.0",
"moment": "^2.29.1",
"nprogress": "^0.2.0",
"pinia": "^2.0.13",
"pinia-plugin-persist": "^1.0.0",
"qrcode": "^1.5.0",
"qs": "^6.10.1",
"vue": "^3.2.22",
"qs": "^6.10.3",
"screenfull": "^6.0.1",
"vite-plugin-mock": "^2.9.6",
"vue": "^3.2.25",
"vue-i18n": "^9.1.7",
"vue-router": "^4.0.0-beta.6",
"vuex": "^4.0.0"
"vue-router": "4",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.2.22",
"@vue/eslint-config-prettier": "^6.0.0",
"eslint": "^8.4.1",
"@types/js-cookie": "^3.0.1",
"@types/lodash-es": "^4.17.6",
"@types/mockjs": "^1.0.6",
"@types/node": "^17.0.23",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2",
"@types/qs": "^6.9.7",
"@types/quill": "^2.0.9",
"@types/webpack-env": "^1.16.3",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"@vitejs/plugin-vue": "^2.2.0",
"consola": "^2.15.3",
"eslint": "^8.12.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"less-loader": "^7.0.2",
"postcss": "^8.3.6",
"postcss-pxtorem": "^6.0.0",
"prettier": "^2.5.1",
"eslint-plugin-vue": "^8.6.0",
"prettier": "^2.6.2",
"sass": "^1.37.5",
"vite": "^1.0.0-rc.13"
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.6.9",
"unplugin-element-plus": "^0.3.4",
"unplugin-vue-components": "^0.18.5",
"vite": "^2.8.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-global-api": "^0.4.1",
"vue-tsc": "^0.29.8"
}
}

4241
pnpm-lock.yaml
File diff suppressed because it is too large
View File

13
postcss.config.js

@ -1,13 +0,0 @@
// module.exports = {
// plugins: {
// 'postcss-pxtorem': {
// rootValue: 75, //一个rem等于75个px;相当于vue/css文件中写width:75;会转换成width:1rem;再根据html的fontsize计算实际的width:
// propList: ['*'], //允许哪些属性转成rem;
// unitPrecision: 5, //最多小数位数;
// selectorBlackList: [/^\.van-/,/^\.mescroll/], //忽略.van-和.mescroll开头的类名;
// replace: true,
// mediaQuery: false, //允许在媒体查询中转换px
// minPixelValue: 2, //设置要替换的最小像素值
// }
// }
// }

61
src/App.vue

@ -1,44 +1,31 @@
<template>
<el-config-provider :locale="locale">
<router-view />
</el-config-provider>
<router-view />
</template>
<script>
import * as echarts from "echarts";
import { computed, getCurrentInstance, provide, watch } from "vue";
<script setup lang="ts">
import * as eCharts from "echarts";
import { provide } from "vue";
import Cookies from "js-cookie";
import { defineComponent, toRefs, reactive } from "vue";
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import en from "element-plus/lib/locale/lang/en";
export default defineComponent({
name: "App",
components: {
ElConfigProvider,
},
setup() {
provide("echarts", echarts); //穿Echarts
const state = reactive({
locale: computed(() => {
return Cookies.get("lang") === "en" ? en : zhCn;
}),
});
const proxy = getCurrentInstance();
const settingDefaultLang = () => {
//
if (!Cookies.get("lang")) {
Cookies.set("lang", "zh");
}
};
settingDefaultLang();
import { usePreferredLanguages } from "@vueuse/core";
const defaultLanguages = usePreferredLanguages();
return {
...toRefs(state),
};
},
});
provide("eCharts", eCharts); //穿eCharts
const setDefaultSetting = () => {
//
if (!Cookies.get("lang")) {
//
Cookies.set("lang", defaultLanguages?.value[1] ?? "zh");
}
if (!Cookies.get("size")) {
//size
Cookies.set("size", "default");
}
};
setDefaultSetting();
</script>
<style lang="scss">
@import "./src/style/index.scss";
@import "./src/style/element-ui.scss";
@import "@/assets/style/index.scss";
@import "@/assets/style/element-ui.scss";
</style>

175
src/api/axios.js

@ -1,175 +0,0 @@
import axios from 'axios';
import qs from 'qs';
import { ElMessage } from 'element-plus'
import { Loading } from '../util/utils.js';
import { getToken } from '../util/auth.js';
import router from "../router";
const pendingMap = new Map();
var loading = null;
export default function (config, options) {
//判断是否要展示loading 需要配置则在增加{loading:true} 默认false
let loadingStatus = options?.loading || undefined;
//判断是否要展示消息 需要配置则在增加{message:true} 默认false
let messageStatus = options?.message || undefined;
//判断是否展示未格式化的数据 需要配置则在增加{rawData:true} 默认false
let rawData = options?.rawData || false;
const service = axios.create({
// baseURL: 'xxxxx', // 设置统一的请求前缀
timeout: 10000, // 设置统一的超时时长
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return qs.stringify(data)
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
let res = JSON.parse(data);//返回数据类型转换
if (res.code == 200) {
!messageStatus || ElMessage.success(res.message)
} else if (res.code == 500) {
!messageStatus || ElMessage.error(res.message)
} else {
!messageStatus || ElMessage.warning(res.message)
}
return data;
}],
// `paramsSerializer` 是一个负责 `params` 序列化的函数
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
});
// 请求拦截
service.interceptors.request.use(config => {
removePending(config);
addPending(config);
//发起请求的时候 展示loading
!loadingStatus || (loading = Loading());
// 自动携带token
if (getToken() && typeof window !== "undefined") {
config.headers.Authorization = getToken();
}
return config;
}, error => {
return Promise.reject(error);
})
// 响应拦截
service.interceptors.response.use(
response => {
//返回请求的时候关闭loading
!loadingStatus || loading.close(), loading = null;
let data = JSON.parse(response.data);
if (data.code == 200) { //如果返回成功则返回数据 错误只能拿到null
return rawData ? response : data;
} else {
return null;
}
},
error => {
//返回请求的时候关闭loading
!loadingStatus || loading.close(), loading = null;
// error.config && removePending(error.config);
// if(error.config){
// removePending(error.config);
// }
httpErrorStatusHandle(error);//失败的回应
return Promise.reject(error);
}
);
return service(config)
};
function httpErrorStatusHandle(error) {
// 处理被取消的请求
if (axios.isCancel(error)) return console.error('请求的重复请求:' + error.message);
let message = '';
if (error && error.response) {
switch (error.response.status) {
case 302: message = '接口重定向了!'; break;
case 400: message = '参数不正确!'; break;
case 401: message = '您未登录,或者登录已经超时,请先登录!'; break;
case 403: message = '您没有权限操作!'; break;
case 404: message = `请求地址出错: ${error.response.config.url}`; break; // 在正确域名下
case 408: message = '请求超时!'; break;
case 409: message = '系统已存在相同数据!'; break;
case 500: message = '服务器内部错误!'; break;
case 501: message = '服务未实现!'; break;
case 502: message = '网关错误!'; break;
case 503: message = '服务不可用!'; break;
case 504: message = '服务暂时无法访问,请稍后再试!'; break;
case 505: message = 'HTTP版本不受支持!'; break;
default: message = '异常问题,请联系管理员!'; break
}
}
if (error.message.includes('timeout')) message = '网络请求超时!';
if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!';
ElMessage({
type: 'error',
message
})
if (error.response.status == '401') {
//如果登陆失效立马回到登录页
setTimeout(() => {
router.push({
path: "/login",
})
}, 2000)
}
}
/**
* 储存每个请求的唯一cancel回调, 以此为标识
* @param {*} config
*/
function addPending(config) {
const pendingKey = getPendingKey(config);
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, cancel);
}
});
}
/**
* 删除重复的请求
* @param {*} config
*/
function removePending(config) {
const pendingKey = getPendingKey(config);
if (pendingMap.has(pendingKey)) {
const cancelToken = pendingMap.get(pendingKey);
cancelToken(pendingKey);
pendingMap.delete(pendingKey);
}
}
/**
* 生成唯一的每个请求的唯一key
* @param {*} config
* @returns
*/
function getPendingKey(config) {
let { url, method, params, data } = config;
if (typeof data === 'string') data = JSON.parse(data); // response里面返回的config.data是个字符串对象
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}

15
src/api/http.ts

@ -0,0 +1,15 @@
import Request from "./request";
import * as qs from "qs";
export const http = new Request({
baseURL: import.meta.env.BASE_URL,
timeout: 10000,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
transformRequest: [
function (data, headers) {
// 对 data 进行任意转换处理
return qs.stringify(data);
},
],
});

37
src/api/request.ts

@ -0,0 +1,37 @@
import axios from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { getToken, removeToken } from "@/utils/auth";
class Request {
// axios 实例
instance: AxiosInstance | undefined;
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config);
this.instance.interceptors.request.use(
(req: AxiosRequestConfig) => {
//全局请求拦截器
if (getToken()) {
req.headers.token = getToken();
}
return req;
},
(err: any) => err
);
this.instance.interceptors.response.use(
//全局响应拦截器
(res: AxiosResponse) => {
return res.data;
},
(err: any) => err
);
}
request(config: AxiosRequestConfig) {
if (!this.instance) {
return;
}
return this.instance.request(config);
}
}
export default Request;

62
src/api/resource.js

@ -1,62 +0,0 @@
import request from './axios';
export function getAll(data) { // 资源列表查询
return request({
url: 'resource/getResource',
method: 'get',
params: data,
})
}
export function saveResource(data) { // 资源列表新增
return request({
url: 'resource/save',
method: 'post',
data: data,
}, {
loading: true,
message: true,
})
}
export function editResource(data) { // 资源列表编辑
return request({
url: 'resource/edit',
method: 'put',
data: data,
}, {
loading: true,
message: true,
})
}
export function delResource(data) { // 资源删除
return request({
url: 'resource/delete',
method: 'delete',
params: data,
}, {
loading: true,
message: true,
})
}
export function relationResource(data) { // 角色资源关联
return request({
url: 'resource/relation',
method: 'post',
data: data,
}, {
loading: true,
message: true,
})
}
export function getRoleRelation(data) { // 角色资源关联查询
return request({
url: 'resource/getRoleRelation',
method: 'get',
params: data,
})
}

59
src/api/role.js

@ -1,59 +0,0 @@
import request from './axios';
export function getRole(data) { // 角色查询
return request({
url: 'role/getRole',
method: 'get',
params: data,
})
}
export function saveRole(data) { // 角色新增
return request({
url: 'role/save',
method: 'post',
data: data,
}, {
loading: true,
message: true,
})
}
export function editRole(data) { // 角色编辑
return request({
url: 'role/edit',
method: 'put',
data: data,
}, {
loading: true,
message: true,
})
}
export function delRole(data) { // 角色删除
return request({
url: 'role/delete',
method: 'delete',
params: data,
}, {
loading: true,
message: true,
})
}
export function roleRelation(data) { // 角色关联用户
return request({
url: 'role/relationUser',
method: 'post',
data: data,
})
}
export function getUserRole(data) { // 角色关联用户
return request({
url: '/role/getUserRole',
method: 'get',
params: data,
})
}

72
src/api/user.js

@ -1,72 +0,0 @@
import request from './axios';
export function login(data) { //例子 登陆
return request({
url: '/user/login',
method: 'post',
data:data,
},{
message:true,
})
}
export function register(data) { //例子 注册
return request({
url: '/user/addUser',
method: 'post',
data:data,
},{
message:true,
loading:true,
})
}
export function getAll(data) { //例子 用户列表查询
return request({
url: '/user/getAll',
method: 'get',
params: data,
})
}
export function getUserInfo(data) { //例子 用户信息查询
return request({
url: '/user/getUserInfo',
method: 'get',
params: data,
})
}
export function delUser(data) { //例子 删除用户
return request({
url: '/user/delUser',
method: 'post',
data: data,
}, {
message: true,
loading: true,
})
}
export function changePass(data) { //密码修改
return request({
url: '/user/changePass',
method: 'post',
data: data,
}, {
message: true,
})
}
export function modifyBaseInfo(data) { //用户信息修改
return request({
url: '/user/modifyBaseInfo',
method: 'post',
data: data,
}, {
message: true,
})
}

11
src/api/user.ts

@ -0,0 +1,11 @@
import { http } from "./http";
export function getInfo(data: any) {
// 资源列表查询
return http.request({
url: "/api/login",
method: "post",
data,
});
}

24
src/assets/css/font-awesome.css

@ -5,9 +5,15 @@
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-family: "FontAwesome";
src: url("../fonts/fontawesome-webfont.eot?v=4.7.0");
src: url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0")
format("embedded-opentype"),
url("../fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),
url("../fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"),
url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),
url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular")
format("svg");
font-weight: normal;
font-style: normal;
}
@ -60,9 +66,9 @@
left: -1.85714286em;
}
.fa-border {
padding: .2em .25em .15em;
padding: 0.2em 0.25em 0.15em;
border: solid 0.08em #eeeeee;
border-radius: .1em;
border-radius: 0.1em;
}
.fa-pull-left {
float: left;
@ -71,10 +77,10 @@
float: right;
}
.fa.fa-pull-left {
margin-right: .3em;
margin-right: 0.3em;
}
.fa.fa-pull-right {
margin-left: .3em;
margin-left: 0.3em;
}
/* Deprecated as of 4.4.0 */
.pull-right {
@ -84,10 +90,10 @@
float: left;
}
.fa.pull-left {
margin-right: .3em;
margin-right: 0.3em;
}
.fa.pull-right {
margin-left: .3em;
margin-left: 0.3em;
}
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;

2335
src/assets/css/font-awesome.min.css
File diff suppressed because it is too large
View File

1
src/assets/image/workbench.svg
File diff suppressed because it is too large
View File

15
src/assets/js/dictionarie.ts

@ -0,0 +1,15 @@
export const FONT_SIZE = [
//字体大小设置
{
value: "large",
label: "大的按钮",
},
{
value: "default",
label: "标准按钮",
},
{
value: "small",
label: "小的按钮",
},
];

61
src/assets/js/guide.js

@ -1,61 +0,0 @@
//配置引导设置
const steps = [
{
element: '.noviceGuide',
popover: {
title: "Hello",
description: "欢迎来到Admin-Frame的新手引导",
position: "bottom"
}
},
{
element: '.zan-sidebar-nav',
popover: {
title: "介绍",
description: "Admin-Frame是一款中/后台管理系统",
position: "bottom"
}
},
{
element: '.fa-arrows-alt',
popover: {
title: "全屏",
description: "打开全屏模式",
position: "left"
}
},
{
element: '.g-language',
popover: {
title: "国际化",
description: "点击切换语言",
position: "left"
}
},
{
element: '.fa-bell-o',
popover: {
title: "消息中心",
description: "点击查看消息中心",
position: "left"
}
},
{
element: '.fabtn',
popover: {
title: "菜单开关",
description: "控制菜单的显示/隐藏",
position: "right"
}
},
{
element: '.el-menu',
popover: {
title: "快来试试吧~",
description: "菜单中包含首页图、各种功能、页面模板、编辑器、返回结果、异常页面、消息中心、权限管理等... 详情请点击菜单查看",
position: "right"
}
},
];
export default steps

70
src/assets/js/guide.ts

@ -0,0 +1,70 @@
//配置引导设置
const steps = [
{
element: ".noviceGuide",
popover: {
title: "Hello",
description: "欢迎来到Admin-Frame的新手引导",
position: "bottom",
},
},
{
element: ".admin-sidebar-nav",
popover: {
title: "介绍",
description: "Admin-Frame是一款中/后台管理系统",
position: "bottom",
},
},
{
element: ".fa-arrows-alt",
popover: {
title: "全屏",
description: "打开全屏模式",
position: "left",
},
},
{
element: ".g-language",
popover: {
title: "国际化",
description: "点击切换语言",
position: "left",
},
},
{
element: ".g-font",
popover: {
title: "按钮设置",
description: "点击切换按钮大小",
position: "left",
},
},
{
element: ".fa-bell-o",
popover: {
title: "我的消息",
description: "点击查看我的消息",
position: "left",
},
},
{
element: ".fabtn",
popover: {
title: "菜单开关",
description: "控制菜单的显示/隐藏",
position: "right",
},
},
{
element: ".el-menu",
popover: {
title: "快来试试吧~",
description:
"菜单中包含首页图、各种功能、页面模板、编辑器、返回结果、异常页面、消息中心、权限管理等... 详情请点击菜单查看",
position: "right",
},
},
];
export default steps;

201
src/assets/js/resource.js

@ -1,201 +0,0 @@
import { i18n } from '../../locales/i18n'; //国际化
const { global: { t, tm, locale } } = i18n;
export default [
//资源信息
{
resourceName: 'message.resource.homePage.name',
resourceUrl: "/homePage",
resourceIcon: 'fa fa-tachometer',
children: []
},
{
resourceName: "message.resource.function.name",
resourceUrl: "/gongneng",
resourceIcon: 'fa fa-wrench',
children: [
{
resourceName: "message.resource.function.chlidren.dialogDrag",
resourceUrl: "/dialogDrag",
children: []
},
{
resourceName: "message.resource.function.chlidren.wartermark",
resourceUrl: "/wartermark",
children: []
},
{
resourceName: "message.resource.function.chlidren.timePicker",
resourceUrl: "/timePicker",
children: []
},
{
resourceName: "message.resource.function.chlidren.map",
resourceUrl: "/map",
children: []
},
{
resourceName: "message.resource.function.chlidren.copy",
resourceUrl: "/copy",
children: []
},
{
resourceName: "message.resource.function.chlidren.qrcode",
resourceUrl: "/qrcode",
children: []
},
{
resourceName: "message.resource.function.chlidren.infiniteScroll",
resourceUrl: "/infiniteScroll",
children: []
},
{
resourceName: "message.resource.function.chlidren.computerMonitor",
resourceUrl: "/computerMonitor",
children: []
},
]
},
{
resourceName: "message.resource.template.name",
resourceUrl: "/content",
resourceIcon: "fa fa-file-text",
children: [
{
resourceName: "message.resource.template.chlidren.easyForm",
resourceUrl: "/easyForm",
children: []
},
{
resourceName: "message.resource.template.chlidren.tableOperation",
resourceUrl: "/tableOperation",
children: []
},
{
resourceName: "message.resource.template.chlidren.cardList",
resourceUrl: "/cardList",
children: []
},
]
},
{
resourceName: "message.resource.editor.name",
resourceUrl: "/editor",
resourceIcon: "fa fa-bold",
children: [
{
resourceName: "message.resource.editor.chlidren.markdown",
resourceUrl: "/markdown",
children: []
},
{
resourceName: "message.resource.editor.chlidren.textEditor",
resourceUrl: "/textEditor",
children: []
},
]
},
{
resourceName: "message.resource.result.name",
resourceUrl: "/result",
resourceIcon: "fa fa-random",
children: [
{
resourceName: "message.resource.result.chlidren.successTip",
resourceUrl: "/successTip",
children: []
}, {
resourceName: "message.resource.result.chlidren.warningTip",
resourceUrl: "/warningTip",
children: []
},
{
resourceName: "message.resource.result.chlidren.errorTip",
resourceUrl: "/errorTip",
children: []
},
]
},
{
resourceName: "message.resource.abnormalPage.name",
resourceUrl: "/error",
resourceIcon: "fa fa-exclamation-triangle",
children: [{
resourceName: "message.resource.abnormalPage.children.404",
resourceUrl: "/404",
children: []
},
{
resourceName: "message.resource.abnormalPage.children.403",
resourceUrl: "/403",
children: []
},
{
resourceName: "message.resource.abnormalPage.children.noData",
resourceUrl: "/noData",
children: []
},
{
resourceName: "message.resource.abnormalPage.children.build",
resourceUrl: "/build",
children: []
},
{
resourceName: "message.resource.abnormalPage.children.networkError",
resourceUrl: "/networkError",
children: []
},
]
},
{
resourceName: "message.resource.workflow.name",
resourceUrl: "/workflow",
resourceIcon: 'fa fa-crosshairs',
children: []
},
{
resourceName: "message.resource.message.name",
resourceUrl: "/messageManagement",
resourceIcon: "fa fa-comment",
children: [
{
resourceName: "message.resource.message.children.messageCenter",
resourceUrl: "/messageCenter",
children: []
}
]
},
{
resourceName: "message.resource.authority.name",
resourceUrl: "/setting",
resourceIcon: "fa fa-cog",
children: [
{
resourceName: "message.resource.authority.children.user",
resourceUrl: "/user",
children: []
},
{
resourceName: "message.resource.authority.children.role",
resourceUrl: "/role",
children: []
},
{
resourceName: "message.resource.authority.children.resource",
resourceUrl: "/resource",
children: []
},
]
},
{
resourceName: "message.resource.noviceGuide.name",
resourceUrl: "/noviceGuide",
resourceIcon: 'fa fa-question-circle-o',
children: []
},
{
resourceName: "message.resource.i18n.name",
resourceUrl: "/i18n",
resourceIcon: 'fa fa-language',
children: []
},
]

191
src/assets/js/resource.ts

@ -0,0 +1,191 @@
export default [
//资源信息
{
resourceName: "message.resource.dashboard.name",
resourceUrl: "/dashboard",
resourceIcon: "fa fa-tachometer",
children: [
{
resourceName: "message.resource.dashboard.children.workbench",
resourceUrl: "/dashboard/workbench",
children: [],
},
{
resourceName: "message.resource.dashboard.children.analysis",
resourceUrl: "/dashboard/analysis",
children: [],
},
],
},
{
resourceName: "message.resource.function.name",
resourceUrl: "/components",
resourceIcon: "fa fa-wrench",
children: [
{
resourceName: "message.resource.function.children.dialogDrag",
resourceUrl: "/components/dialogDrag",
children: [],
},
{
resourceName: "message.resource.function.children.watermark",
resourceUrl: "/components/watermark",
children: [],
},
{
resourceName: "message.resource.function.children.copy",
resourceUrl: "/components/copy",
children: [],
},
{
resourceName: "message.resource.function.children.qrCode",
resourceUrl: "/components/qrCode",
children: [],
},
{
resourceName: "message.resource.function.children.infiniteScroll",
resourceUrl: "/components/infiniteScroll",
children: [],
},
{
resourceName: "message.resource.function.children.computerMonitor",
resourceUrl: "/components/computerMonitor",
children: [],
},
],
},
{
resourceName: "message.resource.template.name",
resourceUrl: "/template",
resourceIcon: "fa fa-file-text",
children: [
{
resourceName: "message.resource.template.children.easyForm",
resourceUrl: "/template/easyForm",
children: [],
},
{
resourceName: "message.resource.template.children.tableOperation",
resourceUrl: "/template/tableOperation",
children: [],
},
{
resourceName: "message.resource.template.children.cardList",
resourceUrl: "/template/cardList",
children: [],
},
],
},
{
resourceName: "message.resource.editor.name",
resourceUrl: "/editor",
resourceIcon: "fa fa-bold",
children: [
{
resourceName: "message.resource.editor.children.markdown",
resourceUrl: "/editor/markdown",
children: [],
},
{
resourceName: "message.resource.editor.children.textEditor",
resourceUrl: "/editor/textEditor",
children: [],
},
],
},
{
resourceName: "message.resource.result.name",
resourceUrl: "/tips",
resourceIcon: "fa fa-random",
children: [
{
resourceName: "message.resource.result.children.successTip",
resourceUrl: "/tips/successTip",
children: [],
},
{
resourceName: "message.resource.result.children.warningTip",
resourceUrl: "/tips/warningTip",
children: [],
},
{
resourceName: "message.resource.result.children.errorTip",
resourceUrl: "/tips/errorTip",
children: [],
},
],
},
{
resourceName: "message.resource.abnormalPage.name",
resourceUrl: "/global/abnormal",
resourceIcon: "fa fa-exclamation-triangle",
children: [
{
resourceName: "message.resource.abnormalPage.children.404",
resourceUrl: "/global/abnormal/404",
children: [],
},
{
resourceName: "message.resource.abnormalPage.children.403",
resourceUrl: "/global/abnormal/403",
children: [],
},
{
resourceName: "message.resource.abnormalPage.children.build",
resourceUrl: "/global/abnormal/build",
children: [],
},
{
resourceName: "message.resource.abnormalPage.children.networkError",
resourceUrl: "/global/abnormal/networkError",
children: [],
},
],
},
{
resourceName: "message.resource.message.name",
resourceUrl: "/messageManagement",
resourceIcon: "fa fa-comment",
children: [
{
resourceName: "message.resource.message.children.feedbackCenter",
resourceUrl: "/main/feedbackCenter",
children: [],
},
],
},
{
resourceName: "message.resource.authority.name",
resourceUrl: "/setting",
resourceIcon: "fa fa-cog",
children: [
{
resourceName: "message.resource.authority.children.user",
resourceUrl: "/setting/user",
children: [],
},
{
resourceName: "message.resource.authority.children.role",
resourceUrl: "/setting/role",
children: [],
},
{
resourceName: "message.resource.authority.children.resource",
resourceUrl: "/setting/resource",
children: [],
},
],
},
{
resourceName: "message.resource.noviceGuide.name",
resourceUrl: "/main/noviceGuide",
resourceIcon: "fa fa-question-circle-o",
children: [],
},
{
resourceName: "message.resource.i18n.name",
resourceUrl: "/main/i18n",
resourceIcon: "fa fa-language",
children: [],
},
];

50
src/assets/js/versionLog.js

@ -1,50 +0,0 @@
//记录版本日志
export const versionLog = [
{
version: "V1.0.6",
description:
['配置国际化.',
],
releaseDate: "2021/12/20"
},
{
version: "V1.0.5",
description:
['新增新手引导功能.',
],
releaseDate: "2021/12/09"
},
{
version: "V1.0.4",
description:
['优化首屏加载等待动画.',
'修复Echarts切换页面不显示问题.',
],
releaseDate: "2021/12/08"
},
{
version: "V1.0.3",
description:
['新增版本日志.',
'新增工作流程(建设中).',
'添加被动事件监听器来阻止touchstart事件.',
],
releaseDate: "2021/12/07"
},
{
version: "V1.0.2",
description:
['新增卡片列表.'
],
releaseDate: "2021/11/26"
},
{
version: "V1.0.1",
description:
['vue版本升级至3.2.22.',
'项目中添加vueuse库.',
'新增生成二维码、监测电脑信息功能.'
],
releaseDate: "2021/11/24"
},
];

41
src/assets/js/versionLog.ts

@ -0,0 +1,41 @@
//记录版本日志
export const versionLog = [
{
version: "V1.0.6",
description: ["配置国际化."],
releaseDate: "2021/12/20",
},
{
version: "V1.0.5",
description: ["新增新手引导功能."],
releaseDate: "2021/12/09",
},
{
version: "V1.0.4",
description: ["优化首屏加载等待动画.", "修复Echarts切换页面不显示问题."],
releaseDate: "2021/12/08",
},
{
version: "V1.0.3",
description: [
"新增版本日志.",
"新增工作流程(建设中).",
"添加被动事件监听器来阻止touchstart事件.",
],
releaseDate: "2021/12/07",
},
{
version: "V1.0.2",
description: ["新增卡片列表."],
releaseDate: "2021/11/26",
},
{
version: "V1.0.1",
description: [
"vue版本升级至3.2.22.",
"项目中添加vueUse库.",
"新增生成二维码、监测电脑信息功能.",
],
releaseDate: "2021/11/24",
},
];

12
src/assets/scss/_animated.scss

@ -3,32 +3,32 @@
.#{$fa-css-prefix}-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.#{$fa-css-prefix}-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
transform: rotate(359deg);
}
}

38
src/assets/scss/_bordered-pulled.scss

@ -2,24 +2,40 @@
// -------------------------
.#{$fa-css-prefix}-border {
padding: .2em .25em .15em;
border: solid .08em $fa-border-color;
border-radius: .1em;
padding: 0.2em 0.25em 0.15em;
border: solid 0.08em $fa-border-color;
border-radius: 0.1em;
}
.#{$fa-css-prefix}-pull-left { float: left; }
.#{$fa-css-prefix}-pull-right { float: right; }
.#{$fa-css-prefix}-pull-left {
float: left;
}
.#{$fa-css-prefix}-pull-right {
float: right;
}
.#{$fa-css-prefix} {
&.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
&.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
&.#{$fa-css-prefix}-pull-left {
margin-right: 0.3em;
}
&.#{$fa-css-prefix}-pull-right {
margin-left: 0.3em;
}
}
/* Deprecated as of 4.4.0 */
.pull-right { float: right; }
.pull-left { float: left; }
.pull-right {
float: right;
}
.pull-left {
float: left;
}
.#{$fa-css-prefix} {
&.pull-left { margin-right: .3em; }
&.pull-right { margin-left: .3em; }
&.pull-left {
margin-right: 0.3em;
}
&.pull-right {
margin-left: 0.3em;
}
}

4
src/assets/scss/_core.scss

@ -3,10 +3,10 @@
.#{$fa-css-prefix} {
display: inline-block;
font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base}
FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

2700
src/assets/scss/_icons.scss
File diff suppressed because it is too large
View File

16
src/assets/scss/_larger.scss

@ -7,7 +7,15 @@
line-height: (3em / 4);
vertical-align: -15%;
}
.#{$fa-css-prefix}-2x { font-size: 2em; }
.#{$fa-css-prefix}-3x { font-size: 3em; }
.#{$fa-css-prefix}-4x { font-size: 4em; }
.#{$fa-css-prefix}-5x { font-size: 5em; }
.#{$fa-css-prefix}-2x {
font-size: 2em;
}
.#{$fa-css-prefix}-3x {
font-size: 3em;
}
.#{$fa-css-prefix}-4x {
font-size: 4em;
}
.#{$fa-css-prefix}-5x {
font-size: 5em;
}

4
src/assets/scss/_list.scss

@ -5,7 +5,9 @@
padding-left: 0;
margin-left: $fa-li-width;
list-style-type: none;
> li { position: relative; }
> li {
position: relative;
}
}
.#{$fa-css-prefix}-li {
position: absolute;

15
src/assets/scss/_mixins.scss

@ -3,29 +3,28 @@
@mixin fa-icon() {
display: inline-block;
font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base}
FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@mixin fa-icon-rotate($degrees, $rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})";
-webkit-transform: rotate($degrees);
-ms-transform: rotate($degrees);
transform: rotate($degrees);
-ms-transform: rotate($degrees);
transform: rotate($degrees);
}
@mixin fa-icon-flip($horiz, $vert, $rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)";
-webkit-transform: scale($horiz, $vert);
-ms-transform: scale($horiz, $vert);
transform: scale($horiz, $vert);
-ms-transform: scale($horiz, $vert);
transform: scale($horiz, $vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
@ -37,7 +36,7 @@
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
clip: rect(0, 0, 0, 0);
border: 0;
}

21
src/assets/scss/_path.scss

@ -2,14 +2,19 @@
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
// src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-family: "FontAwesome";
src: url("#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}");
src: url("#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}")
format("embedded-opentype"),
url("#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}")
format("woff2"),
url("#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}")
format("woff"),
url("#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}")
format("truetype"),
url("#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular")
format("svg");
// src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-weight: normal;
font-style: normal;
}

20
src/assets/scss/_rotated-flipped.scss

@ -1,12 +1,22 @@
// Rotated & Flipped Icons
// -------------------------
.#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
.#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
.#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
.#{$fa-css-prefix}-rotate-90 {
@include fa-icon-rotate(90deg, 1);
}
.#{$fa-css-prefix}-rotate-180 {
@include fa-icon-rotate(180deg, 2);
}
.#{$fa-css-prefix}-rotate-270 {
@include fa-icon-rotate(270deg, 3);
}
.#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
.#{$fa-css-prefix}-flip-horizontal {
@include fa-icon-flip(-1, 1, 0);
}
.#{$fa-css-prefix}-flip-vertical {
@include fa-icon-flip(1, -1, 2);
}
// Hook for IE8-9
// -------------------------

8
src/assets/scss/_screen-reader.scss

@ -1,5 +1,9 @@
// Screen Readers
// -------------------------
.sr-only { @include sr-only(); }
.sr-only-focusable { @include sr-only-focusable(); }
.sr-only {
@include sr-only();
}
.sr-only-focusable {
@include sr-only-focusable();
}

15
src/assets/scss/_stacked.scss

@ -9,12 +9,19 @@
line-height: 2em;
vertical-align: middle;
}
.#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x {
.#{$fa-css-prefix}-stack-1x,
.#{$fa-css-prefix}-stack-2x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
.#{$fa-css-prefix}-stack-1x { line-height: inherit; }
.#{$fa-css-prefix}-stack-2x { font-size: 2em; }
.#{$fa-css-prefix}-inverse { color: $fa-inverse; }
.#{$fa-css-prefix}-stack-1x {
line-height: inherit;
}
.#{$fa-css-prefix}-stack-2x {
font-size: 2em;
}
.#{$fa-css-prefix}-inverse {
color: $fa-inverse;
}

15
src/assets/scss/_variables.scss

@ -1,15 +1,15 @@
// Variables
// --------------------------
$fa-font-path: "../fonts" !default;
$fa-font-size-base: 14px !default;
$fa-font-path: "../fonts" !default;
$fa-font-size-base: 14px !default;
$fa-line-height-base: 1 !default;
//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly
$fa-css-prefix: fa !default;
$fa-version: "4.7.0" !default;
$fa-border-color: #eee !default;
$fa-inverse: #fff !default;
$fa-li-width: (30em / 14) !default;
$fa-css-prefix: fa !default;
$fa-version: "4.7.0" !default;
$fa-border-color: #eee !default;
$fa-inverse: #fff !default;
$fa-li-width: (30em / 14) !default;
$fa-var-500px: "\f26e";
$fa-var-address-book: "\f2b9";
@ -797,4 +797,3 @@ $fa-var-yoast: "\f2b1";
$fa-var-youtube: "\f167";
$fa-var-youtube-play: "\f16a";
$fa-var-youtube-square: "\f166";

40
src/style/element-ui.scss → src/assets/style/element-ui.scss

@ -35,7 +35,6 @@
//form
//tag
//.el-tag--dark {
// background-color: rgb(48, 92, 208) !important;
@ -57,6 +56,7 @@
display: none;
}
}
.el-menu:not(.el-menu--collapse) {
width: 200px;
}
@ -69,13 +69,10 @@
width: 0;
}
.sidebar > ul {
height: calc(100% - 65px);
}
.el-menu-item:focus,
.el-menu-item:hover {
outline: 0px;
@ -90,34 +87,37 @@
padding-left: 8px !important;
}
.sidebar-nullIcon{
.sidebar-nullIcon {
display: block;
text-align: center;
}
// dialog
.el-dialog{
border-radius: 6px 6px 0 0!important;
.el-dialog__header{
background: #6190E8; /* fallback for old browsers */
background: -webkit-linear-gradient(to left, #A7BFE8, #6190E8); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to left, #A7BFE8, #6190E8); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
.el-dialog {
border-radius: 6px 6px 0 0 !important;
.el-dialog__header {
background: #6190e8;
/* fallback for old browsers */
background: -webkit-linear-gradient(to left, #a7bfe8, #6190e8);
/* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to left, #a7bfe8, #6190e8);
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
padding: 15px 10px 10px 10px !important;
border-radius: 6px 6px 0 0!important;
border-radius: 6px 6px 0 0 !important;
margin-right: 0px !important;
}
.el-dialog__title,.el-icon-close{
.el-dialog__title,
.el-icon-close {
color: #fff !important;
font-size: 16px !important;
}
}
// drawer
#el-drawer__title{
padding: 10px!important;
margin-bottom: 20px!important;
font-size: 16px !important;
#el-drawer__title {
padding: 10px !important;
margin-bottom: 20px !important;
font-size: 16px !important;
}

79
src/style/index.scss → src/assets/style/index.scss

@ -1,53 +1,43 @@
@mixin min-wid($width) {
// 最低宽度
min-width: $width;
};
}
@mixin max-wid($width) {
// 最大宽度
max-width: $width;
};
}
@mixin min-hei($height) {
// 最低高度
// 最低高度
min-height: $height;
};
}
@mixin max-hei($height) {
// 最高高度
max-height: $height;
};
@mixin font-s($size){
max-height: $height;
}
@mixin font-s($size) {
//字体大小
font-size: $size;
}
@mixin font-col($color){
@mixin font-col($color) {
//字体颜色
color: $color;
color: $color;
}
@mixin abnormal {
//异常页面样式
@include max-hei(300px);
};
}
.abnormal-img{
.abnormal-img {
//异常页面样式
@include abnormal;
padding-top:calc(100vh - 750px);
};
.abnormal-tip{
@include font-col(#909399);
@include font-s(17px);
padding:0 0 24px 0;
};
.zan-pagination {
text-align: right;
margin-top: 12px;
padding-top: calc(100vh - 750px);
}
.zan-nav, .zan-table {
box-sizing: border-box;
.abnormal-tip {
@include font-col(#909399);
@include font-s(17px);
padding: 0 0 24px 0;
}
#nprogress .bar {
@ -71,62 +61,61 @@
}
::-webkit-scrollbar-thumb {
/* background-color: #01c8dc;
/* background-color: #01c8dc;
border-radius: 3px; */
background-color: #b6b6b6;
}
/*全局滚动条样式结束*/
.m-t16 {
margin-top: 16px;
}
.m-b8 {
margin-bottom: 8px;
}
.m-t8 {
margin-top: 8px;
}
.m-l8{
.m-l8 {
margin-left: 8px;
}
.p-defult{
.p-default {
padding: 24px;
}
.bg-defult{
background-color:#F2F6FC;
.bg-default {
background-color: #f2f6fc;
width: calc(100vw - 800px);
font-size: 14px;
color: #000;
}
.t-l{
.t-l {
text-align: left;
}
.font-32{
.font-32 {
font-size: 32px;
}
.min-w600{
.min-w600 {
min-width: 600px;
}
.min-h500{
.min-h500 {
min-height: 500px;
}
.min-h350{
.min-h350 {
min-height: 350px;
}
.defult-h{
.default-h {
min-height: 500px;
height: calc(100vh - 175px);
}
.df-c{
.df-c {
display: flex;
align-items: center;
}

8
src/assets/style/main.scss

@ -0,0 +1,8 @@
/* 只需要重写你需要的即可 */
@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"primary": (
"base": green,
),
)
);

8
src/common/event.ts

@ -0,0 +1,8 @@
// event.js
import { onMounted, onUnmounted } from "vue";
export function useEventListener(target: any, event: any, callback: any) {
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
onMounted(() => target.addEventListener(event, callback));
onUnmounted(() => target.removeEventListener(event, callback));
}

20
src/common/mouse.ts

@ -0,0 +1,20 @@
// mouse.js
import { ref } from "vue";
import { useEventListener } from "./event";
export function useMouse() {
const x = ref<string | number>(0);
const y = ref<string | number>(0);
interface Event {
pageX: string | number;
pageY: string | number;
}
useEventListener(window, "mousemove", (e: Event) => {
x.value = e.pageX;
y.value = e.pageY;
})
return { x, y };
}

60
src/common/wartermark.js

@ -1,60 +0,0 @@
let watermark = {};
const idd = '1.234523841642.1234124163';
var _interval = null;
let setWatermark = (str) => {
let id = idd;
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id));
}
// 创建一个画布
let can = document.createElement('canvas');
// 设置画布的长宽
can.width = 220;
can.height = 180;
let cans = can.getContext('2d');
// 设置旋转角度
cans.rotate(-20 * Math.PI / 150);
cans.font = '16px Vedana';
// 设置填充绘画的颜色、渐变或者模式
cans.fillStyle = 'rgba(200, 200, 200, 0.70)';
// 设置文本内容的当前对齐方式
cans.textAlign = 'center';
// 设置在绘制文本时使用的当前文本基线
cans.textBaseline = 'Middle';
// 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
cans.fillText(str, can.width / 3, can.height / 2);
let div = document.createElement('div');
div.id = id;
div.style.pointerEvents = 'none';
div.style.top = '20px';
div.style.left = '0px';
div.style.position = 'fixed';
div.style.zIndex = '100000';
div.style.width = document.documentElement.clientWidth + 'px';
div.style.height = document.documentElement.clientHeight + 'px';
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat';
document.body.appendChild(div);
return id;
};
// 该方法只允许调用一次
// 添加水印的方法
watermark.set = (str) => {
let id = setWatermark(str);
_interval = setInterval(() => {
if (document.getElementById(id) === null) {
id = setWatermark(str);
}
}, 500);
window.onresize = () => {
setWatermark(str);
};
};
// 移除水印的方法
watermark.remove = () => {
if (document.getElementById(idd) !== null) {
var box = document.getElementById(idd);
box.parentNode.removeChild(box);
_interval ? clearInterval(_interval) : '';
}
};
export default watermark;

52
src/common/watermark.ts

@ -0,0 +1,52 @@
const id = "1.234523841642.1234124163";
const setWatermark = (str: string) => {
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id) as HTMLElement);
}
// 创建一个画布
const canvas = document.createElement("canvas");
// 设置画布的长宽
canvas.width = 220;
canvas.height = 180;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
// 设置旋转角度
ctx.rotate((-20 * Math.PI) / 150);
ctx.font = "15px Vedana";
// 设置填充绘画的颜色、渐变或者模式
ctx.fillStyle = "rgba(200, 200, 200, 0.70)";
// 设置文本内容的当前对齐方式
ctx.textAlign = "center";
// 设置在绘制文本时使用的当前文本基线
ctx.textBaseline = "middle";
// 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
ctx.fillText(str, canvas.width / 3, canvas.height / 2);
const div = document.createElement("div");
div.id = id;
div.style.pointerEvents = "none";
div.style.top = "40px";
div.style.left = "0px";
div.style.opacity = "0.6";
div.style.position = "fixed";
div.style.zIndex = "100000";
div.style.width = document.documentElement.clientWidth + "px";
div.style.height = document.documentElement.clientHeight + "px";
div.style.background =
"url(" + canvas.toDataURL("image/png") + ") left top repeat";
document.body.appendChild(div);
return id;
}
// 该方法只允许调用一次
// 添加水印的方法
export const setWaterMarker = (str: string) => {
let id = setWatermark(str);
if (document.getElementById(id) === null) {
id = setWatermark(str);
}
};
// 移除水印的方法
export const removeWaterMarker = () => {
if (document.getElementById(id) !== null) {
const box = document.getElementById(id) as HTMLElement;
box?.parentNode?.removeChild(box);
}
};

288
src/components/Header/index.vue

@ -1,288 +0,0 @@
<template>
<div class="zan-header">
<div class="collapse-btn" @click="switchCollapse">
<i v-if="collapse" class="fa fabtn fa-indent"></i>
<i v-else class="fa fabtn fa-dedent"></i>
</div>
<div class="collapse-right">
<el-tooltip
class="item"
effect="dark"
:content="transitionLocal('message.public.fullScreen')"
placement="bottom"
>
<span class="faSpan">
<i class="fa fa-arrows-alt" @click="requestFullScreen('body')"></i>
</span>
</el-tooltip>
<el-dropdown @command="changeI18n">
<span class="el-dropdown-link faSpan">
<el-tooltip
class="item"
effect="dark"
:content="transitionLocal('message.public.languageSwitch')"
placement="left"
>
<i class="fa fa-language g-language" style="font-size: 18px"></i>
</el-tooltip>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="locale in $i18n.availableLocales"
:key="`locale-${locale}`"
:command="locale"
:style="{
color: $i18n.locale == locale ? 'rgb(64, 158, 255)' : '',
}"
>{{ $filters.langFilter(locale) }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-tooltip
class="item"
effect="dark"
:content="transitionLocal('message.public.messageCenter')"
placement="bottom"
>
<span class="faSpan">
<el-badge is-dot class="item">
<i class="fa faPad fa-bell-o" @click="toGetMessage"></i>
</el-badge>
</span>
</el-tooltip>
<!-- 用户名下拉菜单 -->
<el-dropdown size="small" trigger="click" @command="handleCommand">
<span class="el-dropdown-link btn_username_group">
<span class="btn_username" :title="username">{{ username }}</span>
<i class="el-icon-caret-bottom"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
divided
command="signOut"
>{{ transitionLocal('message.public.loggedOut') }}</el-dropdown-item>
<el-dropdown-item
command="versionLog"
divided
>{{ transitionLocal('message.public.versionLog') }}</el-dropdown-item>
<el-dropdown-item
command="baseInfo"
divided
>{{ transitionLocal('message.public.basicInfo') }}</el-dropdown-item>
<el-dropdown-item
command="checkPass"
divided
>{{ transitionLocal('message.public.changePassword') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 用户头像 -->
<div class="user-avatar">
<img src="../../assets/image/touxiang.jpg" />
<!-- <el-avatar icon="el-icon-user-solid">
</el-avatar>-->
</div>
</div>
<check-pass v-model:passVisible="passVisible"></check-pass>
<base-info ref="baseInfoRef" v-model:baseVisible="baseVisible"></base-info>
<version-log v-model:versionVisible="versionVisible"></version-log>
</div>
</template>
<script>
import {
defineComponent,
getCurrentInstance,
toRefs,
reactive,
ref,
computed,
watch,
provide,
shallowRef,
} from "vue";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import Cookies from "js-cookie";
import checkPass from "../Setting/checkPass.vue";
import baseInfo from "../Setting/baseInfo.vue";
import versionLog from "../Setting/versionLog.vue";
import { useI18n } from "vue-i18n";
import { transitionLocal } from '../../locales/i18n'
export default defineComponent({
name: "zan-header",
components: {
checkPass,
baseInfo,
versionLog,
},
setup(context, props) {
let { proxy } = getCurrentInstance(); // vue
const { t, locale: language } = useI18n();
const store = useStore(); //vuex
const router = useRouter(); //
const baseInfoRef = ref("null");
const state = reactive({
collapse: computed(() => store.state.collapse),
username: computed(() => localStorage.getItem("username") || "待完善"),
passVisible: false, //
baseVisible: false, //
versionVisible: false, //
driver: null, //
});
const requestFullScreen = (element) => {
// 退
const isFullScreen =
document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen ||
document.msFullscreenElement; //
let ele = document.querySelector(element) || document.documentElement; //
if (!isFullScreen) {
if (ele.requestFullscreen) {
ele.requestFullscreen();
} else if (ele.mozRequestFullScreen) {
ele.mozRequestFullScreen();
} else if (ele.webkitRequestFullscreen) {
ele.webkitRequestFullscreen();
} else if (ele.msRequestFullscreen) {
ele.msRequestFullscreen();
}
} else {
if (document.exitFullScreen) {
document.exitFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (element.msExitFullscreen) {
element.msExitFullscreen();
}
}
};
const switchCollapse = () => {
//
setTimeout(() => {
store.commit("switchCollapse", !state.collapse);
}, 0);
};
const handleCommand = (command) => {
//
if (command == "signOut") {
Cookies.remove("token"); //token 1
store.commit("delRightMenu", {
//退
whiteTags: [],
});
router.push("/login");
ElMessage.success("登出成功");
} else if (command == "checkPass") {
//
state.passVisible = true;
} else if (command == "baseInfo") {
//
baseInfoRef.value.openBaseInfo(store.state.user.user);
} else if (command == "versionLog") {
//
state.versionVisible = true;
}
};
const toGetMessage = () => {
//
router.push("/messageCenter");
};
const changeI18n = (type) => {
//
if (type == Cookies.get("lang")) {
//
ElMessage.warning(`${t("message.public.existence")}`);
return false;
}
Cookies.set("lang", type); //
language.value = type; //i18n
proxy.$i18n.locale = type; //i18n
ElMessage.success(`${t("message.public.editLang")}`);
};
return {
...toRefs(state),
baseInfoRef,
switchCollapse,
handleCommand,
toGetMessage,
changeI18n,
requestFullScreen,
transitionLocal
};
},
});
</script>
<style lang="scss" scoped>
.zan-header {
box-sizing: border-box;
width: 100%;
height: 64px;
font-size: 18px;
color: #616161;
background: #fff;
display: flex;
justify-content: space-between;
.collapse-btn {
// float: left;
padding: 0px 15px 0 15px;
cursor: pointer;
line-height: 64px;
}
.el-icon-s-fold,
.el-icon-s-unfold {
font-size: 25px;
cursor: pointer;
}
.collapse-right {
float: right;
padding-right: 20px;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
.faSpan {
padding-right: 15px;
cursor: pointer;
}
}
.user-avatar {
margin: 0 15px 0 5px;
}
img {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
}
.btn_username_group {
display: flex;
justify-content: space-between;
.btn_username {
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 60px;
padding-right: 8px;
}
}
}
</style>

71
src/components/Pagination/index.vue

@ -1,31 +1,52 @@
<template>
<el-pagination
@current-change="currentChange"
:current-page="pagination.currentPage"
:page-sizes="[10]"
:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
></el-pagination>
<el-pagination v-model:currentPage="pagination.page" v-model:page-size="pagination.pageSize" background
:page-sizes="[10, 20, 100, 200]" layout="sizes,prev, pager, next, jumper,total" :total="pagination.total"
@size-change="sizeChange" @current-change="currentChange" class="p-location" />
</template>
<script>
import { defineComponent, reactive, toRefs, watch, inject } from "vue";
export default defineComponent({
setup(props, context) {
const pagination = inject("pagination"); //
const currentChange = (val) => {
//
context.emit("change", {
<script setup lang="ts" name="AdminPagination">
import { PaginationState } from "@/types";
withDefaults(
defineProps<{
pagination: Partial<PaginationState>;
}>(),
{
pagination: () => {
return {
page: 1,
pageSize: 10,
page: val,
});
};
total: 100,
};
},
}
);
const emits = defineEmits<{
(
e: "change",
target: {
page?: number;
pageSize?: number;
}
): void;
}>();
const currentChange = (page: number) => {
//
emits("change", {
page,
});
}
return {
pagination,
currentChange,
};
},
});
const sizeChange = (pageSize: number) => {
//
emits("change", { pageSize, page: 1 });
}
</script>
<style lang="scss" scoped>
.p-location {
display: flex;
justify-content: right;
}
</style>

192
src/components/Setting/baseInfo.vue

@ -1,192 +0,0 @@
<template>
<el-drawer
:before-close="close"
:model-value="baseVisible"
title="基本信息"
size="800px"
@open="getInit"
>
<el-form ref="baseInfoRef" :model="baseInfo" :rules="baseInfoRules" label-width="114px">
<el-row>
<el-col :span="10">
<el-form-item label="姓名:">
<el-input v-model="baseInfo.staffName"></el-input>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="用户名:" prop="username">
<el-input v-model="baseInfo.username"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="性别:">
<el-select v-model="baseInfo.sex" placeholder="请选择性别">
<el-option label="女" value="0"></el-option>
<el-option label="男" value="1"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="手机号码:">
<el-input v-model="baseInfo.phone"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="出生日期:">
<el-date-picker v-model="baseInfo.birthDate" placeholder="选择日期" type="date"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="账号状态:">
<el-select v-model="baseInfo.userState" placeholder="请选择">
<el-option label="正常" value="0"></el-option>
<el-option label="冻结" value="1"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="权限分配:" prop="jurisdiction">
<el-select v-model="baseInfo.jurisdiction" multiple placeholder="请选择">
<el-option
v-for="item in roleList"
:key="item.roleId"
:label="item.marks"
:value="item.roleId"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="家庭住址:">
<el-input v-model="baseInfo.address"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="个人说明:">
<el-input
v-model="baseInfo.marks"
:autosize="{ minRows: 4, maxRows: 6 }"
type="textarea"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<div class="baseInfo_footer">
<el-form-item>
<el-button size="medium" @click="close">取消</el-button>
<el-button size="medium" type="primary" @click="saveBaseInfo">确定</el-button>
</el-form-item>
</div>
</el-col>
</el-row>
</el-form>
</el-drawer>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
import { useStore } from "vuex";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import Cookies from "js-cookie";
export default defineComponent({
name: "baseInfo",
props: {
baseVisible: Boolean,
},
setup(props, context) {
const store = useStore(); //vuex
const router = useRouter(); //
const baseInfoRef = ref(null); //ref
const state = reactive({
baseInfo: {
username: "",
sex: "",
staffName: "",
phone: "",
marks: "",
birthDate: "",
address: "",
userState: "",
jurisdiction: "",
image: "",
},
baseInfoRules: {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" },
],
jurisdiction: [
{ required: true, message: "请至少选择一个角色", trigger: "change" },
],
},
roleList: [
{
marks: "普通用户",
roleId: "1",
},
{
marks: "系统管理员",
roleId: "2",
},
{
marks: "超级管理员",
roleId: "3",
},
], //
});
const getInit = () => {
//
console.log("基本信息初始化");
};
const close = () => {
context.emit("update:baseVisible", false);
};
const openBaseInfo = (row) => {
context.emit("update:baseVisible", true);
state.baseInfo = Object.assign({ ...row }, {});
};
const saveBaseInfo = () => {
//
ElMessage.warning({
message: "检测到您修改了本账号的信息,3秒后回到登陆页",
type: "warning",
});
setTimeout(() => {
router.push({ path: "/login" });
context.emit("reFresh", false);
Cookies.remove("token");
}, 3000);
close();
};
return {
...toRefs(state),
close,
saveBaseInfo,
openBaseInfo,
baseInfoRef,
getInit,
};
},
});
</script>
<style lang="scss" scoped>
.baseInfo_footer {
text-align: right;
}
</style>

112
src/components/Setting/checkPass.vue

@ -1,112 +0,0 @@
<template>
<el-dialog :before-close="close" :model-value="passVisible" title="修改密码" width="500px">
<el-form
ref="checkPassRef"
:hide-required-asterisk="true"
:model="ruleForm"
:rules="rules"
class="demo-ruleForm"
label-width="100px"
status-icon
>
<el-form-item label="原密码" prop="oldPassword">
<el-input v-model="ruleForm.oldPassword" autocomplete="off" type="password"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="ruleForm.newPassword" autocomplete="off" type="password"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input v-model="ruleForm.checkPass" autocomplete="off" type="password"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button size="medium" @click="close"> </el-button>
<el-button size="medium" type="primary" @click="ok"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { defineComponent, toRefs, reactive, inject, ref, computed } from "vue";
import { useStore } from "vuex";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import Cookies from "js-cookie";
export default defineComponent({
name: "checkPass",
props: {
passVisible: Boolean,
},
setup(props, context) {
const checkPassRef = ref(null); //ref
const store = useStore(); //vuex
const router = useRouter(); //
const validatePass = (rule, value, callback) => {
//
if (value === "") {
callback(new Error("请输入新密码"));
} else {
if (state.ruleForm.checkPass !== "") {
checkPassRef.value.validateField("checkPass");
}
callback();
}
};
const validateCheckPass = (rule, value, callback) => {
//
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== state.ruleForm.newPassword) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
};
const state = reactive({
ruleForm: {
oldPassword: "",
newPassword: "",
checkPass: "",
},
rules: {
oldPassword: [
{ required: true, message: "请输入原密码", trigger: "blur" },
],
newPassword: [{ validator: validatePass, trigger: "blur" }],
checkPass: [{ validator: validateCheckPass, trigger: "blur" }],
},
userInfo: computed(() => store.state.user.user), //
});
const close = () => {
checkPassRef.value.resetFields();
context.emit("update:passVisible", false);
};
const ok = () => {
checkPassRef.value.validate((valid) => {
if (valid) {
ElMessage.warning({
message: "检测到您修改了密码,请重新登陆",
type: "warning",
});
close(); //
Cookies.remove("token"); //token 1
setTimeout(() => {
router.push({ path: "/login" });
}, 1000);
}
});
};
return {
...toRefs(state),
checkPassRef,
close,
ok,
};
},
});
</script>

178
src/components/Tags/index.vue

@ -1,178 +0,0 @@
<template>
<div class="tag_content">
<div v-if="tagsList.length > 0" class="tags">
<el-tag
:key="tag"
type
:class="path === tag.path ? 'tag_check' : 'tag_nocheck'"
v-for="(tag, index) in tagsList"
:closable="tag.path == '/homePage' ? false : true"
size="medium"
effect="plain"
@close="aClosingTag(tag, index)"
@click="triggerTag(tag, 'go')"
:disable-transitions="false"
>{{ transitionLocal('message.' + tag.title) }}</el-tag>
</div>
<el-dropdown v-if="tagsList.length > 0" @command="rightMenu">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="all">{{ transitionLocal('message.public.closeAll') }}</el-dropdown-item>
<el-dropdown-item command="other">{{ transitionLocal('message.public.closeOther') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script>
import {
defineComponent,
getCurrentInstance,
toRefs,
reactive,
ref,
watch,
computed,
onMounted,
} from "vue";
import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router";
import { useStore } from "vuex";
import { ElMessage } from "element-plus";
import { transitionLocal } from '../../locales/i18n'
export default defineComponent({
name: "tags",
setup() {
const store = useStore(); //vuex
const route = useRoute(); //
const router = useRouter(); //
let { proxy } = getCurrentInstance(); // vue
const state = reactive({
tagsList: computed(() => store.state.tagsList), //
path: "", //
});
const setTags = (route) => {
//
const isExist = state.tagsList.some((item) => {
return item.path === route.fullPath;
});
if (!isExist) {
if (state.tagsList.length >= 10) {
//10
store.commit("delTags", { index: 0 });
}
store.commit("setTags", {
name: route.name,
title: route.meta.title,
path: route.fullPath,
});
}
};
const aClosingTag = (tag, index) => {
//
if (state.tagsList.length <= 1) {
//
ElMessage.warning({
message: "最后一个标签了哦!",
type: "warning",
});
return false;
}
store.commit("delTags", { index: index });
triggerTag(state.tagsList[state.tagsList.length - 1], "go");
};
const triggerTag = (tag, type) => {
//
// debugger
proxy._public.debounce(() => {
state.path = tag.path;
if (type) {
//
router.push(tag.path);
}
}, 100);
};
const rightMenu = (menu) => {
//
let whiteTags = ["/homePage"];
if (state.path !== whiteTags[0] && menu === "other") {
whiteTags.push(state.path);
}
store.commit("delRightMenu", {
whiteTags,
});
router.push(whiteTags[whiteTags.length - 1]);
};
onBeforeRouteUpdate((to) => {
//
setTags(to);
triggerTag(to);
});
onMounted(() => {
//
setTags(route);
triggerTag(route);
});
return {
...toRefs(state),
setTags,
aClosingTag,
triggerTag,
rightMenu,
transitionLocal
};
},
});
</script>
<style lang="scss" scoped>
.tag_content {
padding: 6px 0px;
margin: 0px 12px;
box-sizing: border-box;
white-space: nowrap;
display: flex;
justify-content: space-between;
align-items: center;
.tags {
width: calc(100vw - 310px);
overflow: auto;
}
.el-tag {
cursor: pointer;
margin-right: 8px;
//height: 30px;
//padding: 0px 13px 0 9px;
//line-height: 28px;
//border-radius: 0;
}
::v-deep(.el-tag .el-icon-close) {
color: rgb(97, 97, 97) !important;
}
::v-deep(.el-tag .el-icon-close:hover) {
background: none;
}
.tag_check {
background-color: rgb(255, 255, 255) !important;
border-color: rgb(255, 255, 255) !important;
color: #409eff !important;
}
.tag_nocheck {
background-color: rgb(255, 255, 255) !important;
border-color: rgb(255, 255, 255) !important;
color: rgb(97, 97, 97) !important;
}
.el-dropdown-link {
text-align: center;
height: 26px;
display: block;
width: 70px;
background: #fff;
line-height: 26px;
}
}
</style>

21
src/components/abnormal/403.vue

@ -0,0 +1,21 @@
<template>
<div class="thereIsNo">
<el-result :image-size="400">
<template #icon>
<img class="abnormal-img" :src="getImage('403','svg')" />
</template>
<template #extra>
<h2 class="abnormal-tip">很抱歉您暂无权限访问</h2>
<el-button plain @click="router.go(-1)"
>返回</el-button
>
</template>
</el-result>
</div>
</template>
<script setup lang="ts" name="Admin403">
import { getImage } from '@/utils';
import { useRouter } from "vue-router";
const router = useRouter(); //
</script>

21
src/components/abnormal/404.vue

@ -0,0 +1,21 @@
<template>
<div class="thereIsNo">
<el-result :image-size="400">
<template #icon>
<img class="abnormal-img" :src="getImage('404','svg')" />
</template>
<template #extra>
<h2 class="abnormal-tip">很抱歉您所访问的页面不存在</h2>
<el-button plain @click="router.go(-1)"
>返回</el-button
>
</template>
</el-result>
</div>
</template>
<script setup lang="ts" name="Admin404">
import { getImage } from '@/utils';
import { useRouter } from "vue-router";
const router = useRouter(); //
</script>

21
src/views/abnormal/build.vue → src/components/abnormal/build.vue

@ -2,27 +2,20 @@
<div class="thereIsNo">
<el-result :image-size="400">
<template #icon>
<img class="abnormal-img" src="/src/assets/image/build.svg" alt />
<img class="abnormal-img" :src="getImage('build','svg')" />
</template>
<template #extra>
<h2 class="abnormal-tip">工作人员正在建设中</h2>
<el-button plain @click="router.go(-1)"
>返回</el-button
>
</template>
</el-result>
</div>
</template>
<script>
import { defineComponent } from "vue";
<script setup lang="ts" name="AdminBuild">
import { getImage } from '@/utils';
import { useRouter } from "vue-router";
export default defineComponent({
setup() {
const router = useRouter(); //
return {
router,
};
},
});
const router = useRouter(); //
</script>

19
src/views/abnormal/networkError.vue → src/components/abnormal/networkError.vue

@ -2,27 +2,18 @@
<div class="thereIsNo">
<el-result :image-size="400">
<template #icon>
<img class="abnormal-img" src="/src/assets/image/networkError.svg" alt />
<img class="abnormal-img" :src="getImage('networkError','svg')" />
</template>
<template #extra>
<h2 class="abnormal-tip">网络不可用请检查你的网络设置</h2>
<el-button plain @click="router.go(-1)">返回</el-button>
</template>
</el-result>
</div>
</template>
<script>
import { defineComponent } from "vue";
<script setup lang="ts" name="AdminNetworkError">
import { getImage } from '@/utils';
import { useRouter } from "vue-router";
export default defineComponent({
setup() {
const router = useRouter(); //
return {
router,
};
},
});
const router = useRouter(); //
</script>

96
src/views/content/computerMonitor.vue → src/components/computerMonitor/index.vue

@ -7,11 +7,9 @@
</div>
</template>
<div class="text item">
<el-tag :type="online ? 'success' : 'danger'">
{{
online ? "当前网络在线" : "当前是离线状态"
}}
</el-tag>
<el-tag :type="online ? 'success' : 'danger'">{{
online ? "当前网络在线" : "当前是离线状态"
}}</el-tag>
</div>
</el-card>
<el-card class="box-card" style="width: 60vw">
@ -37,25 +35,17 @@
</div>
</template>
<div class="text item">
<el-tag :type="capsLockState ? 'success' : 'info'">
{{
capsLockState ? "CapsLock已打开" : "CapsLock未打开"
}}
</el-tag>
<el-tag class="m-l8" :type="altState ? 'success' : 'info'">
{{
altState ? "Alt已打开" : "Alt未打开"
}}
</el-tag>
<el-tag class="m-l8" :type="controlState ? 'success' : 'info'">
{{
controlState ? "Control已打开" : "Control未打开"
}}
<el-tag :type="capsLockState ? 'success' : 'info'">{{
capsLockState ? "CapsLock已打开" : "CapsLock未打开"
}}</el-tag>
<el-tag class="m-l8" :type="altState ? 'success' : 'info'">{{
altState ? "Alt已打开" : "Alt未打开"
}}</el-tag>
<el-tag class="m-l8" :type="controlState ? 'success' : 'info'"
>{{ controlState ? "Control已打开" : "Control未打开" }}
</el-tag>
<el-tag class="m-l8" :type="numLockState ? 'success' : 'info'">
{{
numLockState ? "NumLock已打开" : "NumLock未打开"
}}
<el-tag class="m-l8" :type="numLockState ? 'success' : 'info'"
>{{ numLockState ? "NumLock已打开" : "NumLock未打开" }}
</el-tag>
</div>
</el-card>
@ -81,8 +71,7 @@
</el-card>
</el-space>
</template>
<script>
import { defineComponent, ref } from "vue";
<script setup lang="ts" name="AdminComputerMonitor">
import {
useMouse,
useBrowserLocation,
@ -92,44 +81,25 @@ import {
useOnline,
} from "@vueuse/core";
import { ElNotification } from "element-plus";
export default defineComponent({
setup() {
const { x, y } = useMouse(); //
const location = useBrowserLocation(); //
onKeyStroke(
//
["A", "B", "C", "D"],
(e) => {
ElNotification({
title: "按键监听",
message: `您按下了${e.key}`,
type: "success",
});
},
{ target: document }
);
const { coords, locatedAt, error } = useGeolocation(); //
const capsLockState = useKeyModifier(["CapsLock"]); //
const altState = useKeyModifier(["Alt"]); //
const controlState = useKeyModifier(["Control"]); //
const numLockState = useKeyModifier(["NumLock"]); //
const online = useOnline(); //
return {
x,
y,
location,
coords,
locatedAt,
error,
capsLockState,
altState,
controlState,
numLockState,
online,
};
const { x, y } = useMouse(); //
const location = useBrowserLocation(); //
onKeyStroke(
//
["A", "B", "C", "D"],
(e) => {
ElNotification({
title: "按键监听",
message: `您按下了${e.key}`,
type: "success",
});
},
});
{ target: document }
);
const { coords, locatedAt, error } = useGeolocation(); //
const online = useOnline(); //
const capsLockState = useKeyModifier("CapsLock"); //
const altState = useKeyModifier("Alt"); //
const controlState = useKeyModifier("Control"); //
const numLockState = useKeyModifier("NumLock"); //
</script>

54
src/views/content/copy.vue → src/components/copy/index.vue

@ -8,11 +8,12 @@
</template>
<div class="text item">
<el-button
type="text"
v-copy="'copydir'"
type="text"
class="copydir"
data-clipboard-text="我是指令复制"
>我是指令复制(点击复制)</el-button>
>我是指令复制(点击复制)</el-button
>
</div>
</el-card>
@ -26,39 +27,32 @@
<el-button
type="text"
class="copyBtn"
@click="copy"
data-clipboard-text="我是组件内方法复制"
>我是组件内方法复制(点击复制)</el-button>
@click="copy"
>我是组件内方法复制(点击复制)</el-button
>
</div>
</el-card>
</el-space>
</template>
<script>
<script setup lang="ts" name="AdminCopy">
import Clipboard from "clipboard";
import { ElMessage } from "element-plus";
import { defineComponent } from "vue";
export default defineComponent({
setup() {
const copy = () => {
var clipboard = new Clipboard(".copyBtn");
clipboard.on("success", (e) => {
ElMessage({
message: `组件内方法复制成功`,
type: "success",
});
//
clipboard.destroy();
});
clipboard.on("error", (e) => {
//
//console.log('')
//
clipboard.destroy();
});
};
return {
copy,
};
},
});
const copy = () => {
const clipboard = new Clipboard(".copyBtn");
clipboard.on("success", (e) => {
ElMessage({
message: `组件内方法复制成功`,
type: "success",
});
//
clipboard.destroy();
})
clipboard.on("error", (e) => {
//
//console.log('')
//
clipboard.destroy();
})
}
</script>

309
src/components/dashboard/analysis.vue

@ -0,0 +1,309 @@
<template>
<div class="analysis">
<el-row :gutter="10">
<el-col v-for="(item, index) in dataList" :key="index" :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
<el-card class=" m-b8" shadow="always" :body-style="{ padding: '35px 20px' }">
<template #header>
<div class="card-header">
<span class="card-header-title">{{ item.title }}</span>
<el-tag :type="item.type" effect="dark">{{
item.labelTitle
}}</el-tag>
</div>
</template>
<div class="text item card-h-total">{{ item.total }}</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-card class=" m-b8" shadow="always" :body-style="{ padding: '0' }">
<template #header>
<div class="card-header">
<span class="card-header-title">各时间段流量监控</span>
</div>
</template>
<div class="text item">
<div id="visitChart" class="home_charts"></div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<el-card class="" shadow="always">
<template #header>
<div class="card-header">
<span class="card-header-title">数据来源</span>
</div>
</template>
<div class="text item">
<div id="originChart" class="home_charts"></div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<el-card class="" shadow="always">
<template #header>
<div class="card-header">
<span class="card-header-title">周活跃量统计</span>
</div>
</template>
<div class="text item">
<div id="activeChart" class="home_charts"></div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<el-card class="" shadow="always">
<template #header>
<div class="card-header">
<span class="card-header-title">用户数据统计</span>
</div>
</template>
<div class="text item">
<div id="genderChart" class="home_charts"></div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="AdminAnalysis">
import type { TagProps } from "element-plus";
import { inject, onMounted, onUnmounted, ref } from "vue";
type DataList = {
type: TagProps["type"];
title: string;
labelTitle: string;
total: number | string;
};
const eCharts: any = inject("eCharts");
const dataList: DataList[] = [
{
title: "待办任务",
total: "300,000",
labelTitle: "总数",
type: "danger",
},
{
title: "用户数量",
total: "335,084",
labelTitle: "总数",
type: "",
},
{
title: "访问量",
total: "88,846,565",
labelTitle: "总数",
type: "success",
},
{
title: "商品数量",
total: "120",
labelTitle: "总数",
type: "warning",
},
];
let visitChart = ref<any>(null);
let originChart = ref<any>(null);
let activeChart = ref<any>(null);
onMounted(() => {
// window.onresize = function () {
// //
// chartsInit();
// };
chartsInit();
})
onUnmounted(() => {
});
const chartsInit = () => {
//
loadVisitChart();
loadOriginChart();
loadActiveChart();
loadGenderChart();
}
const loadVisitChart = () => {
//访
const labels = [];
const values = [];
for (let index = 1; index <= 24; index++) {
labels.push(`${index}:00`);
if (index % 2 === 0) {
values.push(index * 104);
} else {
values.push(index * 26);
}
};
if (visitChart.value) {
visitChart.value.dispose()
}
visitChart.value = eCharts.init(document.getElementById("visitChart"));
const option = {
xAxis: {
type: "category",
data: labels,
},
yAxis: {
type: "value",
},
series: [
{
data: values,
type: "line",
},
],
};
visitChart.value.setOption(option);
}
const loadOriginChart = () => {
//访
if (originChart.value) {
originChart.value.dispose()
}
originChart.value = eCharts.init(document.getElementById("originChart"));
const option = {
tooltip: {
trigger: "item",
},
legend: {
top: "5%",
left: "center",
},
series: [
{
name: "访问来源",
type: "pie",
radius: ["40%", "70%"],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: "#fff",
borderWidth: 2,
},
label: {
show: false,
position: "center",
},
emphasis: {
label: {
show: true,
fontSize: "40",
fontWeight: "bold",
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: "搜索引擎" },
{ value: 735, name: "直接访问" },
{ value: 580, name: "邮件营销" },
{ value: 484, name: "联盟广告" },
{ value: 300, name: "视频广告" },
],
},
],
};
originChart.value.setOption(option);
}
const loadActiveChart = () => {
//访
if (activeChart.value) {
activeChart.value.dispose()
}
activeChart.value = eCharts.init(document.getElementById("activeChart"));
//
const option = {
xAxis: {
type: "category",
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
},
yAxis: {
type: "value",
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: "bar",
showBackground: true,
backgroundStyle: {
color: "rgba(180, 180, 180, 0.2)",
},
},
],
};
activeChart.value.setOption(option);
}
const loadGenderChart = () => {
//
const myChart = eCharts.init(document.getElementById("genderChart"));
//
const option = {
xAxis: {
type: "category",
data: ["男生", "女生", "未知"],
},
yAxis: {
type: "value",
},
series: [
{
data: [
{
value: 12234,
itemStyle: {
color: "#409EFF",
},
},
{
value: 64132,
itemStyle: {
color: "#7B76D6",
},
},
{
value: 8755,
itemStyle: {
color: "#C0C4CC",
},
},
],
type: "bar",
},
],
};
myChart.setOption(option);
}
</script>
<style lang="scss" scoped>
.analysis {
.home_charts {
height: 380px;
}
.card-header {
display: flex;
justify-content: space-between;
.card-header-title {
font-size: 15px;
}
}
.card-h-total {
font-size: 28px;
}
}
</style>

311
src/components/dashboard/workbench.vue

@ -0,0 +1,311 @@
<template>
<div class="workbench">
<el-card shadow="always" :body-style="{ padding: '10px 30px' }">
<div class="workbench-nav">
<el-image style="width: 300px; height: 200px" :src="getImage('workbench', 'svg')" fit="fill" />
<div class="image-space">
<strong class="workbench-nav-title">{{ hoursTip }}欢迎使用{{ siteName }}祝你开开心心每一天 !</strong>
<small class="workbench-nav-introduce">轻松创建部署您的中后台系统提升研发效率降低业务成本</small>
</div>
</div>
</el-card>
<el-row :gutter="10">
<el-col :xs="16" :sm="18">
<el-card class="box-card m-t16" shadow="always" :body-style="{ padding: '35px 20px' }"> <template
#header>
<div class="card-header">
<i class="fa fa-th-large module-icon" aria-hidden="true"></i> 项目统计
</div>
</template>
<ul class="workbench-statistics">
<li>
<span class="workbench-statistics-label">项目总数</span>
<span class="workbench-statistics-value">5</span>
</li>
<li>
<el-badge type="info" :value="180">
<span class="workbench-statistics-label">待处理</span>
</el-badge>
<span class="workbench-statistics-value">180</span>
</li>
<li>
<el-badge type="success" :value="5400">
<span class="workbench-statistics-label">已处理</span>
</el-badge>
<span class="workbench-statistics-value">5400</span>
</li>
<li>
<el-badge type="danger" :value="118">
<span class="workbench-statistics-label">用户反馈</span>
</el-badge>
<span class="workbench-statistics-value">118</span>
</li>
</ul>
</el-card>
<el-card class="box-card m-t16" shadow="always" :body-style="{ padding: '20px' }">
<template #header>
<div class="card-header">
<i class="fa fa-check-square-o module-icon" aria-hidden="true"></i> 待办事项
</div>
</template>
<el-table :data="taskData" style="width: 100%" height="280">
<el-table-column prop="taskName" label="任务名称" width="180" />
<el-table-column prop="name" label="发起人" width="180" />
<el-table-column prop="taskDetail" label="任务详情" />
<el-table-column fixed="right" label="操作" width="120">
<template #default>
<el-button type="text" size="small">查看详情</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :xs="8" :sm="6">
<el-card class="box-card m-t16" shadow="always" :body-style="{ padding: '10px 20px 35px 20px' }">
<template #header>
<div class="card-header">
<i class="fa fa-bars module-icon" aria-hidden="true"></i>快速导航 / 最近使用
</div>
</template>
<ul class="workbench-navigation">
<li v-for="item in QuickNavigation" :key="item.url" @click="$router.push(item.url)">
<i :class="item.icon" aria-hidden="true" :style="{ color: item.color }"></i>
{{ item.name }}
</li>
</ul>
</el-card>
<el-card class="box-card m-t16" shadow="always" :body-style="{ padding: '10px 20px 35px 20px' }">
<template #header>
<div class="card-header">
<i class="fa fa-question-circle module-icon" aria-hidden="true"></i>使用帮助
</div>
</template>
<div class="workbench-use-help">
<ul>
<li v-for="item in useHelpData" :key="item.url">
<el-link type="info" @click="toViewDocument(item.url)">{{ item.name }}</el-link>
</li>
</ul>
<div class="other-help">
<span class="other-help-label">其他</span>
<el-button type="text">新手引导</el-button>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row>
</el-row>
</div>
</template>
<script setup lang="ts" name="AdminWorkbench">
import {
getCurrentInstance,
onMounted,
ref,
} from "vue";
import { siteName } from "@/router/middleware";
import { getImage } from "@/utils";
import {
useRoute,
} from "vue-router";
const { proxy } = <any>getCurrentInstance();
let hoursTip = ref<string>('');
const route = useRoute();
const QuickNavigation = [
{
name: "数据分析",
url: "/dashboard/analysis",
icon: "fa fa-area-chart",
color: "#1890ff"
},
{
name: "用户管理",
url: "/setting/user",
icon: "fa fa-cog",
color: "#13c2c2"
},
{
name: "表单",
url: "/template/tableOperation",
icon: "fa fa-file-text",
color: "#722ed1"
},
{
name: "工具",
url: "/components/watermark",
icon: "fa fa-wrench",
color: "#1d39c4"
},
{
name: "反馈中心",
url: "/main/feedbackCenter",
icon: "fa fa-comment",
color: "#a0d911"
},
{
name: "新手引导",
url: "/main/noviceGuide",
icon: "fa fa-question-circle-o",
color: "#595959"
},
];
const taskData = [
{
taskName: '办理入职',
name: 'Tom',
taskDetail: '带新人办理入职',
},
{
taskName: '材料领取',
name: 'Tom',
taskDetail: '带新人领取入职材料',
},
{
taskName: '办理出入证',
name: 'Tom',
taskDetail: '带新人办理出入证',
},
]
const useHelpData = [
{
name: "Element-Plus的官方文档",
url: "https://element-plus.org/zh-CN/guide/design.html"
},
{
name: "Vue3的中文文档",
url: "https://staging-cn.vuejs.org/?mode=light"
},
{
name: "VueRouter的中文文档",
url: "https://router.vuejs.org/zh/"
},
{
name: "Pinia的中文文档",
url: "https://pinia.web3doc.top/"
},
]
onMounted(() => {
accessTimeRange()
})
const accessTimeRange = () => {
//
let hour = new Date().getHours();
if (hour >= 0 && hour < 12) {
hoursTip.value = '早上好';
} else if (hour >= 12 && hour < 18) {
hoursTip.value = '下午好';
} else {
hoursTip.value = '晚上好';
}
}
const toViewDocument = (url: string) => {
//
window.open(url);
}
</script>
<style lang="scss" scoped>
.workbench {
.module-icon {
padding-right: 4px;
font-size: 16px;
}
&-nav {
background-color: #ffffff;
border-radius: 4px;
display: flex;
align-items: center;
.image-space {
padding-left: 50px;
}
.workbench-nav-title {
font-size: 24px;
}
.workbench-nav-introduce {
display: block;
color: #808695;
padding-top: 30px;
}
}
&-statistics {
display: flex;
justify-content: space-around;
list-style: none;
text-align: center;
&-label {
font-size: 15px;
display: block;
color: #595959;
padding: 4px 5px;
}
&-value {
display: block;
padding-top: 10px;
font-size: 25px;
font-weight: bold;
color: #000000;
}
}
&-navigation {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
list-style: none;
li {
display: flex;
flex-direction: column;
cursor: pointer;
width: 94px;
text-align: center;
height: 50px;
border-radius: 3px;
line-height: 50px;
margin-top: 25px;
color: #606266;
font-size: 14px;
i {
font-size: 22px;
}
}
li:hover {
color: #409EFF;
}
}
&-use-help {
ul {
list-style: none;
li {
margin-top: 10px;
}
}
.other-help {
border-top: 1px solid #e4e7ed;
padding-top: 15px;
margin-top: 20px;
&-label {
display: block;
padding-bottom: 10px;
}
}
}
}
</style>

31
src/components/dialogDrag/index.vue

@ -0,0 +1,31 @@
<template>
<div>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
可拖拽弹框
</div>
</template>
<el-button type="primary" @click="dialogVisible = true">点击打开可拖拽弹框</el-button>
<el-dialog v-model="dialogVisible" draggable title="可拖拽弹框" width="30%" center>
<span>可以尝试拖拽我</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">确认</el-button>
</span>
</template>
</el-dialog>
</el-card>
</div>
</template>
<script setup lang="ts" name="AdminDialogDrag">
import { ref } from "vue";
const dialogVisible = ref<boolean>(false);
// const handleClose = (done: () => void) => {
// done();
// };
</script>

224
src/components/dropDownItem/baseInfo.vue

@ -0,0 +1,224 @@
<template>
<el-drawer
:before-close="close"
:model-value="baseVisible"
title="基本信息"
size="800px"
@open="getInit"
>
<el-form
ref="baseInfoFormRef"
:model="state.baseInfoForm"
:rules="state.baseInfoRules"
label-width="114px"
>
<el-row>
<el-col :span="10">
<el-form-item label="姓名:">
<el-input v-model="state.baseInfoForm.staffName"></el-input>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="用户名:" prop="username">
<el-input v-model="state.baseInfoForm.username"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="性别:">
<el-select
v-model="state.baseInfoForm.sex"
placeholder="请选择性别"
>
<el-option label="女" value="0"></el-option>
<el-option label="男" value="1"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="手机号码:">
<el-input v-model="state.baseInfoForm.phone"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="出生日期:">
<el-date-picker
v-model="state.baseInfoForm.birthDate"
placeholder="选择日期"
type="date"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="账号状态:">
<el-select
v-model="state.baseInfoForm.userState"
placeholder="请选择"
>
<el-option label="正常" value="0"></el-option>
<el-option label="冻结" value="1"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="权限分配:" prop="jurisdiction">
<el-select
v-model="state.baseInfoForm.jurisdiction"
multiple
placeholder="请选择"
>
<el-option
v-for="item in roleList"
:key="item.roleId"
:label="item.marks"
:value="item.roleId"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="家庭住址:">
<el-input v-model="state.baseInfoForm.address"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="个人说明:">
<el-input
v-model="state.baseInfoForm.marks"
:autosize="{ minRows: 4, maxRows: 6 }"
type="textarea"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<div class="baseInfo_footer">
<el-form-item>
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="saveBaseInfo">确定</el-button>
</el-form-item>
</div>
</el-col>
</el-row>
</el-form>
</el-drawer>
</template>
<script setup lang="ts" name="AdminBaseInfo">
import { reactive, ref } from "vue";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import type { FormInstance } from "element-plus";
import { removeToken } from "@/utils/auth";
import { useUserStore } from "@/pinia/modules/user";
import { BaseInfoState } from "@/types/setting";
import { DeepPartial } from "@/types/index";
withDefaults(
defineProps<{
baseVisible: boolean;
}>(),
{
baseVisible: false, //
}
);
const emits = defineEmits<{
(e: "update:baseVisible", baseVisible: boolean): void;
(e: "reFresh", status: boolean): void;
}>();
const userStore = useUserStore();
const router = useRouter();
const baseInfoFormRef = ref<FormInstance>();
const roleList = [
//
{
marks: "普通用户",
roleId: "1",
},
{
marks: "系统管理员",
roleId: "2",
},
{
marks: "超级管理员",
roleId: "3",
},
];
const state = reactive<BaseInfoState>({
baseInfoForm: {
username: "",
sex: "",
staffName: "",
phone: "",
marks: "",
birthDate: "",
address: "",
userState: "",
jurisdiction: "",
image: "",
},
baseInfoRules: {
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
jurisdiction: [
{ required: true, message: "请至少选择一个角色", trigger: "change" },
],
},
});
const getInit = () => {
//
state.baseInfoForm = Object.assign(state.baseInfoForm, {
...userStore.user,
});
}
const close = () => {
emits("update:baseVisible", false);
}
const openBaseInfo = <T extends Partial<BaseInfoState["baseInfoForm"]>>(
row: T
) => {
emits("update:baseVisible", true);
state.baseInfoForm = Object.assign(state.baseInfoForm, { ...row });
}
const saveBaseInfo = () => {
//
ElMessage.warning({
message: "检测到您修改了本账号的信息,3秒后回到登陆页",
type: "warning",
});
setTimeout(() => {
router.push({ path: "/login" });
emits("reFresh", false);
removeToken();
}, 3000);
close();
}
defineExpose({
openBaseInfo,
});
</script>
<style lang="scss" scoped>
.baseInfo_footer {
text-align: right;
}
</style>

139
src/components/dropDownItem/checkPass.vue

@ -0,0 +1,139 @@
<template>
<el-dialog
:before-close="close"
:model-value="passVisible"
title="修改密码"
width="500px"
>
<el-form
ref="checkPassFormRef"
:hide-required-asterisk="true"
:model="checkPassForm"
:rules="rules"
class="demo-checkPassForm"
label-width="100px"
status-icon
>
<el-form-item label="原密码" prop="oldPassword">
<el-input
v-model="checkPassForm.oldPassword"
autocomplete="off"
type="password"
></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input
v-model="checkPassForm.newPassword"
autocomplete="off"
type="password"
></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input
v-model="checkPassForm.checkPass"
autocomplete="off"
type="password"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="submitForm(checkPassFormRef)"
> </el-button
>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="AdminCheckPass">
import { reactive, ref, toRefs } from "vue";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import { removeToken } from "@/utils/auth";
import type { FormInstance } from "element-plus";
import { CheckPassState } from "@/types/setting";
withDefaults(
defineProps<{
passVisible: boolean;
}>(),
{
passVisible: false, //
}
);
const emits = defineEmits<{
(e: "update:passVisible", passVisible: boolean): void;
}>();
const checkPassFormRef = ref<FormInstance>();
const router = useRouter();
const validatePass = (rule: any, value: any, callback: Function) => {
//
if (value === "") {
callback(new Error("请输入新密码"));
} else {
if (state.checkPassForm.checkPass !== "") {
if (!checkPassFormRef.value) {
return;
}
checkPassFormRef.value.validateField("checkPass", () => null);
}
callback();
}
};
const validateCheckPass = (rule: any, value: any, callback: Function) => {
//
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== state.checkPassForm.newPassword) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
};
const state = reactive<Required<CheckPassState>>({
checkPassForm: {
oldPassword: "",
newPassword: "",
checkPass: "",
},
rules: {
oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
newPassword: [{ validator: validatePass, trigger: "blur" }],
checkPass: [{ validator: validateCheckPass, trigger: "blur" }],
},
});
const { checkPassForm, rules } = { ...toRefs(state) };
const close = () => {
//
if (!checkPassFormRef.value) {
return;
}
checkPassFormRef.value.resetFields();
emits("update:passVisible", false);
}
const submitForm = async (formEl: FormInstance | undefined) => {
//
if (!formEl) {
return;
}
await formEl.validate((valid, fields) => {
if (valid) {
ElMessage.warning({
message: "检测到您修改了密码,请重新登陆",
type: "warning",
});
close(); //
removeToken(); //token 1
setTimeout(() => {
router.push({ path: "/login" });
}, 1000);
}
});
}
</script>

62
src/components/Setting/versionLog.vue → src/components/dropDownItem/versionLog.vue

@ -2,59 +2,62 @@
<el-dialog
:before-close="close"
:model-value="versionVisible"
title="版本日志(Admin Frame 版权所有)"
:title="`版本日志(${siteName} 版权所有)`"
width="900px"
>
<ul class="v-log-content">
<li v-for="(item, index) in versionInfo" :key="item.version">
<div class="v-log-nav df-c">
<div class="df-c">
<el-tag effect="dark" size="small" v-if="!index">NEW</el-tag>
<el-tag v-if="!index" effect="dark" size="small">NEW</el-tag>
<span
class="v-n-title"
:style="{
paddingLeft: index ? '50px' : '4px',
}"
>{{ item.version }}</span>
>{{ item.version }}</span
>
</div>
<span class="v-n-date">{{ item.releaseDate }}</span>
</div>
<ul class="v-log-body">
<li v-for="(it, idx) in item.description" :key="idx">{{ idx + 1 + "" + it }}</li>
<li v-for="(it, idx) in item.description" :key="idx">
{{ idx + 1 + "、" + it }}
</li>
</ul>
</li>
</ul>
<template #footer>
<span class="dialog-footer">
<el-button size="medium" type="primary" plain @click="close"> </el-button>
<el-button type="primary" plain @click="close"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { defineComponent, toRefs, reactive, ref, computed } from "vue";
import { versionLog } from "../../assets/js/versionLog";
export default defineComponent({
name: "versionLog",
props: {
versionVisible: Boolean,
},
setup(props, context) {
const state = reactive({
versionInfo: computed(() => versionLog), //
});
<script setup lang="ts" name="AdminVersionLog">
import { computed } from "vue";
import { versionLog } from "@/assets/js/versionLog";
import { siteName } from "@/router/middleware";
const close = () => {
//
context.emit("update:versionVisible", false);
};
withDefaults(
defineProps<{
versionVisible: boolean;
}>(),
{
versionVisible: false, //
}
);
const emits = defineEmits<{
(e: "update:versionVisible", versionVisible: boolean): void;
}>();
const versionInfo = computed(() => versionLog);
return {
...toRefs(state),
close,
};
},
});
const close = () => {
//
emits("update:versionVisible", false);
}
</script>
<style lang="scss" scoped>
.v-log-content {
@ -62,18 +65,23 @@ export default defineComponent({
padding: 0 12px;
height: 500px;
overflow: auto;
.v-log-nav {
justify-content: space-between;
.v-n-title {
font-size: 20px;
}
.v-n-date {
font-size: 18px;
}
}
.v-log-body {
list-style: none;
padding: 12px 50px;
li {
font-size: 15px;
padding-bottom: 6px;

16
src/components/editor/markdown.vue

@ -0,0 +1,16 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }" >
<template #header>
<div class="card-header">
Markdown编辑器基于vue3使用jsx和typescript语法开发支持切换主题prettier美化文本等
</div>
</template>
<md-editor v-model="text" class="m-t8" />
</el-card>
</template>
<script lang="ts" setup name="AdminMarkdown">
import { ref } from "vue";
import MdEditor from "md-editor-v3";
import "md-editor-v3/lib/style.css";
const text = ref<string>("## Admin-Frame");
</script>

77
src/components/editor/textEditor.vue

@ -0,0 +1,77 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
QuillEditor 轻量级 web 富文本编辑器配置方便使用简单
</div>
</template>
<QuillEditor class="m-t8 min-h350" :read-only="readOnly" placeholder="欢迎来到Admin-Frame-Vue3" theme="snow"
:toolbar="toolbarOptions" content-type="html" :content="content" @text-change="textChange" @ready="ready"
@update:content="update" />
<el-card class="m-t8">
<template #header>
<div class="card-header">
<span style="font-size: 18px">Html代码</span>
</div>
</template>
<div v-text="content ? content : '输入内容后自动同步'"></div>
</el-card>
<el-card class="m-t8">
<template #header>
<div class="card-header">
<span style="font-size: 18px">Text文本</span>
</div>
</template>
<div v-html="content ? content : '输入内容后自动同步'"></div>
</el-card>
</el-card>
</template>
<script setup lang="ts" name="AdminTextEditor">
import { reactive, ref } from "vue";
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
const readOnly = ref<boolean>(false); //
const content = ref<string>(
"<h1><strong>欢迎来到Admin-Frame-Vue3</strong></h1>"
); //
const toolbarOptions = reactive([
["bold", "italic", "underline", "strike"], // toggled buttons
["blockquote", "code-block"],
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: "ordered" }, { list: "bullet" }],
[{ script: "sub" }, { script: "super" }], // superscript/subscript
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent
[{ direction: "rtl" }], // text direction
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ font: [] }],
[{ align: [] }],
["clean"], // remove formatting button
]);
const textChange = (text: string) => {
//
console.log("文本改变时触发", text);
}
const update = (text: string) => {
//
console.log("当编辑器内容改变时触发", text);
content.value = text ? text : "";
}
const ready = () => {
//Quill
console.log("Quill初始化后触发");
}
</script>
<style lang="scss" scoped>
</style>

53
src/components/i18n/index.vue

@ -0,0 +1,53 @@
<template>
<div>
<el-space direction="vertical">
<el-card class="box-card" style="width: 60vw">
<template #header>
<div class="card-header">
<span>{{ t("message.introduce.i18n") }}</span>
</div>
</template>
<div class="text item">
<el-radio
v-for="locale in $i18n.availableLocales"
:key="`locale-${locale}`"
v-model="state.chooseI18n"
:label="locale"
@change="changLang"
>{{ $filters.inspectLanguage(locale) }}</el-radio
>
</div>
</el-card>
</el-space>
</div>
</template>
<script setup lang="ts" name="AdminI18n">
import { watch, reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import Cookies from "js-cookie";
import $filters from "@/filters/index";
const { t, locale: language } = useI18n();
const state = reactive({
chooseI18n: "",
});
watch(
() => language.value,
(val) => {
state.chooseI18n = val;
}
);
const getDefaultLang = () => {
//
state.chooseI18n = language.value;
}
const changLang = (lang: any) => {
Cookies.set("lang", lang); //
language.value = lang; //i18n
}
getDefaultLang();
</script>

45
src/components/infiniteScroll/index.vue

@ -0,0 +1,45 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
无限滚动列表
</div>
</template>
<ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
<li v-for="i in count" :key="i" class="infinite-list-item">
{{ `我是第${i}` }}
</li>
</ul>
</el-card>
</template>
<script lang="ts" setup name="AdminInfiniteScroll">
import { ref } from "vue";
const count = ref(0);
const load = () => {
count.value += 1;
}
</script>
<style>
.infinite-list {
height: calc(100vh - 300px);
padding: 0;
margin: 0;
list-style: none;
}
.infinite-list .infinite-list-item {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
background: var(--el-color-primary-light-9);
margin: 10px;
color: var(--el-color-primary);
}
.infinite-list .infinite-list-item+.list-item {
margin-top: 10px;
}
</style>

71
src/components/message/feedbackCenter.vue

@ -0,0 +1,71 @@
<template>
<div class="feedbackCenter">
<el-card shadow="always" :body-style="{ padding: '30px 10px 15px 10px' }">
<el-form :inline="true" :model="queryForm" label-position="right" label-width="84px">
<el-form-item label="反馈人:">
<el-input v-model.trim="queryForm.name" clearable placeholder="请输入反馈人姓名">
</el-input>
</el-form-item>
<el-form-item label="人员ID:">
<el-input v-model.trim="queryForm.id" clearable placeholder="请输入人员ID">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary"> </el-button>
<el-button type="primary">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="always" :body-style="{ padding: '30px 10px 15px 10px' }" class="m-t16">
<el-table :data="tableData" height="calc(100vh - 345px)" style="width: 100%">
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="name" label="创建人"></el-table-column>
<el-table-column prop="address" label="反馈问题"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-space spacer="|" style="color: #dedede">
<el-button type="text">查看详情</el-button>
<el-button type="text">处理</el-button>
</el-space>
</template>
</el-table-column>
</el-table>
<Pagination :pagination="pagination"></Pagination>
</el-card>
</div>
</template>
<script setup lang="ts" name="AdminFeedbackCenter">
import Pagination from "@/components/Pagination/index.vue";
let queryForm = ref({
name: "",
id: "",
});
let pagination = ref({
total: 100,
page: 1,
pageSize: 10,
})
let tableData = ref([
{
id: "8008208828",
name: "曹植饭",
address: "图片无法上传",
},
{
id: "8008208828",
name: "曹植饭",
address: "图片无法下载",
},
{
id: "8008208828",
name: "曹植饭",
address: "偶先黑屏问题",
}
])
</script>
<style lang="scss" scoped>
.feedbackCenter {}
</style>

34
src/components/noviceGuide/index.vue

@ -0,0 +1,34 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
新手引导
</div>
</template>
<el-button type="primary" @click="toGuide">进入新手引导</el-button>
</el-card>
</template>
<script lang="ts" setup name="AdminNoviceGuide">
import Driver from "driver.js";
import "driver.js/dist/driver.min.css";
import steps from "../../assets/js/guide";
const driver = new Driver({
className: "scoped-class", // className to wrap driver.js popover
animate: true, // Animate while changing highlighted element
opacity: 0.75, // Background opacity (0 means only popovers and without overlay)
padding: 6, // Distance of element from around the edges
allowClose: false, // Whether clicking on overlay should close or not
overlayClickNext: false, // Should it move to next step on overlay click
doneBtnText: "完成", // Text on the final button
closeBtnText: "关闭", // Text on the close button for this step
nextBtnText: "下一步", // Next button text for this step
prevBtnText: "上一步", // Previous button text for this step
// Called when moving to next step on any step
});
const toGuide = () => {
//
driver.defineSteps(steps);
driver.start();
}
</script>

19
src/views/content/qrcode.vue → src/components/qrCode/index.vue

@ -8,23 +8,14 @@
</template>
<div class="text item">
<el-input v-model="text" placeholder="输入内容展示二维码" />
<img :src="qrcode" alt="QR Code" v-if="text" />
<img v-if="text" :src="qrCode" alt="QR Code" />
</div>
</el-card>
</el-space>
</template>
<script>
import { defineComponent, ref } from "vue";
<script setup lang="ts" name="AdminQrCode">
import { ref } from "vue";
import { useQRCode } from "@vueuse/integrations/useQRCode";
export default defineComponent({
setup() {
const text = ref("Admin-Frame");
const qrcode = useQRCode(text);
return {
text,
qrcode,
};
},
});
const text = ref<string>("Admin-Frame");
const qrCode = useQRCode(text);
</script>

237
src/components/template/cardList.vue

@ -0,0 +1,237 @@
<template>
<div class="cardList">
<el-card shadow="always" :body-style="{ padding: '30px 10px 15px 10px' }">
<el-form :inline="true" :model="queryParams" class="demo-form-inline" label-position="right" label-width="84px">
<el-form-item label="商品名称:">
<el-input v-model.trim="queryParams.name" clearable placeholder="请输入商品名称" @keyup.enter="getList"></el-input>
</el-form-item>
<el-form-item label="商品ID:">
<el-input v-model.trim="queryParams.id" clearable placeholder="请输入商品ID" @keyup.enter="getList"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList"> </el-button>
<el-button type="primary" @click="handlePopup('add', {})">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="m-t8">
<el-row :gutter="10">
<el-col v-for="(item, index) in list" :key="index" :xs="24" :sm="24" :md="12" :lg="12" :xl="6"
style="margin-top: 9px">
<el-card :body-style="{ padding: '24px 0px 6px 0px' }">
<div class="list-nav">
<el-image style="width: 130px; height: 120px" :src="item.image" fit="fill"></el-image>
<div class="l-n-body">
<div class="l-n-b-title">{{ item.name }}</div>
<span class="l-n-b-introduce">{{ item.introduce }}</span>
</div>
</div>
<div style="padding: 8px 4px 0px 4px">
<div class="bottom">
<time class="time">上架日期{{ item.putWayDate }}</time>
<el-space :size="10" spacer="|">
<el-button type="text" @click="handlePopup('edit', {})">编辑</el-button>
<el-button type="text" @click="removeDataById(item.id)">删除</el-button>
</el-space>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<el-dialog v-model="isDialog" :title="dialogTitle" :before-close="
() => {
handleClose(tableFormRef);
}
" width="1000px" :close-on-click-modal="false" :close-on-press-escape="false" top="8vh">
<el-form ref="tableFormRef" :model="form" :rules="rules" label-width="108px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="商品名称:" prop="name">
<el-input v-model="form.name" placeholder="请输入商品名称" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="商品类型:" prop="height">
<el-input v-model="form.height" placeholder="请输入商品类型" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="基本介绍:" prop="introduction">
<el-input v-model="form.introduction" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }"
placeholder="请输入基本介绍"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注:" prop="marks">
<el-input v-model="form.marks" type="textarea" :autosize="{ minRows: 3, maxRows: 5 }"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose(tableFormRef)"> </el-button>
<el-button type="primary" @click="onSubmit(tableFormRef)"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="AdminCardList">
import { reactive, toRefs, onMounted, ref, getCurrentInstance } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import type { FormInstance } from "element-plus";
import { operationStatus } from "@/enums";
import { TitleFormat } from "@/types/template";
const { proxy } = <any>getCurrentInstance();
const tableFormRef = ref<FormInstance>();
let isDialog = ref<boolean>(false);
let handleType = ref<string>(operationStatus.add); //
let dialogTitle = ref<string>("新增");
interface ListItem {
name: string;
introduce: string;
image: string;
putWayDate: string;
id: number | string;
}
let list = ref<ListItem[]>([]);
const state = reactive({
queryParams: {
name: "",
id: "",
},
form: {
name: "",
address: "",
englishName: "",
height: "",
weight: "",
introduction: "",
marks: "",
},
rules: {
name: [{ required: true, message: "请输入商品名称", trigger: "blur" }],
},
});
const { queryParams, form, rules } = { ...toRefs(state) };
onMounted(() => {
getList();
});
const getList = () => {
//
for (let index = 0; index <= 24; index++) {
list.value.push({
name: "汉堡包",
introduce:
"小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡小林蜜制小汉堡",
image:
"https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png",
putWayDate: "2021/11/26",
id: index,
});
}
};
const handlePopup = <T extends keyof TitleFormat>(type: T, target: any) => {
//
handleType.value = type;
isDialog.value = true;
const TITLE_ITEM = {
add: "新增",
edit: "编辑",
};
dialogTitle.value = proxy._public.getValues(TITLE_ITEM, type);
if (type == operationStatus.edit) {
state.form = { ...target };
}
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) {
return;
}
await formEl.validate((valid, fields) => {
if (valid) {
ElMessage({
message: `${dialogTitle.value}成功.`,
type: "success",
});
isDialog.value = false;
}
});
};
const removeDataById = (id: string | number) => {
//
ElMessageBox.confirm("此操作将永久删除本商品记录, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => { })
.catch(() => { });
};
const handleClose = (formEl: FormInstance | undefined) => {
//
if (!formEl) {
return;
}
formEl.resetFields();
isDialog.value = false;
};
</script>
<style lang="scss" scoped>
.list-nav {
display: flex;
justify-content: space-around;
.l-n-body {
width: 200px;
.l-n-b-titie {
text-align: center;
font-size: 17px;
}
.l-n-b-introduce {
font-size: 14px;
padding-top: 12px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
}
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
// margin-top: 13px;
padding: 0 12px;
line-height: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

162
src/components/template/easyForm.vue

@ -0,0 +1,162 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
基础表单
</div>
</template>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
class="demo-ruleForm"
size="default"
>
<el-form-item label="Activity name" prop="name">
<el-input v-model="ruleForm.name" />
</el-form-item>
<el-form-item label="Activity zone" prop="region">
<el-select v-model="ruleForm.region" placeholder="Activity zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time" required>
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker
v-model="ruleForm.date1"
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col class="text-center" :span="2">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker
v-model="ruleForm.date2"
placeholder="Pick a time"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="Instant delivery" prop="delivery">
<el-switch v-model="ruleForm.delivery" />
</el-form-item>
<el-form-item label="Activity type" prop="type">
<el-checkbox-group v-model="ruleForm.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
<el-checkbox label="Offline activities" name="type" />
<el-checkbox label="Simple brand exposure" name="type" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources" prop="resource">
<el-radio-group v-model="ruleForm.resource">
<el-radio label="Sponsorship" />
<el-radio label="Venue" />
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form" prop="desc">
<el-input v-model="ruleForm.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(ruleFormRef)"
>提交</el-button
>
<el-button @click="resetForm(ruleFormRef)">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script lang="ts" setup name="AdminEasyForm">
import { reactive, ref } from "vue";
import type { FormInstance } from "element-plus";
const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive({
name: "Hello",
region: "",
date1: "",
date2: "",
delivery: false,
type: [],
resource: "",
desc: "",
});
const rules = ref<any>({
name: [
{ required: true, message: "Please input Activity name", trigger: "blur" },
{ min: 3, max: 5, message: "Length should be 3 to 5", trigger: "blur" },
],
region: [
{
required: true,
message: "Please select Activity zone",
trigger: "change",
},
],
date1: [
{
type: "date",
required: true,
message: "Please pick a date",
trigger: "change",
},
],
date2: [
{
type: "date",
required: true,
message: "Please pick a time",
trigger: "change",
},
],
type: [
{
type: "array",
required: true,
message: "Please select at least one activity type",
trigger: "change",
},
],
resource: [
{
required: true,
message: "Please select activity resource",
trigger: "change",
},
],
desc: [
{ required: true, message: "Please input activity form", trigger: "blur" },
],
});
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) {
return;
}
await formEl.validate((valid, fields) => {
if (valid) {
console.log("submit!");
} else {
console.log("error submit!", fields);
}
});
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) {
return;
}
formEl.resetFields();
}
</script>

234
src/components/template/tableOperation.vue

@ -0,0 +1,234 @@
<template>
<div class="tableOperation">
<el-card shadow="always" :body-style="{ padding: '30px 10px 15px 10px' }" >
<el-form :inline="true" :model="queryForm" label-position="right"
label-width="84px">
<el-form-item label="姓名:">
<el-input v-model.trim="queryForm.name" clearable placeholder="请输入姓名" @keyup.enter="getList"></el-input>
</el-form-item>
<el-form-item label="ID:">
<el-input v-model.trim="queryForm.id" clearable placeholder="请输入ID" @keyup.enter="getList"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList"> </el-button>
<el-button type="primary" @click="handlePopup(operationStatus.add, null)">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="always" :body-style="{ padding: '30px 10px 15px 10px' }" class="m-t16">
<el-table :data="tableData" height="calc(100vh - 345px)" style="width: 100%">
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-space spacer="|" style="color: #dedede">
<el-button type="text" @click="handlePopup(operationStatus.edit, scope.row)">编辑</el-button>
<el-button type="text">{{
scope.row.enabled === "0" ? useStatus.start : useStatus.close
}}</el-button>
<el-button type="text" @click="removeDataById(scope.row.id)">删除</el-button>
</el-space>
</template>
</el-table-column>
</el-table>
<Pagination :pagination="pagination" @change="handleChangeCurrent"></Pagination>
</el-card>
<el-dialog v-model="isDialog" :title="dialogTitle" :before-close="
() => {
handleClose(operationFormRef);
}
" width="1000px" :close-on-click-modal="false" :close-on-press-escape="false" top="8vh">
<el-form ref="operationFormRef" :model="newlyForm" :newly-rules="newlyRules" label-width="108px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名:" prop="name">
<el-input v-model="newlyForm.name" placeholder="请输入姓名" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="英文名:" prop="englishName">
<el-input v-model="newlyForm.englishName" placeholder="请输入英文名" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="身高(cm):" prop="height">
<el-input v-model="newlyForm.height" placeholder="请输入身高" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="体重(kg):" prop="weight">
<el-input v-model="newlyForm.weight" placeholder="请输入体重" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="地址:" prop="address">
<el-input v-model="newlyForm.address" placeholder="请输入地址" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="基本介绍:" prop="introduction">
<el-input v-model="newlyForm.introduction" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }"
placeholder="请输入基本介绍"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注:" prop="marks">
<el-input v-model="newlyForm.marks" type="textarea" :autosize="{ minRows: 3, maxRows: 5 }"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose(operationFormRef)"> </el-button>
<el-button type="primary" @click="onSubmit(operationFormRef)"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="AdminTableOperation">
import { reactive, toRefs, onMounted, ref, getCurrentInstance } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import type { FormInstance } from "element-plus";
import Pagination from "@/components/Pagination/index.vue";
import { useStatus, operationStatus } from "@/enums";
import { TitleFormat } from "@/types/template";
import { PaginationState } from "@/types";
const { proxy } = <any>getCurrentInstance();
const operationFormRef = ref<FormInstance>();
let isDialog = ref<boolean>(false); //
let handleType = ref<string>(operationStatus.add); //
let dialogTitle = ref<string>("新增");
const state = reactive({
tableData: [
{
id: "8008208828",
name: "曹植饭",
address: "南京市雨花台区宁双路19号云密城J栋6楼",
enabled: "0",
},
{
id: "8008208828",
name: "曹植饭",
address: "南京市雨花台区宁双路19号云密城J栋6楼",
enabled: "1",
},
{
id: "8008208828",
name: "曹植饭",
address: "南京市雨花台区宁双路19号云密城J栋6楼",
},
{
id: "8008208828",
name: "曹植饭",
address: "南京市雨花台区宁双路19号云密城J栋6楼",
},
{
id: "8008208828",
name: "曹植饭",
address: "南京市雨花台区宁双路19号云密城J栋6楼",
},
],
queryForm: {
name: "",
id: "",
},
newlyForm: {
name: "",
address: "",
englishName: "",
height: "",
weight: "",
introduction: "",
marks: "",
},
pagination: {
total: 100,
page: 1,
pageSize: 10,
},
newlyRules: {
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
},
});
const { queryForm, pagination, tableData, newlyForm, newlyRules } = {
...toRefs(state),
};
onMounted(() => {
getList();
});
const handleChangeCurrent = (val: any) => {
//
state.pagination = Object.assign(state.pagination, { ...val });
};
const getList = () => {
//
console.log("我查询了列表");
};
const handlePopup = <T extends keyof TitleFormat>(type: T, target: any) => {
//
handleType.value = type;
isDialog.value = true;
const TITLE_ITEM = {
add: "新增",
edit: "编辑",
};
dialogTitle.value = proxy._public.getValues(TITLE_ITEM, type);
if (type == operationStatus.edit) {
state.newlyForm = { ...target };
}
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) {
return;
}
await formEl.validate((valid, fields) => {
if (valid) {
ElMessage({
message: `${dialogTitle.value}成功.`,
type: "success",
});
isDialog.value = false;
}
});
};
const removeDataById = (id: string | number) => {
//
ElMessageBox.confirm("此操作将永久删除本条记录, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => { })
.catch(() => { });
};
const handleClose = (formEl: FormInstance | undefined) => {
if (!formEl) {
return;
}
formEl.resetFields();
isDialog.value = false;
};
</script>
<style lang="scss" scoped>
</style>

25
src/components/tips/errorTip.vue

@ -0,0 +1,25 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
错误提示
</div>
</template>
<el-result icon="error" title="提交失败">
<template #extra>
<div class="bg-default p-default min-w600">
<div class="t-l">失败原因分析</div>
<div class="t-l m-t8">
账号余额不足
<span style="color: #409eff; cursor: pointer">请充值</span>
</div>
</div>
<div class="m-t8">
<el-button type="primary">返回</el-button>
<el-button type="primary">重新再试</el-button>
</div>
</template>
</el-result>
</el-card>
</template>
<script lang="ts" setup name="AdminError"></script>

20
src/components/tips/successTip.vue

@ -0,0 +1,20 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
成功提示
</div>
</template>
<el-result icon="success" title="提交成功">
<template #extra>
<div class="bg-default p-default min-w600">
<div>提交后会在3个工作日里将结果发送到您的手机</div>
</div>
<div class="m-t8">
<el-button type="primary">返回</el-button>
</div>
</template>
</el-result>
</el-card>
</template>
<script lang="ts" setup name="AdminSuccess"></script>

18
src/views/tip/warning.vue → src/components/tips/warningTip.vue

@ -1,8 +1,13 @@
<template>
<div>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
异常提示
</div>
</template>
<el-result icon="warning" title="提交异常">
<template #extra>
<div class="bg-defult p-defult min-w600">
<div class="bg-default p-default min-w600">
<div class="t-l">异常原因分析</div>
<div class="t-l m-t8">
您的账号最近3日内交易过于频繁请尽快
@ -11,10 +16,11 @@
</div>
</div>
<div class="m-t8">
<el-button type="primary" size="medium">返回</el-button>
<el-button type="primary" size="medium">联系管理员</el-button>
<el-button type="primary">返回</el-button>
<el-button type="primary">联系管理员</el-button>
</div>
</template>
</el-result>
</div>
</template>
</el-card>
</template>
<script lang="ts" setup name="AdminWarning"></script>

38
src/components/watermark/index.vue

@ -0,0 +1,38 @@
<template>
<el-card shadow="always" :body-style="{ padding: '30px' }">
<template #header>
<div class="card-header">
添加水印
</div>
</template>
<el-switch
v-model="value"
style="display: block"
active-color="#13ce66"
active-text="开启水印"
inactive-text="关闭水印"
/>
</el-card>
</template>
<script setup lang="ts" name="AdminWatermark">
import { onMounted, onUnmounted, ref, watch } from "vue";
import { setWaterMarker, removeWaterMarker } from "@/common/watermark"; //
const value = ref<boolean>(true);
watch(value, (newV, oldV) => {
if (newV) {
setWaterMarker("Admin Frame 添加水印"); //
} else {
removeWaterMarker(); //
}
});
onMounted(() => {
setWaterMarker("Admin Frame 添加水印"); //
})
onUnmounted(() => {
removeWaterMarker(); //
})
</script>

27
src/directives/copy.js

@ -1,27 +0,0 @@
import { ElMessage } from "element-plus";
import Clipboard from "clipboard";
const copy = (app, options) => {
app.directive('copy', {
mounted(el, binding) {
el.addEventListener("click", (() => {
var clipboard = new Clipboard(`.${binding?.value}`);
clipboard.on("success", (e) => {
console.log(`指令方法复制成功`);
ElMessage({
message: `指令方法复制成功`,
type: "success",
});
// 释放内存
clipboard.destroy();
});
clipboard.on("error", (e) => {
// 不支持复制
//console.log('该浏览器不支持自动复制')
// 释放内存
clipboard.destroy();
});
}));
}
})
}
export default copy;

27
src/directives/copy.ts

@ -0,0 +1,27 @@
import { ElMessage } from "element-plus";
import Clipboard from "clipboard";
const copy = (app: any, options: any) => {
app.directive("copy", {
mounted(el: any, binding: any) {
el.addEventListener("click", () => {
const clipboard = new Clipboard(`.${binding?.value}`);
clipboard.on("success", (e) => {
console.log(`指令方法复制成功`);
ElMessage({
message: `指令方法复制成功`,
type: "success",
});
// 释放内存
clipboard.destroy();
})
clipboard.on("error", (e) => {
// 不支持复制
//console.log('该浏览器不支持自动复制')
// 释放内存
clipboard.destroy();
})
});
},
});
}
export default copy;

70
src/directives/dialogDrag.js

@ -1,70 +0,0 @@
const dialogDrag = (app, options) => {
app.directive('dialogdrag', {
// 渲染完毕
mounted(el, binding) {
// binding.arg
// binding.value
// 可视窗口的宽度
const clientWidth = document.documentElement.clientWidth
// 可视窗口的高度
const clientHeight = document.documentElement.clientHeight
// 记录坐标
let domset = {
x: clientWidth / 4, // 默认width 50%
y: clientHeight * 15 / 100 // 根据 15vh 计算
}
// 弹窗的容器
const domDrag = el.firstElementChild.firstElementChild
// 重新设置上、左距离
domDrag.style.marginTop = domset.y + 'px'
domDrag.style.marginLeft = domset.x + 'px'
// 记录拖拽开始的光标坐标,0 表示没有拖拽
let start = { x: 0, y: 0 }
// 移动中记录偏移量
let move = { x: 0, y: 0 }
// 鼠标按下,开始拖拽
domDrag.onmousedown = (e) => {
// 判断对话框是否重新打开
if (domDrag.style.marginTop === '15vh') {
// 重新打开,设置 domset.y top
domset.y = clientHeight * 15 / 100
}
start.x = e.clientX
start.y = e.clientY
domDrag.style.cursor = 'move' // 改变光标形状
}
// 鼠标移动,实时跟踪
domDrag.onmousemove = (e) => {
if (start.x === 0) { // 不是拖拽状态
return
}
move.x = e.clientX - start.x
move.y = e.clientY - start.y
// 初始位置 + 拖拽距离
domDrag.style.marginLeft = (domset.x + move.x) + 'px'
domDrag.style.marginTop = (domset.y + move.y) + 'px'
}
// 鼠标抬起,结束拖拽
domDrag.onmouseup = (e) => {
move.x = e.clientX - start.x
move.y = e.clientY - start.y
// 记录新坐标,作为下次拖拽的初始位置
domset.x += move.x
domset.y += move.y
domDrag.style.cursor = '' // 恢复光标形状
domDrag.style.marginLeft = domset.x + 'px'
domDrag.style.marginTop = domset.y + 'px'
// 结束拖拽
start.x = 0
}
}
})
}
export default dialogDrag

6
src/directives/index.js

@ -1,6 +0,0 @@
import dialogDrag from './dialogDrag';
import copy from './copy';
export {
dialogDrag,
copy
}

2
src/directives/index.ts

@ -0,0 +1,2 @@
import copy from "./copy";
export { copy };

14
src/enums/index.ts

@ -0,0 +1,14 @@
export enum LoginStatus { //0 注册 1登录
register,
login,
}
export enum useStatus { //使用状态 0启用 1禁用
start = "启用",
close = "禁用",
}
export enum operationStatus { //操作状态 0新增 1修改
add = "add",
edit = "edit",
}

8
src/env.d.ts

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module "*.vue" {
import type { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}

20
src/filters/index.js

@ -1,20 +0,0 @@
export default {
Gender(val) {
//性别过滤 0女 1男 没有则未知
const dataWare = [
'女', '男'
]
return dataWare[val] ?? '未知';
},
langFilter(val) {
//过滤国际化中文
switch (val) {
case 'zh':
return '中文';
case 'zh-cn':
return '中文';
case 'en':
return 'English';
}
}
}

18
src/filters/index.ts

@ -0,0 +1,18 @@
export default {
Gender(val: any) {
//性别过滤 0女 1男 没有则未知
const dataWare: string[] = ["女", "男"];
return dataWare[Number(val)] ?? "未知";
},
inspectLanguage<T extends string>(val: T) {
//过滤国际化中文
switch (val) {
case "zh":
return "中文";
case "zh-cn":
return "中文";
case "en":
return "English";
}
},
};

288
src/layouts/components/Header/index.vue

@ -0,0 +1,288 @@
<template>
<div class="admin-header">
<div class="collapse-left">
<div class="collapse-btn">
<i title="点击打开关闭菜单" @click="switchCollapse" :class="collapse ? 'fa fabtn fa-indent' : 'fa fabtn fa-dedent'"></i>
<i title="刷新页面" @click="reload()" class="fa fa-refresh" aria-hidden="true"></i>
</div>
<div class="collapse-breadcrumb">
<el-breadcrumb>
<el-breadcrumb-item v-for="(item, index) in route.matched" :key="item.path">
<span :style="{
'font-weight': route.matched.length === index + 1 ? 700 : 500
}">{{ transitionLocal("message." + item.meta.title) }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
<div class="collapse-right">
<!-- <el-tooltip class="item" effect="dark" :content="t('message.public.fullScreen')" placement="bottom">
<span class="faSpan">
<i class="fa fa-arrows-alt" @click="requestFullScreen()"></i>
</span>
</el-tooltip> -->
<span class="faSpan">
<i class="fa fa-arrows-alt" @click="requestFullScreen()"></i>
</span>
<el-dropdown @command="changeI18n">
<span class="el-dropdown-link faSpan">
<!-- <el-tooltip class="item" effect="dark" :content="t('message.public.languageSwitch')" placement="left">
<i class="fa fa-language g-language" style="font-size: 18px"></i>
</el-tooltip> -->
<i class="fa fa-language g-language" style="font-size: 18px"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :command="locale"
:style="{
color: $i18n.locale == locale ? 'rgb(64, 158, 255)' : '',
}">{{ $filters.inspectLanguage(locale) }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown @command="settingFontSize">
<span class="el-dropdown-link faSpan">
<!-- <el-tooltip class="item" effect="dark" :content="t('message.public.settingFontSize')" placement="left">
<i class="fa fa-font g-font" style="font-size: 18px"></i>
</el-tooltip> -->
<i class="fa fa-font g-font" style="font-size: 18px"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(item, key) in FONT_SIZE" :key="`fontsize-${key}`" :command="item.value" :style="{
color: useSize == item.value ? 'rgb(64, 158, 255)' : '',
}">{{ t(`message.public.${item.value}`) }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- <el-tooltip class="item" effect="dark" :content="t('message.public.messageCenter')" placement="bottom">
<span class="faSpan">
<el-badge is-dot class="item">
<i class="fa faPad fa-bell-o" @click="toGetMessage"></i>
</el-badge>
</span>
</el-tooltip> -->
<span class="faSpan">
<el-badge is-dot class="item">
<i class="fa faPad fa-bell-o" @click="toGetMessage"></i>
</el-badge>
</span>
<!-- 用户名下拉菜单 -->
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link btn_username_group">
<span class="btn_username" :title="username">{{ username }}</span>
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="signOut">{{
t("message.public.loggedOut")
}}</el-dropdown-item>
<el-dropdown-item command="versionLog">{{
t("message.public.versionLog")
}}</el-dropdown-item>
<el-dropdown-item command="baseInfo">{{
t("message.public.basicInfo")
}}</el-dropdown-item>
<el-dropdown-item command="checkPass">{{
t("message.public.changePassword")
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 用户头像 -->
<div class="user-avatar">
<img :src="getImage('touxiang', 'jpg')" />
</div>
</div>
<check-pass v-model:passVisible="passVisible"></check-pass>
<base-info ref="baseInfoRef" v-model:baseVisible="baseVisible"></base-info>
<version-log v-model:versionVisible="versionVisible"></version-log>
</div>
</template>
<script setup lang="ts" name="AdminHeader">
import {
getCurrentInstance,
ref,
computed,
unref,
inject
} from "vue";
import $filters from "@/filters/index";
import screenFull from "screenfull";
import { ArrowDown } from "@element-plus/icons-vue";
import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import Cookies from "js-cookie";
import CheckPass from "@/components/dropDownItem/checkPass.vue";
import BaseInfo from "@/components/dropDownItem/baseInfo.vue";
import VersionLog from "@/components/dropDownItem/versionLog.vue";
import { useI18n } from "vue-i18n";
import { useTagStore } from "@/pinia/modules/tag";
import { useUserStore } from "@/pinia/modules/user";
import { FONT_SIZE } from "@/assets/js/dictionarie";
import { removeToken } from "@/utils/auth";
import { HeaderState } from "@/types/layout";
import { getImage } from "@/utils";
import { transitionLocal } from "@/locales/i18n"; //
const tagStore = useTagStore();
const userStore = useUserStore();
const route = useRoute();
const { proxy } = <any>getCurrentInstance(); // vue
const { t, locale: language } = useI18n();
const router = useRouter(); //
const baseInfoRef = ref();
const useSize = computed(() => Cookies.get("size"));
const collapse = computed(() => tagStore.collapse); //sidebar
const username = computed(() => userStore?.user?.username || "待完善"); //
let passVisible = ref<boolean>(false); //
let baseVisible = ref<boolean>(false); //
let versionVisible = ref<boolean>(false); //
const reload = inject("reload") as Function;
const requestFullScreen = () => {
// 退
if (screenFull.isEnabled) {
//screenFull
if (screenFull.isFullscreen) {
//退
screenFull.exit();
} else {
//
screenFull.toggle();
}
}
};
const switchCollapse = () => {
//
setTimeout(() => {
tagStore.switchCollapse(!unref(collapse));
}, 0);
};
const handleCommand = <T extends string>(command: T) => {
//
if (command === "signOut") {
removeToken();
tagStore.delRightMenu({ WHITE_TAGS_LIST: [] }); //退
router.push("/login");
ElMessage.success("登出成功");
} else if (command === "checkPass") {
//
passVisible.value = true;
} else if (command === "baseInfo") {
//
unref(baseInfoRef).openBaseInfo()
} else if (command === "versionLog") {
//
versionVisible.value = true;
}
};
const toGetMessage = () => {
//
router.push("/main/feedbackCenter");
};
const changeI18n = <T extends string>(type: T): void | boolean => {
//
if (type == Cookies.get("lang")) {
//
ElMessage.warning(`${t("message.public.recurrentSelection")}`);
return false;
}
Cookies.set("lang", type); //
language.value = type; //i18n
proxy.$i18n.locale = type; //i18n
ElMessage.success(`${t("message.public.editLang")}`);
router.go(0); //
};
const settingFontSize = <T extends string>(type: T): void | boolean => {
//
if (type == Cookies.get("size")) {
//
ElMessage.warning(`${t("message.public.recurrentSelection")}`);
return false;
}
Cookies.set("size", type ?? "default");
ElMessage.success(`${t("message.public.switchSuccess")}`);
router.go(0); //
};
</script>
<style lang="scss" scoped>
.admin-header {
box-sizing: border-box;
width: 100%;
height: 64px;
font-size: 18px;
color: #616161;
background: #fff;
display: flex;
justify-content: space-between;
.collapse-left {
display: flex;
justify-content: space-between;
align-items: center;
.collapse-btn {
padding: 0px 0px 0 15px;
cursor: pointer;
i {
margin-right: 25px;
}
}
}
.el-icon-s-fold,
.el-icon-s-unfold {
font-size: 25px;
cursor: pointer;
}
.collapse-right {
float: right;
padding-right: 8px;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
.faSpan {
padding-right: 20px;
cursor: pointer;
}
}
.user-avatar {
margin: 0 15px 0 5px;
}
img {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
}
.btn_username_group {
cursor: pointer;
display: flex;
justify-content: space-between;
.btn_username {
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 60px;
}
}
}
</style>

92
src/components/SideBar/index.vue → src/layouts/components/SideBar/index.vue

@ -1,9 +1,12 @@
<template>
<div :style="{
width: collapse ? '64px' : '200px',
}" class="sidebar">
<div class="zan-sidebar-nav">
<img alt src="../../assets/image/LG.png" />
<div
:style="{
width: collapse ? '64px' : '200px',
}"
class="sidebar"
>
<div class="admin-sidebar-nav">
<img :src="getImage('LG','png')" />
<h1 v-if="!collapse">Admin Frame</h1>
</div>
<el-menu
@ -19,7 +22,7 @@
>
<template v-for="item in menuItem">
<template v-if="item?.children?.length">
<el-submenu :key="item.resourceUrl" :index="item.resourceUrl">
<el-sub-menu :key="item.resourceUrl" :index="item.resourceUrl">
<template #title>
<i :class="item.resourceIcon"></i>
<span
@ -28,10 +31,11 @@
? 'sidebar-title'
: 'sidebar-title sidebar-nullIcon'
"
>{{ transitionLocal(item.resourceName) }}</span>
>{{ t(item.resourceName) }}</span
>
</template>
<template v-for="childItem in item.children">
<el-submenu
<el-menu-item-group
v-if="childItem?.children?.length"
:key="childItem.resourceUrl"
:index="childItem.resourceUrl"
@ -44,20 +48,19 @@
? 'sidebar-title'
: 'sidebar-title sidebar-nullIcon'
"
>{{ transitionLocal(childItem.resourceName) }}</span>
>{{ t(childItem.resourceName) }}</span
>
</template>
<el-menu-item
v-for="(grandsonItem, i) in childItem.children"
:key="i"
:index="grandsonItem.resourceUrl"
>
<span class="sidebar-title">
{{
transitionLocal(grandsonItem.resourceName)
}}
</span>
<span class="sidebar-title">{{
t(grandsonItem.resourceName)
}}</span>
</el-menu-item>
</el-submenu>
</el-menu-item-group>
<el-menu-item
v-else
:key="childItem.resourceUrl + 'childItem'"
@ -71,74 +74,65 @@
? 'sidebar-title'
: 'sidebar-title sidebar-nullIcon'
"
>{{ transitionLocal(childItem.resourceName) }}</span>
>{{ t(childItem.resourceName) }}</span
>
</template>
</el-menu-item>
</template>
</el-submenu>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :key="item.resourceUrl" :index="item.resourceUrl">
<i :class="item.resourceIcon"></i>
<template #title class="sidebar-title">
{{
transitionLocal(item.resourceName)
}}
</template>
<template #title class="sidebar-title">{{
t(item.resourceName)
}}</template>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</template>
<script>
import { defineComponent, toRefs, reactive, ref, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import resourceList from "../../assets/js/resource";
import { transitionLocal } from '../../locales/i18n'; //
export default defineComponent({
name: "sidebar",
setup() {
const store = useStore();
const router = useRouter(); //
const route = useRoute(); //
const state = reactive({
menuItem: computed(() => resourceList), //
onRoutes: computed(() => route.path),
collapse: computed(() => {
return store.state.collapse;
}),
});
return {
...toRefs(state),
transitionLocal
};
},
});
<script lang="ts" setup name="AdminSidebar">
import { getImage } from '@/utils';
import { computed } from "vue";
import { useRoute } from "vue-router";
import resourceList from "@/assets/js/resource";
import { useI18n } from "vue-i18n";
import { useTagStore } from "@/pinia/modules/tag";
import { ResourceItem } from "@/types/setting";
const { t } = useI18n();
const tagStore = useTagStore();
const route = useRoute();
const menuItem = computed<ResourceItem[]>(() => resourceList);
const onRoutes = computed<string>(() => route.path);
const collapse = computed<boolean>(() => tagStore.collapse);
</script>
<style lang="scss" scoped>
.sidebar {
height: 100%;
box-sizing: border-box;
transition: width 0.3s ease-in-out;
.sidebar-el-menu {
overflow-x: hidden;
overflow-y: scroll;
}
.zan-sidebar-nav {
.admin-sidebar-nav {
box-sizing: border-box;
padding: 16px 10px 16px 10px;
background-color: rgb(0, 21, 41);
display: flex;
align-items: center;
img {
width: 32px;
height: 32px;
padding-left: 5px;
}
h1 {
white-space: nowrap;
display: inline-block;

270
src/layouts/components/Tags/index.vue

@ -0,0 +1,270 @@
<template>
<div class="tag_content">
<TransitionGroup v-if="tagsList.length" name="list" class="tags" tag="div">
<el-tag v-for="(tag, index) in tagsList" :key="index" :type="selectPath === tag.path ? '' : 'info'"
:class="selectPath === tag.path ? 'tag_check' : 'tag_null_check'"
:closable="tag.path == '/dashboard/workbench' ? false : true"
:effect="selectPath === tag.path ? 'dark' : 'plain'" :disable-transitions="false" @close="closeTag(index)"
@click="triggerTag(tag, 'go')" size="large">
{{ t("message." + tag.title) }}
</el-tag>
</TransitionGroup>
<div class="right_trigger_box">
<span class="el-dropdown-link fullScreen" @click="toFullScreen">
<el-icon>
<full-screen />
</el-icon>
</span>
<el-dropdown trigger="click" @command="triggerRight">
<span class="el-dropdown-link">
<i class="fa fa-angle-down"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="reload">
<span>{{ t("message.public.reload") }}</span>
</el-dropdown-item>
<el-dropdown-item command="all" :disabled="tagsList.length === 1">
<span>{{ t("message.public.closeAll") }}</span>
</el-dropdown-item>
<el-dropdown-item command="other" :disabled="tagsList.length <= 2">{{ t("message.public.closeOther") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts" name="AdminTags">
import {
getCurrentInstance,
computed,
onMounted,
ref,
inject,
toRaw,
unref,
isProxy,
watch
} from "vue";
import { FullScreen } from "@element-plus/icons-vue";
import {
useRoute,
useRouter,
} from "vue-router";
import { ElMessage, ElNotification } from "element-plus";
import { useI18n } from "vue-i18n";
import { useTagStore } from "@/pinia/modules/tag";
import screenFull from "screenfull";
import { Tag } from "@/types/layout";
import { storeToRefs } from "pinia";
const tagStore = useTagStore();
const { tagsList } = storeToRefs(tagStore);
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const { proxy } = <any>getCurrentInstance();
const reload = inject("reload") as Function;
let selectPath = ref<string>("");
const setTags = <T extends Tag>(target: T) => {
//
const isExist = unref(tagsList).some((item: { path: string }) => {
return item.path === target.fullPath;
});
if (!isExist) {
if (unref(tagsList).length >= tagStore.MAX_TAG_LENGTH) {
//10
let index: number = 0;
for (const [i, v] of unref(tagsList).entries()) {
if (v.path === "/dashboard/workbench") {
if (i === 0) {
index += 1;
break;
}
}
}
tagStore.delTags({ index });
}
tagStore.setTags({
title: target.meta.title,
...target,
});
}
};
const closeTag = <K extends number>(index: K) => {
//
tagStore.delTags({ index });
triggerTag(unref(tagsList)[unref(tagsList).length === index ? index - 1 : index], "go");
};
const triggerTag = <T extends Tag, K extends string>(tag: T, type?: K) => {
//
proxy._public.debounce(() => {
selectPath.value = tag.path;
if (type) {
//
router.push(tag.path);
}
}, 150);
};
const triggerRight = <V extends string>(menu: V) => {
//
if (["all", "other"].includes(menu)) {
//
let WHITE_TAGS_LIST: string[] = ["/dashboard/workbench"];
if (unref(selectPath) !== WHITE_TAGS_LIST[0] && menu === "other") {
//
WHITE_TAGS_LIST.push(unref(selectPath));
}
tagStore.delRightMenu({ WHITE_TAGS_LIST });
router.push(WHITE_TAGS_LIST[WHITE_TAGS_LIST.length - 1]);
} else if (menu === "reload") {
//
reload();
}
};
const toFullScreen = () => {
// 退
const element = document.getElementById("screen-display") as HTMLDivElement;
if (screenFull.isEnabled) {
//screenFull
if (screenFull.isFullscreen) {
//退
screenFull.exit();
} else {
//
screenFull.request(element);
ElNotification({
title: "Success",
message: "当前为全屏模式,退出按ESC.",
type: "success",
duration: 2000,
appendTo: "#screen-display",
});
}
}
};
router.beforeEach(async (to, from) => {
//
if (!tagStore.BLACK_LIST.includes(to.path)) {
await setTags(to);
await triggerTag(to);
}
});
onMounted(() => {
//
setTags(route);
triggerTag(route);
});
</script>
<style lang="scss" scoped>
.tag_content {
padding: 6px 0px;
margin: 0px 12px;
box-sizing: border-box;
white-space: nowrap;
display: flex;
justify-content: space-between;
align-items: center;
.list-move,
/* 对移动中的元素应用的过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/*
以便能够正确地计算移动的动画 */
.list-leave-active {
position: absolute;
}
.tags {
width: calc(100vw - 310px);
overflow: auto;
}
.list-move,
/* 对移动中的元素应用的过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/*
以便能够正确地计算移动的动画 */
.list-leave-active {
position: absolute;
}
.el-tag {
cursor: pointer;
margin-right: 8px;
}
.tag_check {
border-radius: 1px;
}
.tag_null_check {
background-color: #ffffff !important;
border-color: #e4e7ed !important;
color: rgb(97, 97, 97) !important;
border-radius: 1px;
}
.right_trigger_box {
display: flex;
border: 1px solid #e4e7ed;
.fullScreen {
border-right: 1px solid #e4e7ed;
}
.el-dropdown-link {
cursor: pointer;
height: 26px;
width: 50px;
display: flex;
background: #fff;
border-radius: 2px;
align-items: center;
justify-content: center;
color: #a8abb2;
.fa-angle-down {
font-size: 20px;
}
}
.el-dropdown-link:hover {
color: #000;
}
}
}
</style>

3
src/layouts/global/index.vue

@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>

84
src/layouts/index.vue

@ -0,0 +1,84 @@
<template>
<div class="app-wrapper">
<el-container style="height: 100vh">
<el-aside style="width: auto">
<side-bar></side-bar>
</el-aside>
<el-container>
<el-header height="64px">
<Header></Header>
</el-header>
<el-main>
<Tags></Tags>
<div id="screen-display" v-loading="!isReload" class="content">
<router-view v-if="isReload" v-slot="{ Component }">
<Transition appear name="fade" appear-active-class="animate__animated animate__pulse"
enter-active-class="animate__animated animate__fadeIn">
<!--进入 enter-active-class 移出 leave-active-class 初始 appear-active-class-->
<component :is="Component" v-if="Component" />
</Transition>
</router-view>
<el-backtop target=".content"></el-backtop>
</div>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts" name="AdminMain">
import SideBar from "@/layouts/components/SideBar/index.vue";
import Header from "@/layouts/components/Header/index.vue";
import Tags from "@/layouts/components/Tags/index.vue";
import { nextTick, provide, ref } from "vue";
const isReload = ref<boolean>(true);
const reload = () => {
isReload.value = false;
nextTick(() => {
setTimeout(() => {
isReload.value = true;
}, 500);
})
}
provide("reload", reload);
</script>
<style lang="scss" scoped>
.app-wrapper {
width: 100%;
height: 100%;
overflow: hidden;
.el-aside {
position: relative;
overflow: hidden;
}
.el-header,
.el-main {
padding: 0;
}
.el-main {
background: #f0f2f5;
.content {
padding: 0px 5px 8px 5px;
box-sizing: border-box;
margin: 0px 8px 0 8px;
// background: #ffffff;
overflow-x: hidden;
overflow-y: auto;
height: calc(100vh - 115px);
color: #515a6e;
min-width: 1000px;
}
}
.el-affix--fixed,
.el-overlay {
right: 8px !important;
position: fixed;
}
}
</style>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save