Compare commits
2 Commits
master
...
vue-next-a
| Author | SHA1 | Date | |
|---|---|---|---|
| 67e79a5786 | |||
| a9e1b6770a |
11
.env
@ -1,11 +0,0 @@
|
||||
# port 端口号
|
||||
VITE_PORT = 8888
|
||||
|
||||
# open 运行 npm run dev 时自动打开浏览器
|
||||
VITE_OPEN = false
|
||||
|
||||
# 打包是否开启 cdn(源文件 utils/build.ts),可自行修改
|
||||
VITE_OPEN_CDN = false
|
||||
|
||||
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
|
||||
VITE_PUBLIC_PATH = /vue-next-admin-preview/
|
||||
@ -1,5 +0,0 @@
|
||||
# 本地环境
|
||||
ENV = development
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = http://localhost:8888/
|
||||
@ -1,5 +0,0 @@
|
||||
# 线上环境
|
||||
ENV = production
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = https://lyt-top.gitee.io/vue-next-admin-preview/
|
||||
32
.gitignore
vendored
@ -1,23 +1,11 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
|
||||
# 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?
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
frontend/LICENSE
|
||||
*.zip
|
||||
498
CHANGELOG.md
@ -1,498 +0,0 @@
|
||||
# <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.34
|
||||
|
||||
`2023.04.14`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 演示界面 [列表自适应](https://lyt-top.gitee.io/vue-next-admin-preview/#/pages/listAdapt) 与 [图片懒加载](https://lyt-top.gitee.io/vue-next-admin-preview/#/pages/lazyImg)
|
||||
- 🐞 修复 `font-awesome.min.css` 在线 `cdn` 无法访问问题,[在线 cdn](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/build/#_3-cdn-%E5%8A%A0%E9%80%9F),感谢群友@Lauyping
|
||||
|
||||
## 2.4.33
|
||||
|
||||
`2023.04.11`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 `/make/tableDemo` 中添加打印、图片预览功能
|
||||
- 🐞 修复 菜单收起时(isCollapse),图标不居中问题
|
||||
- 🐞 修复 演示 `权限管理 -> 前端控制 -> 页面权限` 切换不生效,感谢群友@傲世盛唐
|
||||
- 🐞 修复 `"typescript": "5.x"` 中 `tsconfig.json`,`compilerOptions.suppressImplicitAnyIndexErrors` 弃用问题 [TypeScript/issues/51909](https://github.com/microsoft/TypeScript/issues/51909)、[suppressImplicitAnyIndexErrors](https://www.typescriptlang.org/tsconfig#suppressImplicitAnyIndexErrors)
|
||||
- 🎨 合并 [!47cdn 打包支持 pnpm,消除无 external 的报错](https://gitee.com/lyt-top/vue-next-admin/commit/8de54a844bb54468d0bdccca158bf9bcb449f270),感谢[@yujiacheng](https://gitee.com/YujiaCheng1996)
|
||||
- 🎯 优化 `layout/navBars/breadcrumb` 文件夹名称改成 `layout/navBars/topBar` 更易理解(`可全局替换`),感谢群友@傲世盛唐
|
||||
- 🎯 优化 `layout/navBars/topBar/user.vue` 组件,`UserNews` 点击消息图标触发范围,改用 [element plus Popover 气泡卡片 虚拟触发方式](https://element-plus.org/zh-CN/component/popover.html#%E8%99%9A%E6%8B%9F%E8%A7%A6%E5%8F%91),防止点击消息通知背景色时不触发 `Popover` 弹出框
|
||||
|
||||
## 2.4.32
|
||||
|
||||
💔💔💔 图片不显示问题(README.md、演示中使用的图片,[vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images)),通过网站 [https://www.hd-r.cn/](https://www.hd-r.cn/) 转在线链接,如若侵权请联系作者 qq:1105290566
|
||||
|
||||
`2023.03.26`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 [关于开发环境 sourceMap 的问题](https://gitee.com/lyt-top/vue-next-admin/issues/I6DNDQ),感谢[@XiaoSongJiang](https://gitee.com/XiaoSongJiang)
|
||||
- 🐞 修复 打包提示 `[@vue/compiler-sfc] ::v-deep usage as a combinator has been deprecated. Use :deep(<inner-selector>) instead.`,不能使用 `:deep {}`,而应使用 `:deep() {}`
|
||||
- 🎨 合并 [feat: 一级菜单重定向为空,分栏模式下,点击一次菜单时现在会切换子菜单列表,而不是打开空白页](https://gitee.com/lyt-top/vue-next-admin/commit/a91f84e3a1a86d8d303a5b46171622913d9d0737),感谢[@写意](https://gitee.com/xjj_0906)
|
||||
- 🎯 优化 经典布局分割菜单只有一项子级时,收起左侧导航菜单
|
||||
- 🎯 优化 watch 监听范围
|
||||
- 🎯 优化 打包:分包(manualChunks)、gzip 压缩、cdn 加速 `默认关闭 .env 中开启`(可查看文章[vue-next-admin vue3 + vite 打包 gzip 压缩、cdn 加速](https://blog.csdn.net/qq_34450741/article/details/129766676))
|
||||
|
||||
## 2.4.31
|
||||
|
||||
`2023.03.10`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 顶栏背景渐变设置不生效
|
||||
- 🐞 修复 顶栏背景渐变、菜单背景渐变时,深色主题不生效
|
||||
- 🐞 修复 顶栏搜索框移动端显示问题
|
||||
- 🎯 优化 `main.ts`,相关 issues [#I6KNFH](https://gitee.com/lyt-top/vue-next-admin/issues/I6KNFH)、[#I6JRH6](https://gitee.com/lyt-top/vue-next-admin/issues/I6JRH6)
|
||||
- 🎯 优化 菜单横向模式显示(horizontal)
|
||||
- 🎯 优化 分栏布局,[希望分栏布局做一下优化,在没有二级菜单的时候,直接全屏展示一级菜单链接](https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H),感谢[@jiuping](https://gitee.com/jiuping),`tagsview` 点击时处理 `收起/展开` 菜单
|
||||
|
||||
## 2.4.3
|
||||
|
||||
`2023.02.22`
|
||||
|
||||
🚩🚩🚩 感谢 [驰骋工作流引擎-表单引擎-低代码开发平台](http://www.ccflow.org/) 赞助商的赞助。驰骋公司为社会提供流程引擎+表单引擎+低代码开发平台一体的开源软件解决方案,欢迎广大开发者前去体验!
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 赞助商组件(`/src/layout/sponsors`),[项目目录结构查看](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/)
|
||||
- 🐞 修复 [过滤筛选组件展开点击不了](https://gitee.com/lyt-top/vue-next-admin/issues/I688WG)
|
||||
- 🐞 修复 [设置锁屏时间时直接白屏了不能恢复,除非删除主题配置才会重新加载](https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P),感谢[@baizunxian](https://gitee.com/xb_xiaobai)
|
||||
- 🐞 修复 `分栏布局` 地址栏输入不存在的路由报错问题
|
||||
- 🎨 合并 [!44 tagsViewName 正则匹配错误,匹配到含 en 单词](https://gitee.com/lyt-top/vue-next-admin/pulls/44/files),感谢[@tony 星](https://gitee.com/tony_tong_xin)
|
||||
- 🎨 合并 [!45 fix 地址栏出现 false 问题](https://gitee.com/lyt-top/vue-next-admin/pulls/45),感谢[@随心](https://gitee.com/jiangqiang1996)
|
||||
- 🎯 优化 `/src/utils/storage` 下 `key` 编写成 `${__NEXT_NAME__}:${key}`,防止部署多套系统到同一域名不同目录时,变量共用的问题(`__NEXT_NAME__`为 `package.json` 中的 `name`)
|
||||
- 🎯 优化 watermark 单词拼写错误
|
||||
|
||||
## 2.4.21
|
||||
|
||||
`2022.12.12`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎉 新增 菜单背景高亮颜色可自定义,通过 `布局配置 -> 菜单设置 -> 菜单高亮背景色` 进行设置
|
||||
- 🐞 修复 `分栏布局` 二级导航菜单内容多时,无法滚动问题,感谢群友@静雨轩主人
|
||||
- 🐞 修复 [!42 修复 工作流无法添加新节点问题](https://gitee.com/lyt-top/vue-next-admin/pulls/42),感谢[@beta](https://gitee.com/beta_dz)
|
||||
- 🎯 优化 `/make/tableDemo` 表头很多时,无法滚动问题,感谢群友@糊涂涂涂
|
||||
|
||||
## 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/topBar/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
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 lyt-Top
|
||||
Copyright (c) 2022 倔强嘴角留下一抹殇
|
||||
|
||||
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
Normal file
25
backend/.eslintrc.js
Normal file
@ -0,0 +1,25 @@
|
||||
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
Normal file
@ -0,0 +1,37 @@
|
||||
# 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
|
||||
4
backend/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
5
backend/nest-cli.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
}
|
||||
90
backend/package.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
47
backend/src/Config/Index.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* @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,
|
||||
}
|
||||
}
|
||||
123
backend/src/Controller/AuthController.ts
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* @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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
143
backend/src/Controller/CityController.ts
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* @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
|
||||
|
||||
}
|
||||
57
backend/src/Controller/IndexController.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* @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}条记录!`
|
||||
);
|
||||
}
|
||||
}
|
||||
294
backend/src/Controller/MenuController.ts
Normal file
@ -0,0 +1,294 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
189
backend/src/Controller/RoleController.ts
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* @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
|
||||
|
||||
}
|
||||
188
backend/src/Controller/UserController.ts
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
43
backend/src/Entity/SysBase.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
35
backend/src/Entity/SysCity.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
19
backend/src/Entity/SysDictionaries.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
61
backend/src/Entity/SysMenu.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* @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[];
|
||||
}
|
||||
22
backend/src/Entity/SysRole.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* @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[];
|
||||
}
|
||||
18
backend/src/Entity/SysRoleMenu.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
57
backend/src/Entity/SysUser.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
78
backend/src/EntiyDto/CommonDto.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
21
backend/src/EntiyDto/LoginDto.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
70
backend/src/EntiyDto/MenuDto.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
25
backend/src/EntiyDto/RoleDto.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* @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[];
|
||||
}
|
||||
26
backend/src/EntiyDto/RouteMenu.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
65
backend/src/EntiyDto/UserDto.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* @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;
|
||||
}
|
||||
28
backend/src/Enum/Global.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
24
backend/src/Expand/RestfulReturn.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* @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()
|
||||
}
|
||||
}
|
||||
}
|
||||
15
backend/src/Logging/LoggerPrint.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @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) {
|
||||
}
|
||||
}
|
||||
33
backend/src/Middleware/JwtHandle.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
backend/src/SeedData/City.ts
Normal file
216
backend/src/SeedData/Menu.ts
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* @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
|
||||
}];
|
||||
179
backend/src/SeedData/Role.ts
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* @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
|
||||
}];
|
||||
60
backend/src/SeedData/User.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* @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
|
||||
}];
|
||||
94
backend/src/Service/CityService.ts
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
94
backend/src/Service/MenuService.ts
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
156
backend/src/Service/RoleService.ts
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
93
backend/src/Service/UserService.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* @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
|
||||
}
|
||||
50
backend/src/Utils/LoadModules.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* @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
|
||||
];
|
||||
}
|
||||
61
backend/src/app.module.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* @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("*");
|
||||
}
|
||||
|
||||
}
|
||||
33
backend/src/main.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @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();
|
||||
24
backend/test/app.e2e-spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
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!');
|
||||
});
|
||||
});
|
||||
9
backend/test/jest-e2e.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
4
backend/tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
|
||||
}
|
||||
21
backend/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"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
Normal file
@ -0,0 +1,9 @@
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
BIN
doc/微信图片_20221208092411.jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
doc/微信截图_20221124112526.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
doc/微信截图_20221124112609.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
doc/微信截图_20221124112646.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
doc/微信截图_20221124112739.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
@ -13,18 +13,9 @@ 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',
|
||||
@ -35,9 +26,6 @@ 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',
|
||||
@ -71,6 +59,5 @@ module.exports = {
|
||||
'no-v-model-argument': 'off',
|
||||
'no-case-declarations': 'off',
|
||||
'no-console': 'error',
|
||||
'no-redeclare': 'off',
|
||||
},
|
||||
};
|
||||
25
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
.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?
|
||||
82
frontend/package.json
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "tsplatform",
|
||||
"version": "1.1.0",
|
||||
"description": "vue3 vite next admin template",
|
||||
"author": "SweetHoney",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite --force",
|
||||
"build": "vite build",
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@wangeditor/editor-for-vue": "^5.1.11",
|
||||
"axios": "^1.1.3",
|
||||
"countup.js": "^2.3.2",
|
||||
"cropperjs": "^1.5.12",
|
||||
"echarts": "^5.4.0",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"echarts-wordcloud": "^2.0.0",
|
||||
"element-plus": "^2.2.21",
|
||||
"js-cookie": "^3.0.1",
|
||||
"jsplumb": "^2.15.6",
|
||||
"mitt": "^3.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.23",
|
||||
"print-js": "^1.6.0",
|
||||
"qrcodejs2-fixes": "^0.0.2",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"splitpanes": "^3.1.5",
|
||||
"ts-md5": "^1.3.1",
|
||||
"vue": "^3.2.45",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-grid-layout": "^3.0.0-beta1",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vue/compiler-sfc": "^3.2.45",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-plugin-vue": "^9.7.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^3.2.4",
|
||||
"vue-eslint-parser": "^9.1.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0",
|
||||
"npm": ">= 6.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"vue3",
|
||||
"vuejs/vue-next",
|
||||
"element-ui",
|
||||
"element-plus",
|
||||
"vue-next-admin",
|
||||
"next-admin"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/lyt-top/vue-next-admin.git"
|
||||
}
|
||||
}
|
||||
4
frontend/plugins.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module 'vue-grid-layout';
|
||||
declare module 'qrcodejs2-fixes';
|
||||
declare module 'splitpanes';
|
||||
declare module 'js-cookie';
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
13
frontend/shim.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/* 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
Normal file
@ -0,0 +1,6 @@
|
||||
declare module '*.json';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.scss';
|
||||
declare module '*.ts';
|
||||
declare module '*.js';
|
||||
107
frontend/src/App.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<!--
|
||||
* @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>
|
||||
23
frontend/src/api/city/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* @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
|
||||
});
|
||||
}
|
||||
34
frontend/src/api/login/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* @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'
|
||||
});
|
||||
}
|
||||
51
frontend/src/api/menu/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* @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
|
||||
});
|
||||
}
|
||||
52
frontend/src/api/role/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* @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
|
||||
});
|
||||
}
|
||||
51
frontend/src/api/user/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* @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
|
||||
});
|
||||
}
|
||||
BIN
frontend/src/assets/avtar.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
1
frontend/src/assets/login-icon-two.svg
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
30
frontend/src/components/auth/auth.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<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>
|
||||
31
frontend/src/components/auth/authAll.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<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>
|
||||
36
frontend/src/components/auth/auths.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<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,21 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog title="更换头像" v-model="state.isShowDialog" width="769px">
|
||||
<el-dialog title="更换头像" v-model="isShowDialog" width="769px">
|
||||
<div class="cropper-warp">
|
||||
<div class="cropper-warp-left">
|
||||
<img :src="state.cropperImg" class="cropper-warp-left-img" />
|
||||
<img :src="cropperImg" class="cropper-warp-left-img" />
|
||||
</div>
|
||||
<div class="cropper-warp-right">
|
||||
<div class="cropper-warp-right-title">预览</div>
|
||||
<div class="cropper-warp-right-item">
|
||||
<div class="cropper-warp-right-value">
|
||||
<img :src="state.cropperImgBase64" class="cropper-warp-right-value-img" />
|
||||
<img :src="cropperImgBase64" class="cropper-warp-right-value-img" />
|
||||
</div>
|
||||
<div class="cropper-warp-right-label">100 x 100</div>
|
||||
</div>
|
||||
<div class="cropper-warp-right-item">
|
||||
<div class="cropper-warp-right-value">
|
||||
<img :src="state.cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
|
||||
<img :src="cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
|
||||
</div>
|
||||
<div class="cropper-warp-right-label">50 x 50</div>
|
||||
</div>
|
||||
@ -31,60 +31,66 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="cropper">
|
||||
import { reactive, nextTick } from 'vue';
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, nextTick, defineComponent } from 'vue';
|
||||
import Cropper from 'cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
isShowDialog: false,
|
||||
cropperImg: '',
|
||||
cropperImgBase64: '',
|
||||
cropper: '' as RefType,
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (imgs: string) => {
|
||||
state.cropperImg = imgs;
|
||||
state.isShowDialog = true;
|
||||
nextTick(() => {
|
||||
initCropper();
|
||||
});
|
||||
};
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
state.isShowDialog = false;
|
||||
};
|
||||
// 取消
|
||||
const onCancel = () => {
|
||||
closeDialog();
|
||||
};
|
||||
// 更换
|
||||
const onSubmit = () => {
|
||||
// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
|
||||
};
|
||||
// 初始化cropperjs图片裁剪
|
||||
const initCropper = () => {
|
||||
const letImg = <HTMLImageElement>document.querySelector('.cropper-warp-left-img');
|
||||
state.cropper = new Cropper(letImg, {
|
||||
viewMode: 1,
|
||||
dragMode: 'none',
|
||||
initialAspectRatio: 1,
|
||||
aspectRatio: 1,
|
||||
preview: '.before',
|
||||
background: false,
|
||||
autoCropArea: 0.6,
|
||||
zoomOnWheel: false,
|
||||
crop: () => {
|
||||
state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
export default defineComponent({
|
||||
name: 'cropperIndex',
|
||||
setup() {
|
||||
const state = reactive({
|
||||
isShowDialog: false,
|
||||
cropperImg: '',
|
||||
cropperImgBase64: '',
|
||||
cropper: null,
|
||||
});
|
||||
// 打开弹窗
|
||||
const openDialog = (imgs: any) => {
|
||||
state.cropperImg = imgs;
|
||||
state.isShowDialog = true;
|
||||
nextTick(() => {
|
||||
initCropper();
|
||||
});
|
||||
};
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
state.isShowDialog = false;
|
||||
};
|
||||
// 取消
|
||||
const onCancel = () => {
|
||||
closeDialog();
|
||||
};
|
||||
// 更换
|
||||
const onSubmit = () => {
|
||||
// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
|
||||
};
|
||||
// 初始化cropperjs图片裁剪
|
||||
const initCropper = () => {
|
||||
const letImg: any = document.querySelector('.cropper-warp-left-img');
|
||||
(<any>state.cropper) = new Cropper(letImg, {
|
||||
viewMode: 1,
|
||||
dragMode: 'none',
|
||||
initialAspectRatio: 1,
|
||||
aspectRatio: 1,
|
||||
preview: '.before',
|
||||
background: false,
|
||||
autoCropArea: 0.6,
|
||||
zoomOnWheel: false,
|
||||
crop: () => {
|
||||
state.cropperImgBase64 = (<any>state.cropper).getCroppedCanvas().toDataURL('image/jpeg');
|
||||
},
|
||||
});
|
||||
};
|
||||
return {
|
||||
openDialog,
|
||||
closeDialog,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
initCropper,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
88
frontend/src/components/editor/index.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<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>
|
||||
252
frontend/src/components/iconSelector/index.vue
Normal file
@ -0,0 +1,252 @@
|
||||
<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>
|
||||
195
frontend/src/components/noticeBar/index.vue
Normal file
@ -0,0 +1,195 @@
|
||||
<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>
|
||||
73
frontend/src/components/svgIcon/index.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<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>
|
||||
68
frontend/src/i18n/index.ts
Normal file
@ -0,0 +1,68 @@
|
||||
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,
|
||||
});
|
||||
@ -2,78 +2,15 @@
|
||||
export default {
|
||||
router: {
|
||||
home: 'home',
|
||||
system: 'system',
|
||||
systemMenu: 'systemMenu',
|
||||
systemRole: 'systemRole',
|
||||
systemUser: 'systemUser',
|
||||
systemDept: 'systemDept',
|
||||
systemDic: 'systemDic',
|
||||
limits: 'limits',
|
||||
limitsFrontEnd: 'FrontEnd',
|
||||
limitsFrontEndPage: 'FrontEndPage',
|
||||
limitsFrontEndBtn: 'FrontEndBtn',
|
||||
limitsBackEnd: 'BackEnd',
|
||||
limitsBackEndEndPage: 'BackEndEndPage',
|
||||
menu: 'menu',
|
||||
menu1: 'menu1',
|
||||
menu11: 'menu11',
|
||||
menu12: 'menu12',
|
||||
menu121: 'menu121',
|
||||
menu122: 'menu122',
|
||||
menu13: 'menu13',
|
||||
menu2: 'menu2',
|
||||
funIndex: 'function',
|
||||
funTagsView: 'funTagsView',
|
||||
funCountup: 'countup',
|
||||
funWangEditor: 'wangEditor',
|
||||
funCropper: 'cropper',
|
||||
funQrcode: 'qrcode',
|
||||
funEchartsMap: 'EchartsMap',
|
||||
funPrintJs: 'PrintJs',
|
||||
funClipboard: 'Copy cut',
|
||||
funGridLayout: 'Drag layout',
|
||||
funSplitpanes: 'Pane splitter',
|
||||
funDragVerify: 'Validator',
|
||||
pagesIndex: 'pages',
|
||||
pagesFiltering: 'Filtering',
|
||||
pagesFilteringDetails: 'FilteringDetails',
|
||||
pagesFilteringDetails1: 'FilteringDetails1',
|
||||
pagesIocnfont: 'iconfont icon',
|
||||
pagesElement: 'element icon',
|
||||
pagesAwesome: 'awesome icon',
|
||||
pagesFormAdapt: 'FormAdapt',
|
||||
pagesTableRules: 'pagesTableRules',
|
||||
pagesFormI18n: 'FormI18n',
|
||||
pagesFormRules: 'Multi form validation',
|
||||
pagesDynamicForm: 'Dynamic complex form',
|
||||
pagesWorkflow: 'Workflow',
|
||||
pagesListAdapt: 'ListAdapt',
|
||||
pagesWaterfall: 'Waterfall',
|
||||
pagesSteps: 'Steps',
|
||||
pagesPreview: 'Large preview',
|
||||
pagesWaves: 'Wave effect',
|
||||
pagesTree: 'tree alter table',
|
||||
pagesDrag: 'Drag command',
|
||||
pagesLazyImg: 'Image lazy loading',
|
||||
makeIndex: 'makeIndex',
|
||||
makeSelector: 'Icon selector',
|
||||
makeNoticeBar: 'notification bar',
|
||||
makeSvgDemo: 'Svgicon demo',
|
||||
makeTableDemo: 'table demo',
|
||||
paramsIndex: 'Routing parameters',
|
||||
paramsCommon: 'General routing',
|
||||
paramsDynamic: 'Dynamic routing',
|
||||
paramsCommonDetails: 'General routing details',
|
||||
paramsDynamicDetails: 'Dynamic routing details',
|
||||
chartIndex: 'chartIndex',
|
||||
visualizingIndex: 'visualizingIndex',
|
||||
visualizingLinkDemo1: 'visualizingLinkDemo1',
|
||||
visualizingLinkDemo2: 'visualizingLinkDemo2',
|
||||
system: {
|
||||
system: 'system',
|
||||
menu: 'systemMenu',
|
||||
role: 'systemRole',
|
||||
user: 'systemUser',
|
||||
dic: 'systemDic',
|
||||
city: 'systemDept'
|
||||
},
|
||||
personal: 'personal',
|
||||
tools: 'tools',
|
||||
layoutLinkView: 'LinkView',
|
||||
layoutIframeViewOne: 'IframeViewOne',
|
||||
layoutIframeViewTwo: 'IframeViewTwo',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: 'signIn',
|
||||
@ -137,7 +74,6 @@ export default {
|
||||
twoIsTopBarColorGradual: 'Top bar gradient',
|
||||
twoMenuBar: 'Menu background',
|
||||
twoMenuBarColor: 'Menu default font color',
|
||||
twoMenuBarActiveColor: 'Menu Highlight Color',
|
||||
twoIsMenuBarColorGradual: 'Menu gradient',
|
||||
twoColumnsMenuBar: 'Column menu background',
|
||||
twoColumnsMenuBarColor: 'Default font color bar menu',
|
||||
@ -181,12 +117,4 @@ export default {
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
copyTextError: 'Copy failed!',
|
||||
},
|
||||
upgrade: {
|
||||
title: 'New version',
|
||||
msg: 'The new version is available, please update it now! Dont worry, the update is fast!',
|
||||
desc: 'Prompt: Update will restore the default configuration',
|
||||
btnOne: 'Cruel refusal',
|
||||
btnTwo: 'Update now',
|
||||
btnTwoLoading: 'Updating',
|
||||
},
|
||||
};
|
||||
@ -2,78 +2,15 @@
|
||||
export default {
|
||||
router: {
|
||||
home: '首页',
|
||||
system: '系统设置',
|
||||
systemMenu: '菜单管理',
|
||||
systemRole: '角色管理',
|
||||
systemUser: '用户管理',
|
||||
systemDept: '部门管理',
|
||||
systemDic: '字典管理',
|
||||
limits: '权限管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '页面权限',
|
||||
limitsFrontEndBtn: '按钮权限',
|
||||
limitsBackEnd: '后端控制',
|
||||
limitsBackEndEndPage: '页面权限',
|
||||
menu: '菜单嵌套',
|
||||
menu1: '菜单1',
|
||||
menu11: '菜单11',
|
||||
menu12: '菜单12',
|
||||
menu121: '菜单121',
|
||||
menu122: '菜单122',
|
||||
menu13: '菜单13',
|
||||
menu2: '菜单2',
|
||||
funIndex: '功能',
|
||||
funTagsView: 'tagsView 操作',
|
||||
funCountup: '数字滚动',
|
||||
funWangEditor: 'Editor 编辑器',
|
||||
funCropper: '图片裁剪',
|
||||
funQrcode: '二维码生成',
|
||||
funEchartsMap: '地理坐标/地图',
|
||||
funPrintJs: '页面打印',
|
||||
funClipboard: '复制剪切',
|
||||
funGridLayout: '拖拽布局',
|
||||
funSplitpanes: '窗格拆分器',
|
||||
funDragVerify: '验证器',
|
||||
pagesIndex: '页面',
|
||||
pagesFiltering: '过滤筛选组件',
|
||||
pagesFilteringDetails: '过滤筛选组件详情',
|
||||
pagesFilteringDetails1: '过滤筛选组件详情111',
|
||||
pagesIocnfont: 'ali 字体图标',
|
||||
pagesElement: 'ele 字体图标',
|
||||
pagesAwesome: 'awe 字体图标',
|
||||
pagesFormAdapt: '表单自适应',
|
||||
pagesTableRules: '表单表格验证',
|
||||
pagesFormI18n: '表单国际化',
|
||||
pagesFormRules: '多表单验证',
|
||||
pagesDynamicForm: '动态复杂表单',
|
||||
pagesWorkflow: '工作流',
|
||||
pagesListAdapt: '列表自适应',
|
||||
pagesWaterfall: '瀑布屏',
|
||||
pagesSteps: '步骤条',
|
||||
pagesPreview: '大图预览',
|
||||
pagesWaves: '波浪效果',
|
||||
pagesTree: '树形改表格',
|
||||
pagesDrag: '拖动指令',
|
||||
pagesLazyImg: '图片懒加载',
|
||||
makeIndex: '组件封装',
|
||||
makeSelector: '图标选择器',
|
||||
makeNoticeBar: '滚动通知栏',
|
||||
makeSvgDemo: 'svgIcon 演示',
|
||||
makeTableDemo: '表格封装演示',
|
||||
paramsIndex: '路由参数',
|
||||
paramsCommon: '普通路由',
|
||||
paramsDynamic: '动态路由',
|
||||
paramsCommonDetails: '普通路由详情',
|
||||
paramsDynamicDetails: '动态路由详情',
|
||||
chartIndex: '大数据图表',
|
||||
visualizingIndex: '数据可视化',
|
||||
visualizingLinkDemo1: '数据可视化演示1',
|
||||
visualizingLinkDemo2: '数据可视化演示2',
|
||||
system: {
|
||||
system: '系统设置',
|
||||
menu: '菜单管理',
|
||||
role: '角色管理',
|
||||
user: '用户管理',
|
||||
dic: '字典管理',
|
||||
city: '城市管理'
|
||||
},
|
||||
personal: '个人中心',
|
||||
tools: '工具类集合',
|
||||
layoutLinkView: '外链',
|
||||
layoutIframeViewOne: '内嵌 iframe1',
|
||||
layoutIframeViewTwo: '内嵌 iframe2',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: '登录',
|
||||
@ -137,7 +74,6 @@ export default {
|
||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||
twoMenuBar: '菜单背景',
|
||||
twoMenuBarColor: '菜单默认字体颜色',
|
||||
twoMenuBarActiveColor: '菜单高亮背景色',
|
||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||
twoColumnsMenuBar: '分栏菜单背景',
|
||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||
@ -181,12 +117,4 @@ export default {
|
||||
copyTextSuccess: '复制成功!',
|
||||
copyTextError: '复制失败!',
|
||||
},
|
||||
upgrade: {
|
||||
title: '新版本升级',
|
||||
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
|
||||
desc: '提示:更新会还原默认配置',
|
||||
btnOne: '残忍拒绝',
|
||||
btnTwo: '马上更新',
|
||||
btnTwoLoading: '更新中',
|
||||
},
|
||||
};
|
||||
@ -2,78 +2,16 @@
|
||||
export default {
|
||||
router: {
|
||||
home: '首頁',
|
||||
system: '系統設置',
|
||||
systemMenu: '選單管理',
|
||||
systemRole: '角色管理',
|
||||
systemUser: '用戶管理',
|
||||
systemDept: '部門管理',
|
||||
systemDic: '字典管理',
|
||||
limits: '許可權管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '頁面許可權',
|
||||
limitsFrontEndBtn: '按鈕許可權',
|
||||
limitsBackEnd: '後端控制',
|
||||
limitsBackEndEndPage: '頁面許可權',
|
||||
menu: '選單嵌套',
|
||||
menu1: '選單1',
|
||||
menu11: '選單11',
|
||||
menu12: '選單12',
|
||||
menu121: '選單121',
|
||||
menu122: '選單122',
|
||||
menu13: '選單13',
|
||||
menu2: '選單2',
|
||||
funIndex: '功能',
|
||||
funTagsView: 'tagsView 操作',
|
||||
funCountup: '數位滾動',
|
||||
funWangEditor: 'Editor 編輯器',
|
||||
funCropper: '圖片裁剪',
|
||||
funQrcode: '二維碼生成',
|
||||
funEchartsMap: '地理座標/地圖',
|
||||
funPrintJs: '頁面列印',
|
||||
funClipboard: '複製剪切',
|
||||
funGridLayout: '拖拽佈局',
|
||||
funSplitpanes: '窗格折開器',
|
||||
funDragVerify: '驗證器',
|
||||
pagesIndex: '頁面',
|
||||
pagesFiltering: '過濾篩選組件',
|
||||
pagesFilteringDetails: '過濾篩選組件詳情',
|
||||
pagesFilteringDetails1: '過濾篩選組件詳情111',
|
||||
pagesIocnfont: 'ali 字體圖標',
|
||||
pagesElement: 'ele 字體圖標',
|
||||
pagesAwesome: 'awe 字體圖標',
|
||||
pagesFormAdapt: '表單自我調整',
|
||||
pagesTableRules: '表單表格驗證',
|
||||
pagesFormI18n: '表單國際化',
|
||||
pagesFormRules: '多表單驗證',
|
||||
pagesDynamicForm: '動態複雜表單',
|
||||
pagesWorkflow: '工作流',
|
||||
pagesListAdapt: '清單自我調整',
|
||||
pagesWaterfall: '瀑布屏',
|
||||
pagesSteps: '步驟條',
|
||||
pagesPreview: '大圖預覽',
|
||||
pagesWaves: '波浪效果',
|
||||
pagesTree: '樹形改表格',
|
||||
pagesDrag: '拖動指令',
|
||||
pagesLazyImg: '圖片懶加載',
|
||||
makeIndex: '組件封裝',
|
||||
makeSelector: '圖標選擇器',
|
||||
makeNoticeBar: '滾動通知欄',
|
||||
makeSvgDemo: 'svgIcon 演示',
|
||||
makeTableDemo: '表格封裝演示',
|
||||
paramsIndex: '路由參數',
|
||||
paramsCommon: '普通路由',
|
||||
paramsDynamic: '動態路由',
|
||||
paramsCommonDetails: '普通路由詳情',
|
||||
paramsDynamicDetails: '動態路由詳情',
|
||||
chartIndex: '大資料圖表',
|
||||
visualizingIndex: '數據視覺化',
|
||||
visualizingLinkDemo1: '數據視覺化演示1',
|
||||
visualizingLinkDemo2: '數據視覺化演示2',
|
||||
system: {
|
||||
system: '系統設置',
|
||||
menu: '選單管理',
|
||||
role: '角色管理',
|
||||
user: '用戶管理',
|
||||
dic: '字典管理',
|
||||
city: '城市管理'
|
||||
},
|
||||
personal: '個人中心',
|
||||
tools: '工具類集合',
|
||||
layoutLinkView: '外鏈',
|
||||
layoutIframeViewOne: '内嵌 iframe1',
|
||||
layoutIframeViewTwo: '内嵌 iframe2',
|
||||
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: '登入',
|
||||
@ -137,7 +75,6 @@ export default {
|
||||
twoIsTopBarColorGradual: '頂欄背景漸變',
|
||||
twoMenuBar: '選單背景',
|
||||
twoMenuBarColor: '選單默認字體顏色',
|
||||
twoMenuBarActiveColor: '選單高亮背景色',
|
||||
twoIsMenuBarColorGradual: '選單背景漸變',
|
||||
twoColumnsMenuBar: '分欄選單背景',
|
||||
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
|
||||
@ -181,12 +118,4 @@ export default {
|
||||
copyTextSuccess: '複製成功!',
|
||||
copyTextError: '複製失敗!',
|
||||
},
|
||||
upgrade: {
|
||||
title: '新版本陞級',
|
||||
msg: '新版本來啦,馬上更新嘗鮮吧! 不用擔心,更新很快的哦!',
|
||||
desc: '提示:更新會還原默認配寘',
|
||||
btnOne: '殘忍拒絕',
|
||||
btnTwo: '馬上更新',
|
||||
btnTwoLoading: '更新中',
|
||||
},
|
||||
};
|
||||
165
frontend/src/layout/component/aside.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<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>
|
||||
296
frontend/src/layout/component/columnsAside.vue
Normal file
@ -0,0 +1,296 @@
|
||||
<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>
|
||||
25
frontend/src/layout/component/header.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<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>
|
||||