mirror of
https://gitee.com/lyt-top/vue-next-admin
synced 2026-06-16 13:50:47 +08:00
Compare commits
14 Commits
vue-next-a
...
2.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
| d2d83fd70b | |||
| 56139e72a7 | |||
| 07e0f742d8 | |||
| c09b154a3f | |||
| 6e59014357 | |||
| 02e7c49750 | |||
| 7b26cb21dd | |||
| bfecc6f6d2 | |||
| 14981044b9 | |||
| 8ed7986a96 | |||
| 852075ccfb | |||
| ba80b9bc76 | |||
| 4f8f13a722 | |||
| 1787f09bdc |
8
.env
Normal file
8
.env
Normal file
@ -0,0 +1,8 @@
|
||||
# port 端口号
|
||||
VITE_PORT = 8888
|
||||
|
||||
# open 运行 npm run dev 时自动打开浏览器
|
||||
VITE_OPEN = false
|
||||
|
||||
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
|
||||
VITE_PUBLIC_PATH = /vue-next-admin-preview/
|
||||
5
.env.development
Normal file
5
.env.development
Normal file
@ -0,0 +1,5 @@
|
||||
# 本地环境
|
||||
ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = 'http://localhost:8888/'
|
||||
5
.env.production
Normal file
5
.env.production
Normal file
@ -0,0 +1,5 @@
|
||||
# 线上环境
|
||||
ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = 'https://lyt-top.gitee.io/vue-next-admin-preview/'
|
||||
@ -13,9 +13,18 @@ module.exports = {
|
||||
},
|
||||
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.vue'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
// http://eslint.cn/docs/rules/
|
||||
// https://eslint.vuejs.org/rules/
|
||||
// https://typescript-eslint.io/rules/no-unused-vars/
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
@ -26,6 +35,9 @@ module.exports = {
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [2],
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
@ -59,5 +71,6 @@ module.exports = {
|
||||
'no-v-model-argument': 'off',
|
||||
'no-case-declarations': 'off',
|
||||
'no-console': 'error',
|
||||
'no-redeclare': 'off',
|
||||
},
|
||||
};
|
||||
32
.gitignore
vendored
32
.gitignore
vendored
@ -1,11 +1,23 @@
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
frontend/LICENSE
|
||||
*.zip
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
425
CHANGELOG.md
Normal file
425
CHANGELOG.md
Normal file
@ -0,0 +1,425 @@
|
||||
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
|
||||
|
||||
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
|
||||
|
||||
## 2.4.2
|
||||
|
||||
`2022.12.09`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 国际化自动导入文件功能,只需在 `/src/i18n/pages` 下新建文件夹定义即可
|
||||
- 🎉 新增 `/make/tableDemo` 中 [搜索框展开,收缩功能,高级筛选组件 有计划做吗](https://gitee.com/lyt-top/vue-next-admin/issues/I6511L)
|
||||
- 🐞 修复 [!40 开启 TagsView 缓存后,刷新后所有的路由都变成组件缓存了](https://gitee.com/lyt-top/vue-next-admin/pulls/40),感谢[@mrjimin](https://gitee.com/mrjimin)
|
||||
- 🐞 修复 [!41 修复 get 请求传递嵌套对象或数组时无法正常编码问题](https://gitee.com/lyt-top/vue-next-admin/pulls/41),感谢[@随心](https://gitee.com/jiangqiang1996)
|
||||
- 🐞 修复 组件 wangEditor 回显值的问题
|
||||
- 🐞 修复 `/fun/echartsMap`(地理坐标/地图)、`visualizingDemo2`(数据可视化演示 2) 演示报错问题
|
||||
- 🎯 优化 版本升级提示
|
||||
- 🎯 优化 无权限登录时增加提示信息,[BUG:因前端加载路由(initFrontEndControlRoutes)中当前用户角色为一个陌生角色, 导致 router.beforeEach 会死循环 浏览器崩溃](https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO),感谢[@canroc](https://gitee.com/canroc)、[@随心](https://gitee.com/jiangqiang1996)
|
||||
- 🌈 重构 `/views/system` 新增修改组件合并。[可以把新增修改组件合并成一个吧](https://gitee.com/lyt-top/vue-next-admin/issues/I64WES)
|
||||
- 🌈 重构 图标选择器,[图标选择器没办法筛选,只能筛选 ali 的](https://gitee.com/lyt-top/vue-next-admin/issues/I64HZD),感谢[@随心](https://gitee.com/jiangqiang1996)
|
||||
|
||||
## 2.4.1
|
||||
|
||||
`2022.11.30`
|
||||
|
||||
- 🎉 新增 版本升级提示
|
||||
- 🐞 修复 [先打开 F12 再登录进去,然后改变浏览器大小 js 报错](https://gitee.com/lyt-top/vue-next-admin/issues/I63ZZT),感谢[@Quber](https://gitee.com/quber)
|
||||
|
||||
## 2.4.0
|
||||
|
||||
`2022.11.29`
|
||||
|
||||
⚡⚡⚡ 此版为破坏性更新,应群友建议 `script lang="ts"` 改 `script lang="ts" setup 语法糖`。
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 表格封装演示,路径:`组件封装 -> 表格封装演示`
|
||||
- 🎉 新增 master 分支 script lang="ts" 改成 script lang="ts" setup 语法糖,将同步基础分支
|
||||
- 🐞 修复 [v2.3.0 版本报错问题处理](https://gitee.com/lyt-top/vue-next-admin/issues/I623RP)
|
||||
- 🐞 修复 [el-backtop 滚动高度不触发(固定了 header)](https://gitee.com/lyt-top/vue-next-admin/issues/I63N0D),感谢[@dejavuuuuu](https://gitee.com/zc19951010)
|
||||
- 🎯 优化 完善 ts 类型,删除根目录 `plugins.d.ts、shim.d.ts、source.d.ts`,移入到 `/src/types/global.d.ts`
|
||||
- 🎯 优化 代码 `watch` 移动到 `生命周期钩子` 最后,文字注释等
|
||||
|
||||
## 2.3.0
|
||||
|
||||
`2022.11.16`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 新版登录页
|
||||
- 🎉 新增 tagsview 鼠标中键 `关闭当前 tagsview`
|
||||
- 🎉 新增 `分栏菜单鼠标悬停预加载`。[分栏模式如何去掉鼠标悬浮父级菜单,分栏菜单自动加载的功能啊](https://gitee.com/lyt-top/vue-next-admin/issues/I5RUY7)。操作路径:`布局配置 -> 分栏设置`
|
||||
- 🐞 修复 [vue-i18n](https://vue-i18n.intlify.dev/api/general.html#createi18n) 报错,[!39 修复 i18n 兼容性问题](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/39/files),感谢[@随心](https://toscode.gitee.com/jiangqiang1996)
|
||||
- 🐞 修复 顶栏搜索功能点击蒙蔽弹窗不关闭
|
||||
- 🐞 修复 [!38 fix: bug refreshRouterViewKey 值为 null 导致路由缓存第一次无效](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files),感谢[@P)](https://toscode.gitee.com/foxp8y)
|
||||
- 🐞 修复 `路由参数 -> 普通路由/动态路由` 国际化演示时,`tagsView` 和 `浏览器标题` 显示异常。[演示中:路由参数界面 -> 动态路由,国际化显示时面包屑、浏览器标题有 bug](https://gitee.com/lyt-top/vue-next-admin/issues/I5JRJG)
|
||||
- 🐞 修复 `路由参数 -> 普通路由/动态路由` 动态设置 `tagsViewName` 时,`tagsView 右键菜单刷新` 功能失效(也就是路由后面有参数时,query、params)。[普通或动态路由新建页面后点击 tagview 刷新无效](https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO),感谢[@dejavuuuuu](https://gitee.com/zc19951010)
|
||||
- 🐞 修复 [表单(el-form)中,字体图标偏移问题](https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM)
|
||||
- 🐞 修复 路由 `router.addRoute` 时,一直提示 `No match found for location with path 'xxx'`
|
||||
- 🎯 优化 全局 `getCurrentInstance` 替换成 [`provide/inject`](https://cn.vuejs.org/api/application.html#app-provide) 或通过 `ref` 处理
|
||||
- 🎯 优化 引入组件方式 `(import xxx from xxx)` 改成 `defineAsyncComponent(() => import(xxx))`
|
||||
- 🎯 优化 页面高度 100% 问题,重写布局配置 `界面设置 -> 固定 Header` 多余的 `el-scrollbar` 逻辑、重写各界面需 `计算属性 computed` 设置动态高度问题(改为 css `flex` 设置自适应高度,具体查看文档:[设置可视区高度 100%](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/otherIssues/#%E8%AE%BE%E7%BD%AE%E5%8F%AF%E8%A7%86%E5%8C%BA%E9%AB%98%E5%BA%A6-100)。[!31 修复页面样式无法通过百分比设置的问题](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/31),感谢[@LostDeer](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/31/files)。`(改动较大,删除多余代码)`
|
||||
- 🎯 优化 [wangeditor](https://www.wangeditor.com/) 组件,`@wangeditor/editor-for-vue`。可自行修改,组件位置:`/src/components/editor`。相关 Issues:[wangeditor 编辑器多个菜单不能回弹](https://gitee.com/lyt-top/vue-next-admin/issues/I5M5H7)
|
||||
- 🌈 重构 外链、内嵌 iframe 逻辑 + 美化,iframe 支持缓存
|
||||
|
||||
## 2.2.0
|
||||
|
||||
`2022.07.10`
|
||||
|
||||
⚡⚡⚡ [/sec/stores/userInfo.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/stores/userInfo.ts) 下添加了 `getApiUserInfo` 接口模拟数据 `setTimeout` 为 3 秒
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 [主界面重新授权按钮点击卡死不跳转登录界面#I5C3JS](https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS),感谢[@Hero-Typ](https://gitee.com/tian_yu_peng)
|
||||
- 🐞 修复 编译警告[#I5CVSB](https://gitee.com/lyt-top/vue-next-admin/issues/I5CVSB),全局替换成 `:deep(attr)`,感谢[@Linvas](https://gitee.com/linvas)。参考文档:[vue3 sfc-style](https://v3.cn.vuejs.org/api/sfc-style.html#style-scoped)。`node_modules\print-js\dist\print.js` 需 `print-js` 作者适配或去除 `package.json` 中的 `"print-js": "^1.6.0"`
|
||||
- 🐞 修复 [vue-next-admin-template-js 版本前端控制路由:userInfo.js 请求用户信息接口报错,加载不到路由 可以写个定时器模拟一下接口 一样的报错#I5F1HP](https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP),感谢[@白开水](https://gitee.com/libin951223)
|
||||
|
||||
## 2.1.1
|
||||
|
||||
`2022.05.27`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 深色模式下,`<el-button text></el-button>` 时,`:active` 样式
|
||||
- 🎯 优化 [页面缓存在刷新之后失效 #I58U75](https://gitee.com/lyt-top/vue-next-admin/issues/I58U75)),感谢[@ls0428](https://gitee.com/ls0428)
|
||||
- 🎯 优化 [SvgIcon 对下载的 Svg 图像设置颜色无效 #I59ND0](https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0)),感谢[@elus_z](https://gitee.com/elus_z)
|
||||
- 🎯 优化 `/src/utils/toolsValidate.ts` 工具类
|
||||
- 🐞 修复 [布局切换,TagsView 显示的 tab 会多一个出来 #I58WGM](https://gitee.com/lyt-top/vue-next-admin/issues/I58WGM),感谢[@lg_boy](https://gitee.com/lg_boy)
|
||||
- 🐞 修复 [如果设置顶部面包屑导航开启图标 isBreadcrumbIcon=true 后,样式有点问题 如果不开启就是正常的 #I58VB8](https://gitee.com/lyt-top/vue-next-admin/issues/I58VB8)
|
||||
- 🐞 修复 地址栏路由地址输入错误时,返回首页后,再次输入路由地址错误时,不跳转 404 问题
|
||||
- 🐞 修复 [2.1.0 版本的图标选择组件多次点击后功能失效 #I590TH](https://gitee.com/lyt-top/vue-next-admin/issues/I590TH),感谢[@quber](https://gitee.com/quber)
|
||||
|
||||
## 2.1.0
|
||||
|
||||
`2022.04.18`
|
||||
|
||||
⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。因为 `vuex` 替换成 `pinia`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 部分界面图片不显示问题(更换 gitee 在线图片地址源)
|
||||
- 🎯 优化 各界面方法引入与逻辑之间添加一行空行,方便区分内容
|
||||
- 🎯 优化 图标选择器 [#I4YAHB](https://gitee.com/lyt-top/vue-next-admin/issues/I4YAHB),感谢[@真有你的](https://gitee.com/sunliusen)
|
||||
- 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
|
||||
- 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts)
|
||||
- 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白
|
||||
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue`
|
||||
- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线、全屏时关闭按钮消失问题
|
||||
- 🎯 优化 [如果 url 中有中文等特殊字符,第一次切换该 tab 时 keep-alive 失效#I55JS7](https://gitee.com/lyt-top/vue-next-admin/issues/I55JS7),感谢[yuyong1566](https://gitee.com/yuyong1566)
|
||||
- 🎯 优化 [wangEditor](https://www.wangeditor.com/) 更新到 v5,[vue3 版本线上示例中 wangeditor 富文本编辑器 demo 实例,无法换行#I5565B](https://gitee.com/lyt-top/vue-next-admin/issues/I5565B),感谢@[jenchih](https://gitee.com/jenchih)
|
||||
- 🎯 优化 [在关闭 tagview 时,高度刷新时会会变化,出现滚动条](https://gitee.com/lyt-top/vue-next-admin/issues/I55FHM),感谢[张松](https://gitee.com/zs310071113)
|
||||
- 🎯 优化 [路由参数](https://lyt-top.gitee.io/vue-next-admin-preview/#/params/common)演示
|
||||
- 🎉 新增 [vuex](https://vuex.vuejs.org/) 替换成 [pinia](https://pinia.vuejs.org/getting-started.html)
|
||||
- 🎉 新增 tagsView 支持自定义 tagsView 名称(文章详情时有用),前往体验:[路由参数/普通路由](https://lyt-top.gitee.io/vue-next-admin-preview/#/params/common)。新增 tagsView 支持自定义名称国际化,感谢[@q7but](https://gitee.com/q7but)、[!22 add 添加自定义 tagVIewName 拓展,支持国际化](https://gitee.com/lyt-top/vue-next-admin/pulls/22/files)、感谢[@tony_tong_xin](https://gitee.com/tony_tong_xin)
|
||||
- 🐞 修复 适配 `"element-plus": "^2.1.9",2.2.0` 版本
|
||||
- 🐞 修复 [导航栏横向布局后,一级菜单显示问题#I4Z3M3](https://gitee.com/lyt-top/vue-next-admin/issues/I4Z3M3)
|
||||
- 🐞 修复 横向布局三级及以上导航菜单高亮、导航高度不统一问题
|
||||
- 🐞 修复 分栏模式下,选中的菜单是 primary 样式,鼠标移入字也变成 primary 色了,感谢群友@孤夜-流殇
|
||||
- 🐞 修复 [vuex 里面改了颜色 但是不生效 #I4WFMA](https://gitee.com/lyt-top/vue-next-admin/issues/I4WFMA)
|
||||
- 🐞 修复 全局主题 primary 清空颜色后报错,[#I4X0LG](https://gitee.com/lyt-top/vue-next-admin/issues/I4X0LG),感谢[面向 BUG 编程](https://gitee.com/fhtfy)
|
||||
- 🐞 修复 [.eslintrc.js 文件 rules 标签名错误 #I53IPK](https://gitee.com/lyt-top/vue-next-admin/issues/I53IPK),感谢[yuyong1566](https://gitee.com/yuyong1566)
|
||||
- 🐞 修复 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题
|
||||
- 🐞 修复 `router.push` 路径找不到时报错问题,`404、401 界面` 已移入到 `main` 主布局里(之前全屏)
|
||||
- 🐞 修复 [全局修改组件大小失效了](https://gitee.com/lyt-top/vue-next-admin/issues/I551RP),感谢[lg_boy](https://gitee.com/lg_boy)
|
||||
- 🐞 修复 [修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效,问题解决#I567R1](https://gitee.com/lyt-top/vue-next-admin/issues/I567R1),感谢[@lanbao123](https://gitee.com/lanbao123)
|
||||
- 🐞 修复 [标记为需要缓存的 tab 页后,再次从左侧菜单打开,还是显示被缓存的页面内容#I4UY3G](https://gitee.com/lyt-top/vue-next-admin/issues/I4UY3G),感谢@axcc1234、特别感谢群友@华仔
|
||||
- 🌈 重构 路由(`/src/router/index.ts`)解决 No match found for location with path "xxx"(前端控制,后端控制未解决) 问题
|
||||
|
||||
## 2.0.2
|
||||
|
||||
`2022.03.04`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 Alert 提示添加边框
|
||||
- 🎯 优化 功能 / 数字滚动 演示界面
|
||||
- 🐞 修复 全局主题按钮颜色 :active 问题
|
||||
- 🐞 修复 Dropdown 下拉菜单样式问题
|
||||
- 🐞 修复 SvgIcon 图标组件动态切换时报警告问题,[SvgIcon 改变 name 时可能导致图像不显示](https://gitee.com/lyt-top/vue-next-admin/issues/I4VGE0),感谢@axcc1234
|
||||
|
||||
## 2.0.1
|
||||
|
||||
`2022.02.25`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 svgIcon 图标组件
|
||||
- 🎯 优化 vite.config.ts 打包,感谢群友@YourObjec
|
||||
- 🐞 修复 tagViews 开启图标不显示问题(风格 5),感谢群友@坏人
|
||||
- 🐞 修复 [Element Plus 1.2.0-beta.6 以后的版本 el-table 在移动端无法左右滑动](https://gitee.com/lyt-top/vue-next-admin/issues/I4UPTP),感谢@YGDada
|
||||
|
||||
## 2.0.0
|
||||
|
||||
`2022.02.21`
|
||||
|
||||
⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。演示界面建议直接覆盖文件。如需使用之前版本,请前往[gitee 发行版](https://gitee.com/lyt-top/vue-next-admin/releases) 进行对应版本下载。基础版会基于 `master` 分支进行修改
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🌟 更新 登录页、首页
|
||||
- 💔 移除 vue-web-screen-shot
|
||||
- 💔 移除 城市多级联动,完整 json 数据请去 [vue-next-admin-images/menu](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 仓库查看
|
||||
- 💔 移除 功能/echartsTree 树图
|
||||
- 💔 移除 其它设置/Tagsview 风格 2、Tagsview 风格 3
|
||||
- 💔 移除 功能/验证器
|
||||
- 🚧 调整 src/api 编写方式
|
||||
- 🚧 调整 自定义封装公用组件演示,更好的维护
|
||||
- 🎉 新增 Volar 支持,vs code 配置参考 [Vue Language Features (Volar)](https://lyt-top.gitee.io/vue-next-admin-doc-preview/home/vscode/)
|
||||
- 🎉 新增 `SvgIcon` 支持本地 svg 图标使用
|
||||
- 🎉 新增 表单表格验证演示
|
||||
- 🎯 优化 全局主题(移除 success、info、warning、danger)
|
||||
- 🎯 优化 工作流(开源)
|
||||
- 🎯 优化 element plus svg 图标,`elementXXX` 改成 `ele-XXX`
|
||||
- 🌈 重构 深色模式
|
||||
- 🌹 合并 [处理 parent 的 h100 由于外层有 min-height 导致失效的问题](https://gitee.com/lyt-top/vue-next-admin/pulls/20),感谢@MaxNull、@21030442-mao
|
||||
- 🐞 修复 element plus 升级 `^1.3.0-beta.5` 后 组件 size 大小问题(大改:涉及布局、演示界面)
|
||||
- 🐞 修复 vs code 使用 Vue Language Features (Volar) 插件 代码报红问题(可以把公用的 ts 类型定义封装起来公用)
|
||||
|
||||
## 1.2.2
|
||||
|
||||
`2021.12.21`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 iframes 滚动条问题
|
||||
- 🎯 优化 部署后每次都要强制刷新清浏览器缓存问题
|
||||
- 🎉 新增 工具类百分比验证演示
|
||||
- 🐞 修复 [tag-view 标签右键会超出浏览器 #I4KN78](https://gitee.com/lyt-top/vue-next-admin/issues/I4KN78)
|
||||
|
||||
## 1.2.1
|
||||
|
||||
`2021.12.12`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 cropper 裁剪时卡顿问题 [#I4M2VQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4M2VQ)
|
||||
- 🎯 优化 Wangeditor 富文本编辑器的问题 [#I4LPC1](https://gitee.com/lyt-top/vue-next-admin/issues/I4LPC1)、[#I4LM7I](https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I)
|
||||
- 🐞 修复 浏览器标题问题
|
||||
- 🐞 修复 element plus svg 图标引入
|
||||
- 🐞 修复 工作流不可以拖线连接问题
|
||||
|
||||
## 1.2.0
|
||||
|
||||
`2021.11.28`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 深色模式
|
||||
- 🎯 优化 `/@/utils` 文件夹,合并删除单一内容
|
||||
- 🎯 优化 系统设置:菜单管理(新增、修改)、角色管理(新增菜单权限)、用户管理、部门管理、字典管理
|
||||
- 🎯 优化 登录界面逻辑、权限管理逻辑
|
||||
- 🎯 优化 同步 [vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 后端控制菜单模拟数据
|
||||
- 🎉 新增 适配 Font Icon 向 SVG Icon 迁移(改动大,"element-plus": "^1.2.0-beta.4" 谨慎更新)
|
||||
- 🐞 修复 热更新问题,感谢@甜蜜蜜
|
||||
- 🐞 修复 页面/element 字体图标演示
|
||||
- 🐞 修复 功能/图标选择器演示,新增高级功能 [issues #I4GJXQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4GJXQ)
|
||||
|
||||
## 1.1.2
|
||||
|
||||
`2021.10.17`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 开启全屏时,刷新界面被还原成未全屏的状态
|
||||
- 🎯 优化 tagsView 右键菜单关闭逻辑
|
||||
- 🎯 优化 wangeditor 富文本编辑器(增加双向绑定)
|
||||
- 🎉 新增 工作流(暂不开源)
|
||||
- 🎉 新增 基础版 ts(不带国际化),切换 `vue-next-admin-template` 分支
|
||||
|
||||
## 1.1.1
|
||||
|
||||
`2021.09.25`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本(`"element-plus": "^1.1.0-beta.13"` 版本运行错误,`^1.1.0-beta.16`修复横向菜单卡死问题)
|
||||
- 🐞 修复 Dialog 弹窗位置错误、Drawer 抽屉内边距、el-menu 菜单收起时背景色问题
|
||||
- 🎯 优化 锁屏界面自动锁屏(s/秒)必须设置至少 1 秒
|
||||
- 🎉 新增 分栏布局,鼠标移入当前项时,显示当前项菜单内容
|
||||
- 🎉 新增 工作流(未完成)
|
||||
|
||||
## 1.1.0
|
||||
|
||||
`2021.09.10`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 小屏模式下登录页二维码遮挡标题问题
|
||||
- 🎉 新增 图片验证器
|
||||
- 🎉 新增 动态复杂表单
|
||||
- 🎉 新增 工作流(未完成)
|
||||
- 🎉 新增 深色主题(伪深色,样式变动大,谨慎更新)
|
||||
|
||||
## 1.0.18
|
||||
|
||||
`2021.08.29`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 权限组件去掉顶级 div(`/src/components/auth`)
|
||||
- 🎉 新增 布局配置添加恢复默认按钮
|
||||
- 🐞 修复 升级 <a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">element plus 1.1.0-beta.7</a>后项目无法启动、el-menu 菜单
|
||||
- 🐞 修复 表格固定列时的层级、设置了相对定位时,遮挡左侧导航菜单问题
|
||||
|
||||
## 1.0.17
|
||||
|
||||
`2021.08.22`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 去除设置布局切换,重置主题样式(initSetLayoutChange),切换布局需手动设置样式,设置的样式自动同步各布局
|
||||
- 🎯 优化 Dropdown 下拉菜单用户账号靠边时换行问题
|
||||
- 🎯 优化 左侧导航菜单,共用菜单树,防止 `布局配置` 设置 `菜单 / 顶栏` 时,样式丢失等问题
|
||||
- 🐞 修复 固定 header 后没有回到顶部的 bug,拉取项目后运行不起来的 bug。<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/14" target="_blank">!14</a>,感谢<a href="https://gitee.com/wjs0509" target="_blank">@wjs0509</a>
|
||||
- 🐞 修复 tagView 右键全屏后,浏览器窗口大小发生任何变化都会导致左边菜单显示出来,并且可点击打开对应页面。<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I46E6T" target="_blank">I46E6T</a>
|
||||
- 🐞 修复 默认设置 `菜单 / 顶栏` 样式不生效问题(/@/src/store/modules/themeConfig.ts)
|
||||
|
||||
## 1.0.16
|
||||
|
||||
`2021.08.14`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 菜单高亮(详情且详情设置了 meta.isHide 时,顶级菜单高亮),感谢群友@YourObject
|
||||
- 🎯 优化 详情路径写法:如父级(/pages/filtering),那么详情为(/pages/filtering/details?id=1)。这样写可实现(详情时,父级菜单高亮),否则写成(/pages/filteringDetails?id=1)顶级菜单将不会高亮。可参考:`页面/过滤筛选组件`,点击当前图片进行测试
|
||||
- 🎯 优化 tagsView 右键菜单全屏时,打开的界面高度问题
|
||||
- 🎯 优化 图表批量 resize 问题
|
||||
- 🐞 修复 菜单收起时(设置全局主题:primary 且有二级菜单时),文字高亮颜色不对
|
||||
- 🐞 修复 国际化 <a href="https://gitee.com/lyt-top/vue-next-admin/issues/I43NPE" target="_blank">#I43NPE</a>。可参考:`页面/过滤筛选组件`,点击顶部语言切换,进行底部分页国际化查看
|
||||
|
||||
## 1.0.15
|
||||
|
||||
`2021.08.06`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 tagsView 右键菜单点击时的字段名(id 已修改成 contextMenuClickId)与路由中返回的 id 名冲突问题,感谢群友@伯牙已遇钟子期
|
||||
- 🎉 新增 多个 form 表单验证界面演示
|
||||
|
||||
## 1.0.14
|
||||
|
||||
`2021.07.29`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本(vue、vuex、vue-router),出现问题,请手动降级。版本查看:<a href="https://www.npmjs.com/" target="_blank">vnpm</a>
|
||||
- 🎯 优化 数据可视化图表演示加载卡顿问题、优化有图表的演示界面
|
||||
- 🎯 优化 路由参数演示界面
|
||||
- 🎯 优化 tagsView 操作演示界面,由于存在相同路由多标签,必须要传全部参数值(query 或者 params)
|
||||
- 🎉 新增 开启 TagsView 共用,开启时:(多个路由菜单共用一个详情组件(参数为后点击的覆盖前面点击的),tagsView 中只会出现一个(不支持同时出现多个 tagsView 标签))。关闭时:(多个路由菜单共用一个详情组件,参数不同,会同时出现多个 tagsView 标签)
|
||||
- 🐞 修复 tagsView 共用(单标签)时,右键菜单功能点击,参数不对的问题(第 2n+个参数未覆盖第一个参数值)
|
||||
- 🐞 修复 多 tagsView 标签(参数不同)、单个 tagsView 标签公用(参数不同)所带来的刷新功能、横向自动滚动等问题
|
||||
- 🐞 修复 处理全屏若干问题,<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/12" target="_blank">pr!12</a>,感谢群友@另一个前端
|
||||
|
||||
## 1.0.13
|
||||
|
||||
`2021.07.25`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 数据可视化演示界面(/visualizingDemo1、/visualizingDemo2)
|
||||
- 🎉 新增 登录页扫码登录
|
||||
|
||||
## 1.0.12
|
||||
|
||||
`2021.07.16`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 数据可视化演示空界面(待完善)
|
||||
- 🎯 优化 tagsView 动态路由(xxx/:id/:name)时的右键菜单刷新、关闭其它时参数丢失问题(2021.07.15 优化)
|
||||
- 🐞 修复 路由带参数时,复制路径到登录页,跳转后参数消失的问题
|
||||
- 🐞 修复 设置多个外链,点击后,页面内容停留在上一个内容(内容未改变)、国际化处理、打开新窗口 sessionStorage 共享等
|
||||
|
||||
## 1.0.11
|
||||
|
||||
`2021.07.14`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 路由参数、图片懒加载界面演示
|
||||
- ⚠️ 警告 Form 表单 `binding value must be a string or number`,解决:加上 `label-position="top"` 不报警告(等待官方修复)
|
||||
- 🎯 优化 锁屏界面动画效果、首页图表显示
|
||||
- 🎯 优化 tagsView 右键菜单 `关闭` 功能逻辑
|
||||
- 🐞 修复 开启 TagsView 拖拽报错及小于 `1000px` 时自动设置禁止拖拽(<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI" target="_blank">#I3ZRRI</a>)
|
||||
- 🐞 修复 `iframe 内嵌、外链` 高度问题,使用 computed 进行计算
|
||||
- 🐞 修复 默认布局开启 `侧边栏 Logo` 与关闭 `菜单水平折叠`,切换到横向布局时,菜单看不见的问题
|
||||
- 🐞 修复 切换不同布局时,再去开启 `经典布局分割菜单` 功能不生效问题
|
||||
- 🐞 修复 浏览器窗口标题中/英文切换不实时生效的问题
|
||||
- 🐞 修复 切换布局时,某些功能不可以使用。部分界面不需要取消事件监听(proxy.mittBus.off('xxx'))
|
||||
- 🐞 修复 动态路由带参数,router-link 跳转问题(<a href="hhttps://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G" target="_blank">#I3YX6G</a>)
|
||||
- 🐞 修复 横向菜单有二级菜单时,点击子级菜单不高亮问题
|
||||
- 🐞 修复 功能 tagsView 操作演示不生效
|
||||
|
||||
## 1.0.10
|
||||
|
||||
`2021.07.07`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本(字体图标无问题)
|
||||
- 🎯 优化 内嵌 iframe、外链,解决 tagsView 刷新问题
|
||||
|
||||
## 1.0.9
|
||||
|
||||
`2021.07.02`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 图标选择器设置宽度、v-model 等问题
|
||||
- 🎯 优化 滚动通知栏在手机上的体验
|
||||
- 🎯 优化 系统管理/新增菜单(编辑菜单),使用 `图标选择器` 进行模拟
|
||||
- 🎯 优化 字体图标(自动载入) 逻辑
|
||||
- 🐞 修复 screenfull 全屏时,按键盘 esc 键图标不改变问题,感谢群友@伯牙已遇钟子期
|
||||
|
||||
## 1.0.8
|
||||
|
||||
`2021.06.29`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 表单中英文切换演示
|
||||
- 🎯 优化 登录页查看密码 icon 图标
|
||||
- 🎯 优化 图标选择器
|
||||
- 🎯 优化 拖动指令
|
||||
- 🐞 修复 form 表单在页面小于 576px 时的排版问题
|
||||
|
||||
## 1.0.7
|
||||
|
||||
`2021.06.24`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 拖动指令及其演示界面
|
||||
- 🎯 优化 锁屏界面,解锁提示
|
||||
- 🎯 优化 登录页在手机上显示的效果
|
||||
|
||||
## 1.0.6
|
||||
|
||||
`2021.06.23`
|
||||
|
||||
- 🎯 优化 去掉内嵌 iframe 内边距(padding)
|
||||
- 🎯 优化 城市多级联动组件
|
||||
- 🎯 优化 Tree 树形控件改成表格组件
|
||||
- 🐞 修复 Cascader 级联选择器高度问题
|
||||
|
||||
## 1.0.5
|
||||
|
||||
`2021.06.22`
|
||||
|
||||
- 🌟 更新 vite 降级为@vite2.3.7,降级方法 `cnpm install vite@2.3.7`,防止 element plus 字体图标消失
|
||||
- 🐞 修复 开启后端控制路由(isRequestRoutes = true)时,内嵌 iframe、外链不可使用的问题
|
||||
|
||||
## 1.0.4
|
||||
|
||||
`2021.06.19`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本("vite": "^2.3.7")热更新无问题
|
||||
- 🎉 新增 深克隆工具,方便开发,感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/6" target="_blank">#6</a>)
|
||||
- 🎯 优化 vuex 模块自动导入。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/4" target="_blank">#4</a>),感谢群友@web 小学生-第五君
|
||||
- 🎯 优化 类型定义提高编码体验,修复不能将类型“string | undefined”分配给类型“string”的问题。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/5" target="_blank">#5</a>)
|
||||
- 🎯 优化 `layout` 文件夹移动到与 `views` 文件夹同级(改动较大,`/@/views/layout` 变成 `/@/layout`)
|
||||
- 🎯 优化 页面有 `console.log` 时 `eslint` 不生效问题
|
||||
- 🎯 优化 页面、ts 中 `any` 类型问题(改动较大)
|
||||
- 🎯 优化 登录页在手机上显示的效果
|
||||
- 🎯 优化 多行注释信息,鼠标放到方法名即可查看,更加直观的知道方法参数等。引入方法时需去掉以 `.ts` 结尾的后缀(改动较大)
|
||||
- 🎯 优化 移除 `utils/storage.ts` 下的旧写法(改动较大)
|
||||
- 🎯 优化 拆分 `router` 下内容,路由、前端、后端控制分开写,方便理解
|
||||
- 🐞 修复 鼠标移入顶部用户信息栏 `开/关全屏` 文字反向问题
|
||||
- 🐞 修复 热更新时,NextLoading(界面 loading) 不消失问题 `window.nextLoading === undefined`
|
||||
- 🐞 修复 vuex 中不可以使用 `/@/api/xxx` 下的接口调用问题
|
||||
|
||||
## 1.0.3
|
||||
|
||||
`2021.06.02`
|
||||
|
||||
- ❄️ 删除 G6 思维导图界面
|
||||
- 🌟 更新 手动更新 vue、vue-router、vuex 到最近最多人使用的版本,出现不可预测的问题请降低版本。版本查看:<a href="https://www.npmjs.com/package/vue" target="_blank">vue 版本查看</a>
|
||||
- 🐞 修复 开启后端控制路由 `isRequestRoutes` 在非首页刷新页面后,回到首页的问题,感谢群友@伯牙已遇钟子期
|
||||
|
||||
## 1.0.2
|
||||
|
||||
`2021.06.01`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 菜单搜索中文不可以搜索的问题,感谢群友@逍遥天意
|
||||
|
||||
## 1.0.1
|
||||
|
||||
`2021.05.31`
|
||||
|
||||
- 🎉 新增 更新日志文件 `CHANGELOG.md`,以后每次更新都会在这里显示对应内容
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 分栏、经典布局路由设置 `meta.isHide` 为 `true` 时报错问题,感谢群友@29、@芭芭拉
|
||||
- 🐞 修复 经典布局点击 `tagsView` 左侧菜单数据不变问题
|
||||
4
LICENSE
4
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 倔强嘴角留下一抹殇
|
||||
Copyright (c) 2021 lyt-Top
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
91
README.en.md
91
README.en.md
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir : __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
37
backend/.gitignore
vendored
37
backend/.gitignore
vendored
@ -1,37 +0,0 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
package-lock.json
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
*.lock
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
{
|
||||
"name": "tsplatform",
|
||||
"version": "1.1.0",
|
||||
"description": "nestjs backend",
|
||||
"author": "SweetHoney",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^6.5.0",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/jwt": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.0",
|
||||
"@nestjs/platform-fastify": "^9.2.0",
|
||||
"@nestjs/swagger": "^6.1.3",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"csurf": "^1.11.0",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"fastify-swagger": "^5.2.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"helmet": "^6.0.0",
|
||||
"mysql2": "^2.3.3",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
"ts-md5": "^1.3.1",
|
||||
"typeorm": "^0.3.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.0",
|
||||
"@nestjs/testing": "^9.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "28.1.8",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/passport-jwt": "^3.0.7",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "28.1.3",
|
||||
"prettier": "^2.3.2",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "28.0.8",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 10:59:16
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:58:50
|
||||
* @FilePath: \tsplatform\backend\src\Config\Index.ts
|
||||
* @Description: 系统全局配置
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
*/
|
||||
export const AppConfig = {
|
||||
debug: false,
|
||||
port: 3000,
|
||||
jwt: {
|
||||
secret: "4vRk^ga52xVP$B2vYK$%r8a8hctLgbU9",
|
||||
expiresIn: "60000s"
|
||||
},
|
||||
md5Key: `4vRk^ga52xVP$B2vYK$%r8a8hctLgbU9`
|
||||
}
|
||||
/**
|
||||
* 数据库配置
|
||||
*/
|
||||
export const DataBaseConfig = {
|
||||
DataBase: {
|
||||
type: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: 3306,
|
||||
database: 'sweet_honey',
|
||||
username: 'root',
|
||||
password: 'root',
|
||||
//是否自动迁移同步
|
||||
synchronize: true,
|
||||
},
|
||||
DataBase1: {
|
||||
type: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: 3306,
|
||||
database: 'test1',
|
||||
username: 'root',
|
||||
password: 'root',
|
||||
//是否自动迁移同步
|
||||
synchronize: true,
|
||||
}
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:06:21
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 11:11:11
|
||||
* @FilePath: \tsplatform\backend\src\Controller\AuthController.ts
|
||||
* @Description: 系统授权控制器
|
||||
*/
|
||||
import { Body, Controller, Get, Post, Request, Req, UseGuards } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { UserService } from 'src/Service/UserService';
|
||||
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { LoginDto } from 'src/EntiyDto/LoginDto';
|
||||
import { AppConfig } from 'src/Config/Index';
|
||||
import { RoleService } from 'src/Service/RoleService';
|
||||
import { MenuService } from 'src/Service/MenuService';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('系统授权管理')
|
||||
@Controller('/auth')
|
||||
export class AuthController {
|
||||
constructor(
|
||||
public readonly _restful: RestfulReturn,
|
||||
public readonly _service: UserService,
|
||||
public readonly _roleService: RoleService,
|
||||
public readonly _menuService: MenuService,
|
||||
public readonly _jwt: JwtService
|
||||
) { }
|
||||
|
||||
@ApiOperation({
|
||||
summary: '获取账号登录验证码',
|
||||
description: '获取账号登录验证码'
|
||||
})
|
||||
@Get('/captcha')
|
||||
async captcha() {
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, ""
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '账号密码登录',
|
||||
description: '账号密码登录'
|
||||
})
|
||||
@Post('/login')
|
||||
async login(@Body() dto: LoginDto) {
|
||||
const result = await this._service.repository.findOne({ where: { UserName: dto.UserName, Password: dto.Password } });
|
||||
if (result && result.Id > 0) {
|
||||
const token = await this._jwt.signAsync({ uid: result.Id }, {
|
||||
secret: AppConfig.jwt.secret,
|
||||
expiresIn: AppConfig.jwt.expiresIn,
|
||||
});
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, { userinfo: result, token: token }
|
||||
);
|
||||
} else {
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "账号密码有误!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据token获取用户信息',
|
||||
description: '根据token获取用户信息'
|
||||
})
|
||||
|
||||
@Get('/getUserInfo')
|
||||
async getUserInfo(@Req() request: Request) {
|
||||
try {
|
||||
const token = request.headers["authorization"];
|
||||
const jwtResult = await this._jwt.verifyAsync(token, { secret: AppConfig.jwt.secret, ignoreExpiration: false })
|
||||
const uid = jwtResult.uid;
|
||||
const result = await this._service.getById(uid);
|
||||
const menuRole = await this._roleService.getMenusByRoleId(result.RoleId);
|
||||
const menuIds: number[] = [];
|
||||
for await (const info of menuRole) {
|
||||
menuIds.push(info.MenuId);
|
||||
}
|
||||
const menus = await this._menuService.repository.createQueryBuilder().whereInIds(menuIds).getMany()
|
||||
const permissions: string[] = [];
|
||||
for await (const info of menus) {
|
||||
permissions.push(info.Permission);
|
||||
}
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, { userInfo: result, permission: permissions }
|
||||
);
|
||||
}
|
||||
catch (ex) {
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据token退出登录',
|
||||
description: '根据token退出登录'
|
||||
})
|
||||
@Get('/loginOut')
|
||||
async loginOut(@Req() request: Request) {
|
||||
try {
|
||||
const token = request.headers["authorization"];
|
||||
const jwtResult = await this._jwt.verifyAsync(token, { secret: AppConfig.jwt.secret, ignoreExpiration: false })
|
||||
const uid = jwtResult.uid;
|
||||
const result = await this._service.getById(uid);
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
catch (ex) {
|
||||
return await this._restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, ex
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:05:40
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 11:11:20
|
||||
* @FilePath: \tsplatform\backend\src\Controller\RoleController.ts
|
||||
* @Description: 省市区控制器
|
||||
*/
|
||||
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { IdDto, IdsDto, ListDto, PageDto } from 'src/EntiyDto/CommonDto';
|
||||
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
|
||||
import { CityService } from 'src/Service/CityService';
|
||||
import { SysCity } from 'src/Entity/SysCity';
|
||||
import { Equal, Not } from 'typeorm';
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('省市区管理')
|
||||
@Controller('/city')
|
||||
export class CityController {
|
||||
constructor(
|
||||
public readonly restful: RestfulReturn,
|
||||
public readonly service: CityService
|
||||
) {
|
||||
}
|
||||
//#region 基础控制器
|
||||
@ApiOperation({
|
||||
summary: '分页查询省市区列表',
|
||||
description: '分页查询省市区列表'
|
||||
})
|
||||
@Get('/getPage')
|
||||
async getPage(@Query() dto: PageDto) {
|
||||
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS,
|
||||
{
|
||||
total: result[1],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
rows: result[0],
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '查询省市区列表',
|
||||
description: '查询省市区列表'
|
||||
})
|
||||
@Get('/getList')
|
||||
async getList(@Query() dto: ListDto) {
|
||||
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID查询省市区',
|
||||
description: '根据ID查询省市区'
|
||||
})
|
||||
@Get('/getById')
|
||||
async getById(@Query() dto: IdDto) {
|
||||
const result = await this.service.getById(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID数组查询省市区',
|
||||
description: '根据ID数组查询省市区'
|
||||
})
|
||||
@Get('/getByIds')
|
||||
async getByIds(@Query() dto: IdsDto) {
|
||||
const result = await this.service.getByIds(dto.Ids);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '新增省市区',
|
||||
description: '新增省市区'
|
||||
})
|
||||
@Post('/add')
|
||||
async add(@Body() model: SysCity) {
|
||||
const isRepeat = await this.service.repository.createQueryBuilder()
|
||||
.orWhere({ RegionName: Equal(model.RegionName.trim()), })
|
||||
.orWhere({ RegionCode: Equal(model.RegionCode.trim()) }).getCount();
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "地区名称或者行政编号重复!"
|
||||
);
|
||||
} else {
|
||||
const result = await this.service.save(new SysCity());
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '修改省市区',
|
||||
description: '修改省市区'
|
||||
})
|
||||
@Post('/edit')
|
||||
async edit(@Body() model: SysCity) {
|
||||
const isRepeat = await this.service.repository.createQueryBuilder()
|
||||
.where({ Id: Not(model.Id) })
|
||||
.andWhere([{ RegionName: Equal(model.RegionName.trim()) }, { RegionCode: Equal(model.RegionCode.trim()) }])
|
||||
.getCount();
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "地区名称或者行政编号重复!"
|
||||
);
|
||||
} else {
|
||||
const result = await this.service.save(new SysCity());
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '假删除省市区',
|
||||
description: '假删除省市区'
|
||||
})
|
||||
@Post('/del')
|
||||
async del(@Body() dto: IdDto) {
|
||||
const result = await this.service.destroy(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:05:41
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:11:01
|
||||
* @FilePath: \tsplatform\backend\src\Controller\UserController.ts
|
||||
* @Description: 系统用户控制器
|
||||
*/
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
|
||||
import { CityService } from 'src/Service/CityService';
|
||||
import { RoleService } from 'src/Service/RoleService';
|
||||
import { MenuService } from 'src/Service/MenuService';
|
||||
import { UserService } from 'src/Service/UserService';
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('系统管理')
|
||||
@Controller('/')
|
||||
export class IndexController {
|
||||
constructor(
|
||||
public readonly restful: RestfulReturn,
|
||||
public readonly role_service: RoleService,
|
||||
public readonly menu_service: MenuService,
|
||||
public readonly user_service: UserService,
|
||||
public readonly city_service: CityService,
|
||||
) {
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '数据初始化',
|
||||
description: '数据初始化'
|
||||
})
|
||||
@Get('/seed')
|
||||
async seedData() {
|
||||
//初始化账号数据
|
||||
const userCount = await this.user_service.seedData();
|
||||
//初始化菜单数据
|
||||
const menuCount = await this.menu_service.seedData();
|
||||
//初始化角色数据
|
||||
const roleCounts = await this.role_service.seedData();
|
||||
//初始化省市区数据
|
||||
const cityCount = await this.city_service.seedData();
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS,
|
||||
`一共Seed${userCount}条记录!\r\n` +
|
||||
`一共Seed${menuCount}条记录!\r\n` +
|
||||
`一共Seed${roleCounts[0]}条记录!\r\n` +
|
||||
`一共Seed${roleCounts[0]}条记录!\r\n` +
|
||||
`一共Seed${cityCount}条记录!`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,294 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 00:23:12
|
||||
* @FilePath: \tsplatform\backend\src\Controller\MenuController.ts
|
||||
* @Description: 系统菜单控制器
|
||||
*/
|
||||
|
||||
import { Body, Controller, Get, HttpException, HttpStatus, Post, Query } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiProperty,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { SysMenu } from 'src/Entity/SysMenu';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { IdDto, IdsDto, IdStatusDto, ListDto, PageDto, RoleIdDto } from 'src/EntiyDto/CommonDto';
|
||||
import { MenuService } from 'src/Service/MenuService';
|
||||
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
|
||||
import { MenuDto } from 'src/EntiyDto/MenuDto';
|
||||
import { RouteMenu } from 'src/EntiyDto/RouteMenu';
|
||||
import { Equal, In, Not } from 'typeorm';
|
||||
import { RoleService } from 'src/Service/RoleService';
|
||||
|
||||
@Controller('/menu')
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('系统菜单管理')
|
||||
export class MenuController {
|
||||
constructor(
|
||||
public readonly restful: RestfulReturn,
|
||||
public readonly service: MenuService,
|
||||
public readonly _roleService: RoleService,
|
||||
) {
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '获取所有菜单Tree,菜单列表使用',
|
||||
description: '获取所有菜单Tree'
|
||||
})
|
||||
@Get('/getMenuTree')
|
||||
async getMenuTree() {
|
||||
const result = await this.MenuTree(0);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
@ApiOperation({
|
||||
summary: '根据角色ID获取用户授权的菜单Tree,前端router使用',
|
||||
description: '根据角色ID获取用户授权的菜单Tree'
|
||||
})
|
||||
@Get('/getAuthMenuTree')
|
||||
async getAuthMenuTree(@Query() dto: RoleIdDto) {
|
||||
if (dto.RoleId > 0) {
|
||||
const result = await this.RouteTree(0, dto.RoleId);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
throw new HttpException("角色Id不能为空!", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
//递归获取菜单Tree
|
||||
private async MenuTree(parentId: number): Promise<SysMenu[]> {
|
||||
const result = await this.service.repository.createQueryBuilder()
|
||||
.where({ ParentId: parentId })
|
||||
.orderBy("sort", "ASC")
|
||||
.getMany();
|
||||
const treeList: SysMenu[] = new Array<SysMenu>;
|
||||
for await (let info of result) {
|
||||
const children = await this.MenuTree(info.Id);
|
||||
const node: any = {
|
||||
Id: info.Id,
|
||||
ParentId: info.ParentId,
|
||||
Type: info.Type,
|
||||
Title: info.Title,
|
||||
Path: info.Path,
|
||||
Permission: info.Permission,
|
||||
Component: info.Component,
|
||||
Icon: info.Icon,
|
||||
Name: info.Name,
|
||||
Redirect: info.Redirect,
|
||||
IsLink: info.IsLink,
|
||||
IsHide: info.IsHide,
|
||||
IsKeepAlive: info.IsKeepAlive,
|
||||
IsAffix: info.IsAffix,
|
||||
IsIframe: info.IsIframe,
|
||||
Children: [],
|
||||
Sort: info.Sort,
|
||||
};
|
||||
if (children.length > 0) {
|
||||
node.Children = children;
|
||||
}
|
||||
treeList.push(node);
|
||||
}
|
||||
return treeList;
|
||||
}
|
||||
//递归获取前端路由Tree
|
||||
private async RouteTree(parentId: number, RoleId: number): Promise<RouteMenu[]> {
|
||||
const menuRole = await this._roleService.getMenusByRoleId(RoleId);
|
||||
const menuIds: number[] = [];
|
||||
for await (const info of menuRole) {
|
||||
menuIds.push(info.MenuId);
|
||||
}
|
||||
const result = await this.service.repository.createQueryBuilder()
|
||||
.where({ ParentId: parentId, Id: In(menuIds) })
|
||||
.orderBy("sort", "ASC")
|
||||
.getMany();
|
||||
const treeList: RouteMenu[] = new Array<RouteMenu>;
|
||||
for await (let info of result) {
|
||||
const children = await this.RouteTree(info.Id, RoleId);
|
||||
const node: RouteMenu = {
|
||||
name: info.Name,
|
||||
path: info.Path,
|
||||
component: info.Component,
|
||||
redirect: info.Redirect,
|
||||
meta: {
|
||||
title: info.Title,
|
||||
icon: info.Icon,
|
||||
isLink: info.IsLink,
|
||||
isHide: info.IsHide,
|
||||
isKeepAlive: info.IsKeepAlive,
|
||||
isAffix: info.IsAffix,
|
||||
isIframe: info.IsIframe,
|
||||
permission: info.Permission,
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
if (children.length > 0) {
|
||||
for await (let item of children) {
|
||||
node.children.push({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
component: item.component,
|
||||
redirect: item.redirect,
|
||||
meta: {
|
||||
title: item.meta.title,
|
||||
icon: item.meta.icon,
|
||||
isLink: item.meta.isLink,
|
||||
isHide: item.meta.isHide,
|
||||
isKeepAlive: item.meta.isKeepAlive,
|
||||
isAffix: item.meta.isAffix,
|
||||
isIframe: item.meta.isIframe,
|
||||
permission: item.meta.permission,
|
||||
},
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
treeList.push(node);
|
||||
}
|
||||
return treeList;
|
||||
}
|
||||
//#region 基础控制器
|
||||
@ApiOperation({
|
||||
summary: '分页查询菜单列表',
|
||||
description: '分页查询菜单列表'
|
||||
})
|
||||
@ApiProperty()
|
||||
@Get('/getPage')
|
||||
async getPage(@Query() dto: PageDto) {
|
||||
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS,
|
||||
{
|
||||
total: result[1],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
rows: result[0],
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '查询菜单列表',
|
||||
description: '查询菜单列表'
|
||||
})
|
||||
@Get('/getList')
|
||||
async getList(@Query() dto: ListDto) {
|
||||
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID查询菜单',
|
||||
description: '根据ID查询菜单'
|
||||
})
|
||||
@Get('/getById')
|
||||
async getById(@Query() dto: IdDto) {
|
||||
const result = await this.service.getById(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID数组查询菜单',
|
||||
description: '根据ID数组查询菜单'
|
||||
})
|
||||
@Get('/getByIds')
|
||||
async getByIds(@Query() dto: IdsDto) {
|
||||
const result = await this.service.getByIds(dto.Ids);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '新增菜单',
|
||||
description: '新增菜单'
|
||||
})
|
||||
@Post('/add')
|
||||
async add(@Body() dto: MenuDto) {
|
||||
const model = plainToClass(SysMenu, dto);
|
||||
const isRepeat = await this.service.repository.createQueryBuilder()
|
||||
.orWhere({ Name: Equal(model.Name.trim()), })
|
||||
.orWhere({ Permission: Equal(model.Permission.trim()) }).getCount();
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "路由名称或者权限标识重复!"
|
||||
);
|
||||
} else {
|
||||
const result = await this.service.save(model);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '修改菜单',
|
||||
description: '修改菜单'
|
||||
})
|
||||
@Post('/edit')
|
||||
async edit(@Body() dto: MenuDto) {
|
||||
const model = plainToClass(SysMenu, dto);
|
||||
const isRepeat = await this.service.repository.createQueryBuilder()
|
||||
.where({ Id: Not(model.Id) })
|
||||
.andWhere([{ Name: Equal(model.Name.trim()) }, { Permission: Equal(model.Permission.trim()) }])
|
||||
.getCount();
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "路由名称或者权限标识重复!"
|
||||
);
|
||||
} else {
|
||||
const result = await this.service.save(model);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '假删除菜单',
|
||||
description: '假删除菜单'
|
||||
})
|
||||
@Post('/del')
|
||||
async del(@Body() dto: IdDto) {
|
||||
const result = await this.service.destroy(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
@ApiOperation({
|
||||
summary: '修改菜单状态',
|
||||
description: '修改菜单状态'
|
||||
})
|
||||
@Post('/setStatus')
|
||||
async setStatus(@Body() dto: IdStatusDto) {
|
||||
const result = await this.service.repository.createQueryBuilder()
|
||||
.update()
|
||||
.set({
|
||||
Status: dto.Status
|
||||
})
|
||||
.where("Id = :Id", { Id: dto.Id })
|
||||
.execute();
|
||||
if (result.affected > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -1,189 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:05:40
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 22:32:29
|
||||
* @FilePath: \tsplatform\backend\src\Controller\RoleController.ts
|
||||
* @Description: 系统角色控制器
|
||||
*/
|
||||
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { RoleService } from 'src/Service/RoleService';
|
||||
import { IdDto, IdsDto, IdStatusDto, ListDto, PageDto } from 'src/EntiyDto/CommonDto';
|
||||
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
|
||||
import { SysRole } from 'src/Entity/SysRole';
|
||||
import { RoleDto } from 'src/EntiyDto/RoleDto';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { Not } from 'typeorm';
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('系统角色管理')
|
||||
@Controller('/role')
|
||||
export class RoleController {
|
||||
constructor(
|
||||
public readonly restful: RestfulReturn,
|
||||
public readonly service: RoleService
|
||||
) {
|
||||
}
|
||||
|
||||
//#region 基础控制器
|
||||
@ApiOperation({
|
||||
summary: '分页查询角色列表',
|
||||
description: '分页查询角色列表'
|
||||
})
|
||||
@Get('/getPage')
|
||||
async getPage(@Query() dto: PageDto) {
|
||||
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS,
|
||||
{
|
||||
total: result[1],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
rows: result[0],
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '查询角色列表',
|
||||
description: '查询角色列表'
|
||||
})
|
||||
@Get('/getList')
|
||||
async getList(@Query() dto: ListDto) {
|
||||
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID查询角色',
|
||||
description: '根据ID查询角色'
|
||||
})
|
||||
@Get('/getById')
|
||||
async getById(@Query() dto: IdDto) {
|
||||
const result = await this.service.getById(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID数组查询角色',
|
||||
description: '根据ID数组查询角色'
|
||||
})
|
||||
@Get('/getByIds')
|
||||
async getByIds(@Query() dto: IdsDto) {
|
||||
const result = await this.service.getByIds(dto.Ids);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '新增角色',
|
||||
description: '新增角色'
|
||||
})
|
||||
@Post('/add')
|
||||
async add(@Body() dto: RoleDto) {
|
||||
const model = plainToClass(SysRole, dto);
|
||||
const isRepeat = await this.service.repository.count({
|
||||
where: { Name: model.Name.trim() }
|
||||
});
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "角色名称重复!"
|
||||
);
|
||||
} else {
|
||||
const result = await this.service.save(model);
|
||||
if (result) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '修改角色',
|
||||
description: '修改角色'
|
||||
})
|
||||
@Post('/edit')
|
||||
async edit(@Body() dto: RoleDto) {
|
||||
const model = plainToClass(SysRole, dto);
|
||||
const isRepeat = await this.service.repository.count({
|
||||
where: { Id: Not(model.Id), Name: model.Name.trim() }
|
||||
});
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "角色名称重复!"
|
||||
);
|
||||
} else {
|
||||
const result = await this.service.save(model);
|
||||
if (result) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '删除角色',
|
||||
description: '删除角色'
|
||||
})
|
||||
@Post('/del')
|
||||
async del(@Body() dto: IdDto) {
|
||||
const result = await this.service.destroy(dto.Id);
|
||||
if (result) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '修改角色状态',
|
||||
description: '修改角色状态'
|
||||
})
|
||||
@Post('/setStatus')
|
||||
async setStatus(@Body() dto: IdStatusDto) {
|
||||
const result = await this.service.repository.createQueryBuilder()
|
||||
.update()
|
||||
.set({
|
||||
Status: dto.Status
|
||||
})
|
||||
.where("Id = :Id", { Id: dto.Id })
|
||||
.execute();
|
||||
if (result.affected > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:05:41
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 22:49:42
|
||||
* @FilePath: \tsplatform\backend\src\Controller\UserController.ts
|
||||
* @Description: 系统用户控制器
|
||||
*/
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { UserService } from 'src/Service/UserService';
|
||||
import { SysUser } from 'src/Entity/SysUser';
|
||||
import { IdDto, IdsDto, IdStatusDto, ListDto, PageDto } from 'src/EntiyDto/CommonDto';
|
||||
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { AppConfig } from 'src/Config/Index';
|
||||
import { Md5 } from 'ts-md5';
|
||||
import { UserDto } from 'src/EntiyDto/UserDto';
|
||||
import { Equal, Not } from 'typeorm';
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('系统用户管理')
|
||||
@Controller('/user')
|
||||
export class UserController {
|
||||
constructor(
|
||||
public readonly restful: RestfulReturn,
|
||||
public readonly service: UserService
|
||||
) {
|
||||
}
|
||||
//#region 基础控制器
|
||||
@ApiOperation({
|
||||
summary: '分页查询用户列表',
|
||||
description: '分页查询用户列表'
|
||||
})
|
||||
@Get('/getPage')
|
||||
async getPage(@Query() dto: PageDto) {
|
||||
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS,
|
||||
{
|
||||
total: result[1],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
rows: result[0],
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '查询用户列表',
|
||||
description: '查询用户列表'
|
||||
})
|
||||
@Get('/getList')
|
||||
async getList(@Query() dto: ListDto) {
|
||||
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID查询用户',
|
||||
description: '根据ID查询用户'
|
||||
})
|
||||
@Get('/getById')
|
||||
async getById(@Query() dto: IdDto) {
|
||||
const result = await this.service.getById(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '根据ID数组查询用户',
|
||||
description: '根据ID数组查询用户'
|
||||
})
|
||||
@Get('/getByIds')
|
||||
async getByIds(@Query() dto: IdsDto) {
|
||||
const result = await this.service.getByIds(dto.Ids);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '新增用户',
|
||||
description: '新增用户'
|
||||
})
|
||||
@Post('/add')
|
||||
async add(@Body() dto: UserDto) {
|
||||
const model = plainToClass(SysUser, dto);
|
||||
const isRepeat = await this.service.repository.createQueryBuilder()
|
||||
.orWhere({ UserName: Equal(model.UserName.trim()), })
|
||||
.orWhere({ Phone: Equal(model.Phone.trim()) })
|
||||
.orWhere({ Email: Equal(model.Email.trim()) }).getCount();
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "账号/手机号/邮箱重复!"
|
||||
);
|
||||
} else {
|
||||
//给新用户设置个默认密码:888888,用户登录后可以自行修改密码
|
||||
model.Password = Md5.hashStr(Md5.hashStr("888888") + AppConfig.md5Key);
|
||||
const result = await this.service.save(model);
|
||||
if (result) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '修改用户',
|
||||
description: '修改用户'
|
||||
})
|
||||
@Post('/edit')
|
||||
async edit(@Body() dto: UserDto) {
|
||||
const model = plainToClass(SysUser, dto);
|
||||
const isRepeat = await this.service.repository.createQueryBuilder()
|
||||
.where({ Id: Not(model.Id) })
|
||||
.andWhere([{ UserName: Equal(model.UserName.trim()) }, { Phone: Equal(model.Phone.trim()) }, { Email: Equal(model.Email.trim()) }])
|
||||
.getCount();
|
||||
if (isRepeat > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, "账号/手机号/邮箱重复!"
|
||||
);
|
||||
} else {
|
||||
//给新用户设置个默认密码:888888,用户登录后可以自行修改密码
|
||||
model.Password = Md5.hashStr(Md5.hashStr("888888") + AppConfig.md5Key);
|
||||
const result = await this.service.save(model);
|
||||
if (result) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '假删除用户',
|
||||
description: '假删除用户'
|
||||
})
|
||||
@Post('/del')
|
||||
async del(@Body() dto: IdDto) {
|
||||
const result = await this.service.destroy(dto.Id);
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: '修改用户状态',
|
||||
description: '修改用户状态'
|
||||
})
|
||||
@Post('/setStatus')
|
||||
async setStatus(@Body() dto: IdStatusDto) {
|
||||
const result = await this.service.repository.createQueryBuilder()
|
||||
.update()
|
||||
.set({
|
||||
Status: dto.Status
|
||||
})
|
||||
.where("Id = :Id", { Id: dto.Id })
|
||||
.execute();
|
||||
if (result.affected > 0) {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.SUCCESS, result
|
||||
);
|
||||
} else {
|
||||
return await this.restful.toJson(
|
||||
RestfulHttpCodeEnum.FAIL, result
|
||||
);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 11:26:13
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-22 21:57:27
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysBase.ts
|
||||
* @Description: 系统基类实体模型
|
||||
*/
|
||||
|
||||
import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, VersionColumn } from 'typeorm';
|
||||
@Entity()
|
||||
export class SysBase {
|
||||
@PrimaryGeneratedColumn()
|
||||
@PrimaryColumn({ comment: "ID" })
|
||||
Id: number;
|
||||
|
||||
@Column({ comment: "状态", default: 0, nullable: true })
|
||||
Status: number;
|
||||
|
||||
@Column({ comment: "商户/站点ID", default: 0, nullable: true })
|
||||
MerchantId: number;
|
||||
|
||||
//自动为实体插入日期
|
||||
@CreateDateColumn()
|
||||
CreateTime: Date;
|
||||
|
||||
//每次调用实体管理器或存储库的save时,自动更新实体日期
|
||||
@UpdateDateColumn()
|
||||
UpdateTime: Date;
|
||||
|
||||
@Column({ comment: "操作人ID", default: 0, nullable: true })
|
||||
OperatorId: number;
|
||||
|
||||
@Column({ comment: "修改人ID", default: 0, nullable: true })
|
||||
UpdateUserId: number;
|
||||
|
||||
@Column({ comment: "是否删除", default: false, nullable: true })
|
||||
IsDelete: boolean;
|
||||
|
||||
//每次调用实体管理器或存储库的save时自动增长实体版本
|
||||
@VersionColumn()
|
||||
Version: number;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 13:12:19
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-18 14:18:50
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysCity.ts
|
||||
* @Description: 系统城市实体模型
|
||||
*/
|
||||
|
||||
import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
|
||||
@Entity()
|
||||
export class SysCity {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
@PrimaryColumn({ comment: "ID" })
|
||||
Id: number;
|
||||
|
||||
@Column({ comment: "地区编号" })
|
||||
RegionId: string;
|
||||
|
||||
@Column({ comment: "地区名称" })
|
||||
RegionName: string;
|
||||
|
||||
@Column({ comment: "地区简称", nullable: true })
|
||||
RegionShortName: string;
|
||||
|
||||
@Column({ comment: "地区行政编号", nullable: true })
|
||||
RegionCode: string;
|
||||
|
||||
@Column({ comment: "地区父级ID" })
|
||||
RegionParentId: string;
|
||||
|
||||
@Column({ comment: "地区级别" })
|
||||
RegionLevel: number;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 13:10:02
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-22 15:08:00
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysDictionaries.ts
|
||||
* @Description: 系统字典实体模型
|
||||
*/
|
||||
|
||||
import { Entity, Column } from 'typeorm';
|
||||
import { SysBase } from 'src/Entity/SysBase';
|
||||
@Entity()
|
||||
export class SysDictionaries extends SysBase {
|
||||
@Column({ comment: "字典名称" })
|
||||
Name: string;
|
||||
|
||||
@Column({ comment: "备注", nullable: true })
|
||||
Remark: string;
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 11:21:40
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 21:52:49
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysMenu.ts
|
||||
* @Description: 系统菜单实体模型
|
||||
*/
|
||||
|
||||
import { Entity, Column } from 'typeorm';
|
||||
import { SysBase } from 'src/Entity/SysBase';
|
||||
import { MenuEnum } from 'src/Enum/Global';
|
||||
@Entity()
|
||||
export class SysMenu extends SysBase {
|
||||
@Column({ comment: "父级ID", default: 0 })
|
||||
ParentId: number;
|
||||
|
||||
@Column({ comment: "菜单类型", default: 0 })
|
||||
Type: MenuEnum;
|
||||
|
||||
@Column({ comment: "路由名称", unique: true })
|
||||
Name: string;
|
||||
|
||||
@Column({ comment: "路由地址", nullable: true })
|
||||
Path: string;
|
||||
|
||||
@Column({ comment: "组件名称", nullable: true })
|
||||
Component: string;
|
||||
|
||||
@Column({ comment: "重定向路径", nullable: true })
|
||||
Redirect: string;
|
||||
|
||||
@Column({ comment: "菜单名称" })
|
||||
Title: string;
|
||||
|
||||
@Column({ comment: "是否外链,开启外链条件,`1、isLink: 链接地址不为空 2、isIframe:false`", nullable: true })
|
||||
IsLink: string;
|
||||
|
||||
@Column({ comment: "是否隐藏此路由" })
|
||||
IsHide: boolean;
|
||||
|
||||
@Column({ comment: "是否缓存组件状态" })
|
||||
IsKeepAlive: boolean;
|
||||
|
||||
@Column({ comment: "是否固定在 tagsView 栏上" })
|
||||
IsAffix: boolean;
|
||||
|
||||
@Column({ comment: "是否内嵌窗口,开启条件,`1、isIframe:true 2、isLink:链接地址不为空`" })
|
||||
IsIframe: boolean;
|
||||
|
||||
@Column({ comment: "路由权限标识", unique: true })
|
||||
Permission: string;
|
||||
|
||||
@Column({ comment: "菜单图标", nullable: true })
|
||||
Icon: string;
|
||||
|
||||
@Column({ comment: "排序", nullable: true, default: 0 })
|
||||
Sort: number;
|
||||
|
||||
Children: SysMenu[];
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 13:08:01
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 16:46:20
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysRole.ts
|
||||
* @Description: 系统角色实体模型
|
||||
*/
|
||||
|
||||
import { Entity, Column } from 'typeorm';
|
||||
import { SysBase } from 'src/Entity/SysBase';
|
||||
import { SysRoleMenu } from 'src/Entity/SysRoleMenu';
|
||||
@Entity()
|
||||
export class SysRole extends SysBase {
|
||||
@Column({ comment: "角色名称", unique: true })
|
||||
Name: string;
|
||||
|
||||
@Column({ comment: "备注", nullable: true })
|
||||
Remark: string;
|
||||
|
||||
SysRoleMenu: SysRoleMenu[];
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 13:54:50
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 17:32:38
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysRoleMenu.ts
|
||||
* @Description: 系统角色菜单映射实体模型
|
||||
*/
|
||||
|
||||
import { Entity, Column, ManyToOne } from 'typeorm';
|
||||
import { SysBase } from 'src/Entity/SysBase';
|
||||
@Entity()
|
||||
export class SysRoleMenu extends SysBase {
|
||||
@Column({ comment: "菜单Id", default: 0 })
|
||||
MenuId: number;
|
||||
@Column({ comment: "角色Id", default: 0 })
|
||||
RoleId: number;
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 13:55:35
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 23:13:45
|
||||
* @FilePath: \tsplatform\backend\src\Entity\SysUser.ts
|
||||
* @Description: 系统用户映射实体模型
|
||||
*/
|
||||
import { Entity, Column, Index } from 'typeorm';
|
||||
import { SysBase } from 'src/Entity/SysBase';
|
||||
import { SexEnum, UserTypeEnum } from 'src/Enum/Global';
|
||||
@Entity()
|
||||
export class SysUser extends SysBase {
|
||||
@Column({ comment: "账号类型", default: 1 })
|
||||
UserType: UserTypeEnum;
|
||||
|
||||
@Column({ comment: "账号", unique: true })
|
||||
UserName: string;
|
||||
|
||||
@Column({ comment: "密码" })
|
||||
Password: string;
|
||||
|
||||
@Column({ comment: "昵称", nullable: true })
|
||||
NickName: string;
|
||||
|
||||
@Column({ comment: "头像", nullable: true })
|
||||
Avatar: string;
|
||||
|
||||
@Column({ comment: "出生日期", nullable: true })
|
||||
Birthday: string;
|
||||
|
||||
@Column({ comment: "性别", nullable: true, default: 0 })
|
||||
Sex: SexEnum;
|
||||
|
||||
@Column({ comment: "邮箱", unique: true })
|
||||
Email: string;
|
||||
|
||||
@Column({ comment: "手机号码", unique: true })
|
||||
Phone: string;
|
||||
|
||||
@Column({ comment: "真实姓名", nullable: true })
|
||||
RealName: string;
|
||||
|
||||
@Column({ comment: "身份证号", nullable: true })
|
||||
IdCard: string;
|
||||
|
||||
@Column({ comment: "个性签名", nullable: true })
|
||||
Signature: string;
|
||||
@Column({ comment: "个人简介", nullable: true })
|
||||
Introduction: string;
|
||||
|
||||
@Column({ comment: "备注", nullable: true })
|
||||
Remark: string;
|
||||
|
||||
@Column({ comment: "角色Id", default: 0 })
|
||||
RoleId: number;
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-18 09:02:28
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 00:20:22
|
||||
* @FilePath: \tsplatform\backend\src\Expand\CommonDto.ts
|
||||
* @Description: 公共DTO服务
|
||||
*/
|
||||
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsArray, IsNotEmpty, IsNumber, IsNumberString } from "class-validator";
|
||||
//分页查询DTO
|
||||
@Injectable()
|
||||
export class PageDto {
|
||||
@IsNotEmpty()
|
||||
@IsNumberString()
|
||||
@ApiProperty({ name: "Page", required: true })
|
||||
Page: number
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumberString()
|
||||
@ApiProperty({ name: "PageSize", required: true })
|
||||
PageSize: number
|
||||
|
||||
@ApiProperty({ name: "Keywords", required: false })
|
||||
Keywords: string
|
||||
|
||||
@ApiProperty({ name: "OrderBy", required: false })
|
||||
OrderBy: string
|
||||
}
|
||||
|
||||
//列表查询DTO
|
||||
@Injectable()
|
||||
export class ListDto {
|
||||
@ApiProperty({ name: "Keywords", required: false })
|
||||
Keywords: string
|
||||
|
||||
@ApiProperty({ name: "OrderBy", required: false })
|
||||
OrderBy: string
|
||||
}
|
||||
|
||||
//ids DTO
|
||||
@Injectable()
|
||||
export class IdsDto {
|
||||
@IsNotEmpty()
|
||||
@IsArray()
|
||||
@ApiProperty({ name: "Ids", required: true })
|
||||
Ids: number[]
|
||||
}
|
||||
//id DTO
|
||||
@Injectable()
|
||||
export class IdDto {
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({ name: "Id", required: true })
|
||||
Id: number
|
||||
}
|
||||
@Injectable()
|
||||
export class RoleIdDto {
|
||||
@ApiProperty({ name: "RoleId" })
|
||||
RoleId: number;
|
||||
}
|
||||
|
||||
|
||||
//Id Status DTO
|
||||
@Injectable()
|
||||
export class IdStatusDto {
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({ name: "Id", required: true })
|
||||
Id: number
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({ name: "Status", required: true })
|
||||
Status: number
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-18 17:35:08
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 12:12:42
|
||||
* @FilePath: \tsplatform\backend\src\EntiyDto\Login.ts
|
||||
* @Description: DTO类
|
||||
*/
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsString } from "class-validator";
|
||||
export class LoginDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ name: "UserName", required: true })
|
||||
UserName: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ name: "Password", required: true })
|
||||
Password: string;
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-18 17:35:08
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 00:14:13
|
||||
* @FilePath: \tsplatform\backend\src\EntiyDto\MenuDto.ts
|
||||
* @Description: SysMenu DTO类
|
||||
*/
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
import { MenuEnum } from "src/Enum/Global";
|
||||
|
||||
export class MenuDto {
|
||||
@ApiProperty({ name: "ParentId" })
|
||||
ParentId: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({ name: "Type", required: true })
|
||||
Type: MenuEnum;
|
||||
|
||||
@ApiProperty({ name: "Name", required: false })
|
||||
Name: string;
|
||||
|
||||
@ApiProperty({ name: "Path", required: false })
|
||||
Path: string;
|
||||
|
||||
@ApiProperty({ name: "Component", required: false })
|
||||
Component: string;
|
||||
|
||||
@ApiProperty({ name: "Redirect", required: false })
|
||||
Redirect: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ name: "Title", required: true })
|
||||
Title: string;
|
||||
|
||||
|
||||
@ApiProperty({ name: "IsLink", required: false })
|
||||
IsLink: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({ name: "IsHide", required: true })
|
||||
IsHide: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({ name: "IsKeepAlive", required: true })
|
||||
IsKeepAlive: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({ name: "IsAffix", required: true })
|
||||
IsAffix: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({ name: "IsIframe", required: true })
|
||||
IsIframe: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ name: "Permission", required: true })
|
||||
Permission: string;
|
||||
|
||||
@ApiProperty({ name: "Icon", required: false })
|
||||
Icon: string;
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-22 16:16:08
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 12:12:56
|
||||
* @FilePath: \backend\src\EntiyDto\RoleDto.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsArray, IsNotEmpty } from "class-validator";
|
||||
|
||||
export class RoleDto {
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ name: "Name", required: true })
|
||||
Name: string;
|
||||
|
||||
@ApiProperty({ name: "Remark" })
|
||||
Remark: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsArray()
|
||||
@ApiProperty({ name: "SysRoleMenu", required: true })
|
||||
SysRoleMenu: number[];
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-18 17:35:08
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 12:13:13
|
||||
* @FilePath: \tsplatform\backend\src\EntiyDto\RouteMenu.ts
|
||||
* @Description: RouteMenu类 菜单树查询,不需要dto映射
|
||||
*/
|
||||
export class RouteMenu {
|
||||
name: string;
|
||||
path: string;
|
||||
component: string;
|
||||
redirect: string;
|
||||
meta: Meta;
|
||||
children: RouteMenu[];
|
||||
}
|
||||
export class Meta {
|
||||
title: string;
|
||||
isLink: string;
|
||||
isHide: boolean;
|
||||
isKeepAlive: boolean;
|
||||
isAffix: boolean;
|
||||
isIframe: boolean;
|
||||
permission: string;
|
||||
icon: string;
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-22 16:16:19
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 21:32:25
|
||||
* @FilePath: \backend\src\EntiyDto\UserDto.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsNumber, IsNumberString } from "class-validator";
|
||||
|
||||
export class UserDto {
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({ name: "UserType", required: true })
|
||||
UserType: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ name: "Name", required: true })
|
||||
UserName: string;
|
||||
|
||||
@ApiProperty({ name: "Password" })
|
||||
Password: string;
|
||||
|
||||
@ApiProperty({ name: "NickName" })
|
||||
NickName: string;
|
||||
|
||||
@ApiProperty({ name: "Avatar" })
|
||||
Avatar: string;
|
||||
|
||||
@ApiProperty({ name: "Birthday" })
|
||||
Birthday: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({ name: "Sex" })
|
||||
Sex: number;
|
||||
|
||||
@ApiProperty({ name: "Email", required: true })
|
||||
Email: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumberString()
|
||||
@ApiProperty({ name: "Phone", required: true })
|
||||
Phone: string;
|
||||
|
||||
@ApiProperty({ name: "RealName" })
|
||||
RealName: string;
|
||||
|
||||
@ApiProperty({ name: "IdCard" })
|
||||
IdCard: string;
|
||||
|
||||
@ApiProperty({ name: "Signature" })
|
||||
Signature: string;
|
||||
|
||||
@ApiProperty({ name: "Introduction" })
|
||||
Introduction: string;
|
||||
|
||||
@ApiProperty({ name: "Remark" })
|
||||
Remark: string;
|
||||
|
||||
@ApiProperty({ name: "RoleId", required: true })
|
||||
RoleId: number;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 13:25:43
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-18 09:15:04
|
||||
* @FilePath: \tsplatform\backend\src\Enum\Global.ts
|
||||
* @Description: 全局枚举类
|
||||
*/
|
||||
|
||||
export enum RestfulHttpCodeEnum {
|
||||
SUCCESS = 200,
|
||||
FAIL = 300,
|
||||
NODATA = 201,
|
||||
}
|
||||
export enum UserTypeEnum {
|
||||
Admin = 0,
|
||||
None = 1,
|
||||
SuperAdmin = 999,
|
||||
}
|
||||
export enum SexEnum {
|
||||
Man = 0,
|
||||
WoMan = 1
|
||||
}
|
||||
export enum MenuEnum {
|
||||
Directory = 0,
|
||||
Menu = 1,
|
||||
Btn = 2
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-18 09:10:15
|
||||
* @FilePath: \tsplatform\backend\src\Expand\RestfulReturn.ts
|
||||
* @Description: RestfulAPI全局响应结构
|
||||
*/
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { RestfulHttpCodeEnum } from "src/Enum/Global";
|
||||
@Injectable()
|
||||
export class RestfulReturn {
|
||||
//HttpStatus=200时的状态码规则
|
||||
toJson(code: RestfulHttpCodeEnum, data: any) {
|
||||
return {
|
||||
code: code,
|
||||
msg: code === RestfulHttpCodeEnum.SUCCESS ? "操作成功!" :
|
||||
code === RestfulHttpCodeEnum.FAIL ? "操作失败!" :
|
||||
code === RestfulHttpCodeEnum.NODATA ? "数据为空!" : "未知错误!",
|
||||
data: data,
|
||||
timespan: new Date().getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-18 09:02:28
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:13:53
|
||||
* @FilePath: \tsplatform\backend\src\Logging\LoggerPrint.ts
|
||||
* @Description: 全局日志打印服务
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
@Injectable()
|
||||
export class LoggerPrint {
|
||||
constructor(private readonly logger: Logger) {
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-21 11:57:30
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-22 10:41:09
|
||||
* @FilePath: \tsplatform\backend\src\Middleware\JwtHandle.ts
|
||||
* @Description: 授权中间件
|
||||
*/
|
||||
import { HttpException, HttpStatus, Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { NextFunction } from 'express';
|
||||
import { AppConfig } from 'src/Config/Index';
|
||||
|
||||
@Injectable()
|
||||
export class JwtHandle implements NestMiddleware {
|
||||
constructor(
|
||||
public readonly _jwt: JwtService
|
||||
) { }
|
||||
async use(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const token = req.headers["authorization"];
|
||||
if (!token) {
|
||||
throw new HttpException("Token指令牌不存在!", HttpStatus.UNAUTHORIZED);
|
||||
} else {
|
||||
await this._jwt.verifyAsync(token, { secret: AppConfig.jwt.secret, ignoreExpiration: false })
|
||||
next();
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
throw new HttpException(ex.message, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,216 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-24 09:22:50
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 09:58:45
|
||||
* @FilePath: \backend\src\SeedData\Menu.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
export const menus = [{
|
||||
"Id": 8,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-23T07:53:21.453Z",
|
||||
"UpdateTime": "2022-11-24T01:29:41.000Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 5,
|
||||
"ParentId": 2,
|
||||
"Type": 1,
|
||||
"Name": "system.city",
|
||||
"Path": "/system/city",
|
||||
"Component": "/system/city/index",
|
||||
"Redirect": "",
|
||||
"Title": "message.router.system.city",
|
||||
"IsLink": "",
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "system.city",
|
||||
"Icon": "ele-OfficeBuilding",
|
||||
"Sort": 101
|
||||
},
|
||||
{
|
||||
"Id": 7,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-23T07:50:37.192Z",
|
||||
"UpdateTime": "2022-11-24T01:21:19.762Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 4,
|
||||
"ParentId": 2,
|
||||
"Type": 1,
|
||||
"Name": "system.dic",
|
||||
"Path": "/system/dic",
|
||||
"Component": "/system/dic/index",
|
||||
"Redirect": "",
|
||||
"Title": "message.router.system.dic",
|
||||
"IsLink": "",
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "system.dic",
|
||||
"Icon": "iconfont icon-zhongyingwen1",
|
||||
"Sort": 100
|
||||
},
|
||||
{
|
||||
"Id": 6,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.445Z",
|
||||
"UpdateTime": "2022-11-23T05:56:53.726Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"ParentId": 0,
|
||||
"Type": 0,
|
||||
"Name": "personal",
|
||||
"Path": "/personal",
|
||||
"Component": "personal/index",
|
||||
"Redirect": null,
|
||||
"Title": "message.router.personal",
|
||||
"IsLink": "",
|
||||
"IsHide": true,
|
||||
"IsKeepAlive": false,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "personal",
|
||||
"Icon": "iconfont icon-gerenzhongxin",
|
||||
"Sort": 6
|
||||
},
|
||||
{
|
||||
"Id": 5,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.445Z",
|
||||
"UpdateTime": "2022-11-23T08:05:06.779Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"ParentId": 2,
|
||||
"Type": 1,
|
||||
"Name": "system.role",
|
||||
"Path": "/system/role",
|
||||
"Component": "system/role/index",
|
||||
"Redirect": null,
|
||||
"Title": "message.router.system.role",
|
||||
"IsLink": "",
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "system.role",
|
||||
"Icon": "fa fa-street-view",
|
||||
"Sort": 5
|
||||
},
|
||||
{
|
||||
"Id": 4,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.445Z",
|
||||
"UpdateTime": "2022-11-23T08:05:03.689Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"ParentId": 2,
|
||||
"Type": 1,
|
||||
"Name": "system.user",
|
||||
"Path": "/system/user",
|
||||
"Component": "system/user/index",
|
||||
"Redirect": null,
|
||||
"Title": "message.router.system.user",
|
||||
"IsLink": null,
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "system.user",
|
||||
"Icon": "iconfont icon-icon-",
|
||||
"Sort": 4
|
||||
},
|
||||
{
|
||||
"Id": 3,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.445Z",
|
||||
"UpdateTime": "2022-11-23T08:05:00.650Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"ParentId": 2,
|
||||
"Type": 1,
|
||||
"Name": "system.menu",
|
||||
"Path": "/system/menu",
|
||||
"Component": "system/menu/index",
|
||||
"Redirect": null,
|
||||
"Title": "message.router.system.menu",
|
||||
"IsLink": "",
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "system.menu",
|
||||
"Icon": "iconfont icon-caidan",
|
||||
"Sort": 3
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.445Z",
|
||||
"UpdateTime": "2022-11-23T08:05:16.188Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"ParentId": 0,
|
||||
"Type": 0,
|
||||
"Name": "system",
|
||||
"Path": "/system",
|
||||
"Component": "layout/routerView/parent",
|
||||
"Redirect": "/system/menu",
|
||||
"Title": "message.router.system.system",
|
||||
"IsLink": "",
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": false,
|
||||
"IsIframe": false,
|
||||
"Permission": "system",
|
||||
"Icon": "iconfont icon-xitongshezhi",
|
||||
"Sort": 2
|
||||
},
|
||||
{
|
||||
"Id": 1,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.445Z",
|
||||
"UpdateTime": "2022-11-22T13:59:37.471Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"ParentId": 0,
|
||||
"Type": 0,
|
||||
"Name": "home",
|
||||
"Path": "/home",
|
||||
"Component": "home/index",
|
||||
"Redirect": "",
|
||||
"Title": "message.router.home",
|
||||
"IsLink": "",
|
||||
"IsHide": false,
|
||||
"IsKeepAlive": true,
|
||||
"IsAffix": true,
|
||||
"IsIframe": false,
|
||||
"Permission": "home",
|
||||
"Icon": "iconfont icon-shouye",
|
||||
"Sort": 1
|
||||
}];
|
||||
@ -1,179 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-24 09:23:07
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:04:17
|
||||
* @FilePath: \backend\src\SeedData\Role.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
export const roles = [
|
||||
{
|
||||
"Id": 1,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.567Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.000Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 4,
|
||||
"Name": "超级管理员",
|
||||
"Remark": null
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.567Z",
|
||||
"UpdateTime": "2022-11-24T01:36:45.000Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 3,
|
||||
"Name": "普通用户",
|
||||
"Remark": null
|
||||
}
|
||||
];
|
||||
export const roleMenus = [{
|
||||
"Id": 1,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 1,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 2,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 3,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 3,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 4,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 4,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 5,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 5,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 6,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 6,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 7,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 7,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 8,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:35:03.437Z",
|
||||
"UpdateTime": "2022-11-24T01:35:03.437Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 8,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 9,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:36:45.486Z",
|
||||
"UpdateTime": "2022-11-24T01:36:45.486Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 1,
|
||||
"RoleId": 2
|
||||
},
|
||||
{
|
||||
"Id": 10,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:36:45.486Z",
|
||||
"UpdateTime": "2022-11-24T01:36:45.486Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 2,
|
||||
"RoleId": 2
|
||||
},
|
||||
{
|
||||
"Id": 11,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-24T01:36:45.486Z",
|
||||
"UpdateTime": "2022-11-24T01:36:45.486Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 1,
|
||||
"MenuId": 4,
|
||||
"RoleId": 2
|
||||
}];
|
||||
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-24 09:22:58
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:04:55
|
||||
* @FilePath: \backend\src\SeedData\User.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
export const users = [{
|
||||
"Id": 1,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-22T13:59:37.496Z",
|
||||
"UpdateTime": "2022-11-24T01:21:53.423Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 0,
|
||||
"UserType": 999,
|
||||
"UserName": "admin",
|
||||
"Password": "1a23c5066d05474304c03342e57850d9",
|
||||
"NickName": "甜蜜蜜",
|
||||
"Avatar": "https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500",
|
||||
"Birthday": null,
|
||||
"Sex": 0,
|
||||
"Email": "490912587@qq.com",
|
||||
"Phone": "13000000000",
|
||||
"RealName": null,
|
||||
"IdCard": null,
|
||||
"Signature": null,
|
||||
"Introduction": null,
|
||||
"Remark": null,
|
||||
"RoleId": 1
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"Status": 0,
|
||||
"MerchantId": 0,
|
||||
"CreateTime": "2022-11-23T08:45:37.321Z",
|
||||
"UpdateTime": "2022-11-24T01:39:33.000Z",
|
||||
"OperatorId": 0,
|
||||
"UpdateUserId": 0,
|
||||
"IsDelete": false,
|
||||
"Version": 4,
|
||||
"UserType": 0,
|
||||
"UserName": "test",
|
||||
"Password": "7b0ea1aa91470cd870111415a94b475b",
|
||||
"NickName": "甜蜜蜜的小号",
|
||||
"Avatar": "https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500",
|
||||
"Birthday": null,
|
||||
"Sex": 1,
|
||||
"Email": "1844045442@qq.com",
|
||||
"Phone": "15000000000",
|
||||
"RealName": null,
|
||||
"IdCard": null,
|
||||
"Signature": null,
|
||||
"Introduction": null,
|
||||
"Remark": null,
|
||||
"RoleId": 2
|
||||
}];
|
||||
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:08:52
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 11:12:25
|
||||
* @FilePath: \tsplatform\backend\src\Service\UserService.ts
|
||||
* @Description: 省市区服务
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
|
||||
import { LoggerPrint } from 'src/Logging/LoggerPrint';
|
||||
import { SysCity } from 'src/Entity/SysCity';
|
||||
import { citys } from 'src/SeedData/City';
|
||||
@Injectable()
|
||||
export class CityService {
|
||||
private readonly tableName: string = "省市区";
|
||||
constructor(
|
||||
public readonly loggerPrint: LoggerPrint,
|
||||
public readonly logger: Logger,
|
||||
@InjectRepository(SysCity)
|
||||
public readonly repository: Repository<SysCity>
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//#region 基础控制器
|
||||
// 初始化数据
|
||||
async seedData(): Promise<number> {
|
||||
let result: InsertResult;
|
||||
await this.repository.queryRunner?.startTransaction();
|
||||
try {
|
||||
await this.repository.clear();
|
||||
result = await this.repository.createQueryBuilder().insert().values(citys).execute();
|
||||
await this.repository.queryRunner?.commitTransaction();
|
||||
await this.repository.createQueryBuilder().useTransaction(true);
|
||||
return result.identifiers.length;
|
||||
} catch (err) {
|
||||
//如果遇到错误,可以回滚事务
|
||||
await this.repository.queryRunner?.rollbackTransaction();
|
||||
this.logger.warn(err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
async getPage(
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<[SysCity[], number]> {
|
||||
const result = await this.repository.findAndCount({
|
||||
where: [{ RegionName: Like("%" + keywords + "%") }, { RegionShortName: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "ASC"
|
||||
},
|
||||
skip: page - 1,
|
||||
take: pageSize
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async getList(
|
||||
keywords: string = "",
|
||||
orderBy: string = "",
|
||||
): Promise<SysCity[]> {
|
||||
const result = await this.repository.find({
|
||||
where: [{ RegionName: Like("%" + keywords + "%") }, { RegionShortName: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "ASC"
|
||||
},
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async getById(id: number): Promise<SysCity> {
|
||||
const result = await this.repository.findOne({ where: { Id: id } });
|
||||
return result;
|
||||
}
|
||||
async getByIds(ids: number[]): Promise<SysCity[]> {
|
||||
const result = await this.repository.find({
|
||||
where: { Id: In(ids) },
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async save(model: SysCity): Promise<SysCity> {
|
||||
const result = await this.repository.save(model);
|
||||
return result;
|
||||
}
|
||||
async destroy(id: number): Promise<DeleteResult> {
|
||||
const result = await this.repository.delete(id);
|
||||
return result;
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:08:52
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditSysMenuime: 2022-11-18 11:31:43
|
||||
* @FilePath: \tsplatform\backend\src\Service\MenuService.ts
|
||||
* @Description: 系统菜单服务
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { SysMenu } from 'src/Entity/SysMenu';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
|
||||
import { LoggerPrint } from 'src/Logging/LoggerPrint';
|
||||
import { menus } from 'src/SeedData/Menu';
|
||||
@Injectable()
|
||||
export class MenuService {
|
||||
private readonly tableName: string = "系统菜单";
|
||||
constructor(
|
||||
public readonly loggerPrint: LoggerPrint,
|
||||
public readonly logger: Logger,
|
||||
|
||||
@InjectRepository(SysMenu)
|
||||
public readonly repository: Repository<SysMenu>
|
||||
) {
|
||||
}
|
||||
//#region 基础控制器
|
||||
// 初始化数据
|
||||
async seedData(): Promise<number> {
|
||||
let result: InsertResult;
|
||||
await this.repository.queryRunner?.startTransaction();
|
||||
try {
|
||||
await this.repository.clear();
|
||||
result = await this.repository.createQueryBuilder().insert().values(menus).execute();
|
||||
await this.repository.queryRunner?.commitTransaction();
|
||||
await this.repository.createQueryBuilder().useTransaction(true);
|
||||
return result.identifiers.length;
|
||||
} catch (err) {
|
||||
//如果遇到错误,可以回滚事务
|
||||
await this.repository.queryRunner?.rollbackTransaction();
|
||||
this.logger.warn(err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
async getPage(
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<[SysMenu[], number]> {
|
||||
const result = await this.repository.findAndCount({
|
||||
where: [{ Title: Like("%" + keywords + "%") }, { Name: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "DESC"
|
||||
},
|
||||
skip: page - 1,
|
||||
take: pageSize
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
async getList(
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<SysMenu[]> {
|
||||
const result = await this.repository.find({
|
||||
where: [{ Name: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "DESC"
|
||||
},
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async getById(id: number): Promise<SysMenu> {
|
||||
const result = await this.repository.findOne({ where: { Id: id } });
|
||||
return result;
|
||||
}
|
||||
async getByIds(ids: number[]): Promise<SysMenu[]> {
|
||||
const result = await this.repository.find({
|
||||
where: { Id: In(ids) },
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async save(model: SysMenu): Promise<SysMenu> {
|
||||
const result = await this.repository.save(model);
|
||||
|
||||
return result;
|
||||
}
|
||||
async destroy(id: number): Promise<DeleteResult> {
|
||||
const result = await this.repository.delete(id);
|
||||
return result;
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:08:52
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:15:40
|
||||
* @FilePath: \tsplatform\backend\src\Service\RoleService.ts
|
||||
* @Description: 系统角色服务
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
|
||||
import { SysRole } from 'src/Entity/SysRole';
|
||||
import { LoggerPrint } from 'src/Logging/LoggerPrint';
|
||||
import { SysRoleMenu } from 'src/Entity/SysRoleMenu';
|
||||
import { roleMenus, roles } from 'src/SeedData/Role';
|
||||
// where: { Name: keywords },//and查询
|
||||
// where: [{ Name: keywords }],//or查询
|
||||
// where: { Name: Like("%out #%") },//like查询
|
||||
// where: { Id: In([0,1]) },//in查询
|
||||
// where: { Name: Like("%out #%") },//like查询
|
||||
@Injectable()
|
||||
export class RoleService {
|
||||
private readonly tableName: string = "系统角色";
|
||||
constructor(
|
||||
public readonly loggerPrint: LoggerPrint,
|
||||
public readonly logger: Logger,
|
||||
|
||||
@InjectRepository(SysRole)
|
||||
public readonly repository: Repository<SysRole>,
|
||||
@InjectRepository(SysRoleMenu)
|
||||
public readonly roleMenuRepository: Repository<SysRoleMenu>,
|
||||
) {
|
||||
}
|
||||
//根据角色Id获得路由菜单
|
||||
async getMenusByRoleId(id: number): Promise<SysRoleMenu[]> {
|
||||
//根据角色Id获得角色菜单映射数据
|
||||
const result = await this.roleMenuRepository.find({
|
||||
where: { RoleId: id }
|
||||
});
|
||||
return result;
|
||||
}
|
||||
//#region 基础控制器
|
||||
// 初始化数据
|
||||
async seedData(): Promise<number[]> {
|
||||
let result: InsertResult;
|
||||
let result1: InsertResult;
|
||||
await this.repository.queryRunner?.startTransaction();
|
||||
try {
|
||||
await this.repository.clear();
|
||||
result = await this.repository.createQueryBuilder().insert().values(roles).execute();
|
||||
result1 = await this.roleMenuRepository.createQueryBuilder().insert().values(roleMenus).execute();
|
||||
await this.repository.queryRunner?.commitTransaction();
|
||||
await this.repository.createQueryBuilder().useTransaction(true);
|
||||
return [result.identifiers.length, result1.identifiers.length]
|
||||
} catch (err) {
|
||||
//如果遇到错误,可以回滚事务
|
||||
await this.repository.queryRunner?.rollbackTransaction();
|
||||
this.logger.warn(err);
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
async getPage(
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<[SysRole[], number]> {
|
||||
const result = await this.repository.findAndCount({
|
||||
where: [{ Name: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "ASC"
|
||||
},
|
||||
skip: page - 1,
|
||||
take: pageSize
|
||||
})
|
||||
for await (let item of result[0]) {
|
||||
|
||||
const roleMenuResult = await this.roleMenuRepository.find({
|
||||
where: { RoleId: item.Id },
|
||||
})
|
||||
item.SysRoleMenu = roleMenuResult;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async getList(
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<SysRole[]> {
|
||||
const result = await this.repository.find({
|
||||
where: [{ Name: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "ASC"
|
||||
},
|
||||
})
|
||||
for await (let item of result) {
|
||||
const roleMenuResult = await this.roleMenuRepository.find({
|
||||
where: { RoleId: item.Id },
|
||||
})
|
||||
item.SysRoleMenu = roleMenuResult;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async getById(id: number): Promise<SysRole> {
|
||||
const result = await this.repository.findOne({ where: { Id: id } });
|
||||
return result;
|
||||
}
|
||||
async getByIds(ids: number[]): Promise<SysRole[]> {
|
||||
const result = await this.repository.find({
|
||||
where: { Id: In(ids) }
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async save(model: SysRole): Promise<boolean> {
|
||||
await this.repository.queryRunner?.startTransaction();
|
||||
try {
|
||||
//如果是编辑先删除主子表后,修改主表,新增子表
|
||||
if (model.Id > 0) {
|
||||
await this.repository.createQueryBuilder().update().set({ Name: model.Name, Remark: model.Remark, Status: model.Status }).where("Id = :Id", { Id: model.Id }).execute();
|
||||
await this.roleMenuRepository.delete({ RoleId: model.Id });
|
||||
for await (let item of model.SysRoleMenu) {
|
||||
item.RoleId = model.Id
|
||||
}
|
||||
} else {
|
||||
const result = await this.repository.createQueryBuilder().insert().values(model).execute();
|
||||
for await (let item of model.SysRoleMenu) {
|
||||
item.RoleId = result.identifiers[0].Id
|
||||
}
|
||||
}
|
||||
await this.roleMenuRepository.createQueryBuilder().insert().values(model.SysRoleMenu).execute();
|
||||
await this.repository.queryRunner?.commitTransaction();
|
||||
await this.repository.createQueryBuilder().useTransaction(true);
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.repository.queryRunner?.rollbackTransaction();
|
||||
this.logger.warn(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async destroy(id: number): Promise<boolean> {
|
||||
await this.repository.queryRunner?.startTransaction();
|
||||
try {
|
||||
//删除主子表
|
||||
await this.repository.delete({ Id: id });
|
||||
await this.roleMenuRepository.delete({ RoleId: id });
|
||||
await this.repository.queryRunner?.commitTransaction();
|
||||
await this.repository.createQueryBuilder().useTransaction(true);
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.repository.queryRunner?.rollbackTransaction();
|
||||
this.logger.warn(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 17:08:52
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:15:57
|
||||
* @FilePath: \tsplatform\backend\src\Service\UserService.ts
|
||||
* @Description: 系统用户服务
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
|
||||
import { LoggerPrint } from 'src/Logging/LoggerPrint';
|
||||
import { SysUser } from 'src/Entity/SysUser';
|
||||
import { users } from 'src/SeedData/User';
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
private readonly tableName: string = "系统用户";
|
||||
constructor(
|
||||
public readonly loggerPrint: LoggerPrint,
|
||||
public readonly logger: Logger,
|
||||
@InjectRepository(SysUser)
|
||||
public readonly repository: Repository<SysUser>
|
||||
) {
|
||||
|
||||
}
|
||||
//#region 基础控制器
|
||||
// 初始化数据
|
||||
async seedData(): Promise<number> {
|
||||
let result: InsertResult;
|
||||
await this.repository.queryRunner?.startTransaction();
|
||||
try {
|
||||
await this.repository.clear();
|
||||
result = await this.repository.createQueryBuilder().insert().values(users).execute();
|
||||
await this.repository.queryRunner?.commitTransaction();
|
||||
await this.repository.createQueryBuilder().useTransaction(true);
|
||||
return result.identifiers.length;
|
||||
} catch (err) {
|
||||
//如果遇到错误,可以回滚事务
|
||||
await this.repository.queryRunner?.rollbackTransaction();
|
||||
this.logger.warn(err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
async getPage(
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<[SysUser[], number]> {
|
||||
const result = await this.repository.findAndCount({
|
||||
where: [{ Phone: Like("%" + keywords + "%") }, { UserName: Like("%" + keywords + "%") }, { NickName: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "ASC"
|
||||
},
|
||||
skip: page - 1,
|
||||
take: pageSize
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
async getList(
|
||||
keywords: string = "",
|
||||
orderBy: string = ""
|
||||
): Promise<SysUser[]> {
|
||||
const result = await this.repository.find({
|
||||
where: [{ Phone: Like("%" + keywords + "%") }, { UserName: Like("%" + keywords + "%") }, { NickName: Like("%" + keywords + "%") }],
|
||||
order: {
|
||||
Id: "ASC"
|
||||
},
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async getById(id: number): Promise<SysUser> {
|
||||
const result = await this.repository.findOne({ where: { Id: id } });
|
||||
return result;
|
||||
}
|
||||
async getByIds(ids: number[]): Promise<SysUser[]> {
|
||||
const result = await this.repository.find({
|
||||
where: { Id: In(ids) },
|
||||
})
|
||||
return result;
|
||||
}
|
||||
async save(model: SysUser): Promise<SysUser> {
|
||||
const result = await this.repository.save(model);
|
||||
return result;
|
||||
}
|
||||
async destroy(id: number): Promise<DeleteResult> {
|
||||
const result = await this.repository.delete(id);
|
||||
return result;
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 11:44:48
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 17:51:54
|
||||
* @FilePath: \tsplatform\backend\src\Utils\LoadModules.ts
|
||||
* @Description: 全局加载模块
|
||||
*/
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { RestfulReturn } from 'src/Expand/RestfulReturn';
|
||||
import { LoggerPrint } from 'src/Logging/LoggerPrint';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { SysMenu } from 'src/Entity/SysMenu';
|
||||
import { SysCity } from 'src/Entity/SysCity';
|
||||
import { SysUser } from 'src/Entity/SysUser';
|
||||
import { SysRole } from 'src/Entity/SysRole';
|
||||
import { SysRoleMenu } from 'src/Entity/SysRoleMenu';
|
||||
|
||||
import { IndexController } from 'src/Controller/IndexController';
|
||||
import { MenuController } from 'src/Controller/MenuController';
|
||||
import { AuthController } from 'src/Controller/AuthController';
|
||||
import { UserController } from 'src/Controller/UserController';
|
||||
import { RoleController } from 'src/Controller/RoleController';
|
||||
import { CityController } from 'src/Controller/CityController';
|
||||
|
||||
import { MenuService } from 'src/Service/MenuService';
|
||||
import { UserService } from 'src/Service/UserService';
|
||||
import { RoleService } from 'src/Service/RoleService';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CityService } from 'src/Service/CityService';
|
||||
|
||||
|
||||
//自动导入Entity
|
||||
export function LoadPlatFormEntity() {
|
||||
return [SysCity, SysMenu, SysUser, SysRole, SysRoleMenu];
|
||||
}
|
||||
//自动导入Controller
|
||||
export function LoadController() {
|
||||
return [IndexController, CityController, MenuController, UserController, AuthController, RoleController,];
|
||||
}
|
||||
//自动导入Service服务
|
||||
export function LoadService() {
|
||||
return [
|
||||
//特殊服务提供者
|
||||
RestfulReturn, Logger, LoggerPrint, Repository,JwtService,
|
||||
//Service服务提供者
|
||||
MenuService, UserService, RoleService, CityService
|
||||
];
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 10:16:52
|
||||
* @FilePath: \tsplatform\backend\src\app.module.ts
|
||||
* @Description: 全局模块入口
|
||||
*/
|
||||
|
||||
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { LoadController, LoadPlatFormEntity, LoadService } from 'src/Utils/LoadModules';
|
||||
import { AppConfig, DataBaseConfig } from 'src/Config/Index';
|
||||
import { JwtHandle } from 'src/Middleware/JwtHandle';
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature(LoadPlatFormEntity()),
|
||||
TypeOrmModule.forRootAsync({
|
||||
useFactory: () => ({
|
||||
debug: false,
|
||||
//重试连接数据库的次数
|
||||
retryAttempts: 10,
|
||||
//两次重试连接的间隔(ms)
|
||||
retryDelay: 3000,
|
||||
//自动加载实体
|
||||
autoLoadEntities: true,
|
||||
type: 'mysql',
|
||||
host: DataBaseConfig.DataBase.host,
|
||||
port: DataBaseConfig.DataBase.port,
|
||||
username: DataBaseConfig.DataBase.username,
|
||||
password: DataBaseConfig.DataBase.password,
|
||||
database: DataBaseConfig.DataBase.database,
|
||||
entities: LoadPlatFormEntity(),
|
||||
logging: AppConfig.debug,
|
||||
synchronize: AppConfig.debug,
|
||||
})
|
||||
}),
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: ['.env', '.env.development', '.env.production'],
|
||||
}),
|
||||
|
||||
],
|
||||
controllers: LoadController(),
|
||||
providers: LoadService()
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(JwtHandle)
|
||||
.exclude(
|
||||
{ path: 'auth/login', method: RequestMethod.POST },
|
||||
{ path: 'auth/getUserInfo', method: RequestMethod.GET },
|
||||
{ path: '/seed', method: RequestMethod.GET },
|
||||
|
||||
)
|
||||
.forRoutes("*");
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 11:17:23
|
||||
* @FilePath: \tsplatform\backend\src\main.ts
|
||||
* @Description: 主程序入口
|
||||
*/
|
||||
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from 'src/app.module';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { AppConfig } from 'src/Config/Index';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, { logger: ['error', 'warn', "log", "debug", "verbose"], cors: true });
|
||||
|
||||
//全局Dto参数校验管道
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
const config = new DocumentBuilder()
|
||||
//基础文档里添加安全定义
|
||||
.addBearerAuth()
|
||||
.setTitle('甜蜜蜜TS开发总平台')
|
||||
.setDescription('甜蜜蜜TS开发总平台')
|
||||
.setContact("甜蜜蜜", "https://gitee.com/tmm-top/vue-next-admin-ts", "490912587@qq.com")
|
||||
.setVersion('1.0')
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('/api', app, document);
|
||||
await app.listen(AppConfig.port, '0.0.0.0');
|
||||
}
|
||||
bootstrap();
|
||||
@ -1,24 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from './../src/app.module';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
});
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2022",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
9
doc/.gitignore
vendored
9
doc/.gitignore
vendored
@ -1,9 +0,0 @@
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 149 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
25
frontend/.gitignore
vendored
25
frontend/.gitignore
vendored
@ -1,25 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.development
|
||||
.env.production
|
||||
*.lock
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
package-lock.json
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
4
frontend/plugins.d.ts
vendored
4
frontend/plugins.d.ts
vendored
@ -1,4 +0,0 @@
|
||||
declare module 'vue-grid-layout';
|
||||
declare module 'qrcodejs2-fixes';
|
||||
declare module 'splitpanes';
|
||||
declare module 'js-cookie';
|
||||
13
frontend/shim.d.ts
vendored
13
frontend/shim.d.ts
vendored
@ -1,13 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx,使用 getCurrentInstance() 来获取
|
||||
interface Window {
|
||||
nextLoading: boolean;
|
||||
}
|
||||
6
frontend/source.d.ts
vendored
6
frontend/source.d.ts
vendored
@ -1,6 +0,0 @@
|
||||
declare module '*.json';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.scss';
|
||||
declare module '*.ts';
|
||||
declare module '*.js';
|
||||
@ -1,107 +0,0 @@
|
||||
<!--
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 17:25:54
|
||||
* @FilePath: \frontend\src\App.vue
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
-->
|
||||
<template>
|
||||
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
|
||||
<router-view v-show="themeConfig.lockScreenTime > 1" />
|
||||
<LockScreen v-if="themeConfig.isLockScreen" />
|
||||
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
|
||||
<CloseFull v-if="!themeConfig.isLockScreen" />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import { Local, Session } from '/@/utils/storage';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import setIntroduction from '/@/utils/setIconfont';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'app',
|
||||
components: {
|
||||
LockScreen: defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')),
|
||||
Setings: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue')),
|
||||
CloseFull: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue')),
|
||||
},
|
||||
setup() {
|
||||
const { messages, locale } = useI18n();
|
||||
const setingsRef = ref();
|
||||
const route = useRoute();
|
||||
const stores = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 获取全局组件大小
|
||||
const getGlobalComponentSize = computed(() => {
|
||||
return other.globalComponentSize();
|
||||
});
|
||||
// 获取全局 i18n
|
||||
const getGlobalI18n = computed(() => {
|
||||
return messages.value[locale.value];
|
||||
});
|
||||
// 设置初始化,防止刷新时恢复默认
|
||||
onBeforeMount(() => {
|
||||
// 设置批量第三方 icon 图标
|
||||
setIntroduction.cssCdn();
|
||||
// 设置批量第三方 js
|
||||
setIntroduction.jsCdn();
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 监听布局配置弹窗点击打开
|
||||
mittBus.on('openSetingsDrawer', () => {
|
||||
setingsRef.value.openDrawer();
|
||||
});
|
||||
// 获取缓存中的布局配置
|
||||
if (Local.get('themeConfig')) {
|
||||
storesThemeConfig.setThemeConfig(Local.get('themeConfig'));
|
||||
document.documentElement.style.cssText = Local.get('themeConfigStyle');
|
||||
}
|
||||
// 获取缓存中的全屏配置
|
||||
if (Session.get('isTagsViewCurrenFull')) {
|
||||
stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
|
||||
}
|
||||
});
|
||||
});
|
||||
// 页面销毁时,关闭监听布局配置/i18n监听
|
||||
onUnmounted(() => {
|
||||
mittBus.off('openSetingsDrawer', () => {});
|
||||
});
|
||||
// 监听路由的变化,设置网站标题
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
themeConfig,
|
||||
setingsRef,
|
||||
getGlobalComponentSize,
|
||||
getGlobalI18n,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.searchBox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
</style>
|
||||
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 09:43:23
|
||||
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '/@/utils/request';
|
||||
export function getPage(params: object) {
|
||||
return request({
|
||||
url: '/city/getPage',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
export function getList(params: object) {
|
||||
return request({
|
||||
url: '/city/getList',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 17:01:13
|
||||
* @FilePath: \tsplatform\frontend\src\api\login\index.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 登录api接口集合
|
||||
* @method signIn 用户登录
|
||||
* @method signOut 用户退出登录
|
||||
*/
|
||||
export function signIn(params: object) {
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'post',
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
export function getUserInfo() {
|
||||
return request({
|
||||
url: '/auth/getUserInfo',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
export function signOut() {
|
||||
return request({
|
||||
url: '/auth/loginOut',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-24 00:24:11
|
||||
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '/@/utils/request';
|
||||
export function getAuthMenu(params: object) {
|
||||
return request({
|
||||
url: '/menu/getAuthMenuTree',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
export function getMenuTree(params: object) {
|
||||
return request({
|
||||
url: '/menu/getMenuTree',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
export function add(params: object) {
|
||||
return request({
|
||||
url: '/menu/add',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function edit(params: object) {
|
||||
return request({
|
||||
url: '/menu/edit',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function del(params: object) {
|
||||
return request({
|
||||
url: '/menu/del',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function setStatus(params: object) {
|
||||
return request({
|
||||
url: '/menu/setStatus',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 21:35:46
|
||||
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export function getPage(params: object) {
|
||||
return request({
|
||||
url: '/role/getPage',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
export function getList(params: object) {
|
||||
return request({
|
||||
url: '/role/getList',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
export function add(params: object) {
|
||||
return request({
|
||||
url: '/role/add',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function edit(params: object) {
|
||||
return request({
|
||||
url: '/role/edit',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function del(params: object) {
|
||||
return request({
|
||||
url: '/role/del',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function setStatus(params: object) {
|
||||
return request({
|
||||
url: '/role/setStatus',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-23 21:35:53
|
||||
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '/@/utils/request';
|
||||
export function getPage(params: object) {
|
||||
return request({
|
||||
url: '/user/getPage',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
export function getList(params: object) {
|
||||
return request({
|
||||
url: '/role/getList',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
export function add(params: object) {
|
||||
return request({
|
||||
url: '/user/add',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function edit(params: object) {
|
||||
return request({
|
||||
url: '/user/edit',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function del(params: object) {
|
||||
return request({
|
||||
url: '/user/del',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function setStatus(params: object) {
|
||||
return request({
|
||||
url: '/user/setStatus',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<slot v-if="getUserAuthBtnList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'auth',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const stores = useUserInfo();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
return userInfos.value.authBtnList.some((v: string) => v === props.value);
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<slot v-if="getUserAuthBtnList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'authAll',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const stores = useUserInfo();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
// 获取 pinia 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
return judementSameArr(props.value, userInfos.value.authBtnList);
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<slot v-if="getUserAuthBtnList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'auths',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const stores = useUserInfo();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
let flag = false;
|
||||
userInfos.value.authBtnList.map((val: string) => {
|
||||
props.value.map((v) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
});
|
||||
return flag;
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<div class="editor-container">
|
||||
<Toolbar :editor="editorRef" :mode="mode" />
|
||||
<Editor :mode="mode" :defaultConfig="editorConfig" :style="{ height }" v-model="editorVal" @onCreated="handleCreated" @onChange="handleChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// https://www.wangeditor.com/v5/for-frame.html#vue3
|
||||
import '@wangeditor/editor/dist/css/style.css';
|
||||
import { defineComponent, reactive, toRefs, shallowRef, watch, onBeforeUnmount } from 'vue';
|
||||
import { Toolbar, Editor } from '@wangeditor/editor-for-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'wngEditor',
|
||||
components: { Toolbar, Editor },
|
||||
props: {
|
||||
// 是否禁用
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: () => true,
|
||||
},
|
||||
// 内容框默认 placeholder
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => '请输入内容...',
|
||||
},
|
||||
// 双向绑定:双向绑定值,字段名为固定,改了之后将不生效
|
||||
// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
|
||||
modelValue: String,
|
||||
// https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F
|
||||
// 模式,可选 <default|simple>,默认 default
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => 'default',
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: String,
|
||||
default: () => '310px',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const editorRef = shallowRef();
|
||||
const state = reactive({
|
||||
editorConfig: {
|
||||
placeholder: props.placeholder,
|
||||
},
|
||||
editorVal: props.modelValue,
|
||||
});
|
||||
// 编辑器回调函数
|
||||
const handleCreated = (editor: any) => {
|
||||
editorRef.value = editor;
|
||||
};
|
||||
// 编辑器内容改变时
|
||||
const handleChange = (editor: any) => {
|
||||
// console.log(editor.getText());
|
||||
// console.log(editor.getHtml());
|
||||
emit('update:modelValue', editor.getHtml());
|
||||
};
|
||||
// 监听是否禁用改变
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
|
||||
watch(
|
||||
() => props.disable,
|
||||
(bool) => {
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
bool ? editor.disable() : editor.enable();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面销毁时
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
editor.destroy();
|
||||
});
|
||||
return {
|
||||
editorRef,
|
||||
handleCreated,
|
||||
handleChange,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,252 +0,0 @@
|
||||
<template>
|
||||
<div class="icon-selector w100 h100">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
:width="fontIconWidth"
|
||||
trigger="click"
|
||||
transition="el-zoom-in-top"
|
||||
popper-class="icon-selector-popper"
|
||||
@show="onPopoverShow"
|
||||
>
|
||||
<template #reference>
|
||||
<el-input
|
||||
v-model="fontIconSearch"
|
||||
:placeholder="fontIconPlaceholder"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
ref="inputWidthRef"
|
||||
@clear="onClearFontIcon"
|
||||
@focus="onIconFocus"
|
||||
@blur="onIconBlur"
|
||||
>
|
||||
<template #prepend>
|
||||
<SvgIcon
|
||||
:name="fontIconPrefix === '' ? prepend : fontIconPrefix"
|
||||
class="font14"
|
||||
v-if="fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : fontIconPrefix?.indexOf('ele-') > -1"
|
||||
/>
|
||||
<i v-else :class="fontIconPrefix === '' ? prepend : fontIconPrefix" class="font14"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="icon-selector-warp">
|
||||
<div class="icon-selector-warp-title flex">
|
||||
<div class="flex-auto">{{ title }}</div>
|
||||
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
|
||||
<span :class="{ 'span-active': fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标">ali</span>
|
||||
<span :class="{ 'span-active': fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标">ele</span>
|
||||
<span :class="{ 'span-active': fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标">awe</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-selector-warp-row">
|
||||
<el-scrollbar ref="selectorScrollbarRef">
|
||||
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
|
||||
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
|
||||
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconPrefix === v }">
|
||||
<div class="flex-margin">
|
||||
<div class="icon-selector-warp-item-value">
|
||||
<SvgIcon :name="v" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
|
||||
import initIconfont from '/@/utils/getStyleSheets';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'iconSelector',
|
||||
emits: ['update:modelValue', 'get', 'clear'],
|
||||
props: {
|
||||
// 输入框前置内容
|
||||
prepend: {
|
||||
type: String,
|
||||
default: () => 'ele-Pointer',
|
||||
},
|
||||
// 输入框占位文本
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => '请输入内容搜索图标或者选择图标',
|
||||
},
|
||||
// 输入框占位文本
|
||||
size: {
|
||||
type: String,
|
||||
default: () => 'default',
|
||||
},
|
||||
// 弹窗标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '请选择图标',
|
||||
},
|
||||
// icon 图标类型
|
||||
type: {
|
||||
type: String,
|
||||
default: () => 'ele',
|
||||
},
|
||||
// 禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
// 是否可清空
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: () => true,
|
||||
},
|
||||
// 自定义空状态描述文字
|
||||
emptyDescription: {
|
||||
type: String,
|
||||
default: () => '无相关图标',
|
||||
},
|
||||
// 双向绑定值,默认为 modelValue,
|
||||
// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
|
||||
// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
|
||||
modelValue: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const inputWidthRef = ref();
|
||||
const selectorScrollbarRef = ref();
|
||||
const state = reactive({
|
||||
fontIconPrefix: '',
|
||||
fontIconWidth: 0,
|
||||
fontIconSearch: '',
|
||||
fontIconTabsIndex: 0,
|
||||
fontIconSheetsList: [],
|
||||
fontIconPlaceholder: '',
|
||||
fontIconType: 'ali',
|
||||
fontIconShow: true,
|
||||
});
|
||||
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
|
||||
const onIconFocus = () => {
|
||||
if (!props.modelValue) return false;
|
||||
state.fontIconSearch = '';
|
||||
state.fontIconPlaceholder = props.modelValue;
|
||||
};
|
||||
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
|
||||
const onIconBlur = () => {
|
||||
setTimeout(() => {
|
||||
const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch);
|
||||
if (icon.length <= 0) state.fontIconSearch = '';
|
||||
}, 300);
|
||||
};
|
||||
// 处理 icon 双向绑定数值回显
|
||||
const initModeValueEcho = () => {
|
||||
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
|
||||
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
|
||||
(<string | undefined>state.fontIconPrefix) = props.modelValue;
|
||||
};
|
||||
// 处理 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
|
||||
const initFontIconTypeEcho = () => {
|
||||
if ((<any>props.modelValue)?.indexOf('iconfont') > -1) onIconChange('ali');
|
||||
else if ((<any>props.modelValue)?.indexOf('ele-') > -1) onIconChange('ele');
|
||||
else if ((<any>props.modelValue)?.indexOf('fa') > -1) onIconChange('awe');
|
||||
else onIconChange('ali');
|
||||
};
|
||||
// 图标搜索及图标数据显示
|
||||
const fontIconSheetsFilterList = computed(() => {
|
||||
if (!state.fontIconSearch) return state.fontIconSheetsList;
|
||||
let search = state.fontIconSearch.trim().toLowerCase();
|
||||
return state.fontIconSheetsList.filter((item: any) => {
|
||||
if (item.toLowerCase().indexOf(search) !== -1) return item;
|
||||
});
|
||||
});
|
||||
// 获取 input 的宽度
|
||||
const getInputWidth = () => {
|
||||
nextTick(() => {
|
||||
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
|
||||
});
|
||||
};
|
||||
// 监听页面宽度改变
|
||||
const initResize = () => {
|
||||
window.addEventListener('resize', () => {
|
||||
getInputWidth();
|
||||
});
|
||||
};
|
||||
// 初始化数据
|
||||
const initFontIconData = async (type: string) => {
|
||||
state.fontIconSheetsList = [];
|
||||
if (type === 'ali') {
|
||||
await initIconfont.ali().then((res: any) => {
|
||||
// 阿里字体图标使用 `iconfont xxx`
|
||||
state.fontIconSheetsList = res.map((i: string) => `iconfont ${i}`);
|
||||
});
|
||||
} else if (type === 'ele') {
|
||||
await initIconfont.ele().then((res: any) => {
|
||||
state.fontIconSheetsList = res;
|
||||
});
|
||||
} else if (type === 'awe') {
|
||||
await initIconfont.awe().then((res: any) => {
|
||||
// fontawesome字体图标使用 `fa xxx`
|
||||
state.fontIconSheetsList = res.map((i: string) => `fa ${i}`);
|
||||
});
|
||||
}
|
||||
// 初始化 input 的 placeholder
|
||||
// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
|
||||
state.fontIconPlaceholder = props.placeholder;
|
||||
// 初始化双向绑定回显
|
||||
initModeValueEcho();
|
||||
};
|
||||
// 图标点击切换
|
||||
const onIconChange = (type: string) => {
|
||||
state.fontIconType = type;
|
||||
initFontIconData(type);
|
||||
};
|
||||
// 获取当前点击的 icon 图标
|
||||
const onColClick = (v: any) => {
|
||||
state.fontIconPlaceholder = v;
|
||||
state.fontIconPrefix = v;
|
||||
emit('get', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
};
|
||||
// 清空当前点击的 icon 图标
|
||||
const onClearFontIcon = () => {
|
||||
state.fontIconPrefix = '';
|
||||
emit('clear', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
};
|
||||
// 监听 Popover 打开,用于双向绑定值回显
|
||||
const onPopoverShow = () => {
|
||||
initModeValueEcho();
|
||||
initFontIconTypeEcho();
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initModeValueEcho();
|
||||
initResize();
|
||||
getInputWidth();
|
||||
});
|
||||
|
||||
// 监听双向绑定 modelValue 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
initModeValueEcho();
|
||||
}
|
||||
);
|
||||
return {
|
||||
inputWidthRef,
|
||||
selectorScrollbarRef,
|
||||
fontIconSheetsFilterList,
|
||||
onColClick,
|
||||
onIconChange,
|
||||
onClearFontIcon,
|
||||
onIconFocus,
|
||||
onIconBlur,
|
||||
onPopoverShow,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,195 +0,0 @@
|
||||
<template>
|
||||
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!isMode">
|
||||
<div class="notice-bar-warp" :style="{ color, fontSize: `${size}px` }">
|
||||
<i v-if="leftIcon" class="notice-bar-warp-left-icon" :class="leftIcon"></i>
|
||||
<div class="notice-bar-warp-text-box" ref="noticeBarWarpRef">
|
||||
<div class="notice-bar-warp-text" ref="noticeBarTextRef" v-if="!scrollable">{{ text }}</div>
|
||||
<div class="notice-bar-warp-slot" v-else><slot /></div>
|
||||
</div>
|
||||
<SvgIcon :name="rightIcon" v-if="rightIcon" class="notice-bar-warp-right-icon" @click="onRightIconClick" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'noticeBar',
|
||||
props: {
|
||||
// 通知栏模式,可选值为 closeable link
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
// 通知文本内容
|
||||
text: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
// 通知文本颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: () => 'var(--el-color-warning)',
|
||||
},
|
||||
// 通知背景色
|
||||
background: {
|
||||
type: String,
|
||||
default: () => 'var(--el-color-warning-light-9)',
|
||||
},
|
||||
// 字体大小,单位px
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: () => 14,
|
||||
},
|
||||
// 通知栏高度,单位px
|
||||
height: {
|
||||
type: Number,
|
||||
default: () => 40,
|
||||
},
|
||||
// 动画延迟时间 (s)
|
||||
delay: {
|
||||
type: Number,
|
||||
default: () => 1,
|
||||
},
|
||||
// 滚动速率 (px/s)
|
||||
speed: {
|
||||
type: Number,
|
||||
default: () => 100,
|
||||
},
|
||||
// 是否开启垂直滚动
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
// 自定义左侧图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
// 自定义右侧图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const noticeBarWarpRef = ref();
|
||||
const noticeBarTextRef = ref();
|
||||
const state = reactive({
|
||||
order: 1,
|
||||
oneTime: 0,
|
||||
twoTime: 0,
|
||||
warpOWidth: 0,
|
||||
textOWidth: 0,
|
||||
isMode: false,
|
||||
});
|
||||
// 初始化 animation 各项参数
|
||||
const initAnimation = () => {
|
||||
nextTick(() => {
|
||||
state.warpOWidth = noticeBarWarpRef.value.offsetWidth;
|
||||
state.textOWidth = noticeBarTextRef.value.offsetWidth;
|
||||
document.styleSheets[0].insertRule(`@keyframes oneAnimation {0% {left: 0px;} 100% {left: -${state.textOWidth}px;}}`);
|
||||
document.styleSheets[0].insertRule(`@keyframes twoAnimation {0% {left: ${state.warpOWidth}px;} 100% {left: -${state.textOWidth}px;}}`);
|
||||
computeAnimationTime();
|
||||
setTimeout(() => {
|
||||
changeAnimation();
|
||||
}, props.delay * 1000);
|
||||
});
|
||||
};
|
||||
// 计算 animation 滚动时长
|
||||
const computeAnimationTime = () => {
|
||||
state.oneTime = state.textOWidth / props.speed;
|
||||
state.twoTime = (state.textOWidth + state.warpOWidth) / props.speed;
|
||||
};
|
||||
// 改变 animation 动画调用
|
||||
const changeAnimation = () => {
|
||||
if (state.order === 1) {
|
||||
noticeBarTextRef.value.style.cssText = `animation: oneAnimation ${state.oneTime}s linear; opactity: 1;}`;
|
||||
state.order = 2;
|
||||
} else {
|
||||
noticeBarTextRef.value.style.cssText = `animation: twoAnimation ${state.twoTime}s linear infinite; opacity: 1;`;
|
||||
}
|
||||
};
|
||||
// 监听 animation 动画的结束
|
||||
const listenerAnimationend = () => {
|
||||
noticeBarTextRef.value.addEventListener(
|
||||
'animationend',
|
||||
() => {
|
||||
changeAnimation();
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
// 右侧 icon 图标点击
|
||||
const onRightIconClick = () => {
|
||||
if (!props.mode) return false;
|
||||
if (props.mode === 'closeable') {
|
||||
state.isMode = true;
|
||||
emit('close');
|
||||
} else if (props.mode === 'link') {
|
||||
emit('link');
|
||||
}
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
if (props.scrollable) return false;
|
||||
initAnimation();
|
||||
listenerAnimationend();
|
||||
});
|
||||
return {
|
||||
noticeBarWarpRef,
|
||||
noticeBarTextRef,
|
||||
onRightIconClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.notice-bar {
|
||||
padding: 0 15px;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
.notice-bar-warp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
.notice-bar-warp-text-box {
|
||||
flex: 1;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.notice-bar-warp-text {
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
.notice-bar-warp-slot {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
:deep(.el-carousel__item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.notice-bar-warp-left-icon {
|
||||
width: 24px;
|
||||
font-size: inherit !important;
|
||||
}
|
||||
.notice-bar-warp-right-icon {
|
||||
width: 24px;
|
||||
text-align: right;
|
||||
font-size: inherit !important;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,73 +0,0 @@
|
||||
<template>
|
||||
<i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
|
||||
<component :is="getIconName" />
|
||||
</i>
|
||||
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
|
||||
<img :src="getIconName" :style="setIconSvgInsStyle" />
|
||||
</div>
|
||||
<i v-else :class="getIconName" :style="setIconSvgStyle" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'svgIcon',
|
||||
props: {
|
||||
// svg 图标组件名字
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
// svg 大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: () => 14,
|
||||
},
|
||||
// svg 颜色
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
// 在线链接、本地引入地址前缀
|
||||
const linesString = ['https', 'http', '/src', '/assets', import.meta.env.VITE_PUBLIC_PATH];
|
||||
|
||||
// 获取 icon 图标名称
|
||||
const getIconName = computed(() => {
|
||||
return props?.name;
|
||||
});
|
||||
// 用于判断 element plus 自带 svg 图标的显示、隐藏
|
||||
const isShowIconSvg = computed(() => {
|
||||
return props?.name?.startsWith('ele-');
|
||||
});
|
||||
// 用于判断在线链接、本地引入等图标显示、隐藏
|
||||
const isShowIconImg = computed(() => {
|
||||
return linesString.find((str) => props.name?.startsWith(str));
|
||||
});
|
||||
// 设置图标样式
|
||||
const setIconSvgStyle = computed(() => {
|
||||
return `font-size: ${props.size}px;color: ${props.color};`;
|
||||
});
|
||||
// 设置图片样式
|
||||
const setIconImgOutStyle = computed(() => {
|
||||
return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
|
||||
});
|
||||
// 设置图片样式
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0
|
||||
const setIconSvgInsStyle = computed(() => {
|
||||
const filterStyle: string[] = [];
|
||||
const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
|
||||
compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
|
||||
return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
|
||||
});
|
||||
return {
|
||||
getIconName,
|
||||
isShowIconSvg,
|
||||
isShowIconImg,
|
||||
setIconSvgStyle,
|
||||
setIconImgOutStyle,
|
||||
setIconSvgInsStyle,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,68 +0,0 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import pinia from '/@/stores/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
|
||||
import enLocale from 'element-plus/lib/locale/lang/en';
|
||||
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
|
||||
|
||||
import nextZhcn from '/@/i18n/lang/zh-cn';
|
||||
import nextEn from '/@/i18n/lang/en';
|
||||
import nextZhtw from '/@/i18n/lang/zh-tw';
|
||||
|
||||
import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn';
|
||||
import pagesLoginEn from '/@/i18n/pages/login/en';
|
||||
import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw';
|
||||
import pagesFormI18nZhcn from '/@/i18n/pages/formI18n/zh-cn';
|
||||
import pagesFormI18nEn from '/@/i18n/pages/formI18n/en';
|
||||
import pagesFormI18nZhtw from '/@/i18n/pages/formI18n/zh-tw';
|
||||
|
||||
// 定义语言国际化内容
|
||||
/**
|
||||
* 说明:
|
||||
* /src/i18n/lang 下的 ts 为框架的国际化内容
|
||||
* /src/i18n/pages 下的 ts 为各界面的国际化内容
|
||||
*/
|
||||
const messages = {
|
||||
[zhcnLocale.name]: {
|
||||
...zhcnLocale,
|
||||
message: {
|
||||
...nextZhcn,
|
||||
...pagesLoginZhcn,
|
||||
...pagesFormI18nZhcn,
|
||||
},
|
||||
},
|
||||
[enLocale.name]: {
|
||||
...enLocale,
|
||||
message: {
|
||||
...nextEn,
|
||||
...pagesLoginEn,
|
||||
...pagesFormI18nEn,
|
||||
},
|
||||
},
|
||||
[zhtwLocale.name]: {
|
||||
...zhtwLocale,
|
||||
message: {
|
||||
...nextZhtw,
|
||||
...pagesLoginZhtw,
|
||||
...pagesFormI18nZhtw,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 读取 pinia 默认语言
|
||||
const stores = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(stores);
|
||||
|
||||
// 导出语言国际化
|
||||
// https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
|
||||
export const i18n = createI18n({
|
||||
legacy: false,
|
||||
silentTranslationWarn: true,
|
||||
missingWarn: false,
|
||||
silentFallbackWarn: true,
|
||||
fallbackWarn: false,
|
||||
locale: themeConfig.value.globalI18n,
|
||||
fallbackLocale: zhcnLocale.name,
|
||||
messages,
|
||||
});
|
||||
@ -1,165 +0,0 @@
|
||||
<template>
|
||||
<div class="h100" v-show="!isTagsViewCurrenFull">
|
||||
<el-aside class="layout-aside" :class="setCollapseStyle">
|
||||
<Logo v-if="setShowLogo" />
|
||||
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
|
||||
<Vertical :menuList="menuList" />
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, toRefs, reactive, computed, watch, onBeforeMount, defineComponent, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutAside',
|
||||
components: {
|
||||
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
|
||||
Vertical: defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue')),
|
||||
},
|
||||
setup() {
|
||||
const layoutAsideScrollbarRef = ref();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const state = reactive({
|
||||
menuList: [],
|
||||
clientWidth: 0,
|
||||
});
|
||||
// 设置菜单展开/收起时的宽度
|
||||
const setCollapseStyle = computed(() => {
|
||||
const { layout, isCollapse, menuBar } = themeConfig.value;
|
||||
const asideBrTheme = ['#FFFFFF', '#FFF', '#fff', '#ffffff'];
|
||||
const asideBrColor = asideBrTheme.includes(menuBar) ? 'layout-el-aside-br-color' : '';
|
||||
// 判断是否是手机端
|
||||
if (state.clientWidth <= 1000) {
|
||||
if (isCollapse) {
|
||||
document.body.setAttribute('class', 'el-popup-parent--hidden');
|
||||
const asideEle = document.querySelector('.layout-container') as HTMLElement;
|
||||
const modeDivs = document.createElement('div');
|
||||
modeDivs.setAttribute('class', 'layout-aside-mobile-mode');
|
||||
asideEle.appendChild(modeDivs);
|
||||
modeDivs.addEventListener('click', closeLayoutAsideMobileMode);
|
||||
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-open'];
|
||||
} else {
|
||||
// 关闭弹窗
|
||||
closeLayoutAsideMobileMode();
|
||||
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
|
||||
}
|
||||
} else {
|
||||
if (layout === 'columns') {
|
||||
// 分栏布局,菜单收起时宽度给 1px
|
||||
if (isCollapse) return [asideBrColor, 'layout-aside-pc-1'];
|
||||
else return [asideBrColor, 'layout-aside-pc-220'];
|
||||
} else {
|
||||
// 其它布局给 64px
|
||||
if (isCollapse) return [asideBrColor, 'layout-aside-pc-64'];
|
||||
else return [asideBrColor, 'layout-aside-pc-220'];
|
||||
}
|
||||
}
|
||||
});
|
||||
// 关闭移动端蒙版
|
||||
const closeLayoutAsideMobileMode = () => {
|
||||
const el = document.querySelector('.layout-aside-mobile-mode');
|
||||
el?.setAttribute('style', 'animation: error-img-two 0.3s');
|
||||
setTimeout(() => {
|
||||
el?.parentNode?.removeChild(el);
|
||||
}, 300);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||
document.body.setAttribute('class', '');
|
||||
};
|
||||
// 设置显示/隐藏 logo
|
||||
const setShowLogo = computed(() => {
|
||||
let { layout, isShowLogo } = themeConfig.value;
|
||||
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
|
||||
});
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
if (themeConfig.value.layout === 'columns') return false;
|
||||
(state.menuList as any) = filterRoutesFun(routesList.value);
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
item = Object.assign({}, item);
|
||||
if (item.children) item.children = filterRoutesFun(item.children);
|
||||
return item;
|
||||
});
|
||||
};
|
||||
// 设置菜单导航是否固定(移动端)
|
||||
const initMenuFixed = (clientWidth: number) => {
|
||||
state.clientWidth = clientWidth;
|
||||
};
|
||||
// 鼠标移入、移出
|
||||
const onAsideEnterLeave = (bool: Boolean) => {
|
||||
let { layout } = themeConfig.value;
|
||||
if (layout !== 'columns') return false;
|
||||
if (!bool) mittBus.emit('restoreDefault');
|
||||
stores.setColumnsMenuHover(bool);
|
||||
};
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(themeConfig.value, (val) => {
|
||||
if (val.isShowLogoChange !== val.isShowLogo) {
|
||||
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
|
||||
}
|
||||
});
|
||||
// 监听vuex值的变化,动态赋值给菜单中
|
||||
watch(
|
||||
pinia.state,
|
||||
(val) => {
|
||||
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
|
||||
if (layout === 'classic' && isClassicSplitMenu) return false;
|
||||
setFilterRoutes();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
initMenuFixed(document.body.clientWidth);
|
||||
setFilterRoutes();
|
||||
// 此界面不需要取消监听(mittBus.off('setSendColumnsChildren))
|
||||
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
|
||||
mittBus.on('setSendColumnsChildren', (res: any) => {
|
||||
state.menuList = res.children;
|
||||
});
|
||||
mittBus.on('setSendClassicChildren', (res: any) => {
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = [];
|
||||
state.menuList = res.children;
|
||||
}
|
||||
});
|
||||
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
setFilterRoutes();
|
||||
});
|
||||
mittBus.on('layoutMobileResize', (res: any) => {
|
||||
initMenuFixed(res.clientWidth);
|
||||
closeLayoutAsideMobileMode();
|
||||
});
|
||||
});
|
||||
return {
|
||||
layoutAsideScrollbarRef,
|
||||
setCollapseStyle,
|
||||
setShowLogo,
|
||||
isTagsViewCurrenFull,
|
||||
onAsideEnterLeave,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,296 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-columns-aside">
|
||||
<el-scrollbar>
|
||||
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
|
||||
<li
|
||||
v-for="(v, k) in columnsAsideList"
|
||||
:key="k"
|
||||
@click="onColumnsAsideMenuClick(v, k)"
|
||||
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
|
||||
:ref="
|
||||
(el) => {
|
||||
if (el) columnsAsideOffsetTopRefs[k] = el;
|
||||
}
|
||||
"
|
||||
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
|
||||
:title="$t(v.meta.title)"
|
||||
>
|
||||
<div :class="themeConfig.columnsAsideLayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
|
||||
<SvgIcon :name="v.meta.icon" />
|
||||
<div class="columns-vertical-title font12">
|
||||
{{
|
||||
$t(v.meta.title) && $t(v.meta.title).length >= 4
|
||||
? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
|
||||
: $t(v.meta.title)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div :class="themeConfig.columnsAsideLayout" v-else>
|
||||
<a :href="v.meta.isLink" target="_blank">
|
||||
<SvgIcon :name="v.meta.icon" />
|
||||
<div class="columns-vertical-title font12">
|
||||
{{
|
||||
$t(v.meta.title) && $t(v.meta.title).length >= 4
|
||||
? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
|
||||
: $t(v.meta.title)
|
||||
}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<div ref="columnsAsideActiveRef" :class="themeConfig.columnsAsideStyle"></div>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, ref, onMounted, nextTick, watch, onUnmounted, defineComponent } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface ColumnsAsideState {
|
||||
columnsAsideList: any[];
|
||||
liIndex: number;
|
||||
liOldIndex: null | number;
|
||||
liHoverIndex: null | number;
|
||||
liOldPath: null | string;
|
||||
difference: number;
|
||||
routeSplit: string[];
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutColumnsAside',
|
||||
setup() {
|
||||
const columnsAsideOffsetTopRefs: any = ref([]);
|
||||
const columnsAsideActiveRef = ref();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive<ColumnsAsideState>({
|
||||
columnsAsideList: [],
|
||||
liIndex: 0,
|
||||
liOldIndex: null,
|
||||
liHoverIndex: null,
|
||||
liOldPath: null,
|
||||
difference: 0,
|
||||
routeSplit: [],
|
||||
});
|
||||
// 设置菜单高亮位置移动
|
||||
const setColumnsAsideMove = (k: number) => {
|
||||
state.liIndex = k;
|
||||
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
|
||||
};
|
||||
// 菜单高亮点击事件
|
||||
const onColumnsAsideMenuClick = (v: Object, k: number) => {
|
||||
setColumnsAsideMove(k);
|
||||
let { path, redirect } = v as any;
|
||||
if (redirect) router.push(redirect);
|
||||
else router.push(path);
|
||||
};
|
||||
// 鼠标移入时,显示当前的子级菜单
|
||||
const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
|
||||
if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
|
||||
let { path } = v;
|
||||
state.liOldPath = path;
|
||||
state.liOldIndex = k;
|
||||
state.liHoverIndex = k;
|
||||
mittBus.emit('setSendColumnsChildren', setSendChildren(path));
|
||||
stores.setColumnsMenuHover(false);
|
||||
stores.setColumnsNavHover(true);
|
||||
};
|
||||
// 鼠标移走时,显示原来的子级菜单
|
||||
const onColumnsAsideMenuMouseleave = async () => {
|
||||
await stores.setColumnsNavHover(false);
|
||||
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
|
||||
setTimeout(() => {
|
||||
if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
|
||||
}, 100);
|
||||
};
|
||||
// 设置高亮动态位置
|
||||
const onColumnsAsideDown = (k: number) => {
|
||||
nextTick(() => {
|
||||
setColumnsAsideMove(k);
|
||||
});
|
||||
};
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
state.columnsAsideList = filterRoutesFun(routesList.value);
|
||||
const resData: any = setSendChildren(route.path);
|
||||
if (Object.keys(resData).length <= 0) return false;
|
||||
onColumnsAsideDown(resData.item[0].k);
|
||||
mittBus.emit('setSendColumnsChildren', resData);
|
||||
};
|
||||
// 传送当前子级数据到菜单中
|
||||
const setSendChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
state.columnsAsideList.map((v: any, k: number) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
currentData['children'] = [{ ...v }];
|
||||
if (v.children) currentData['children'] = v.children;
|
||||
}
|
||||
});
|
||||
return currentData;
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
item = Object.assign({}, item);
|
||||
if (item.children) item.children = filterRoutesFun(item.children);
|
||||
return item;
|
||||
});
|
||||
};
|
||||
// tagsView 点击时,根据路由查找下标 columnsAsideList,实现左侧菜单高亮
|
||||
const setColumnsMenuHighlight = (path: string) => {
|
||||
state.routeSplit = path.split('/');
|
||||
state.routeSplit.shift();
|
||||
const routeFirst = `/${state.routeSplit[0]}`;
|
||||
const currentSplitRoute = state.columnsAsideList.find((v: any) => v.path === routeFirst);
|
||||
if (!currentSplitRoute) return false;
|
||||
// 延迟拿值,防止取不到
|
||||
setTimeout(() => {
|
||||
onColumnsAsideDown((<any>currentSplitRoute).k);
|
||||
}, 0);
|
||||
};
|
||||
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
|
||||
watch(
|
||||
pinia.state,
|
||||
(val) => {
|
||||
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
||||
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
|
||||
state.liHoverIndex = null;
|
||||
mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
|
||||
} else {
|
||||
state.liHoverIndex = state.liOldIndex;
|
||||
if (!state.liOldPath) return false;
|
||||
mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
setFilterRoutes();
|
||||
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
|
||||
mittBus.on('restoreDefault', () => {
|
||||
state.liOldIndex = null;
|
||||
state.liOldPath = null;
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
mittBus.off('restoreDefault', () => {});
|
||||
});
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
setColumnsMenuHighlight(to.path);
|
||||
mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
|
||||
});
|
||||
return {
|
||||
themeConfig,
|
||||
columnsAsideOffsetTopRefs,
|
||||
columnsAsideActiveRef,
|
||||
onColumnsAsideDown,
|
||||
onColumnsAsideMenuClick,
|
||||
onColumnsAsideMenuMouseenter,
|
||||
onColumnsAsideMenuMouseleave,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-columns-aside {
|
||||
width: 70px;
|
||||
height: 100%;
|
||||
background: var(--next-bg-columnsMenuBar);
|
||||
ul {
|
||||
position: relative;
|
||||
.layout-columns-active {
|
||||
color: var(--next-bg-columnsMenuBarColor) !important;
|
||||
transition: 0.3s ease-in-out;
|
||||
}
|
||||
.layout-columns-hover {
|
||||
color: var(--el-color-primary);
|
||||
a {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
li {
|
||||
color: var(--next-bg-columnsMenuBarColor);
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
&:hover {
|
||||
@extend .layout-columns-hover;
|
||||
}
|
||||
.columns-vertical {
|
||||
margin: auto;
|
||||
.columns-vertical-title {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
.columns-horizontal {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
.columns-horizontal-title {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--next-bg-columnsMenuBarColor);
|
||||
}
|
||||
}
|
||||
.columns-round {
|
||||
background: var(--el-color-primary);
|
||||
color: var(--el-color-white);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 2px;
|
||||
height: 44px;
|
||||
width: 65px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 0;
|
||||
transition: 0.3s ease-in-out;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.columns-card {
|
||||
@extend .columns-round;
|
||||
top: 0;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<el-header class="layout-header" v-show="!isTagsViewCurrenFull">
|
||||
<NavBarsIndex />
|
||||
</el-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutHeader',
|
||||
components: {
|
||||
NavBarsIndex: defineAsyncComponent(() => import('/@/layout/navBars/index.vue')),
|
||||
},
|
||||
setup() {
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
return {
|
||||
isTagsViewCurrenFull,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<el-main class="layout-main" :style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
|
||||
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll" wrap-class="layout-main-scroll" view-class="layout-main-scroll">
|
||||
<LayoutParentView />
|
||||
<LayoutFooter v-if="isFooter" />
|
||||
</el-scrollbar>
|
||||
<el-backtop target=".layout-backtop .el-scrollbar__wrap" />
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, onMounted, computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutMain',
|
||||
components: {
|
||||
LayoutParentView: defineAsyncComponent(() => import('/@/layout/routerView/parent.vue')),
|
||||
LayoutFooter: defineAsyncComponent(() => import('/@/layout/footer/index.vue')),
|
||||
},
|
||||
setup() {
|
||||
const layoutMainScrollbarRef = ref('');
|
||||
const route = useRoute();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
// 设置 footer 显示/隐藏
|
||||
const isFooter = computed(() => {
|
||||
return themeConfig.value.isFooter && !route.meta.isIframe;
|
||||
});
|
||||
// 设置 header 固定
|
||||
const isFixedHeader = computed(() => {
|
||||
return themeConfig.value.isFixedHeader;
|
||||
});
|
||||
// 设置主内容区的高度
|
||||
const setMainHeight = computed(() => {
|
||||
if (isTagsViewCurrenFull.value) return '0px';
|
||||
const { isTagsview, layout } = themeConfig.value;
|
||||
if (isTagsview && layout !== 'classic') return '85px';
|
||||
else return '51px';
|
||||
});
|
||||
// 页面加载前
|
||||
onMounted(() => {
|
||||
NextLoading.done(600);
|
||||
});
|
||||
return {
|
||||
layoutMainScrollbarRef,
|
||||
isFooter,
|
||||
isFixedHeader,
|
||||
setMainHeight,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,38 +0,0 @@
|
||||
<!--
|
||||
* @Author: '490912587@qq.com' '490912587@qq.com'
|
||||
* @Date: 2022-11-17 09:19:20
|
||||
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
|
||||
* @LastEditTime: 2022-11-22 13:43:03
|
||||
* @FilePath: \frontend\src\layout\footer\index.vue
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
-->
|
||||
<template>
|
||||
<div class="layout-footer pb15">
|
||||
<div class="layout-footer-warp">
|
||||
<div>TsPlatform,Made by 490912587@qq.com with ❤️</div>
|
||||
<div class="mt5">甜蜜蜜版权所有</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutFooter',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
&-warp {
|
||||
margin: auto;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-align: center;
|
||||
animation: error-num 0.3s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,54 +0,0 @@
|
||||
<template>
|
||||
<component :is="themeConfig.layout" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { onBeforeMount, onUnmounted, defineComponent, defineAsyncComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layout',
|
||||
components: {
|
||||
defaults: defineAsyncComponent(() => import('/@/layout/main/defaults.vue')),
|
||||
classic: defineAsyncComponent(() => import('/@/layout/main/classic.vue')),
|
||||
transverse: defineAsyncComponent(() => import('/@/layout/main/transverse.vue')),
|
||||
columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
|
||||
},
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 窗口大小改变时(适配移动端)
|
||||
const onLayoutResize = () => {
|
||||
if (!Local.get('oldLayout')) Local.set('oldLayout', themeConfig.value.layout);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) {
|
||||
themeConfig.value.isCollapse = false;
|
||||
mittBus.emit('layoutMobileResize', {
|
||||
layout: 'defaults',
|
||||
clientWidth,
|
||||
});
|
||||
} else {
|
||||
mittBus.emit('layoutMobileResize', {
|
||||
layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
|
||||
clientWidth,
|
||||
});
|
||||
}
|
||||
};
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
onLayoutResize();
|
||||
window.addEventListener('resize', onLayoutResize);
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onLayoutResize);
|
||||
});
|
||||
return {
|
||||
themeConfig,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<el-container class="layout-container flex-center">
|
||||
<LayoutHeader />
|
||||
<el-container class="layout-mian-height-50">
|
||||
<LayoutAside />
|
||||
<div class="flex-center layout-backtop">
|
||||
<LayoutTagsView v-if="isTagsview" />
|
||||
<LayoutMain ref="layoutMainRef" />
|
||||
</div>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, computed, ref, watch, nextTick, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutClassic',
|
||||
components: {
|
||||
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
|
||||
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
|
||||
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
|
||||
LayoutTagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
|
||||
},
|
||||
setup() {
|
||||
const layoutMainRef = ref<any>('');
|
||||
const route = useRoute();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 判断是否显示 tasgview
|
||||
const isTagsview = computed(() => {
|
||||
return themeConfig.value.isTagsview;
|
||||
});
|
||||
// 重置滚动条高度,更新子级 scrollbar
|
||||
const updateScrollbar = () => {
|
||||
layoutMainRef.value.layoutMainScrollbarRef.update();
|
||||
};
|
||||
// 重置滚动条高度,由于组件是异步引入的
|
||||
const initScrollBarHeight = () => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
updateScrollbar();
|
||||
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
// 监听路由的变化,切换界面时,滚动条置顶
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
initScrollBarHeight();
|
||||
}
|
||||
);
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(
|
||||
themeConfig,
|
||||
() => {
|
||||
updateScrollbar();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initScrollBarHeight();
|
||||
});
|
||||
return {
|
||||
layoutMainRef,
|
||||
isTagsview,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<ColumnsAside />
|
||||
<el-container class="layout-columns-warp layout-container-view h100">
|
||||
<LayoutAside />
|
||||
<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
|
||||
<LayoutHeader />
|
||||
<LayoutMain ref="layoutMainRef" />
|
||||
</el-scrollbar>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutColumns',
|
||||
components: {
|
||||
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
|
||||
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
|
||||
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
|
||||
ColumnsAside: defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue')),
|
||||
},
|
||||
setup() {
|
||||
const layoutScrollbarRef = ref<any>('');
|
||||
const layoutMainRef = ref<any>('');
|
||||
const route = useRoute();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 重置滚动条高度
|
||||
const updateScrollbar = () => {
|
||||
// 更新父级 scrollbar
|
||||
layoutScrollbarRef.value.update();
|
||||
// 更新子级 scrollbar
|
||||
layoutMainRef.value.layoutMainScrollbarRef.update();
|
||||
};
|
||||
// 重置滚动条高度,由于组件是异步引入的
|
||||
const initScrollBarHeight = () => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
updateScrollbar();
|
||||
layoutScrollbarRef.value.wrap$.scrollTop = 0;
|
||||
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
// 监听路由的变化,切换界面时,滚动条置顶
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
initScrollBarHeight();
|
||||
}
|
||||
);
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(
|
||||
themeConfig,
|
||||
() => {
|
||||
updateScrollbar();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initScrollBarHeight();
|
||||
});
|
||||
return {
|
||||
layoutScrollbarRef,
|
||||
layoutMainRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<LayoutAside />
|
||||
<el-container class="layout-container-view h100">
|
||||
<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
|
||||
<LayoutHeader />
|
||||
<LayoutMain ref="layoutMainRef" />
|
||||
</el-scrollbar>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutDefaults',
|
||||
components: {
|
||||
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
|
||||
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
|
||||
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
|
||||
},
|
||||
setup() {
|
||||
const layoutScrollbarRef = ref<any>('');
|
||||
const layoutMainRef = ref<any>('');
|
||||
const route = useRoute();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 重置滚动条高度
|
||||
const updateScrollbar = () => {
|
||||
// 更新父级 scrollbar
|
||||
layoutScrollbarRef.value.update();
|
||||
// 更新子级 scrollbar
|
||||
layoutMainRef.value.layoutMainScrollbarRef.update();
|
||||
};
|
||||
// 重置滚动条高度,由于组件是异步引入的
|
||||
const initScrollBarHeight = () => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
updateScrollbar();
|
||||
layoutScrollbarRef.value.wrap$.scrollTop = 0;
|
||||
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
// 监听路由的变化,切换界面时,滚动条置顶
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
initScrollBarHeight();
|
||||
}
|
||||
);
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(
|
||||
themeConfig,
|
||||
() => {
|
||||
updateScrollbar();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initScrollBarHeight();
|
||||
NextLoading.done(600);
|
||||
});
|
||||
return {
|
||||
layoutScrollbarRef,
|
||||
layoutMainRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<el-container class="layout-container flex-center layout-backtop">
|
||||
<LayoutHeader />
|
||||
<LayoutMain ref="layoutMainRef" />
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, ref, watch, nextTick, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutTransverse',
|
||||
components: {
|
||||
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
|
||||
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
|
||||
},
|
||||
setup() {
|
||||
const layoutMainRef = ref<any>('');
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
// 重置滚动条高度,更新子级 scrollbar
|
||||
const updateScrollbar = () => {
|
||||
layoutMainRef.value.layoutMainScrollbarRef.update();
|
||||
};
|
||||
// 重置滚动条高度,由于组件是异步引入的
|
||||
const initScrollBarHeight = () => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
updateScrollbar();
|
||||
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
// 监听路由的变化,切换界面时,滚动条置顶
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
initScrollBarHeight();
|
||||
}
|
||||
);
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(
|
||||
themeConfig,
|
||||
() => {
|
||||
updateScrollbar();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initScrollBarHeight();
|
||||
});
|
||||
return {
|
||||
layoutMainRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,163 +0,0 @@
|
||||
<template>
|
||||
<div v-if="isShowBreadcrumb" class="layout-navbars-breadcrumb">
|
||||
<SvgIcon
|
||||
class="layout-navbars-breadcrumb-icon"
|
||||
:name="themeConfig.isCollapse ? 'ele-Expand' : 'ele-Fold'"
|
||||
:size="16"
|
||||
@click="onThemeConfigChange"
|
||||
/>
|
||||
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="!v.meta.tagsViewName ? v.meta.title : v.meta.tagsViewName">
|
||||
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
|
||||
<div v-if="!v.meta.tagsViewName">{{ $t(v.meta.title) }}</div>
|
||||
<div v-else>{{ v.meta.tagsViewName }}</div>
|
||||
</span>
|
||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import other from '/@/utils/other';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface BreadcrumbState {
|
||||
breadcrumbList: Array<any>;
|
||||
routeSplit: Array<string>;
|
||||
routeSplitFirst: string;
|
||||
routeSplitIndex: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumb',
|
||||
setup() {
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive<BreadcrumbState>({
|
||||
breadcrumbList: [],
|
||||
routeSplit: [],
|
||||
routeSplitFirst: '',
|
||||
routeSplitIndex: 1,
|
||||
});
|
||||
// 动态设置经典、横向布局不显示
|
||||
const isShowBreadcrumb = computed(() => {
|
||||
initRouteSplit(route.path);
|
||||
const { layout, isBreadcrumb } = themeConfig.value;
|
||||
if (layout === 'classic' || layout === 'transverse') return false;
|
||||
else return isBreadcrumb ? true : false;
|
||||
});
|
||||
// 面包屑点击时
|
||||
const onBreadcrumbClick = (v: any) => {
|
||||
const { redirect, path } = v;
|
||||
if (redirect) router.push(redirect);
|
||||
else router.push(path);
|
||||
};
|
||||
// 展开/收起左侧菜单点击
|
||||
const onThemeConfigChange = () => {
|
||||
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 存储布局配置
|
||||
const setLocalThemeConfig = () => {
|
||||
Local.remove('themeConfig');
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
};
|
||||
// 处理面包屑数据
|
||||
const getBreadcrumbList = (arr: Array<string>) => {
|
||||
arr.forEach((item: any) => {
|
||||
state.routeSplit.forEach((v: any, k: number, arrs: any) => {
|
||||
if (state.routeSplitFirst === item.path) {
|
||||
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
|
||||
state.breadcrumbList.push(item);
|
||||
state.routeSplitIndex++;
|
||||
if (item.children) getBreadcrumbList(item.children);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
// 当前路由字符串切割成数组,并删除第一项空内容
|
||||
const initRouteSplit = (path: string) => {
|
||||
if (!themeConfig.value.isBreadcrumb) return false;
|
||||
state.breadcrumbList = [routesList.value[0]];
|
||||
state.routeSplit = path.split('/');
|
||||
state.routeSplit.shift();
|
||||
state.routeSplitFirst = `/${state.routeSplit[0]}`;
|
||||
state.routeSplitIndex = 1;
|
||||
getBreadcrumbList(routesList.value);
|
||||
if (route.name === 'home' || (route.name === 'notFound' && state.breadcrumbList.length > 1)) state.breadcrumbList.shift();
|
||||
if (state.breadcrumbList.length > 0) state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(route);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initRouteSplit(route.path);
|
||||
});
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
initRouteSplit(to.path);
|
||||
});
|
||||
return {
|
||||
onThemeConfigChange,
|
||||
isShowBreadcrumb,
|
||||
themeConfig,
|
||||
onBreadcrumbClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-breadcrumb {
|
||||
flex: 1;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.layout-navbars-breadcrumb-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
color: var(--next-bg-topBarColor);
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.layout-navbars-breadcrumb-span {
|
||||
display: flex;
|
||||
opacity: 0.7;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
.layout-navbars-breadcrumb-iconfont {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
:deep(.el-breadcrumb__separator) {
|
||||
opacity: 0.7;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
:deep(.el-breadcrumb__inner a, .el-breadcrumb__inner.is-link) {
|
||||
font-weight: unset !important;
|
||||
color: var(--next-bg-topBarColor);
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb-index">
|
||||
<Logo v-if="setIsShowLogo" />
|
||||
<Breadcrumb />
|
||||
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
|
||||
<User />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, computed, reactive, toRefs, onMounted, onUnmounted, defineComponent } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface IndexState {
|
||||
menuList: object[];
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbIndex',
|
||||
components: {
|
||||
Breadcrumb: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue')),
|
||||
User: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue')),
|
||||
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
|
||||
Horizontal: defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue')),
|
||||
},
|
||||
setup() {
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const route = useRoute();
|
||||
const state = reactive<IndexState>({
|
||||
menuList: [],
|
||||
});
|
||||
// 设置 logo 显示/隐藏
|
||||
const setIsShowLogo = computed(() => {
|
||||
let { isShowLogo, layout } = themeConfig.value;
|
||||
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
|
||||
});
|
||||
// 设置是否显示横向导航菜单
|
||||
const isLayoutTransverse = computed(() => {
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
|
||||
});
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
|
||||
const resData = setSendClassicChildren(route.path);
|
||||
mittBus.emit('setSendClassicChildren', resData);
|
||||
} else {
|
||||
state.menuList = filterRoutesFun(routesList.value);
|
||||
}
|
||||
};
|
||||
// 设置了分割菜单时,删除底下 children
|
||||
const delClassicChildren = (arr: Array<object>) => {
|
||||
arr.map((v: any) => {
|
||||
if (v.children) delete v.children;
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
item = Object.assign({}, item);
|
||||
if (item.children) item.children = filterRoutesFun(item.children);
|
||||
return item;
|
||||
});
|
||||
};
|
||||
// 传送当前子级数据到菜单中
|
||||
const setSendClassicChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
filterRoutesFun(routesList.value).map((v, k) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
currentData['children'] = [{ ...v }];
|
||||
if (v.children) currentData['children'] = v.children;
|
||||
}
|
||||
});
|
||||
return currentData;
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
setFilterRoutes();
|
||||
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
setFilterRoutes();
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
|
||||
});
|
||||
return {
|
||||
setIsShowLogo,
|
||||
isLayoutTransverse,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-breadcrumb-index {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--next-bg-topBar);
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
}
|
||||
</style>
|
||||
@ -1,143 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-search-dialog">
|
||||
<el-dialog v-model="isShowSearch" destroy-on-close :show-close="false">
|
||||
<template #footer>
|
||||
<el-autocomplete
|
||||
v-model="menuQuery"
|
||||
:fetch-suggestions="menuSearch"
|
||||
:placeholder="$t('message.user.searchPlaceholder')"
|
||||
ref="layoutMenuAutocompleteRef"
|
||||
@select="onHandleSelect"
|
||||
:fit-input-width="true"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon">
|
||||
<ele-Search />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default="{ item }">
|
||||
<div>
|
||||
<SvgIcon :name="item.meta.icon" class="mr5" />
|
||||
{{ $t(item.meta.title) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface SearchState {
|
||||
isShowSearch: boolean;
|
||||
menuQuery: string;
|
||||
tagsViewList: object[];
|
||||
}
|
||||
interface Restaurant {
|
||||
path: string;
|
||||
meta: {
|
||||
title: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbSearch',
|
||||
setup() {
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
|
||||
const layoutMenuAutocompleteRef = ref();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const state = reactive<SearchState>({
|
||||
isShowSearch: false,
|
||||
menuQuery: '',
|
||||
tagsViewList: [],
|
||||
});
|
||||
// 搜索弹窗打开
|
||||
const openSearch = () => {
|
||||
state.menuQuery = '';
|
||||
state.isShowSearch = true;
|
||||
initTageView();
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
layoutMenuAutocompleteRef.value.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
// 搜索弹窗关闭
|
||||
const closeSearch = () => {
|
||||
state.isShowSearch = false;
|
||||
};
|
||||
// 菜单搜索数据过滤
|
||||
const menuSearch = (queryString: string, cb: Function) => {
|
||||
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
|
||||
cb(results);
|
||||
};
|
||||
// 菜单搜索过滤
|
||||
const createFilter: any = (queryString: string) => {
|
||||
return (restaurant: Restaurant) => {
|
||||
return (
|
||||
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
|
||||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
|
||||
t(restaurant.meta.title).indexOf(queryString.toLowerCase()) > -1
|
||||
);
|
||||
};
|
||||
};
|
||||
// 初始化菜单数据
|
||||
const initTageView = () => {
|
||||
if (state.tagsViewList.length > 0) return false;
|
||||
tagsViewRoutes.value.map((v: any) => {
|
||||
if (!v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
});
|
||||
};
|
||||
// 当前菜单选中时
|
||||
const onHandleSelect = (item: any) => {
|
||||
let { path, redirect } = item;
|
||||
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
|
||||
else if (redirect) router.push(redirect);
|
||||
else router.push(path);
|
||||
closeSearch();
|
||||
};
|
||||
return {
|
||||
layoutMenuAutocompleteRef,
|
||||
openSearch,
|
||||
closeSearch,
|
||||
menuSearch,
|
||||
onHandleSelect,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-search-dialog {
|
||||
position: relative;
|
||||
:deep(.el-dialog) {
|
||||
.el-dialog__header,
|
||||
.el-dialog__body {
|
||||
display: none;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: -53vh;
|
||||
}
|
||||
}
|
||||
:deep(.el-autocomplete) {
|
||||
width: 560px;
|
||||
position: absolute;
|
||||
top: 150px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,289 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb-user pr15" :style="{ flex: layoutUserFlexNum }">
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="large" :disabled="disabledSize === 'large'">{{
|
||||
$t('message.user.dropdownLarge')
|
||||
}}</el-dropdown-item>
|
||||
<el-dropdown-item command="default" :disabled="disabledSize === 'default'">{{
|
||||
$t('message.user.dropdownDefault')
|
||||
}}</el-dropdown-item>
|
||||
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">{{
|
||||
$t('message.user.dropdownSmall')
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'"
|
||||
:title="$t('message.user.title1')"></i>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
|
||||
<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
|
||||
<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||
<el-icon :title="$t('message.user.title2')">
|
||||
<ele-Search />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
|
||||
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
<el-badge :is-dot="true">
|
||||
<el-icon :title="$t('message.user.title4')">
|
||||
<ele-Bell />
|
||||
</el-icon>
|
||||
</el-badge>
|
||||
</template>
|
||||
<template #default>
|
||||
<UserNews />
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
|
||||
<i class="iconfont" :title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
|
||||
:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"></i>
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link">
|
||||
<img :src="userInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
{{ userInfos.userName === '' ? 'common' : userInfos.userName }}
|
||||
<el-icon class="el-icon--right">
|
||||
<ele-ArrowDown />
|
||||
</el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
|
||||
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
|
||||
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<Search ref="searchRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, ref, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage, ElNotification } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
import { signOut } from '/@/api/login';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbUser',
|
||||
components: {
|
||||
UserNews: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue')),
|
||||
Search: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue')),
|
||||
},
|
||||
setup() {
|
||||
const { locale, t } = useI18n();
|
||||
const router = useRouter();
|
||||
const stores = useUserInfo();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const searchRef = ref();
|
||||
const state = reactive({
|
||||
isScreenfull: false,
|
||||
disabledI18n: 'zh-cn',
|
||||
disabledSize: 'large',
|
||||
});
|
||||
// 设置分割样式
|
||||
const layoutUserFlexNum = computed(() => {
|
||||
let num: string | number = '';
|
||||
const { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
const layoutArr: string[] = ['defaults', 'columns'];
|
||||
if (layoutArr.includes(layout) || (layout === 'classic' && !isClassicSplitMenu)) num = '1';
|
||||
else num = '';
|
||||
return num;
|
||||
});
|
||||
// 全屏点击时
|
||||
const onScreenfullClick = () => {
|
||||
if (!screenfull.isEnabled) {
|
||||
ElMessage.warning('暂不不支持全屏');
|
||||
return false;
|
||||
}
|
||||
screenfull.toggle();
|
||||
screenfull.on('change', () => {
|
||||
if (screenfull.isFullscreen) state.isScreenfull = true;
|
||||
else state.isScreenfull = false;
|
||||
});
|
||||
};
|
||||
// 布局配置 icon 点击时
|
||||
const onLayoutSetingClick = () => {
|
||||
mittBus.emit('openSetingsDrawer');
|
||||
};
|
||||
// 下拉菜单点击时
|
||||
const onHandleCommandClick = (path: string) => {
|
||||
if (path === 'logOut') {
|
||||
ElMessageBox({
|
||||
closeOnClickModal: false,
|
||||
closeOnPressEscape: false,
|
||||
title: t('message.user.logOutTitle'),
|
||||
message: t('message.user.logOutMessage'),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t('message.user.logOutConfirm'),
|
||||
cancelButtonText: t('message.user.logOutCancel'),
|
||||
buttonSize: 'default',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
instance.confirmButtonText = t('message.user.logOutExit');
|
||||
setTimeout(() => {
|
||||
done();
|
||||
setTimeout(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
}, 300);
|
||||
}, 700);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(async () => {
|
||||
const result = await signOut();
|
||||
if (result.data.code === 200) {
|
||||
ElNotification({
|
||||
message: result.data.msg,
|
||||
type: 'success',
|
||||
duration: 500,
|
||||
onClose() {
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => { });
|
||||
} else if (path === 'wareHouse') {
|
||||
window.open('https://gitee.com/tmm-top/vue-next-admin-ts');
|
||||
} else {
|
||||
router.push(path);
|
||||
}
|
||||
};
|
||||
// 菜单搜索点击
|
||||
const onSearchClick = () => {
|
||||
searchRef.value.openSearch();
|
||||
};
|
||||
// 组件大小改变
|
||||
const onComponentSizeChange = (size: string) => {
|
||||
Local.remove('themeConfig');
|
||||
themeConfig.value.globalComponentSize = size;
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
initI18nOrSize('globalComponentSize', 'disabledSize');
|
||||
window.location.reload();
|
||||
};
|
||||
// 语言切换
|
||||
const onLanguageChange = (lang: string) => {
|
||||
Local.remove('themeConfig');
|
||||
themeConfig.value.globalI18n = lang;
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
locale.value = lang;
|
||||
other.useTitle();
|
||||
initI18nOrSize('globalI18n', 'disabledI18n');
|
||||
};
|
||||
// 初始化组件大小/i18n
|
||||
const initI18nOrSize = (value: string, attr: string) => {
|
||||
(<any>state)[attr] = Local.get('themeConfig')[value];
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
if (Local.get('themeConfig')) {
|
||||
initI18nOrSize('globalComponentSize', 'disabledSize');
|
||||
initI18nOrSize('globalI18n', 'disabledI18n');
|
||||
}
|
||||
});
|
||||
return {
|
||||
userInfos,
|
||||
onLayoutSetingClick,
|
||||
onHandleCommandClick,
|
||||
onScreenfullClick,
|
||||
onSearchClick,
|
||||
onComponentSizeChange,
|
||||
onLanguageChange,
|
||||
searchRef,
|
||||
layoutUserFlexNum,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-breadcrumb-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
&-link {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&-photo {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
color: var(--next-bg-topBarColor);
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: var(--next-color-user-hover);
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-dropdown) {
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
|
||||
:deep(.el-badge) {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-navbars-container">
|
||||
<BreadcrumbIndex />
|
||||
<TagsView v-if="setShowTagsView" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, computed, defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutNavBars',
|
||||
components: {
|
||||
BreadcrumbIndex: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue')),
|
||||
TagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
|
||||
},
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 是否显示 tagsView
|
||||
const setShowTagsView = computed(() => {
|
||||
let { layout, isTagsview } = themeConfig.value;
|
||||
return layout !== 'classic' && isTagsview;
|
||||
});
|
||||
return {
|
||||
setShowTagsView,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,138 +0,0 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-center">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
||||
role="tooltip"
|
||||
data-popper-placement="bottom"
|
||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||
:key="Math.random()"
|
||||
v-show="isShow"
|
||||
>
|
||||
<ul class="el-dropdown-menu">
|
||||
<template v-for="(v, k) in dropdownList">
|
||||
<li
|
||||
class="el-dropdown-menu__item"
|
||||
aria-disabled="false"
|
||||
tabindex="-1"
|
||||
:key="k"
|
||||
v-if="!v.affix"
|
||||
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
|
||||
>
|
||||
<SvgIcon :name="v.icon" />
|
||||
<span>{{ $t(v.txt) }}</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<div class="el-popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutTagsViewContextmenu',
|
||||
props: {
|
||||
dropdown: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
isShow: false,
|
||||
dropdownList: [
|
||||
{ contextMenuClickId: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'ele-RefreshRight' },
|
||||
{ contextMenuClickId: 1, txt: 'message.tagsView.close', affix: false, icon: 'ele-Close' },
|
||||
{ contextMenuClickId: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'ele-CircleClose' },
|
||||
{ contextMenuClickId: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'ele-FolderDelete' },
|
||||
{
|
||||
contextMenuClickId: 4,
|
||||
txt: 'message.tagsView.fullscreen',
|
||||
affix: false,
|
||||
icon: 'iconfont icon-fullscreen',
|
||||
},
|
||||
],
|
||||
item: {},
|
||||
arrowLeft: 10,
|
||||
});
|
||||
// 父级传过来的坐标 x,y 值
|
||||
const dropdowns = computed(() => {
|
||||
// 117 为 `Dropdown 下拉菜单` 的宽度
|
||||
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
|
||||
return {
|
||||
x: document.documentElement.clientWidth - 117 - 5,
|
||||
y: props.dropdown.y,
|
||||
};
|
||||
} else {
|
||||
return props.dropdown;
|
||||
}
|
||||
});
|
||||
// 当前项菜单点击
|
||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
||||
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
|
||||
};
|
||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||
const openContextmenu = (item: any) => {
|
||||
state.item = item;
|
||||
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
|
||||
closeContextmenu();
|
||||
setTimeout(() => {
|
||||
state.isShow = true;
|
||||
}, 10);
|
||||
};
|
||||
// 关闭右键菜单
|
||||
const closeContextmenu = () => {
|
||||
state.isShow = false;
|
||||
};
|
||||
// 监听页面监听进行右键菜单的关闭
|
||||
onMounted(() => {
|
||||
document.body.addEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 页面卸载时,移除右键菜单监听事件
|
||||
onUnmounted(() => {
|
||||
document.body.removeEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 监听下拉菜单位置
|
||||
watch(
|
||||
() => props.dropdown,
|
||||
({ x }) => {
|
||||
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||
else state.arrowLeft = 10;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
dropdowns,
|
||||
openContextmenu,
|
||||
closeContextmenu,
|
||||
onCurrentContextmenuClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-contextmenu {
|
||||
transform-origin: center top;
|
||||
z-index: 2190;
|
||||
position: fixed;
|
||||
.el-dropdown-menu__item {
|
||||
font-size: 12px !important;
|
||||
white-space: nowrap;
|
||||
i {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,769 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
|
||||
<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
|
||||
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
|
||||
<li
|
||||
v-for="(v, k) in tagsViewList"
|
||||
:key="k"
|
||||
class="layout-navbars-tagsview-ul-li"
|
||||
:data-url="v.url"
|
||||
:class="{ 'is-active': isActive(v) }"
|
||||
@contextmenu.prevent="onContextmenu(v, $event)"
|
||||
@mousedown="onMousedownMenu(v, $event)"
|
||||
@click="onTagsClick(v, k)"
|
||||
:ref="
|
||||
(el) => {
|
||||
if (el) tagsRefs[k] = el;
|
||||
}
|
||||
"
|
||||
>
|
||||
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont" v-if="isActive(v)"></i>
|
||||
<SvgIcon :name="v.meta.icon" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon" class="pr5" />
|
||||
<span>{{ setTagsViewNameI18n(v) }}</span>
|
||||
<template v-if="isActive(v)">
|
||||
<SvgIcon
|
||||
name="ele-RefreshRight"
|
||||
class="ml5 layout-navbars-tagsview-ul-li-refresh"
|
||||
@click.stop="refreshCurrentTagsView($route.fullPath)"
|
||||
/>
|
||||
<SvgIcon
|
||||
name="ele-Close"
|
||||
class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
||||
v-if="!v.meta.isAffix"
|
||||
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
|
||||
/>
|
||||
</template>
|
||||
<SvgIcon
|
||||
name="ele-Close"
|
||||
class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
||||
v-if="!v.meta.isAffix"
|
||||
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
<Contextmenu :dropdown="dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
toRefs,
|
||||
reactive,
|
||||
onMounted,
|
||||
computed,
|
||||
ref,
|
||||
nextTick,
|
||||
onBeforeUpdate,
|
||||
onBeforeMount,
|
||||
onUnmounted,
|
||||
watch,
|
||||
defineComponent,
|
||||
} from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import Sortable from 'sortablejs';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { isObjectValueEqual } from '/@/utils/arrayOperation';
|
||||
import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface TagsViewState {
|
||||
routeActive: string;
|
||||
routePath: string | unknown;
|
||||
dropdown: {
|
||||
x: string | number;
|
||||
y: string | number;
|
||||
};
|
||||
sortable: any;
|
||||
tagsRefsIndex: number;
|
||||
tagsViewList: any[];
|
||||
tagsViewRoutesList: any[];
|
||||
}
|
||||
interface RouteParams {
|
||||
path: string;
|
||||
url: string;
|
||||
query: object;
|
||||
params: object;
|
||||
}
|
||||
interface CurrentContextmenu {
|
||||
meta: {
|
||||
isDynamic: boolean;
|
||||
};
|
||||
params: any;
|
||||
query: any;
|
||||
path: string;
|
||||
contextMenuClickId: string | number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutTagsView',
|
||||
components: {
|
||||
Contextmenu: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/contextmenu.vue')),
|
||||
},
|
||||
setup() {
|
||||
const tagsRefs = ref<any[]>([]);
|
||||
const scrollbarRef = ref();
|
||||
const contextmenuRef = ref();
|
||||
const tagsUlRef = ref();
|
||||
const stores = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
|
||||
const storesKeepALiveNames = useKeepALiveNames();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive<TagsViewState>({
|
||||
routeActive: '',
|
||||
routePath: route.path,
|
||||
dropdown: { x: '', y: '' },
|
||||
sortable: '',
|
||||
tagsRefsIndex: 0,
|
||||
tagsViewList: [],
|
||||
tagsViewRoutesList: [],
|
||||
});
|
||||
// 动态设置 tagsView 风格样式
|
||||
const setTagsStyle = computed(() => {
|
||||
return themeConfig.value.tagsStyle;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return themeConfig.value;
|
||||
});
|
||||
// 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
|
||||
const setTagsViewNameI18n = computed(() => {
|
||||
return (v: any) => {
|
||||
return other.setTagsViewNameI18n(v);
|
||||
};
|
||||
});
|
||||
// 设置 tagsView 高亮
|
||||
const isActive = (v: RouteParams) => {
|
||||
if (getThemeConfig.value.isShareTagsView) {
|
||||
return v.path === state.routePath;
|
||||
} else {
|
||||
if ((v.query && Object.keys(v.query).length) || (v.params && Object.keys(v.params).length)) {
|
||||
// 普通传参
|
||||
return v.url ? v.url === state.routeActive : v.path === state.routeActive;
|
||||
} else {
|
||||
// 通过 name 传参,params 取值,刷新页面参数消失
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I51RS9
|
||||
return v.path === state.routePath;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
||||
Session.set('tagsViewList', tagsViewList);
|
||||
};
|
||||
// 获取 vuex 中的 tagsViewRoutes 列表
|
||||
const getTagsViewRoutes = async () => {
|
||||
state.routeActive = await setTagsViewHighlight(route);
|
||||
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList = tagsViewRoutes.value;
|
||||
initTagsView();
|
||||
};
|
||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||
const initTagsView = async () => {
|
||||
if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
|
||||
state.tagsViewList = await Session.get('tagsViewList');
|
||||
} else {
|
||||
await state.tagsViewRoutesList.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
storesKeepALiveNames.addCachedView(v);
|
||||
}
|
||||
});
|
||||
await addTagsView(route.path, route);
|
||||
}
|
||||
// 初始化当前元素(li)的下标
|
||||
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
|
||||
};
|
||||
// 处理可开启多标签详情,单标签详情(动态路由(xxx/:id/:name"),普通路由处理)
|
||||
const solveAddTagsView = async (path: string, to?: any) => {
|
||||
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
|
||||
let current = state.tagsViewList.filter(
|
||||
(v: any) =>
|
||||
v.path === isDynamicPath &&
|
||||
isObjectValueEqual(
|
||||
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
|
||||
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
|
||||
)
|
||||
);
|
||||
if (current.length <= 0) {
|
||||
// 防止:Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
|
||||
let findItem = state.tagsViewRoutesList.find((v: any) => v.path === isDynamicPath);
|
||||
if (!findItem) return false;
|
||||
if (findItem.meta.isAffix) return false;
|
||||
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
|
||||
to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
|
||||
findItem.url = setTagsViewHighlight(findItem);
|
||||
state.tagsViewList.push({ ...findItem });
|
||||
await storesKeepALiveNames.addCachedView(findItem);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
};
|
||||
// 处理单标签时,第二次的值未覆盖第一次的 tagsViewList 值(Session Storage)
|
||||
const singleAddTagsView = (path: string, to?: any) => {
|
||||
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
|
||||
state.tagsViewList.forEach((v) => {
|
||||
if (
|
||||
v.path === isDynamicPath &&
|
||||
!isObjectValueEqual(
|
||||
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
|
||||
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
|
||||
)
|
||||
) {
|
||||
to.meta.isDynamic ? (v.params = to.params) : (v.query = to.query);
|
||||
v.url = setTagsViewHighlight(v);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
});
|
||||
};
|
||||
// 1、添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中(可开启多标签详情,单标签详情)
|
||||
const addTagsView = (path: string, to?: any) => {
|
||||
// 防止拿取不到路由信息
|
||||
nextTick(async () => {
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
let item: any = '';
|
||||
if (to && to.meta.isDynamic) {
|
||||
// 动态路由(xxx/:id/:name"):参数不同,开启多个 tagsview
|
||||
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
|
||||
else await singleAddTagsView(path, to);
|
||||
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) {
|
||||
// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
return false;
|
||||
}
|
||||
item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath);
|
||||
} else {
|
||||
// 普通路由:参数不同,开启多个 tagsview
|
||||
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
|
||||
else await singleAddTagsView(path, to);
|
||||
if (state.tagsViewList.some((v: any) => v.path === path)) {
|
||||
// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
return false;
|
||||
}
|
||||
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
|
||||
}
|
||||
if (!item) return false;
|
||||
if (item.meta.isLink && !item.meta.isIframe) return false;
|
||||
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
|
||||
else item.query = to?.query ? to?.query : route.query;
|
||||
item.url = setTagsViewHighlight(item);
|
||||
await storesKeepALiveNames.addCachedView(item);
|
||||
await state.tagsViewList.push({ ...item });
|
||||
await addBrowserSetSession(state.tagsViewList);
|
||||
});
|
||||
};
|
||||
// 2、刷新当前 tagsView:
|
||||
const refreshCurrentTagsView = async (fullPath: string) => {
|
||||
const decodeURIPath = decodeURI(fullPath);
|
||||
let item: any = {};
|
||||
state.tagsViewList.forEach((v: any) => {
|
||||
v.transUrl = transUrlParams(v);
|
||||
if (v.transUrl) {
|
||||
if (v.transUrl === transUrlParams(v)) item = v;
|
||||
} else {
|
||||
if (v.path === decodeURIPath) item = v;
|
||||
}
|
||||
});
|
||||
if (!item) return false;
|
||||
await storesKeepALiveNames.delCachedView(item);
|
||||
mittBus.emit('onTagsViewRefreshRouterView', fullPath);
|
||||
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
|
||||
};
|
||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
||||
const closeCurrentTagsView = (path: string) => {
|
||||
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
||||
if (!v.meta.isAffix) {
|
||||
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
|
||||
storesKeepALiveNames.delCachedView(v);
|
||||
state.tagsViewList.splice(k, 1);
|
||||
setTimeout(() => {
|
||||
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
|
||||
// 最后一个且高亮时
|
||||
if (arr[arr.length - 1].meta.isDynamic) {
|
||||
// 动态路由(xxx/:id/:name")
|
||||
if (k !== arr.length) router.push({ name: arr[k].name, params: arr[k].params });
|
||||
else router.push({ name: arr[arr.length - 1].name, params: arr[arr.length - 1].params });
|
||||
} else {
|
||||
// 普通路由
|
||||
if (k !== arr.length) router.push({ path: arr[k].path, query: arr[k].query });
|
||||
else router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
|
||||
}
|
||||
} else {
|
||||
// 非最后一个且高亮时,跳转到下一个
|
||||
if (state.tagsViewList.length !== k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
|
||||
if (arr[k].meta.isDynamic) {
|
||||
// 动态路由(xxx/:id/:name")
|
||||
router.push({ name: arr[k].name, params: arr[k].params });
|
||||
} else {
|
||||
// 普通路由
|
||||
router.push({ path: arr[k].path, query: arr[k].query });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
};
|
||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
const closeOtherTagsView = (path: string) => {
|
||||
if (Session.get('tagsViewList')) {
|
||||
state.tagsViewList = [];
|
||||
Session.get('tagsViewList').map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
storesKeepALiveNames.delOthersCachedViews(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
}
|
||||
});
|
||||
addTagsView(path, route);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
};
|
||||
// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
const closeAllTagsView = () => {
|
||||
if (Session.get('tagsViewList')) {
|
||||
storesKeepALiveNames.delAllCachedViews();
|
||||
state.tagsViewList = [];
|
||||
Session.get('tagsViewList').map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
|
||||
}
|
||||
});
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
};
|
||||
// 6、开启当前页面全屏
|
||||
const openCurrenFullscreen = async (path: string) => {
|
||||
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
|
||||
if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
|
||||
else await router.push({ name: item.name, query: item.query });
|
||||
stores.setCurrenFullscreen(true);
|
||||
};
|
||||
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
|
||||
// 防止 tagsView 非当前页演示时,操作异常
|
||||
const getCurrentRouteItem = (path: string, cParams: any) => {
|
||||
const itemRoute = Session.get('tagsViewList') ? Session.get('tagsViewList') : state.tagsViewList;
|
||||
return itemRoute.find((v: any) => {
|
||||
if (
|
||||
v.path === path &&
|
||||
isObjectValueEqual(
|
||||
v.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
|
||||
cParams && Object.keys(cParams ? cParams : {}).length > 0 ? cParams : null
|
||||
)
|
||||
) {
|
||||
return v;
|
||||
} else if (v.path === path && Object.keys(cParams ? cParams : {}).length <= 0) {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 当前项右键菜单点击
|
||||
const onCurrentContextmenuClick = async (item: CurrentContextmenu) => {
|
||||
const cParams = item.meta.isDynamic ? item.params : item.query;
|
||||
if (!getCurrentRouteItem(item.path, cParams)) return ElMessage({ type: 'warning', message: '请正确输入路径及完整参数(query、params)' });
|
||||
const { path, name, params, query, meta, url } = getCurrentRouteItem(item.path, cParams);
|
||||
switch (item.contextMenuClickId) {
|
||||
case 0:
|
||||
// 刷新当前
|
||||
if (meta.isDynamic) await router.push({ name, params });
|
||||
else await router.push({ path, query });
|
||||
refreshCurrentTagsView(route.fullPath);
|
||||
break;
|
||||
case 1:
|
||||
// 关闭当前
|
||||
closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
|
||||
break;
|
||||
case 2:
|
||||
// 关闭其它
|
||||
if (meta.isDynamic) await router.push({ name, params });
|
||||
else await router.push({ path, query });
|
||||
closeOtherTagsView(path);
|
||||
break;
|
||||
case 3:
|
||||
// 关闭全部
|
||||
closeAllTagsView();
|
||||
break;
|
||||
case 4:
|
||||
// 开启当前页面全屏
|
||||
openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
|
||||
break;
|
||||
}
|
||||
};
|
||||
// 右键点击时:传 x,y 坐标值到子组件中(props)
|
||||
const onContextmenu = (v: any, e: any) => {
|
||||
const { clientX, clientY } = e;
|
||||
state.dropdown.x = clientX;
|
||||
state.dropdown.y = clientY;
|
||||
contextmenuRef.value.openContextmenu(v);
|
||||
};
|
||||
// 鼠标按下时,判断是鼠标中键就关闭当前 tasgview
|
||||
const onMousedownMenu = (v: any, e: any) => {
|
||||
if (!v.meta.isAffix && e.which === 2) {
|
||||
const item = Object.assign({}, { contextMenuClickId: 1, ...v });
|
||||
onCurrentContextmenuClick(item);
|
||||
}
|
||||
};
|
||||
// 当前的 tagsView 项点击时
|
||||
const onTagsClick = (v: any, k: number) => {
|
||||
state.tagsRefsIndex = k;
|
||||
router.push(v);
|
||||
};
|
||||
// 处理 url,地址栏链接有参数时,tagsview 右键菜单刷新功能失效问题
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
|
||||
const transUrlParams = (v: any) => {
|
||||
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
|
||||
if (!params) return '';
|
||||
let path = '';
|
||||
for (let [key, value] of Object.entries(params)) {
|
||||
if (v.meta.isDynamic) path += `/${value}`;
|
||||
else path += `&${key}=${value}`;
|
||||
}
|
||||
// 判断是否是动态路由(xxx/:id/:name")isDynamic
|
||||
return v.meta.isDynamic ? `${v.path.split(':')[0]}${path.replace(/^\//, '')}` : `${v.path}${path.replace(/^&/, '?')}`;
|
||||
};
|
||||
// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
|
||||
const setTagsViewHighlight = (v: any) => {
|
||||
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
|
||||
if (!params || Object.keys(params).length <= 0) return v.path;
|
||||
let path = '';
|
||||
for (let i in params) {
|
||||
path += params[i];
|
||||
}
|
||||
// 判断是否是动态路由(xxx/:id/:name")
|
||||
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
|
||||
};
|
||||
// 鼠标滚轮滚动
|
||||
const onHandleScroll = (e: any) => {
|
||||
scrollbarRef.value.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
|
||||
};
|
||||
// tagsView 横向滚动
|
||||
const tagsViewmoveToCurrentTag = () => {
|
||||
nextTick(() => {
|
||||
if (tagsRefs.value.length <= 0) return false;
|
||||
// 当前 li 元素
|
||||
let liDom = tagsRefs.value[state.tagsRefsIndex];
|
||||
// 当前 li 元素下标
|
||||
let liIndex = state.tagsRefsIndex;
|
||||
// 当前 ul 下 li 元素总长度
|
||||
let liLength = tagsRefs.value.length;
|
||||
// 最前 li
|
||||
let liFirst: any = tagsRefs.value[0];
|
||||
// 最后 li
|
||||
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
|
||||
// 当前滚动条的值
|
||||
let scrollRefs = scrollbarRef.value.$refs.wrap$;
|
||||
// 当前滚动条滚动宽度
|
||||
let scrollS = scrollRefs.scrollWidth;
|
||||
// 当前滚动条偏移宽度
|
||||
let offsetW = scrollRefs.offsetWidth;
|
||||
// 当前滚动条偏移距离
|
||||
let scrollL = scrollRefs.scrollLeft;
|
||||
// 上一个 tags li dom
|
||||
let liPrevTag: any = tagsRefs.value[state.tagsRefsIndex - 1];
|
||||
// 下一个 tags li dom
|
||||
let liNextTag: any = tagsRefs.value[state.tagsRefsIndex + 1];
|
||||
// 上一个 tags li dom 的偏移距离
|
||||
let beforePrevL: any = '';
|
||||
// 下一个 tags li dom 的偏移距离
|
||||
let afterNextL: any = '';
|
||||
if (liDom === liFirst) {
|
||||
// 头部
|
||||
scrollRefs.scrollLeft = 0;
|
||||
} else if (liDom === liLast) {
|
||||
// 尾部
|
||||
scrollRefs.scrollLeft = scrollS - offsetW;
|
||||
} else {
|
||||
// 非头/尾部
|
||||
if (liIndex === 0) beforePrevL = liFirst.offsetLeft - 5;
|
||||
else beforePrevL = liPrevTag?.offsetLeft - 5;
|
||||
if (liIndex === liLength) afterNextL = liLast.offsetLeft + liLast.offsetWidth + 5;
|
||||
else afterNextL = liNextTag.offsetLeft + liNextTag.offsetWidth + 5;
|
||||
if (afterNextL > scrollL + offsetW) {
|
||||
scrollRefs.scrollLeft = afterNextL - offsetW;
|
||||
} else if (beforePrevL < scrollL) {
|
||||
scrollRefs.scrollLeft = beforePrevL;
|
||||
}
|
||||
}
|
||||
// 更新滚动条,防止不出现
|
||||
scrollbarRef.value.update();
|
||||
});
|
||||
};
|
||||
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
|
||||
const getTagsRefsIndex = (path: string | unknown) => {
|
||||
nextTick(async () => {
|
||||
// await 使用该写法,防止拿取不到 tagsViewList 列表数据不完整
|
||||
let tagsViewList = await state.tagsViewList;
|
||||
state.tagsRefsIndex = tagsViewList.findIndex((v: any) => {
|
||||
if (getThemeConfig.value.isShareTagsView) {
|
||||
return v.path === path;
|
||||
} else {
|
||||
return v.url === path;
|
||||
}
|
||||
});
|
||||
// 添加初始化横向滚动条移动到对应位置
|
||||
tagsViewmoveToCurrentTag();
|
||||
});
|
||||
};
|
||||
// 设置 tagsView 可以进行拖拽
|
||||
const initSortable = async () => {
|
||||
const el = <HTMLElement>document.querySelector('.layout-navbars-tagsview-ul');
|
||||
if (!el) return false;
|
||||
state.sortable.el && state.sortable.destroy();
|
||||
state.sortable = Sortable.create(el, {
|
||||
animation: 300,
|
||||
dataIdAttr: 'data-url',
|
||||
disabled: getThemeConfig.value.isSortableTagsView ? false : true,
|
||||
onEnd: () => {
|
||||
const sortEndList: any = [];
|
||||
state.sortable.toArray().map((val: any) => {
|
||||
state.tagsViewList.map((v: any) => {
|
||||
if (v.url === val) sortEndList.push({ ...v });
|
||||
});
|
||||
});
|
||||
addBrowserSetSession(sortEndList);
|
||||
},
|
||||
});
|
||||
};
|
||||
// 拖动问题,https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
|
||||
const onSortableResize = async () => {
|
||||
await initSortable();
|
||||
if (other.isMobile()) state.sortable.el && state.sortable.destroy();
|
||||
};
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
// 初始化,防止手机端直接访问时还可以拖拽
|
||||
onSortableResize();
|
||||
// 拖动问题,https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
|
||||
window.addEventListener('resize', onSortableResize);
|
||||
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
||||
mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
|
||||
onCurrentContextmenuClick(data);
|
||||
});
|
||||
// 监听布局配置界面开启/关闭拖拽
|
||||
mittBus.on('openOrCloseSortable', () => {
|
||||
initSortable();
|
||||
});
|
||||
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
|
||||
mittBus.on('openShareTagsView', () => {
|
||||
if (getThemeConfig.value.isShareTagsView) {
|
||||
router.push('/home');
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
// 取消非本页面调用监听
|
||||
mittBus.off('onCurrentContextmenuClick', () => {});
|
||||
// 取消监听布局配置界面开启/关闭拖拽
|
||||
mittBus.off('openOrCloseSortable', () => {});
|
||||
// 取消监听布局配置开启 TagsView 共用
|
||||
mittBus.off('openShareTagsView', () => {});
|
||||
// 取消窗口 resize 监听
|
||||
window.removeEventListener('resize', onSortableResize);
|
||||
});
|
||||
// 页面更新时
|
||||
onBeforeUpdate(() => {
|
||||
tagsRefs.value = [];
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// 初始化 pinia 中的 tagsViewRoutes 列表
|
||||
getTagsViewRoutes();
|
||||
initSortable();
|
||||
});
|
||||
// 路由更新时(组件内生命钩子)
|
||||
onBeforeRouteUpdate(async (to) => {
|
||||
state.routeActive = setTagsViewHighlight(to);
|
||||
state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
|
||||
await addTagsView(to.path, to);
|
||||
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
|
||||
});
|
||||
// 监听路由的变化,动态赋值给 tagsView
|
||||
watch(
|
||||
pinia.state,
|
||||
(val) => {
|
||||
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
||||
getTagsViewRoutes();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
isActive,
|
||||
onContextmenu,
|
||||
onMousedownMenu,
|
||||
onTagsClick,
|
||||
tagsRefs,
|
||||
contextmenuRef,
|
||||
scrollbarRef,
|
||||
tagsUlRef,
|
||||
onHandleScroll,
|
||||
getThemeConfig,
|
||||
setTagsStyle,
|
||||
setTagsViewNameI18n,
|
||||
refreshCurrentTagsView,
|
||||
closeCurrentTagsView,
|
||||
onCurrentContextmenuClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-tagsview {
|
||||
background-color: var(--el-color-white);
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
&-ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
padding: 0 15px;
|
||||
&-li {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
padding: 0 15px;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
}
|
||||
&-iconfont {
|
||||
position: relative;
|
||||
left: -5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
&-icon {
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
right: -5px;
|
||||
&:hover {
|
||||
color: var(--el-color-white);
|
||||
background-color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
.layout-icon-active {
|
||||
display: block;
|
||||
}
|
||||
.layout-icon-three {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
color: var(--el-color-white);
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
transition: border-color 3s ease;
|
||||
}
|
||||
}
|
||||
// 风格4
|
||||
.tags-style-four {
|
||||
.layout-navbars-tagsview-ul-li {
|
||||
margin-right: 0 !important;
|
||||
border: none !important;
|
||||
position: relative;
|
||||
border-radius: 3px !important;
|
||||
.layout-icon-active {
|
||||
display: none;
|
||||
}
|
||||
.layout-icon-three {
|
||||
display: block;
|
||||
}
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
background: none !important;
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
// 风格5
|
||||
.tags-style-five {
|
||||
align-items: flex-end;
|
||||
.tags-style-five-svg {
|
||||
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
|
||||
12 27 15;
|
||||
}
|
||||
.layout-navbars-tagsview-ul-li {
|
||||
padding: 0 5px;
|
||||
border-width: 15px 27px 15px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
margin: 0 -15px;
|
||||
.layout-icon-active,
|
||||
.layout-navbars-tagsview-ul-li-iconfont,
|
||||
.layout-navbars-tagsview-ul-li-refresh {
|
||||
display: none;
|
||||
}
|
||||
.layout-icon-three {
|
||||
display: block;
|
||||
}
|
||||
&:hover {
|
||||
@extend .tags-style-five-svg;
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
@extend .tags-style-five-svg;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
color: var(--el-color-primary) !important;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.layout-navbars-tagsview-shadow {
|
||||
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
|
||||
}
|
||||
</style>
|
||||
@ -1,170 +0,0 @@
|
||||
<template>
|
||||
<div class="el-menu-horizontal-warp">
|
||||
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
|
||||
<el-menu router :default-active="defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
|
||||
<template v-for="val in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.meta.title) }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.meta.title) }}
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.meta.title) }}
|
||||
</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, nextTick, onBeforeMount, ref } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { verifyUrl } from '/@/utils/toolsValidate';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navMenuHorizontal',
|
||||
components: {
|
||||
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
|
||||
},
|
||||
props: {
|
||||
menuList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const elMenuHorizontalScrollRef = ref();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive({
|
||||
defaultActive: null,
|
||||
});
|
||||
// 获取父级菜单数据
|
||||
const menuLists = computed(() => {
|
||||
return <any>props.menuList;
|
||||
});
|
||||
// 设置横向滚动条可以鼠标滚轮滚动
|
||||
const onElMenuHorizontalScroll = (e: any) => {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
||||
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft + eventDelta / 4;
|
||||
};
|
||||
// 初始化数据,页面刷新时,滚动条滚动到对应位置
|
||||
const initElMenuOffsetLeft = () => {
|
||||
nextTick(() => {
|
||||
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
||||
if (!els) return false;
|
||||
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = els.offsetLeft;
|
||||
});
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
item = Object.assign({}, item);
|
||||
if (item.children) item.children = filterRoutesFun(item.children);
|
||||
return item;
|
||||
});
|
||||
};
|
||||
// 传送当前子级数据到菜单中
|
||||
const setSendClassicChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
filterRoutesFun(routesList.value).map((v, k) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
currentData['children'] = [{ ...v }];
|
||||
if (v.children) currentData['children'] = v.children;
|
||||
}
|
||||
});
|
||||
return currentData;
|
||||
};
|
||||
// 设置页面当前路由高亮
|
||||
const setCurrentRouterHighlight = (currentRoute: any) => {
|
||||
const { path, meta } = currentRoute;
|
||||
if (themeConfig.value.layout === 'classic') {
|
||||
(<any>state.defaultActive) = `/${path.split('/')[1]}`;
|
||||
} else {
|
||||
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
|
||||
if (pathSplit.length >= 4 && meta.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
|
||||
else state.defaultActive = path;
|
||||
}
|
||||
};
|
||||
// 打开外部链接
|
||||
const onALinkClick = (val: any) => {
|
||||
const { origin, pathname } = window.location;
|
||||
router.push(val.path);
|
||||
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
|
||||
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
|
||||
};
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
setCurrentRouterHighlight(route);
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initElMenuOffsetLeft();
|
||||
});
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
setCurrentRouterHighlight(to);
|
||||
// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
|
||||
}
|
||||
});
|
||||
return {
|
||||
elMenuHorizontalScrollRef,
|
||||
menuLists,
|
||||
onElMenuHorizontalScroll,
|
||||
onALinkClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-menu-horizontal-warp {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-right: 30px;
|
||||
:deep(.el-scrollbar__bar.is-vertical) {
|
||||
display: none;
|
||||
}
|
||||
:deep(a) {
|
||||
width: 100%;
|
||||
}
|
||||
.el-menu.el-menu--horizontal {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<el-menu
|
||||
router
|
||||
:default-active="defaultActive"
|
||||
background-color="transparent"
|
||||
:collapse="isCollapse"
|
||||
:unique-opened="getThemeConfig.isUniqueOpened"
|
||||
:collapse-transition="false"
|
||||
>
|
||||
<template v-for="val in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.meta.title) }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<span>{{ $t(val.meta.title) }}</span>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { verifyUrl } from '/@/utils/toolsValidate';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navMenuVertical',
|
||||
components: {
|
||||
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
|
||||
},
|
||||
props: {
|
||||
menuList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive({
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
|
||||
isCollapse: false,
|
||||
});
|
||||
// 获取父级菜单数据
|
||||
const menuLists = computed(() => {
|
||||
return <any>props.menuList;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return themeConfig.value;
|
||||
});
|
||||
// 菜单高亮(详情时,父级高亮)
|
||||
const setParentHighlight = (currentRoute: any) => {
|
||||
const { path, meta } = currentRoute;
|
||||
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
|
||||
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
|
||||
else return path;
|
||||
};
|
||||
// 打开外部链接
|
||||
const onALinkClick = (val: any) => {
|
||||
const { origin, pathname } = window.location;
|
||||
router.push(val.path);
|
||||
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
|
||||
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
|
||||
};
|
||||
// 设置菜单的收起/展开
|
||||
watch(
|
||||
themeConfig.value,
|
||||
() => {
|
||||
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
state.defaultActive = setParentHighlight(route);
|
||||
});
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
state.defaultActive = setParentHighlight(to);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||
});
|
||||
return {
|
||||
menuLists,
|
||||
getThemeConfig,
|
||||
onALinkClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-padding layout-padding-unset layout-iframe">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white">
|
||||
<transition-group :name="name" mode="out-in">
|
||||
<iframe
|
||||
:src="v.meta.isLink"
|
||||
:key="v.path"
|
||||
frameborder="0"
|
||||
height="100%"
|
||||
width="100%"
|
||||
style="position: absolute"
|
||||
:data-url="v.path"
|
||||
v-show="getRoutePath === v.path"
|
||||
ref="iframeRef"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, watch, ref, nextTick } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutIframeView',
|
||||
props: {
|
||||
refreshKey: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => 'slide-right',
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const iframeRef = ref();
|
||||
const route = useRoute();
|
||||
// 处理 list 列表,当打开时,才进行加载
|
||||
const setIframeList = computed(() => {
|
||||
return (<any>props.list).filter((v: any) => v.meta.isIframeOpen);
|
||||
});
|
||||
// 获取 iframe 当前路由 path
|
||||
const getRoutePath = computed(() => {
|
||||
return route.path;
|
||||
});
|
||||
// 监听路由变化,初始化 iframe 数据,防止多个 iframe 时,切换不生效
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(val) => {
|
||||
const item: any = props.list.find((v: any) => v.path === val);
|
||||
if (item && !item.meta.isIframeOpen) item.meta.isIframeOpen = true;
|
||||
nextTick(() => {
|
||||
if (!iframeRef.value) return false;
|
||||
iframeRef.value.forEach((v: any) => {
|
||||
if (v.dataset.url === val) {
|
||||
v.onload = () => {
|
||||
if (item && item.meta.isIframeOpen && item.meta.loading) item.meta.loading = false;
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
// 监听 iframe refreshKey 变化,用于 tagsview 右键菜单刷新
|
||||
watch(
|
||||
() => props.refreshKey,
|
||||
() => {},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
iframeRef,
|
||||
setIframeList,
|
||||
getRoutePath,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user