同步vue-next-admin最新稳定版本(2.4.33),优化工作流

This commit is contained in:
zsvg
2023-05-08 18:52:59 +08:00
parent 6328c68adb
commit 94b9492b1d
243 changed files with 17191 additions and 16352 deletions
+13
View File
@@ -13,9 +13,18 @@ module.exports = {
},
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.vue'],
rules: {
'no-undef': 'off',
},
},
],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
// https://typescript-eslint.io/rules/no-unused-vars/
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
@@ -26,6 +35,9 @@ module.exports = {
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/no-unused-vars': [2],
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
@@ -59,5 +71,6 @@ module.exports = {
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
'no-redeclare': 'off',
},
};
+103 -1
View File
@@ -2,6 +2,108 @@
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
## 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`
@@ -61,7 +163,7 @@
- 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
- 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts)
- 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue`
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `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)
+19 -19
View File
@@ -1,22 +1,22 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="keywords"
content="vue-next-admin,vue-prev-admin,vue-admin-wonderful,后台管理系统一站式平台模板,希望可以帮你完成快速开发。vue2.x,vue2.0,vue2,vue3,vue3.x,vue3.0,CompositionAPI,typescript,element plus,element,plus,admin,wonderful,wonderful-next,vue-next-admin,vite,vite-admin,快速,高效,后台模板,后台系统,管理系统"
/>
<meta
name="description"
content="vue-next-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus,适配手机、平板、pc 的后台开源免费管理系统模板!vue-prev-admin,基于 vue2 + element ui,适配手机、平板、pc 的后台开源免费管理系统模板!"
/>
<link rel="icon" href="/favicon.ico" />
<title>vue-next-admin</title>
</head>
<body>
<div id="app"></div>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="keywords"
content="vue-next-admin,vue-prev-admin,vue-admin-wonderful,后台管理系统一站式平台模板,希望可以帮你完成快速开发。vue2.x,vue2.0,vue2,vue3,vue3.x,vue3.0,CompositionAPI,typescript,element plus,element,plus,admin,wonderful,wonderful-next,vue-next-admin,vite,vite-admin,快速,高效,后台模板,后台系统,管理系统"
/>
<meta
name="description"
content="vue-next-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus,适配手机、平板、pc 的后台开源免费管理系统模板!vue-prev-admin,基于 vue2 + element ui,适配手机、平板、pc 的后台开源免费管理系统模板!"
/>
<link rel="icon" href="/favicon.ico" />
<title>vue-next-admin</title>
</head>
<body>
<div id="app"></div>
<!-- <script type="text/javascript">-->
<!-- var _hmt = _hmt || [];-->
<!-- (function () {-->
@@ -26,7 +26,7 @@
<!-- s.parentNode.insertBefore(hm, s);-->
<!-- })();-->
<!-- </script>-->
<script type="module" src="/src/main.ts"></script>
<script type="module" src="/src/main.ts"></script>
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>-->
</body>
</body>
</html>
+2390 -2161
View File
File diff suppressed because it is too large Load Diff
+38 -29
View File
@@ -1,65 +1,74 @@
{
"name": "vue-next-admin",
"version": "2.3.0",
"version": "2.4.33",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
"dev": "vite --force",
"dev": "vite",
"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",
"@element-plus/icons-vue": "^2.1.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.3.5",
"countup.js": "^2.6.0",
"cropperjs": "^1.5.13",
"echarts": "^5.4.2",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^2.2.21",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.3.3",
"js-cookie": "^3.0.1",
"js-table2excel": "^1.0.3",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.23",
"pinia": "^2.0.34",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"qs": "^6.11.1",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.5",
"vue": "^3.2.45",
"vue": "^3.2.47",
"vue-clipboard3": "^2.0.0",
"vue-demi": "^0.13.11",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"bpmn-js": "^8.1.0",
"bpmn-js-properties-panel": "^0.39.0",
"codemirror": "^5.59.2",
"codemirror-editor-vue3": "^2.1.7",
"vform3-builds": "^3.0.8",
"vxe-table": "^4.3.5",
"xe-utils": "^3.5.4",
"vxe-table": "^4.3.12",
"xe-utils": "^3.5.7",
"file-saver": "2.0.5"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@types/node": "^18.15.11",
"@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",
"@types/sortablejs": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"@vitejs/plugin-vue": "^4.1.0",
"@vue/compiler-sfc": "^3.2.47",
"eslint": "^8.38.0",
"eslint-plugin-vue": "^9.10.0",
"prettier": "^2.8.7",
"sass": "^1.61.0",
"typescript": "^5.0.4",
"vite": "^4.2.1",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
"vue-eslint-parser": "^9.1.1",
"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",
"@vitejs/plugin-vue-jsx": "^1.1.6"
},
"browserslist": [
@@ -71,8 +80,8 @@
"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
},
"engines": {
"node": ">=12.0.0",
"npm": ">= 6.0.0"
"node": ">=16.0.0",
"npm": ">= 7.0.0"
},
"keywords": [
"vue",
-4
View File
@@ -1,4 +0,0 @@
declare module 'vue-grid-layout';
declare module 'qrcodejs2-fixes';
declare module 'splitpanes';
declare module 'js-cookie';
Vendored
-13
View File
@@ -1,13 +0,0 @@
/* eslint-disable */
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx,使用 getCurrentInstance() 来获取
interface Window {
nextLoading: boolean;
}
Vendored
-6
View File
@@ -1,6 +0,0 @@
declare module '*.json';
declare module '*.png';
declare module '*.jpg';
declare module '*.scss';
declare module '*.ts';
declare module '*.js';
+82 -72
View File
@@ -1,14 +1,16 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" />
<router-view v-show="setLockScreen" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<Setings ref="setingsRef" v-show="setLockScreen" />
<CloseFull v-if="!themeConfig.isLockScreen" />
<!-- <Upgrade v-if="getVersion" />-->
<!-- <Sponsors />-->
</el-config-provider>
</template>
<script lang="ts">
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
<script setup lang="ts" name="app">
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
@@ -19,73 +21,81 @@ 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,
};
},
// 引入组件
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
const Setings = defineAsyncComponent(() => import('/@/layout/navBars/topBar/setings.vue'));
const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/topBar/closeFull.vue'));
const Upgrade = defineAsyncComponent(() => import('/@/layout/upgrade/index.vue'));
const Sponsors = defineAsyncComponent(() => import('/@/layout/sponsors/index.vue'));
// 定义变量内容
const { messages, locale } = useI18n();
const setingsRef = ref();
const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 设置锁屏时组件显示隐藏
const setLockScreen = computed(() => {
// 防止锁屏后,刷新出现不相关界面
// https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P
return themeConfig.value.isLockScreen ? themeConfig.value.lockScreenTime > 1 : themeConfig.value.lockScreenTime >= 0;
});
// 获取版本号
const getVersion = computed(() => {
let isVersion = false;
if (route.path !== '/login') {
// @ts-ignore
if ((Local.get('version') && Local.get('version') !== __NEXT_VERSION__) || !Local.get('version')) isVersion = true;
}
return isVersion;
});
// 获取全局组件大小
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({ themeConfig: 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,
}
);
</script>
+6 -4
View File
@@ -1,24 +1,26 @@
import request from '/@/utils/request';
/**
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
*
* 登录api接口集合
* @method signIn 用户登录
* @method signOut 用户退出登录
*/
export function useLoginApi() {
return {
login: (params: object) => {
signIn: (data: object) => {
return request({
url: '/login',
method: 'post',
data: params,
data,
});
},
logout: (params: object) => {
signOut: (data: object) => {
return request({
url: '/logout',
method: 'post',
data: params,
data,
});
},
};
+10 -6
View File
@@ -1,24 +1,28 @@
import request from '/@/utils/request';
/**
* 以下为模拟接口地址,gitee 的不通,就换自己的真实接口地址
*
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
*
* 后端控制菜单模拟json,路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
* @method getMenuAdmin 获取后端动态路由菜单(admin)
* @method getMenuTest 获取后端动态路由菜单(test)
* @method getAdminMenu 获取后端动态路由菜单(admin)
* @method getTestMenu 获取后端动态路由菜单(test)
*/
export function useMenuApi() {
return {
getMenuAdmin: (params?: object) => {
getAdminMenu: (params?: object) => {
return request({
// url: 'https://gitee.com/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json',
// url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json',
url: 'getMenuList',
method: 'get',
params,
});
},
getMenuTest: (params?: object) => {
getTestMenu: (params?: object) => {
return request({
// url: 'https://gitee.com/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json',
// url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json',
url: 'getMenuList',
method: 'get',
params,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

+16 -20
View File
@@ -2,29 +2,25 @@
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script setup lang="ts" name="auth">
import { computed } 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,
};
// 定义父组件传过来的值
const props = defineProps({
value: {
type: String,
default: () => '',
},
});
// 定义变量内容
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
// 获取 pinia 中的用户权限
const getUserAuthBtnList = computed(() => {
return userInfos.value.authBtnList.some((v: string) => v === props.value);
});
</script>
+16 -20
View File
@@ -2,30 +2,26 @@
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script setup lang="ts" name="authAll">
import { computed } 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,
};
// 定义父组件传过来的值
const props = defineProps({
value: {
type: Array,
default: () => [],
},
});
// 定义变量内容
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
// 获取 pinia 中的用户权限
const getUserAuthBtnList = computed(() => {
return judementSameArr(props.value, userInfos.value.authBtnList);
});
</script>
+22 -26
View File
@@ -2,35 +2,31 @@
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script setup lang="ts" name="auths">
import { computed } 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,
};
// 定义父组件传过来的值
const props = defineProps({
value: {
type: Array,
default: () => [],
},
});
// 定义变量内容
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
// 获取 pinia 中的用户权限
const getUserAuthBtnList = computed(() => {
let flag = false;
userInfos.value.authBtnList.map((val: string) => {
props.value.map((v) => {
if (val === v) flag = true;
});
});
return flag;
});
</script>
+124 -159
View File
@@ -1,178 +1,143 @@
<template>
<div>
<el-dialog title="更换头像" v-model="isShowDialog" width="669px">
<div class="cropper-warp">
<div class="cropper-warp-left">
<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="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="cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
<template #footer>
<div>
<el-dialog title="更换头像" v-model="state.isShowDialog" width="769px">
<div class="cropper-warp">
<div class="cropper-warp-left">
<img :src="state.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" />
</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" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default">取 消</el-button>
<el-button type="primary" @click="onSubmit" size="default">更 换</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, nextTick, defineComponent } from 'vue';
<script setup lang="ts" name="cropper">
import { reactive, nextTick } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import request from '/@/utils/request';
export default defineComponent({
name: 'cropperIndex',
emits: ['close'],
setup(props, { emit }) {
const state = reactive({
isShowDialog: false,
cropperImg: '',
cropperImgBase64: '',
cropper: null as any,
});
// 定义变量内容
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');
},
});
};
let flag=false;
// 打开弹窗
const openDialog = (imgs: any) => {
state.cropperImg = imgs;
state.isShowDialog = true;
nextTick(() => {
if(!flag){
initCropper();
flag=true;
}else{
state.cropper.destroy();
initCropper();
}
});
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 更换
const onSubmit = () => {
let zfileId='' as any;
// state.cropperImgBase64 = state.cropper?.getCroppedCanvas().toDataURL('image/jpeg');
state.cropper?.getCroppedCanvas().toBlob(
async (blob:any) => {
let data = new FormData();
data.append("file", blob, "avatar.jpg");
const zfile= await request({
url: 'gen/user/clip',
method: 'post',
data:data,
}) as any;
zfileId=zfile.id;
// emit('close', { cropperImg: state.cropperImgBase64 });
emit('close', { cropperImg: zfileId });
closeDialog();
},
);
};
// 初始化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),
};
},
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
.cropper-warp {
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 400px;
flex: 1;
border: 1px solid var(--el-border-color);
background: var(--el-color-white);
overflow: hidden;
background-repeat: no-repeat;
cursor: move;
border-radius: var(--el-border-radius-base);
.cropper-warp-left-img {
width: 100%;
height: 100%;
}
}
.cropper-warp-right {
width: 150px;
height: 400px;
.cropper-warp-right-title {
text-align: center;
height: 20px;
line-height: 20px;
}
.cropper-warp-right-item {
margin: 15px 0;
.cropper-warp-right-value {
display: flex;
.cropper-warp-right-value-img {
width: 100px;
height: 100px;
border-radius: var(--el-border-radius-circle);
margin: auto;
}
.cropper-size {
width: 50px;
height: 50px;
}
}
.cropper-warp-right-label {
text-align: center;
font-size: 12px;
color: var(--el-text-color-primary);
height: 30px;
line-height: 30px;
}
}
}
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 350px;
flex: 1;
border: 1px solid var(--el-border-color);
background: var(--el-color-white);
overflow: hidden;
background-repeat: no-repeat;
cursor: move;
border-radius: var(--el-border-radius-base);
.cropper-warp-left-img {
width: 100%;
height: 100%;
}
}
.cropper-warp-right {
width: 150px;
height: 350px;
.cropper-warp-right-title {
text-align: center;
height: 20px;
line-height: 20px;
}
.cropper-warp-right-item {
margin: 15px 0;
.cropper-warp-right-value {
display: flex;
.cropper-warp-right-value-img {
width: 100px;
height: 100px;
border-radius: var(--el-border-radius-circle);
margin: auto;
}
.cropper-size {
width: 50px;
height: 50px;
}
}
.cropper-warp-right-label {
text-align: center;
font-size: 12px;
color: var(--el-text-color-primary);
height: 30px;
line-height: 30px;
}
}
}
}
</style>
+87 -74
View File
@@ -1,88 +1,101 @@
<template>
<div class="editor-container">
<Toolbar :editor="editorRef" :mode="mode" />
<Editor :mode="mode" :defaultConfig="editorConfig" :style="{ height }" v-model="editorVal" @onCreated="handleCreated" @onChange="handleChange" />
<Editor
:mode="mode"
:defaultConfig="state.editorConfig"
:style="{ height }"
v-model="state.editorVal"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</template>
<script lang="ts">
<script setup lang="ts" name="wngEditor">
// 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 { reactive, shallowRef, watch, onBeforeUnmount } from 'vue';
import { IDomEditor } from '@wangeditor/editor';
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',
},
// 定义父组件传过来的值
const props = defineProps({
// 是否禁用
disable: {
type: Boolean,
default: () => false,
},
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),
};
// 内容框默认 placeholder
placeholder: {
type: String,
default: () => '请输入内容...',
},
// 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',
},
// 双向绑定,用于获取 editor.getHtml()
getHtml: String,
// 双向绑定,用于获取 editor.getText()
getText: String,
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['update:getHtml', 'update:getText']);
// 定义变量内容
const editorRef = shallowRef();
const state = reactive({
editorConfig: {
placeholder: props.placeholder,
},
editorVal: props.getHtml,
});
// 编辑器回调函数
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor;
};
// 编辑器内容改变时
const handleChange = (editor: IDomEditor) => {
emit('update:getHtml', editor.getHtml());
emit('update:getText', editor.getText());
};
// 页面销毁时
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
// 监听是否禁用改变
// 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,
}
);
// 监听双向绑定值改变,用于回显
watch(
() => props.getHtml,
(val) => {
state.editorVal = val;
},
{
deep: true,
}
);
</script>
+219 -230
View File
@@ -1,252 +1,241 @@
<template>
<div class="icon-selector w100 h100">
<el-input
v-model="state.fontIconSearch"
:placeholder="state.fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
@clear="onClearFontIcon"
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<SvgIcon
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
class="font14"
v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
/>
<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
</template>
</el-input>
<el-popover
placement="bottom"
:width="fontIconWidth"
trigger="click"
:width="state.fontIconWidth"
transition="el-zoom-in-top"
popper-class="icon-selector-popper"
@show="onPopoverShow"
trigger="click"
:virtual-ref="inputWidthRef"
virtual-triggering
>
<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 class="icon-selector-warp-title">{{ title }}</div>
<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
<el-tab-pane lazy label="ali" name="ali">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="ele" name="ele">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="awe" name="awe">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
</el-tabs>
</div>
</template>
</el-popover>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
<script setup lang="ts" name="iconSelector">
import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
import type { TabsPaneContext } from 'element-plus';
import initIconfont from '/@/utils/getStyleSheets';
import '/@/theme/iconSelector.scss';
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,
// 定义父组件传过来的值
const props = defineProps({
// 输入框前置内容
prepend: {
type: String,
default: () => 'ele-Pointer',
},
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();
});
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'default',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// 禁用
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,
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
return {
inputWidthRef,
selectorScrollbarRef,
fontIconSheetsFilterList,
onColClick,
onIconChange,
onClearFontIcon,
onIconFocus,
onIconBlur,
onPopoverShow,
...toRefs(state),
};
// 定义子组件向父组件传值/事件
const emit = defineEmits(['update:modelValue', 'get', 'clear']);
// 引入组件
const IconList = defineAsyncComponent(() => import('/@/components/iconSelector/list.vue'));
// 定义变量内容
const inputWidthRef = ref();
const state = reactive({
fontIconPrefix: '',
fontIconWidth: 0,
fontIconSearch: '',
fontIconPlaceholder: '',
fontIconTabActive: 'ali',
fontIconList: {
ali: [],
ele: [],
awe: [],
},
});
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
if (!props.modelValue) return false;
state.fontIconSearch = '';
state.fontIconPlaceholder = props.modelValue;
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
const list = fontIconTabNameList();
setTimeout(() => {
const icon = list.filter((icon: string) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = '';
}, 300);
};
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
const list = fontIconTabNameList();
if (!state.fontIconSearch) return list;
let search = state.fontIconSearch.trim().toLowerCase();
return list.filter((item: string) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 根据 tab name 类型设置图标
const fontIconTabNameList = () => {
let iconList: any = [];
if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
return iconList;
};
// 处理 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 类型,用于回显时,tab 高亮与初始化数据
const initFontIconName = () => {
let name = 'ali';
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
// 初始化 tab 高亮回显
state.fontIconTabActive = name;
return name;
};
// 初始化数据
const initFontIconData = async (name: string) => {
if (name === 'ali') {
// 阿里字体图标使用 `iconfont xxx`
if (state.fontIconList.ali.length > 0) return;
await initIconfont.ali().then((res: any) => {
state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
});
} else if (name === 'ele') {
// element plus 图标
if (state.fontIconList.ele.length > 0) return;
await initIconfont.ele().then((res: any) => {
state.fontIconList.ele = res;
});
} else if (name === 'awe') {
// fontawesome字体图标使用 `fa xxx`
if (state.fontIconList.awe.length > 0) return;
await initIconfont.awe().then((res: any) => {
state.fontIconList.awe = 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 onIconClick = (pane: TabsPaneContext) => {
initFontIconData(pane.paneName as string);
inputWidthRef.value.focus();
};
// 获取当前点击的 icon 图标
const onColClick = (v: string) => {
state.fontIconPlaceholder = v;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
inputWidthRef.value.focus();
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
});
};
// 监听页面宽度改变
const initResize = () => {
window.addEventListener('resize', () => {
getInputWidth();
});
};
// 页面加载时
onMounted(() => {
initFontIconData(initFontIconName());
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
initFontIconName();
}
);
</script>
+84
View File
@@ -0,0 +1,84 @@
<template>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="props.list.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
<SvgIcon :name="v" />
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
</el-scrollbar>
</div>
</template>
<script setup lang="ts" name="iconSelectorList">
// 定义父组件传过来的值
const props = defineProps({
// 图标列表数据
list: {
type: Array,
default: () => [],
},
// 自定义空状态描述文字
empty: {
type: String,
default: () => '无相关图标',
},
// 高亮当前选中图标
prefix: {
type: String,
default: () => '',
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['get-icon']);
// 当前 icon 图标点击时
const onColClick = (v: unknown | string) => {
emit('get-icon', v);
};
</script>
<style scoped lang="scss">
.icon-selector-warp-row {
height: 230px;
overflow: hidden;
.el-row {
padding: 15px;
}
.el-scrollbar__bar.is-horizontal {
display: none;
}
.icon-selector-warp-item {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--el-border-color);
border-radius: 5px;
margin-bottom: 10px;
height: 30px;
i {
font-size: 20px;
color: var(--el-text-color-regular);
}
&:hover {
cursor: pointer;
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
i {
color: var(--el-color-primary);
}
}
}
.icon-selector-active {
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
i {
color: var(--el-color-primary);
}
}
}
</style>
+127 -131
View File
@@ -1,5 +1,5 @@
<template>
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!isMode">
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!state.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">
@@ -11,139 +11,135 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
<script setup lang="ts" name="noticeBar">
import { reactive, 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: () => '',
},
// 定义父组件传过来的值
const props = defineProps({
// 通知栏模式,可选值为 closeable link
mode: {
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),
};
// 通知文本内容
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: () => '',
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['close', 'link']);
// 定义变量内容
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();
});
</script>
+48 -58
View File
@@ -8,66 +8,56 @@
<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script setup lang="ts" name="svgIcon">
import { computed } from 'vue';
export default defineComponent({
name: 'svgIcon',
props: {
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
},
// 定义父组件传过来的值
const props = defineProps({
// svg 图标组件名字
name: {
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,
};
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
},
});
// 在线链接、本地引入地址前缀
// https://gitee.com/lyt-top/vue-next-admin/issues/I62OVL
const linesString = ['https', 'http', '/src', '/assets', 'data:image', 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('')}`;
});
</script>
+301
View File
@@ -0,0 +1,301 @@
<template>
<div class="table-container">
<el-table
:data="data"
:border="setBorder"
v-bind="$attrs"
row-key="id"
stripe
style="width: 100%"
v-loading="config.loading"
@selection-change="onSelectionChange"
>
<el-table-column type="selection" :reserve-selection="true" width="30" v-if="config.isSelection" />
<el-table-column type="index" label="序号" width="60" v-if="config.isSerialNo" />
<el-table-column
v-for="(item, index) in setHeader"
:key="index"
show-overflow-tooltip
:prop="item.key"
:width="item.colWidth"
:label="item.title"
>
<template v-slot="scope">
<template v-if="item.type === 'image'">
<el-image
:style="{ width: `${item.width}px`, height: `${item.height}px` }"
:src="scope.row[item.key]"
:zoom-rate="1.2"
:preview-src-list="[scope.row[item.key]]"
preview-teleported
fit="cover"
/>
</template>
<template v-else>
{{ scope.row[item.key] }}
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="100" v-if="config.isOperate">
<template v-slot="scope">
<el-popconfirm title="确定删除吗?" @confirm="onDelRow(scope.row)">
<template #reference>
<el-button text type="primary">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" />
</template>
</el-table>
<div class="table-footer mt15">
<el-pagination
v-model:current-page="state.page.pageNum"
v-model:page-size="state.page.pageSize"
:pager-count="5"
:page-sizes="[10, 20, 30]"
:total="config.total"
layout="total, sizes, prev, pager, next, jumper"
background
@size-change="onHandleSizeChange"
@current-change="onHandleCurrentChange"
>
</el-pagination>
<div class="table-footer-tool">
<SvgIcon name="iconfont icon-dayin" :size="19" title="打印" @click="onPrintTable" />
<SvgIcon name="iconfont icon-yunxiazai_o" :size="22" title="导出" @click="onImportTable" />
<SvgIcon name="iconfont icon-shuaxin" :size="22" title="刷新" @click="onRefreshTable" />
<el-popover
placement="top-end"
trigger="click"
transition="el-zoom-in-top"
popper-class="table-tool-popper"
:width="300"
:persistent="false"
@show="onSetTable"
>
<template #reference>
<SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" />
</template>
<template #default>
<div class="tool-box">
<el-tooltip content="拖动进行排序" placement="top-start">
<SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" />
</el-tooltip>
<el-checkbox
v-model="state.checkListAll"
:indeterminate="state.checkListIndeterminate"
class="ml10 mr1"
label="列显示"
@change="onCheckAllChange"
/>
<el-checkbox v-model="getConfig.isSerialNo" class="ml12 mr1" label="序号" />
<el-checkbox v-model="getConfig.isSelection" class="ml12 mr1" label="多选" />
</div>
<el-scrollbar>
<div ref="toolSetRef" class="tool-sortable">
<div class="tool-sortable-item" v-for="v in header" :key="v.key" :data-key="v.key">
<i class="fa fa-arrows-alt handle cursor-pointer"></i>
<el-checkbox v-model="v.isCheck" size="default" class="ml12 mr8" :label="v.title" @change="onCheckChange" />
</div>
</div>
</el-scrollbar>
</template>
</el-popover>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="netxTable">
import { reactive, computed, nextTick, ref } from 'vue';
import { ElMessage } from 'element-plus';
import printJs from 'print-js';
import table2excel from 'js-table2excel';
import Sortable from 'sortablejs';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import '/@/theme/tableTool.scss';
// 定义父组件传过来的值
const props = defineProps({
// 列表内容
data: {
type: Array<EmptyObjectType>,
default: () => [],
},
// 表头内容
header: {
type: Array<EmptyObjectType>,
default: () => [],
},
// 配置项
config: {
type: Object,
default: () => {},
},
// 打印标题
printName: {
type: String,
default: () => '',
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['delRow', 'pageChange', 'sortHeader']);
// 定义变量内容
const toolSetRef = ref();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
page: {
pageNum: 1,
pageSize: 10,
},
selectlist: [] as EmptyObjectType[],
checkListAll: true,
checkListIndeterminate: false,
});
// 设置边框显示/隐藏
const setBorder = computed(() => {
return props.config.isBorder ? true : false;
});
// 获取父组件 配置项(必传)
const getConfig = computed(() => {
return props.config;
});
// 设置 tool header 数据
const setHeader = computed(() => {
return props.header.filter((v) => v.isCheck);
});
// tool 列显示全选改变时
const onCheckAllChange = <T>(val: T) => {
if (val) props.header.forEach((v) => (v.isCheck = true));
else props.header.forEach((v) => (v.isCheck = false));
state.checkListIndeterminate = false;
};
// tool 列显示当前项改变时
const onCheckChange = () => {
const headers = props.header.filter((v) => v.isCheck).length;
state.checkListAll = headers === props.header.length;
state.checkListIndeterminate = headers > 0 && headers < props.header.length;
};
// 表格多选改变时,用于导出
const onSelectionChange = (val: EmptyObjectType[]) => {
state.selectlist = val;
};
// 删除当前项
const onDelRow = (row: EmptyObjectType) => {
emit('delRow', row);
};
// 分页改变
const onHandleSizeChange = (val: number) => {
state.page.pageSize = val;
emit('pageChange', state.page);
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.page.pageNum = val;
emit('pageChange', state.page);
};
// 搜索时,分页还原成默认
const pageReset = () => {
state.page.pageNum = 1;
state.page.pageSize = 10;
emit('pageChange', state.page);
};
// 打印
const onPrintTable = () => {
// https://printjs.crabbly.com/#documentation
// 自定义打印
let tableTh = '';
let tableTrTd = '';
let tableTd: any = {};
// 表头
props.header.forEach((v) => {
tableTh += `<th class="table-th">${v.title}</th>`;
});
// 表格内容
props.data.forEach((val, key) => {
if (!tableTd[key]) tableTd[key] = [];
props.header.forEach((v) => {
if (v.type === 'text') {
tableTd[key].push(`<td class="table-th table-center">${val[v.key]}</td>`);
} else if (v.type === 'image') {
tableTd[key].push(`<td class="table-th table-center"><img src="${val[v.key]}" style="width:${v.width}px;height:${v.height}px;"/></td>`);
}
});
tableTrTd += `<tr>${tableTd[key].join('')}</tr>`;
});
// 打印
printJs({
printable: `<div style=display:flex;flex-direction:column;text-align:center><h3>${props.printName}</h3></div><table border=1 cellspacing=0><tr>${tableTh}${tableTrTd}</table>`,
type: 'raw-html',
css: ['//at.alicdn.com/t/c/font_2298093_rnp72ifj3ba.css', '//unpkg.com/element-plus/dist/index.css'],
style: `@media print{.mb15{margin-bottom:15px;}.el-button--small i.iconfont{font-size: 12px !important;margin-right: 5px;}}; .table-th{word-break: break-all;white-space: pre-wrap;}.table-center{text-align: center;}`,
});
};
// 导出
const onImportTable = () => {
if (state.selectlist.length <= 0) return ElMessage.warning('请先选择要导出的数据');
table2excel(props.header, state.selectlist, `${themeConfig.value.globalTitle} ${new Date().toLocaleString()}`);
};
// 刷新
const onRefreshTable = () => {
emit('pageChange', state.page);
};
// 设置
const onSetTable = () => {
nextTick(() => {
const sortable = Sortable.create(toolSetRef.value, {
handle: '.handle',
dataIdAttr: 'data-key',
animation: 150,
onEnd: () => {
const headerList: EmptyObjectType[] = [];
sortable.toArray().forEach((val: string) => {
props.header.forEach((v) => {
if (v.key === val) headerList.push({ ...v });
});
});
emit('sortHeader', headerList);
},
});
});
};
// 暴露变量
defineExpose({
pageReset,
});
</script>
<style scoped lang="scss">
.table-container {
display: flex;
flex-direction: column;
.el-table {
flex: 1;
}
.table-footer {
display: flex;
.table-footer-tool {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
i {
margin-right: 10px;
cursor: pointer;
color: var(--el-text-color-regular);
&:last-of-type {
margin-right: 0;
}
}
}
}
}
</style>
+83 -83
View File
@@ -1,23 +1,23 @@
<template>
<el-form class='zform zform100' :inline="true" :model='form' label-width='160px' label-position='left'>
<div class="zform-div">
<el-form-item label='流程说明'>
这是一个测试的流程,目前功能还不完善
</el-form-item>
<el-form-item label='紧急程度'>
<el-radio-group v-model='form.opurg'>
<el-radio label='1'><span style='color:red'>紧急</span></el-radio>
<el-radio label='2'><span style='color:blue'>急</span></el-radio>
<el-radio label='3'><span>一般</span></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label='即将流向'>
{{ state.toExmen }}
</el-form-item>
<el-form-item label='处理意见'>
<el-input v-model='form.opnot' type='textarea' :rows='5' placeholder=' '/>
</el-form-item>
<el-form-item label='附件'>
<el-form class='zform zform100' :inline="true" :model='form' label-width='160px' label-position='left'>
<div class="zform-div">
<el-form-item label='流程说明'>
这是一个测试的流程,目前功能还不完善
</el-form-item>
<el-form-item label='紧急程度'>
<el-radio-group v-model='form.opurg'>
<el-radio label='1'><span style='color:red'>紧急</span></el-radio>
<el-radio label='2'><span style='color:blue'>急</span></el-radio>
<el-radio label='3'><span>一般</span></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label='即将流向'>
{{ state.toExmen }}
</el-form-item>
<el-form-item label='处理意见'>
<el-input v-model='form.opnot' type='textarea' :rows='5' placeholder=' '/>
</el-form-item>
<el-form-item label='附件'>
<el-upload
:action='state.uploadUrl' :headers='state.headers'
:on-preview='handlePreview' style='width:800px' :on-success='handleSuccess'
@@ -25,17 +25,17 @@
multiple :limit='10' :on-exceed='handleExceed' v-model:file-list='form.atts'>
<el-button type='primary'>上传附件</el-button>
</el-upload>
</el-form-item>
<div class="zform-item" style="padding-left: 6px">
<el-checkbox v-model='state.chtag' @change='toggleFlowChart'>流程图</el-checkbox>
</div>
<div class="zform-item" v-show='state.chtag'>
<div style='background-color: white;height:1000px'>
<Modeler2 />
</div>
</div>
</div>
</el-form>
</el-form-item>
<div class="zform-item" style="padding-left: 6px">
<el-checkbox v-model='state.chtag' @change='toggleFlowChart'>流程图</el-checkbox>
</div>
<div class="zform-item" v-show='state.chtag'>
<div style='background-color: white;height:1000px'>
<Modeler2 />
</div>
</div>
</div>
</el-form>
</template>
<script lang='ts' setup>
@@ -50,31 +50,31 @@ import { ElMessage, ElMessageBox, UploadProps } from 'element-plus';
const route = useRoute();
const state = reactive({
form: {opurg:'3'} as any,
chtag: true,
params: {
path: '',
query: '',
},
toExmen: '',
tarno:'',
xml: null as any,
form: {opurg:'3'} as any,
chtag: true,
params: {
path: '',
query: '',
},
toExmen: '',
tarno:'',
xml: null as any,
uploadUrl: '', headers: {} as any,
});
const {
form, params,
form, params,
} = toRefs(state);
onMounted(async () => {
state.uploadUrl = `${import.meta.env.VITE_API_URL}gen/oss/upload`;
state.headers = { 'Authorization': Session.get('token') };
state.params = <any>route;
if (params.value.query?.id) {
state.params = <any>route;
if (params.value.query?.id) {
}
toggleFlowChart();
}
toggleFlowChart();
});
@@ -90,7 +90,7 @@ const getOperateInfo = () => {
}
}
formInfo.atids=atids;
return formInfo;
return formInfo;
};
@@ -98,45 +98,45 @@ defineExpose({getOperateInfo});
const props = defineProps({
tmpid: String,
temid: String,
});
const toggleFlowChart = async () => {
if (!state.xml) {
const map= await request({
url: '/bpm/proc/main/texml',
method: 'get',
params:{tmpid:props.tmpid}
}) as any;
form.value.tarno = map.tarno;
form.value.tarna = map.tarna;
form.value.tamen = map.tamen;
state.toExmen = map.tarno + '.' + map.tarna + '(' + map.tamen + ')';
if (!state.xml) {
const map= await request({
url: '/bpm/proc/main/texml',
method: 'get',
params:{temid:props.temid}
}) as any;
form.value.tarno = map.tarno;
form.value.tarna = map.tarna;
form.value.tamen = map.tamen;
state.toExmen = map.tarno + '.' + map.tarna + '(' + map.tamen + ')';
state.xml=map.xml;
await BpmnStore.importXML(state.xml);
const elementRegistry = BpmnStore.getModeler().get('elementRegistry');
const list1 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'NS') {
list1.push(item);
}
});
state.xml=map.xml;
await BpmnStore.importXML(state.xml);
const elementRegistry = BpmnStore.getModeler().get('elementRegistry');
const list1 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'NS') {
list1.push(item);
}
});
const list2 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'L1') {
list2.push(item);
}
});
const list2 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'L1') {
list2.push(item);
}
});
const list3 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'N1') {
list3.push(item);
}
});
const list3 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'N1') {
list3.push(item);
}
});
const list4 = [] as any;
elementRegistry.forEach((item: any) => {
@@ -146,12 +146,12 @@ const toggleFlowChart = async () => {
});
let modeling = BpmnStore.modeler.get('modeling');
modeling.setColor(list1, {fill: '#e4feef'});
modeling.setColor(list2, {stroke: '#009900'});
modeling.setColor(list3, {fill: '#fed6d6'});
modeling.setColor(list4, {fill: '#e3f1ff'});
}
let modeling = BpmnStore.modeler.get('modeling');
modeling.setColor(list1, {fill: '#e4feef'});
modeling.setColor(list2, {stroke: '#009900'});
modeling.setColor(list3, {fill: '#fed6d6'});
modeling.setColor(list4, {fill: '#e3f1ff'});
}
};
//region -----附件逻辑-----
+71
View File
@@ -0,0 +1,71 @@
<template>
<div>
<OrgModal ref='orgModal' @close='closeOrgModal' />
<el-dialog v-model='state.isShow' :title='state.title' width='600px' draggable>
<div style='height: 500px'>
<el-form ref='formRef' :model='form' label-width='140px'>
<div class='zform-div'>
<el-form-item label='处理意见'>
<el-input v-model='form.opnot' type='textarea' :rows='4' placeholder='待实现' />
</el-form-item>
<el-form-item label='操作'>
<el-radio-group v-model='form.opkey' @change='opChange'>
<el-radio label='finalpass'>终审通过</el-radio>
<el-radio label='jump'>前后跳转</el-radio>
<el-radio label='abandon'>直接废弃</el-radio>
</el-radio-group>
</el-form-item>
</div>
</el-form>
</div>
<template #footer>
<span class='dialog-footer'>
<el-button type='primary' @click='closeModal'>确认</el-button>
<el-button @click='state.isShow = false'>取消</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang='ts' setup>
import { defineExpose, reactive, toRefs } from 'vue';
import OrgModal from '/@/comps/sys/OrgModal.vue';
const state = reactive({
isShow: false, basid: '',title:'',cotag:false,name:'节点名称:',notes:'节点备注:',
list: [] as any, form: {
flway: '1', remod: '1', opway: '1',type:'bpmn:UserTask',
crops: [
{ ornum: 1, type: '1', name: '催办' },
{ ornum: 2, type: '2', name: '撤回' },
{ ornum: 3, type: '3', name: '废弃' },
],
haops: [
{ ornum: 1, type: '1', name: '通过' },
{ ornum: 2, type: '2', name: '驳回' },
{ ornum: 3, type: '3', name: '转办' },
{ ornum: 4, type: '4', name: '沟通' },
{ ornum: 5, type: '5', name: '废弃' },
],
events: [],
} as any,
tabna: '',
});
const open = async () => {
// console.log(shape.businessObject.$attrs);
state.isShow = true;
};
defineExpose({ open });
const { form } = toRefs(state);
</script>
<style scoped>
</style>
@@ -202,6 +202,7 @@ const closeNodeModal = (data: any) => {
name: data.name,
hamen: data.hamen,
flway: data.flway,
hatyp: data.hatyp,
// data:JSON.stringify(data),
});
}
+122
View File
@@ -0,0 +1,122 @@
<template>
<el-dialog v-model='dialogVisible' title='公式定义器' draggable width='900px'>
<div style='height: 440px'>
<el-row gutter='8'>
<el-col :span='8'>
<el-card class='box-card' style='height: 100%;' body-style='height: 100%;overflow: auto'>
<template #header>
<div class='card-header'>
<div class='tree-h-flex'>
<div class='tree-h-left'>
<el-input :prefix-icon='Search' v-model='filterText' placeholder='输入名称查询' />
</div>
<div class='tree-h-right'>
<el-dropdown @command='handleCommand'>
<el-button style='margin-left: 8px;width: 34px'>
<el-icon class='el-icon--center'>
<MoreFilled />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command='expandAll'>全部展开</el-dropdown-item>
<el-dropdown-item command='collapseAll'>全部折叠</el-dropdown-item>
<el-dropdown-item command='rootNode'>根节点</el-dropdown-item>
<el-dropdown-item command='refresh'>刷新</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<div style='margin-bottom: 45px'>
<el-tree highlight-current ref='treeRef' class='filter-tree' :data='state.data' :props='defaultProps' :filter-node-method='filterNode' @node-click='nodeClick' />
</div>
</el-card>
</el-col>
<el-col :span='16' >
<el-card class='box-card' style='height: 100%;' body-style='height: 100%;overflow: auto'>
<template #header>
<div class='card-header'>
<div class='zjustify'>
<div>
<!-- <el-input v-model='state.form.name' placeholder='输入名称回车查询' class='list-search' clearable @keyup.enter='listQuery()' />-->
<!-- <el-button type='primary' @click='listQuery()' plain>查 询</el-button>-->
</div>
<div>
<el-button type='primary' @click='handleConfirm'>确 认</el-button>
<el-button @click='dialogVisible = false'>取 消</el-button>
</div>
</div>
</div>
</template>
<div style='margin-bottom: 45px'>
<el-input type='textarea' placeholder='测试' :rows='8' v-model='state.form.func'/>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-dialog>
</template>
<script lang='ts' setup>
import { defineExpose, onMounted, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import request from '/@/utils/request';
const state = reactive({
loading: true, ids: [],
form: {func:''}, single: true, multiple: true, list: [], total: 0,
});
const props = defineProps({
url: String,
maInit: Boolean,
});
onMounted(() => {
});
const dialogVisible = ref(false);
const open = async (data: any) => {
dialogVisible.value = true;
};
defineExpose({ open });
const emits = defineEmits(['close']);
const handleConfirm = () => {
// const chooseIds = state.ids.join(',');
// if (chooseIds.length <= 0) {
// ElMessage.warning('请先点击选择项');
// return;
// } else if (state.ids.length >= 2) {
// ElMessage.warning('请选择单个选择项,不要多选');
// return;
// }
emits('close', { func: state.form.func });
dialogVisible.value = false;
};
</script>
<style scoped>
.tree-h-flex {
display: flex;
}
.tree-h-left {
flex: 1;
width: 100%;
}
.tree-h-right {
width: 42px;
min-width: 42px;
}
</style>
@@ -1,6 +1,7 @@
<template>
<div>
<OrgModal ref='orgModal' @close='closeOrgModal' />
<FuncModal ref='funcModal' @close='closeFuncModal' />
<el-dialog v-model='state.isShow' :title='state.title' width='600px' draggable>
<div style='height: 500px'>
<el-form ref='formRef' :model='form' label-width='140px'>
@@ -11,7 +12,7 @@
<el-input v-model='form.name' />
</el-form-item>
<el-form-item label='审批人类型:' v-show='cdata.show1||cdata.show2'>
<el-radio-group v-model='form.hatyp'>
<el-radio-group v-model='form.hatyp' @change='hatypChange'>
<el-radio label='1'>从组织架构选择</el-radio>
<el-radio label='2'>使用公式定义器</el-radio>
</el-radio-group>
@@ -38,7 +39,7 @@
</el-form-item>
<el-form-item label='流转条件:' v-show='state.cotag'>
<el-input v-model='form.conds' type='textarea'
placeholder='编写JS代码,返回true,则往下流。(注1:$代表form对象;注2:如果是表达式,则表达式为返回结果;注3:如果是复杂的多条语句,则z代表返回结果;)' :rows='8' />
placeholder='编写JS代码,返回true,则往下流。(注1:$代表form对象;注2:如果是表达式,则表达式为返回结果;注3:如果是复杂的多条语句,则z代表返回结果;)' :rows='8' />
</el-form-item>
<el-form-item :label='state.notes'>
<el-input v-model='form.notes' type='textarea' :rows='4' />
@@ -185,6 +186,7 @@
<script lang='ts' setup>
import { computed, defineExpose, reactive, ref, toRaw, toRefs } from 'vue';
import OrgModal from '/@/comps/sys/OrgModal.vue';
import FuncModal from './func.vue';
import { uuid } from '/@/utils/xutil';
import request from '/@/utils/request';
@@ -216,8 +218,8 @@ const open = async (shape: any) => {
state.form.id = shape.businessObject.id;
console.log(state.form.type);
state.cotag=false;
state.name='节点名称:';
state.notes='节点备注:';
state.name='节点名称:';
state.notes='节点备注:';
switch (state.form.type) {
case "bpmn:UserTask":
state.title="审批节点 "+state.form.id;
@@ -248,8 +250,8 @@ const open = async (shape: any) => {
break;
case "bpmn:SequenceFlow":
state.title="连线 "+state.form.id;
state.name='连线名称:';
state.notes='连线备注:';
state.name='连线名称:';
state.notes='连线备注:';
if(shape.source.type=="bpmn:ExclusiveGateway"){
state.cotag=true;
}
@@ -271,14 +273,19 @@ const open = async (shape: any) => {
}
state.form.conds = shape.businessObject.$attrs.conds;
if (shape.businessObject.$attrs.hamen) {
state.form.hamen = await request({
url: '/gen/org/main/list',
method: 'get',
params: { ids: shape.businessObject.$attrs.hamen },
});
if(state.form.hatyp=="1"){
state.form.hamen = await request({
url: '/gen/org/main/list',
method: 'get',
params: { ids: shape.businessObject.$attrs.hamen },
});
}else{
state.form.hamen=shape.businessObject.$attrs.hamen;
}
} else {
state.form.hamen = [];
}
// console.log(shape.businessObject.$attrs);
state.isShow = true;
};
@@ -288,14 +295,18 @@ const emits = defineEmits(['close']);
const closeModal = () => {
const backData = {} as any;
backData.name = state.form.name;
let hamen = '';
for (const haman of state.form.hamen) {
hamen += haman.id + ';';
if(state.form.hatyp=="1"){
let hamen = '';
for (const haman of state.form.hamen) {
hamen += haman.id + ';';
}
if (hamen != '') {
hamen = hamen.substring(0, hamen.length - 1);
}
backData.hamen = hamen;
}else{
backData.hamen = state.form.hamen;
}
if (hamen != '') {
hamen = hamen.substring(0, hamen.length - 1);
}
backData.hamen = hamen;
backData.flway = state.form.flway;
backData.hatyp = state.form.hatyp;
if(state.cotag){
@@ -308,14 +319,22 @@ const closeModal = () => {
const cdata = reactive({}) as any;
//region -----组织架构逻辑-----
const orgModal = ref();
const funcModal = ref();
const hatypChange=()=>{
form.value.hamen=null;
}
const hamenModal = () => {
orgModal.value.openModal({
opener: 'hamen',
orgType: 44,
selectMode: 2,
orgs: toRaw(form.value.hamen),
});
if(state.form.hatyp=="1"){
orgModal.value.openModal({
opener: 'hamen',
orgType: 44,
selectMode: 2,
orgs: toRaw(form.value.hamen),
});
}else if(state.form.hatyp=="2"){
funcModal.value.open({});
}
};
const closeOrgModal = (data: any) => {
@@ -340,17 +359,27 @@ const closeOrgModal = (data: any) => {
}
};
const closeFuncModal = (data: any) => {
form.value.hamen = data.func;
};
cdata.hamen = computed(() => {
let names = '';
if (form.value.hamen && form.value.hamen.length > 0) {
for (const user of form.value.hamen) {
names += user.name + ';';
if(form.value.hatyp=='1'){
if (form.value.hamen && form.value.hamen.length > 0) {
for (const user of form.value.hamen) {
names += user.name + ';';
}
}
if (names != '') {
names = names.substring(0, names.length - 1);
}
return names;
}else if(form.value.hatyp=='2'){
return form.value.hamen;
}
if (names != '') {
names = names.substring(0, names.length - 1);
}
return names;
});
cdata.show1 = computed(() => {
+37 -8
View File
@@ -2,7 +2,14 @@
<el-form class='zform zform100' :inline='true' :model='form' label-width='160px' label-position='left'>
<div class='zform-div'>
<el-form-item label='流程说明'>
这是一个测试的流程,目前功能还不完善
<div class='zjustify' style='width: 100%'>
<div>
这是一个测试的流程,目前功能还不完善
</div>
<div>
<el-button type='success' @click='pmanage()' plain>特权人处理</el-button>
</div>
</div>
</el-form-item>
<div class='zform-item' style='padding-left: 6px'>
<el-checkbox v-model='state.autag'>显示审批记录</el-checkbox>
@@ -115,6 +122,7 @@
</div>
</div>
<OrgModal ref='orgModal' @close='closeOrgModal' />
<Pmodal ref='pmodal' @close='closePmodal' />
</div>
</el-form>
</template>
@@ -132,6 +140,7 @@ import { ElMessage, ElMessageBox, UploadProps } from 'element-plus';
// import Viewer from 'bpmn-js/lib/Modeler';
// import Viewer from 'bpmn-js/lib/Viewer';
import OrgModal from '/@/comps/sys/OrgModal.vue';
import Pmodal from './pmodal.vue';
import vdownload from '/@/assets/v-download.png';
import { Session } from '/@/utils/storage';
@@ -229,13 +238,22 @@ const bpmInit = async () => {
form.value.opurg = 'a';
if (result.zbpm.tasty === 'review') {
state.opways = [
{ id: 'pass', name: '通过' },
{ id: 'refuse', name: '驳回' },
{ id: 'turn', name: '转办' },
{ id: 'communicate', name: '沟通' },
{ id: 'abandon', name: '废弃' },
];
if(form.value.facno=="N1"){
state.opways = [
{ id: 'pass', name: '通过' },
{ id: 'turn', name: '转办' },
{ id: 'communicate', name: '沟通' },
{ id: 'abandon', name: '废弃' },
];
}else{
state.opways = [
{ id: 'pass', name: '通过' },
{ id: 'refuse', name: '驳回' },
{ id: 'turn', name: '转办' },
{ id: 'communicate', name: '沟通' },
{ id: 'abandon', name: '废弃' },
];
}
}else if(result.zbpm.tasty === 'to_communicate'){
state.opways = [
{ id: 'communicate', name: '沟通' },
@@ -581,6 +599,17 @@ const downloadAtt=async (id:string)=>{
}
//endregion
const pmodal = ref();
const pmanage= ()=>{
pmodal.value.open();
}
const closePmodal = (data: any) => {
alert(data);
};
</script>
<style scoped>
@@ -1,6 +1,6 @@
import type { App } from 'vue';
import { authDirective } from '/@/utils/authDirective';
import { wavesDirective, dragDirective } from '/@/utils/customDirective';
import { authDirective } from '/@/directive/authDirective';
import { wavesDirective, dragDirective } from '/@/directive/customDirective';
/**
* 导出指令方法:v-xxx
+42 -42
View File
@@ -2,53 +2,53 @@ 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 为各界面的国际化内容
* 须在 pages 下新建文件夹(建议 `要国际化界面目录` 与 `i18n 目录` 相同,方便查找),
* 注意国际化定义的字段,不要与原有的定义字段相同。
* 1、/src/i18n/lang 下的 ts 为框架的国际化内容
* 2、/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,
},
},
};
// element plus 自带国际化
import enLocale from 'element-plus/lib/locale/lang/en';
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
// 定义变量内容
const messages = {};
const element = { en: enLocale, 'zh-cn': zhcnLocale, 'zh-tw': zhtwLocale };
const itemize = { en: [], 'zh-cn': [], 'zh-tw': [] };
const modules: Record<string, any> = import.meta.glob('./**/*.ts', { eager: true });
// 对自动引入的 modules 进行分类 en、zh-cn、zh-tw
// https://vitejs.cn/vite3-cn/guide/features.html#glob-import
for (const path in modules) {
const key = path.match(/(\S+)\/(\S+).ts/);
if (itemize[key![2]]) itemize[key![2]].push(modules[path].default);
else itemize[key![2]] = modules[path];
}
// 合并数组对象(非标准数组对象,数组中对象的每项 key、value 都不同)
function mergeArrObj<T>(list: T, key: string) {
let obj = {};
list[key].forEach((i: EmptyObjectType) => {
obj = Object.assign({}, obj, i);
});
return obj;
}
// 处理最终格式
for (const key in itemize) {
messages[key] = {
name: key,
el: element[key].el,
message: mergeArrObj(itemize, key),
};
}
// 读取 pinia 默认语言
const stores = useThemeConfig(pinia);
+10
View File
@@ -59,6 +59,7 @@ export default {
makeSelector: 'Icon selector',
makeNoticeBar: 'notification bar',
makeSvgDemo: 'Svgicon demo',
makeTableDemo: 'table demo',
paramsIndex: 'Routing parameters',
paramsCommon: 'General routing',
paramsDynamic: 'Dynamic routing',
@@ -136,6 +137,7 @@ 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',
@@ -179,4 +181,12 @@ 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',
},
};
+10
View File
@@ -59,6 +59,7 @@ export default {
makeSelector: '图标选择器',
makeNoticeBar: '滚动通知栏',
makeSvgDemo: 'svgIcon 演示',
makeTableDemo: '表格封装演示',
paramsIndex: '路由参数',
paramsCommon: '普通路由',
paramsDynamic: '动态路由',
@@ -136,6 +137,7 @@ export default {
twoIsTopBarColorGradual: '顶栏背景渐变',
twoMenuBar: '菜单背景',
twoMenuBarColor: '菜单默认字体颜色',
twoMenuBarActiveColor: '菜单高亮背景色',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
@@ -179,4 +181,12 @@ export default {
copyTextSuccess: '复制成功!',
copyTextError: '复制失败!',
},
upgrade: {
title: '新版本升级',
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
desc: '提示:更新会还原默认配置',
btnOne: '残忍拒绝',
btnTwo: '马上更新',
btnTwoLoading: '更新中',
},
};
+10
View File
@@ -59,6 +59,7 @@ export default {
makeSelector: '圖標選擇器',
makeNoticeBar: '滾動通知欄',
makeSvgDemo: 'svgIcon 演示',
makeTableDemo: '表格封裝演示',
paramsIndex: '路由參數',
paramsCommon: '普通路由',
paramsDynamic: '動態路由',
@@ -136,6 +137,7 @@ export default {
twoIsTopBarColorGradual: '頂欄背景漸變',
twoMenuBar: '選單背景',
twoMenuBarColor: '選單默認字體顏色',
twoMenuBarActiveColor: '選單高亮背景色',
twoIsMenuBarColorGradual: '選單背景漸變',
twoColumnsMenuBar: '分欄選單背景',
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
@@ -179,4 +181,12 @@ export default {
copyTextSuccess: '複製成功!',
copyTextError: '複製失敗!',
},
upgrade: {
title: '新版本陞級',
msg: '新版本來啦,馬上更新嘗鮮吧! 不用擔心,更新很快的哦!',
desc: '提示:更新會還原默認配寘',
btnOne: '殘忍拒絕',
btnTwo: '馬上更新',
btnTwoLoading: '更新中',
},
};
-1
View File
File diff suppressed because one or more lines are too long
+140 -147
View File
@@ -3,163 +3,156 @@
<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" />
<Vertical :menuList="state.menuList" />
</el-scrollbar>
</el-aside>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, toRefs, reactive, computed, watch, onBeforeMount, defineComponent, ref } from 'vue';
<script setup lang="ts" name="layoutAside">
import { defineAsyncComponent, reactive, computed, watch, onBeforeMount, 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),
};
},
// 引入组件
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
const Vertical = defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue'));
// 定义变量内容
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<AsideState>({
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' || layout === 'classic') {
// 分栏布局、经典布局,菜单收起时宽度给 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'];
}
}
});
// 设置显示/隐藏 logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = themeConfig.value;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
});
// 关闭移动端蒙版
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', '');
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
if (themeConfig.value.layout === 'columns') return false;
state.menuList = filterRoutesFun(routesList.value);
};
// 路由过滤递归函数
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
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');
// 开启 `分栏菜单鼠标悬停预加载` 才设置,防止 columnsAside.vue 监听 pinia.state
if (themeConfig.value.isColumnsMenuHoverPreload) stores.setColumnsMenuHover(bool);
};
// 页面加载前
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
mittBus.on('setSendColumnsChildren', (res: MittMenu) => {
state.menuList = res.children;
});
// 开启经典布局分割菜单时,设置菜单数据
mittBus.on('setSendClassicChildren', (res: MittMenu) => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
// 经典布局分割菜单只要一项子级时,收起左侧导航菜单
res.children.length <= 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false);
state.menuList = [];
state.menuList = res.children;
}
});
// 开启经典布局分割菜单时,重新处理菜单数据
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
// 监听窗口大小改变时(适配移动端)
mittBus.on('layoutMobileResize', (res: LayoutMobileResize) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
() => [themeConfig.value.isShowLogoChange, themeConfig.value.isShowLogo, themeConfig.value.layout, themeConfig.value.isClassicSplitMenu],
([isShowLogoChange, isShowLogo, layout, isClassicSplitMenu]) => {
if (isShowLogoChange !== isShowLogo) {
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
if (layout === 'classic' && isClassicSplitMenu) return false;
}
);
// 监听用户权限切换,用于演示 `权限管理 -> 前端控制 -> 页面权限` 权限切换不生效
watch(
() => routesList.value,
() => {
setFilterRoutes();
}
);
</script>
+172 -166
View File
@@ -3,16 +3,16 @@
<el-scrollbar>
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
<li
v-for="(v, k) in columnsAsideList"
v-for="(v, k) in state.columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v, k)"
@click="onColumnsAsideMenuClick(v)"
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
:ref="
(el) => {
if (el) columnsAsideOffsetTopRefs[k] = el;
}
"
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
:class="{ 'layout-columns-active': state.liIndex === k, 'layout-columns-hover': state.liHoverIndex === k }"
:title="$t(v.meta.title)"
>
<div :class="themeConfig.columnsAsideLayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
@@ -44,175 +44,180 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, ref, onMounted, nextTick, watch, onUnmounted, defineComponent } from 'vue';
<script setup lang="ts" name="layoutColumnsAside">
import { reactive, ref, onMounted, nextTick, watch, onUnmounted } 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),
};
},
// 定义变量内容
const columnsAsideOffsetTopRefs = ref<RefType>([]);
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) => {
if (k === undefined) return false;
state.liIndex = k;
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
};
// 菜单高亮点击事件
const onColumnsAsideMenuClick = async (v: RouteItem) => {
let { path, redirect } = v;
if (redirect) {
onColumnsAsideDown(v.k);
if (route.path.startsWith(redirect)) mittBus.emit('setSendColumnsChildren', setSendChildren(redirect));
else router.push(redirect);
} else {
if (!v.children) {
router.push(path);
} else {
// 显示子级菜单
const resData: MittMenu = setSendChildren(path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item?.k);
mittBus.emit('setSendColumnsChildren', resData);
}
}
// 一个路由设置自动收起菜单
// https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
if (!v.children) themeConfig.value.isCollapse = true;
else if (v.children.length > 1) themeConfig.value.isCollapse = false;
};
// 鼠标移入时,显示当前的子级菜单
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 () => {
if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
await stores.setColumnsNavHover(false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
}, 100);
};
// 设置高亮动态位置
const onColumnsAsideDown = (k: number) => {
nextTick(() => {
setColumnsAsideMove(k);
});
};
// 设置只有一个路由时设置自动收起菜单
// https://gitee.com/lyt-top/vue-next-admin/issues/I6UW2I
const setMenuAutoCollaps = (path: string) => {
const resData: MittMenu = setSendChildren(path);
// https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
resData.children.length <= 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false);
return resData;
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(routesList.value);
const resData: MittMenu = setMenuAutoCollaps(route.path);
onColumnsAsideDown(resData.item?.k);
// 延迟 500 毫秒更新,防止 aside.vue 组件 setSendColumnsChildren 还没有注册
setTimeout(() => {
mittBus.emit('setSendColumnsChildren', resData);
}, 500);
};
// 传送当前子级数据到菜单中
const setSendChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: MittMenu = { children: [] };
state.columnsAsideList.map((v: RouteItem, 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 = <T extends RouteItem>(arr: T[]): T[] => {
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
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: RouteItem) => v.path === routeFirst);
if (!currentSplitRoute) return false;
// 延迟拿值,防止取不到
setTimeout(() => {
onColumnsAsideDown(currentSplitRoute.k);
}, 0);
};
// 页面加载时
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
const resData = setMenuAutoCollaps(to.path);
setColumnsMenuHighlight(to.path);
mittBus.emit('setSendColumnsChildren', resData);
});
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
watch(
[() => themeConfig.value.columnsAsideStyle, isColumnsMenuHover, isColumnsNavHover],
() => {
themeConfig.value.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!isColumnsMenuHover.value && !isColumnsNavHover.value) {
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,
}
);
</script>
<style scoped lang="scss">
@@ -222,7 +227,8 @@ export default defineComponent({
background: var(--next-bg-columnsMenuBar);
ul {
position: relative;
.layout-columns-active {
.layout-columns-active,
.layout-columns-active a {
color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out;
}
+8 -15
View File
@@ -4,22 +4,15 @@
</el-header>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
<script setup lang="ts" name="layoutHeader">
import { defineAsyncComponent } 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,
};
},
});
// 引入组件
const NavBarsIndex = defineAsyncComponent(() => import('/@/layout/navBars/index.vue'));
// 定义变量内容
const storesTagsViewRoutes = useTagsViewRoutes();
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
</script>
+49 -43
View File
@@ -1,59 +1,65 @@
<template>
<el-main class="layout-main" :style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll" wrap-class="layout-main-scroll" view-class="layout-main-scroll">
<el-scrollbar
ref="layoutMainScrollbarRef"
class="layout-main-scroll layout-backtop-header-fixed"
wrap-class="layout-main-scroll"
view-class="layout-main-scroll"
>
<LayoutParentView />
<LayoutFooter v-if="isFooter" />
</el-scrollbar>
<el-backtop target=".layout-backtop .el-scrollbar__wrap" />
<el-backtop :target="setBacktopClass" />
</el-main>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent, onMounted, computed, ref } from 'vue';
<script setup lang="ts" name="layoutMain">
import { defineAsyncComponent, onMounted, computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import { NextLoading } from '/@/utils/loading';
export default defineComponent({
name: 'layoutMain',
components: {
LayoutParentView: defineAsyncComponent(() => import('/@/layout/routerView/parent.vue')),
LayoutFooter: defineAsyncComponent(() => import('/@/layout/footer/index.vue')),
},
setup() {
const layoutMainScrollbarRef = ref('');
const route = useRoute();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置 footer 显示/隐藏
const isFooter = computed(() => {
return themeConfig.value.isFooter && !route.meta.isIframe;
});
// 设置 header 固定
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
});
// 设置主内容区的高度
const setMainHeight = computed(() => {
if (isTagsViewCurrenFull.value) return '0px';
const { isTagsview, layout } = themeConfig.value;
if (isTagsview && layout !== 'classic') return '85px';
else return '51px';
});
// 页面加载前
onMounted(() => {
NextLoading.done(600);
});
return {
layoutMainScrollbarRef,
isFooter,
isFixedHeader,
setMainHeight,
};
},
// 引入组件
const LayoutParentView = defineAsyncComponent(() => import('/@/layout/routerView/parent.vue'));
const LayoutFooter = defineAsyncComponent(() => import('/@/layout/footer/index.vue'));
// 定义变量内容
const layoutMainScrollbarRef = ref();
const route = useRoute();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置 footer 显示/隐藏
const isFooter = computed(() => {
return themeConfig.value.isFooter && !route.meta.isIframe;
});
// 设置 header 固定
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
});
// 设置 Backtop 回到顶部
const setBacktopClass = computed(() => {
if (themeConfig.value.isFixedHeader) return `.layout-backtop-header-fixed .el-scrollbar__wrap`;
else return `.layout-backtop .el-scrollbar__wrap`;
});
// 设置主内容区的高度
const setMainHeight = computed(() => {
if (isTagsViewCurrenFull.value) return '0px';
const { isTagsview, layout } = themeConfig.value;
if (isTagsview && layout !== 'classic') return '85px';
else return '51px';
});
// 页面加载前
onMounted(() => {
NextLoading.done(600);
});
// 暴露变量
defineExpose({
layoutMainScrollbarRef,
});
</script>
+2 -6
View File
@@ -7,12 +7,8 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'layoutFooter',
});
<script setup lang="ts" name="layoutFooter">
// 此处需有内容(注释也得),否则缓存将失败
</script>
<style scoped lang="scss">
+38 -42
View File
@@ -1,54 +1,50 @@
<template>
<component :is="themeConfig.layout" />
<component :is="layouts[themeConfig.layout]" />
</template>
<script lang="ts">
import { onBeforeMount, onUnmounted, defineComponent, defineAsyncComponent } from 'vue';
<script setup lang="ts" name="layout">
import { onBeforeMount, onUnmounted, defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layout',
components: {
defaults: defineAsyncComponent(() => import('/@/layout/main/defaults.vue')),
classic: defineAsyncComponent(() => import('/@/layout/main/classic.vue')),
transverse: defineAsyncComponent(() => import('/@/layout/main/transverse.vue')),
columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
},
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 窗口大小改变时(适配移动端)
const onLayoutResize = () => {
if (!Local.get('oldLayout')) Local.set('oldLayout', themeConfig.value.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
themeConfig.value.isCollapse = false;
mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
} else {
mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
clientWidth,
});
}
};
// 页面加载前
onBeforeMount(() => {
onLayoutResize();
window.addEventListener('resize', onLayoutResize);
// 引入组件
const layouts: any = {
defaults: defineAsyncComponent(() => import('/@/layout/main/defaults.vue')),
classic: defineAsyncComponent(() => import('/@/layout/main/classic.vue')),
transverse: defineAsyncComponent(() => import('/@/layout/main/transverse.vue')),
columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
};
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 窗口大小改变时(适配移动端)
const onLayoutResize = () => {
if (!Local.get('oldLayout')) Local.set('oldLayout', themeConfig.value.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
themeConfig.value.isCollapse = false;
mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
// 页面卸载时
onUnmounted(() => {
window.removeEventListener('resize', onLayoutResize);
} else {
mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
clientWidth,
});
return {
themeConfig,
};
},
}
};
// 页面加载前
onBeforeMount(() => {
onLayoutResize();
window.addEventListener('resize', onLayoutResize);
});
// 页面卸载时
onUnmounted(() => {
window.removeEventListener('resize', onLayoutResize);
});
</script>
+137 -158
View File
@@ -1,23 +1,23 @@
<template>
<div v-show="isShowLockScreen">
<div v-show="state.isShowLockScreen">
<div class="layout-lock-screen-mask"></div>
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': isShowLoockLogin }"></div>
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': state.isShowLoockLogin }"></div>
<div class="layout-lock-screen">
<div
class="layout-lock-screen-date"
ref="layoutLockScreenDateRef"
@mousedown="onDown"
@mousemove="onMove"
@mousedown="onDownPc"
@mousemove="onMovePc"
@mouseup="onEnd"
@touchstart.stop="onDown"
@touchmove.stop="onMove"
@touchstart.stop="onDownApp"
@touchmove.stop="onMoveApp"
@touchend.stop="onEnd"
>
<div class="layout-lock-screen-date-box">
<div class="layout-lock-screen-date-box-time">
{{ time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ time.s }}</span>
{{ state.time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ state.time.s }}</span>
</div>
<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
<div class="layout-lock-screen-date-box-info">{{ state.time.mdq }}</div>
</div>
<div class="layout-lock-screen-date-top">
<SvgIcon name="ele-Top" />
@@ -25,7 +25,7 @@
</div>
</div>
<transition name="el-zoom-in-center">
<div v-show="isShowLoockLogin" class="layout-lock-screen-login">
<div v-show="state.isShowLoockLogin" class="layout-lock-screen-login">
<div class="layout-lock-screen-login-box">
<div class="layout-lock-screen-login-box-img">
<img src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
@@ -35,7 +35,7 @@
<el-input
placeholder="请输入密码"
ref="layoutLockScreenInputRef"
v-model="lockScreenPassword"
v-model="state.lockScreenPassword"
@keyup.enter.native.stop="onLockScreenSubmit()"
>
<template #append>
@@ -59,160 +59,139 @@
</div>
</template>
<script lang="ts">
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, defineComponent } from 'vue';
<script setup lang="ts" name="layoutLockScreen">
import { nextTick, onMounted, reactive, ref, onUnmounted } from 'vue';
import { formatDate } from '/@/utils/formatTime';
import { Local } from '/@/utils/storage';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
// 定义接口来定义对象的类型
interface LockScreenState {
transparency: number;
downClientY: number;
moveDifference: number;
isShowLoockLogin: boolean;
isFlags: boolean;
querySelectorEl: HTMLElement | string;
// 定义变量内容
const layoutLockScreenDateRef = ref<HtmlType>();
const layoutLockScreenInputRef = ref();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
transparency: 1,
downClientY: 0,
moveDifference: 0,
isShowLoockLogin: false,
isFlags: false,
querySelectorEl: '' as HtmlType,
time: {
hm: string;
s: string;
mdq: string;
};
setIntervalTime: number;
isShowLockScreen: boolean;
isShowLockScreenIntervalTime: number;
lockScreenPassword: string;
}
export default defineComponent({
name: 'layoutLockScreen',
setup() {
const layoutLockScreenDateRef = ref();
const layoutLockScreenInputRef = ref();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<LockScreenState>({
transparency: 1,
downClientY: 0,
moveDifference: 0,
isShowLoockLogin: false,
isFlags: false,
querySelectorEl: '',
time: {
hm: '',
s: '',
mdq: '',
},
setIntervalTime: 0,
isShowLockScreen: false,
isShowLockScreenIntervalTime: 0,
lockScreenPassword: '',
});
// 鼠标按下
const onDown = (down: any) => {
state.isFlags = true;
state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
};
// 鼠标移动
const onMove = (move: any) => {
if (state.isFlags) {
const el = <HTMLElement>state.querySelectorEl;
const opacitys = (state.transparency -= 1 / 200);
if (move.touches) {
state.moveDifference = move.touches[0].clientY - state.downClientY;
} else {
state.moveDifference = move.clientY - state.downClientY;
}
if (state.moveDifference >= 0) return false;
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
if (state.moveDifference < -400) {
el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
state.moveDifference = -el.clientHeight;
setTimeout(() => {
el && el.parentNode?.removeChild(el);
}, 300);
}
if (state.moveDifference === -el.clientHeight) {
state.isShowLoockLogin = true;
layoutLockScreenInputRef.value.focus();
}
}
};
// 鼠标松开
const onEnd = () => {
state.isFlags = false;
state.transparency = 1;
if (state.moveDifference >= -400) {
(<HTMLElement>state.querySelectorEl).setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
}
};
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = layoutLockScreenDateRef.value;
});
};
// 时间初始化
const initTime = () => {
state.time.hm = formatDate(new Date(), 'HH:MM');
state.time.s = formatDate(new Date(), 'SS');
state.time.mdq = formatDate(new Date(), 'mm月dd日,WWW');
};
// 时间初始化定时器
const initSetTime = () => {
initTime();
state.setIntervalTime = window.setInterval(() => {
initTime();
}, 1000);
};
// 锁屏时间定时器
const initLockScreen = () => {
if (themeConfig.value.isLockScreen) {
state.isShowLockScreenIntervalTime = window.setInterval(() => {
if (themeConfig.value.lockScreenTime <= 1) {
state.isShowLockScreen = true;
setLocalThemeConfig();
return false;
}
themeConfig.value.lockScreenTime--;
}, 1000);
} else {
clearInterval(state.isShowLockScreenIntervalTime);
}
};
// 存储布局配置
const setLocalThemeConfig = () => {
themeConfig.value.isDrawer = false;
Local.set('themeConfig', themeConfig.value);
};
// 密码输入点击事件
const onLockScreenSubmit = () => {
themeConfig.value.isLockScreen = false;
themeConfig.value.lockScreenTime = 30;
setLocalThemeConfig();
};
// 页面加载时
onMounted(() => {
initGetElement();
initSetTime();
initLockScreen();
});
// 页面卸载时
onUnmounted(() => {
window.clearInterval(state.setIntervalTime);
window.clearInterval(state.isShowLockScreenIntervalTime);
});
return {
layoutLockScreenDateRef,
layoutLockScreenInputRef,
onDown,
onMove,
onEnd,
onLockScreenSubmit,
...toRefs(state),
};
hm: '',
s: '',
mdq: '',
},
setIntervalTime: 0,
isShowLockScreen: false,
isShowLockScreenIntervalTime: 0,
lockScreenPassword: '',
});
// 鼠标按下 pc
const onDownPc = (down: MouseEvent) => {
state.isFlags = true;
state.downClientY = down.clientY;
};
// 鼠标按下 app
const onDownApp = (down: TouchEvent) => {
state.isFlags = true;
state.downClientY = down.touches[0].clientY;
};
// 鼠标移动 pc
const onMovePc = (move: MouseEvent) => {
state.moveDifference = move.clientY - state.downClientY;
onMove();
};
// 鼠标移动 app
const onMoveApp = (move: TouchEvent) => {
state.moveDifference = move.touches[0].clientY - state.downClientY;
onMove();
};
// 鼠标移动事件
const onMove = () => {
if (state.isFlags) {
const el = <HTMLElement>state.querySelectorEl;
const opacitys = (state.transparency -= 1 / 200);
if (state.moveDifference >= 0) return false;
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
if (state.moveDifference < -400) {
el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
state.moveDifference = -el.clientHeight;
setTimeout(() => {
el && el.parentNode?.removeChild(el);
}, 300);
}
if (state.moveDifference === -el.clientHeight) {
state.isShowLoockLogin = true;
layoutLockScreenInputRef.value.focus();
}
}
};
// 鼠标松开
const onEnd = () => {
state.isFlags = false;
state.transparency = 1;
if (state.moveDifference >= -400) {
(<HTMLElement>state.querySelectorEl).setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
}
};
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = layoutLockScreenDateRef.value;
});
};
// 时间初始化
const initTime = () => {
state.time.hm = formatDate(new Date(), 'HH:MM');
state.time.s = formatDate(new Date(), 'SS');
state.time.mdq = formatDate(new Date(), 'mm月dd日,WWW');
};
// 时间初始化定时器
const initSetTime = () => {
initTime();
state.setIntervalTime = window.setInterval(() => {
initTime();
}, 1000);
};
// 锁屏时间定时器
const initLockScreen = () => {
if (themeConfig.value.isLockScreen) {
state.isShowLockScreenIntervalTime = window.setInterval(() => {
if (themeConfig.value.lockScreenTime <= 1) {
state.isShowLockScreen = true;
setLocalThemeConfig();
return false;
}
themeConfig.value.lockScreenTime--;
}, 1000);
} else {
clearInterval(state.isShowLockScreenIntervalTime);
}
};
// 存储布局配置
const setLocalThemeConfig = () => {
themeConfig.value.isDrawer = false;
Local.set('themeConfig', themeConfig.value);
};
// 密码输入点击事件
const onLockScreenSubmit = () => {
themeConfig.value.isLockScreen = false;
themeConfig.value.lockScreenTime = 30;
setLocalThemeConfig();
};
// 页面加载时
onMounted(() => {
initGetElement();
initSetTime();
initLockScreen();
});
// 页面卸载时
onUnmounted(() => {
window.clearInterval(state.setIntervalTime);
window.clearInterval(state.isShowLockScreenIntervalTime);
});
</script>
@@ -234,7 +213,7 @@ export default defineComponent({
}
.layout-lock-screen-img {
@extend .layout-lock-screen-fixed;
background-image: url('https://img-blog.csdnimg.cn/afa9c317667f47d5bea34b85af45979e.png#pic_center');
background-image: url('https://i.hd-r.cn/e4a19d84364f185266666765ac21a5db.jpg');
background-size: 100% 100%;
z-index: 9999991;
}
+23 -28
View File
@@ -8,37 +8,28 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script setup lang="ts" name="layoutLogo">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
// import logoMini from '/@/assets/logo-mini.svg';
import logoMini from '/@/assets/vboot.png';
export default defineComponent({
name: 'layoutLogo',
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 设置 logo 的显示。classic 经典布局默认显示 logo
const setShowLogo = computed(() => {
let { isCollapse, layout } = themeConfig.value;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
});
// logo 点击实现菜单展开/收起
const onThemeConfigChange = () => {
if (themeConfig.value.layout === 'transverse') return false;
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
};
return {
logoMini,
setShowLogo,
themeConfig,
onThemeConfigChange,
};
},
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 设置 logo 的显示。classic 经典布局默认显示 logo
const setShowLogo = computed(() => {
let { isCollapse, layout } = themeConfig.value;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
});
// logo 点击实现菜单展开/收起
const onThemeConfigChange = () => {
if (themeConfig.value.layout === 'transverse') return false;
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
};
</script>
<style scoped lang="scss">
@@ -56,9 +47,9 @@ export default defineComponent({
span {
white-space: nowrap;
display: inline-block;
font-size: 16px;
font-weight: bolder;
color: white;
font-size: 16px;
font-weight: bolder;
color: white;
}
&:hover {
span {
@@ -70,6 +61,10 @@ export default defineComponent({
margin-right: 5px;
}
}
.layout-navbars-container .layout-logo span{
color: #409eff;
}
.layout-logo-size {
width: 100%;
height: 50px;
+54 -58
View File
@@ -11,67 +11,63 @@
</el-container>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent, computed, ref, watch, nextTick, onMounted } from 'vue';
<script setup lang="ts" name="layoutClassic">
import { defineAsyncComponent, computed, ref, watch, nextTick, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
export default defineComponent({
name: 'layoutClassic',
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
LayoutTagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
},
setup() {
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 判断是否显示 tasgview
const isTagsview = computed(() => {
return themeConfig.value.isTagsview;
});
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutMainRef,
isTagsview,
};
},
// 引入组件
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
const LayoutTagsView = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue'));
// 定义变量内容
const layoutMainRef = ref<InstanceType<typeof LayoutMain>>();
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 判断是否显示 tasgview
const isTagsview = computed(() => {
return themeConfig.value.isTagsview;
});
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value?.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
// '!' not null 断言操作符,不执行运行时检查
if (layoutMainRef.value) layoutMainRef.value!.layoutMainScrollbarRef.wrapRef.scrollTop = 0;
}, 500);
});
};
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig isTagsview 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
() => themeConfig.value.isTagsview,
() => {
nextTick(() => {
updateScrollbar();
});
},
{
deep: true,
}
);
</script>
+54 -59
View File
@@ -11,68 +11,63 @@
</el-container>
</template>
<script lang="ts">
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
<script setup lang="ts" name="layoutColumns">
import { defineAsyncComponent, watch, onMounted, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
export default defineComponent({
name: 'layoutColumns',
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
ColumnsAside: defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue')),
},
setup() {
const layoutScrollbarRef = ref<any>('');
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrap$.scrollTop = 0;
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutScrollbarRef,
layoutMainRef,
};
},
// 引入组件
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
const ColumnsAside = defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue'));
// 定义变量内容
const layoutScrollbarRef = ref<RefType>('');
const layoutMainRef = ref<InstanceType<typeof LayoutMain>>();
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value && layoutMainRef.value!.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrapRef.scrollTop = 0;
layoutMainRef.value!.layoutMainScrollbarRef.wrapRef.scrollTop = 0;
}, 500);
});
};
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
() => [themeConfig.value.isTagsview, themeConfig.value.isFixedHeader],
() => {
nextTick(() => {
updateScrollbar();
});
},
{
deep: true,
}
);
</script>
+54 -59
View File
@@ -10,69 +10,64 @@
</el-container>
</template>
<script lang="ts">
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
<script setup lang="ts" name="layoutDefaults">
import { defineAsyncComponent, watch, onMounted, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { NextLoading } from '/@/utils/loading';
export default defineComponent({
name: 'layoutDefaults',
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
},
setup() {
const layoutScrollbarRef = ref<any>('');
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrap$.scrollTop = 0;
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
NextLoading.done(600);
});
return {
layoutScrollbarRef,
layoutMainRef,
};
},
// 引入组件
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
// 定义变量内容
const layoutScrollbarRef = ref<RefType>('');
const layoutMainRef = ref<InstanceType<typeof LayoutMain>>();
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value!.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrapRef.scrollTop = 0;
layoutMainRef.value!.layoutMainScrollbarRef.wrapRef.scrollTop = 0;
}, 500);
});
};
// 页面加载时
onMounted(() => {
initScrollBarHeight();
NextLoading.done(600);
});
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
() => [themeConfig.value.isTagsview, themeConfig.value.isFixedHeader],
() => {
nextTick(() => {
updateScrollbar();
});
},
{
deep: true,
}
);
</script>
+47 -51
View File
@@ -5,60 +5,56 @@
</el-container>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent, ref, watch, nextTick, onMounted } from 'vue';
<script setup lang="ts" name="layoutTransverse">
import { defineAsyncComponent, ref, watch, nextTick, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
export default defineComponent({
name: 'layoutTransverse',
components: {
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
},
setup() {
const layoutMainRef = ref<any>('');
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutMainRef,
};
},
// 引入组件
const LayoutHeader = defineAsyncComponent(() => import('/@/layout/component/header.vue'));
const LayoutMain = defineAsyncComponent(() => import('/@/layout/component/main.vue'));
// 定义变量内容
const layoutMainRef = ref<InstanceType<typeof LayoutMain>>();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value!.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value!.layoutMainScrollbarRef.wrapRef.scrollTop = 0;
}, 500);
});
};
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
() => themeConfig.value.isTagsview,
() => {
nextTick(() => {
updateScrollbar();
});
},
{
deep: true,
}
);
</script>
@@ -1,182 +0,0 @@
<template>
<div v-if="isShowBreadcrumb" class="layout-navbars-breadcrumb">
<div class='layout-navbars-breadcrumb-icon' style='text-align: center;padding-top: 16px;width: 48px;margin-right: 6px' @click="onThemeConfigChange">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18" :class="['hamburger',{'is-active':!themeConfig.isCollapse}]">
<path class='hamburger-path' data-v-49e15297="" d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"/>
</svg>
</div>
<!-- <SvgIcon-->
<!-- class="layout-navbars-breadcrumb-icon"-->
<!-- :name="themeConfig.isCollapse ? 'ele-Expand' : 'ele-Fold'"-->
<!-- :size="16"-->
<!-- @click="onThemeConfigChange"-->
<!-- />-->
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="!v.meta.tagsViewName ? v.meta.title : v.meta.tagsViewName">
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
<div v-if="!v.meta.tagsViewName">{{ $t(v.meta.title) }}</div>
<div v-else>{{ v.meta.tagsViewName }}</div>
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
import { Local } from '/@/utils/storage';
import other from '/@/utils/other';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useRoutesList } from '/@/stores/routesList';
// 定义接口来定义对象的类型
interface BreadcrumbState {
breadcrumbList: Array<any>;
routeSplit: Array<string>;
routeSplitFirst: string;
routeSplitIndex: number;
}
export default defineComponent({
name: 'layoutBreadcrumb',
setup() {
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { routesList } = storeToRefs(stores);
const route = useRoute();
const router = useRouter();
const state = reactive<BreadcrumbState>({
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
});
// 动态设置经典、横向布局不显示
const isShowBreadcrumb = computed(() => {
initRouteSplit(route.path);
const { layout, isBreadcrumb } = themeConfig.value;
if (layout === 'classic' || layout === 'transverse') return false;
else return isBreadcrumb ? true : false;
});
// 面包屑点击时
const onBreadcrumbClick = (v: any) => {
const { redirect, path } = v;
if (redirect) router.push(redirect);
else router.push(path);
};
// 展开/收起左侧菜单点击
const onThemeConfigChange = () => {
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
setLocalThemeConfig();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', themeConfig.value);
};
// 处理面包屑数据
const getBreadcrumbList = (arr: Array<string>) => {
arr.forEach((item: any) => {
state.routeSplit.forEach((v: any, k: number, arrs: any) => {
if (state.routeSplitFirst === item.path) {
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
state.breadcrumbList.push(item);
state.routeSplitIndex++;
if (item.children) getBreadcrumbList(item.children);
}
});
});
};
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (path: string) => {
if (!themeConfig.value.isBreadcrumb) return false;
state.breadcrumbList = [routesList.value[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(routesList.value);
if (route.name === 'home' || (route.name === 'notFound' && state.breadcrumbList.length > 1)) state.breadcrumbList.shift();
if (state.breadcrumbList.length > 0) state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(route);
};
// 页面加载时
onMounted(() => {
initRouteSplit(route.path);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
initRouteSplit(to.path);
});
return {
onThemeConfigChange,
isShowBreadcrumb,
themeConfig,
onBreadcrumbClick,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb {
flex: 1;
height: inherit;
display: flex;
align-items: center;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
color: var(--next-bg-topBarColor);
height: 100%;
width: 40px;
//opacity: 0.8;
&:hover {
//opacity: 1;
background-color: #f6f6f6;
}
}
.layout-navbars-breadcrumb-span {
display: flex;
opacity: 0.7;
color: var(--next-bg-topBarColor);
}
.layout-navbars-breadcrumb-iconfont {
font-size: 14px;
margin-right: 5px;
}
:deep(.el-breadcrumb__separator) {
opacity: 0.7;
color: var(--next-bg-topBarColor);
}
:deep(.el-breadcrumb__inner a, .el-breadcrumb__inner.is-link) {
font-weight: unset !important;
color: var(--next-bg-topBarColor);
&:hover {
color: var(--el-color-primary) !important;
}
}
}
.dark .layout-navbars-breadcrumb .layout-navbars-breadcrumb-icon:hover{
background-color: #111111;
}
.hamburger-path {fill:currentColor;}
.hamburger.is-active {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
</style>
-120
View File
@@ -1,120 +0,0 @@
<template>
<div class="layout-navbars-breadcrumb-index">
<Logo v-if="setIsShowLogo" />
<Breadcrumb />
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
<User />
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, computed, reactive, toRefs, onMounted, onUnmounted, defineComponent } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface IndexState {
menuList: object[];
}
export default defineComponent({
name: 'layoutBreadcrumbIndex',
components: {
Breadcrumb: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue')),
User: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue')),
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
Horizontal: defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue')),
},
setup() {
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { routesList } = storeToRefs(stores);
const route = useRoute();
const state = reactive<IndexState>({
menuList: [],
});
// 设置 logo 显示/隐藏
const setIsShowLogo = computed(() => {
let { isShowLogo, layout } = themeConfig.value;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
});
// 设置是否显示横向导航菜单
const isLayoutTransverse = computed(() => {
let { layout, isClassicSplitMenu } = themeConfig.value;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
const resData = setSendClassicChildren(route.path);
mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(routesList.value);
}
};
// 设置了分割菜单时,删除底下 children
const delClassicChildren = (arr: Array<object>) => {
arr.map((v: any) => {
if (v.children) delete v.children;
});
return arr;
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<string>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
filterRoutesFun(routesList.value).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 页面加载时
onMounted(() => {
setFilterRoutes();
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
});
return {
setIsShowLogo,
isLayoutTransverse,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-index {
height: 50px;
display: flex;
align-items: center;
background: var(--next-bg-topBar);
border-bottom: 1px solid var(--next-border-color-light);
}
</style>
-143
View File
@@ -1,143 +0,0 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="isShowSearch" destroy-on-close :show-close="false">
<template #footer>
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
:fit-input-width="true"
>
<template #prefix>
<el-icon class="el-input__icon">
<ele-Search />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ $t(item.meta.title) }}
</div>
</template>
</el-autocomplete>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
// 定义接口来定义对象的类型
interface SearchState {
isShowSearch: boolean;
menuQuery: string;
tagsViewList: object[];
}
interface Restaurant {
path: string;
meta: {
title: string;
};
}
export default defineComponent({
name: 'layoutBreadcrumbSearch',
setup() {
const storesTagsViewRoutes = useTagsViewRoutes();
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
const layoutMenuAutocompleteRef = ref();
const { t } = useI18n();
const router = useRouter();
const state = reactive<SearchState>({
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
});
// 搜索弹窗打开
const openSearch = () => {
state.menuQuery = '';
state.isShowSearch = true;
initTageView();
nextTick(() => {
setTimeout(() => {
layoutMenuAutocompleteRef.value.focus();
});
});
};
// 搜索弹窗关闭
const closeSearch = () => {
state.isShowSearch = false;
};
// 菜单搜索数据过滤
const menuSearch = (queryString: string, cb: Function) => {
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
cb(results);
};
// 菜单搜索过滤
const createFilter: any = (queryString: string) => {
return (restaurant: Restaurant) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
t(restaurant.meta.title).indexOf(queryString.toLowerCase()) > -1
);
};
};
// 初始化菜单数据
const initTageView = () => {
if (state.tagsViewList.length > 0) return false;
tagsViewRoutes.value.map((v: any) => {
if (!v.meta.isHide) state.tagsViewList.push({ ...v });
});
};
// 当前菜单选中时
const onHandleSelect = (item: any) => {
let { path, redirect } = item;
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
else if (redirect) router.push(redirect);
else router.push(path);
closeSearch();
};
return {
layoutMenuAutocompleteRef,
openSearch,
closeSearch,
menuSearch,
onHandleSelect,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {
.el-dialog__header,
.el-dialog__body {
display: none;
}
.el-dialog__footer {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -53vh;
}
}
:deep(.el-autocomplete) {
width: 560px;
position: absolute;
top: 150px;
left: 50%;
transform: translateX(-50%);
}
}
</style>
-286
View File
@@ -1,286 +0,0 @@
<template>
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
<div>
<el-select v-show="userInfos.portals.length>1" v-model='userInfos.dePortal' placeholder='门户名称' @change="portalChange" style='margin-right: 10px;width: 140px'>
<el-option
v-for='item in userInfos.portals'
:key='item.id'
:label='item.name'
:value='item.id'
/>
</el-select>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('message.user.title1')"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<el-icon :title="$t('message.user.title2')">
<ele-Search />
</el-icon>
</div>
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
<template #reference>
<el-badge :is-dot="true">
<el-icon :title="$t('message.user.title4')">
<ele-Bell />
</el-icon>
</el-badge>
</template>
<template #default>
<UserNews />
</template>
</el-popover>
</div>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i
class="iconfont"
:title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="avatar" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ userInfos.userName === '' ? 'common' : userInfos.userName }}
<el-icon class="el-icon--right">
<ele-ArrowDown />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<Search ref="searchRef" />
<div class="layout-navbars-breadcrumb-user-icon" style="width: 42px" @click="onLayoutSetingClick">
<el-icon class="el-icon--right">
<ele-Setting />
</el-icon>
</div>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, ref, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
import {useRoute, useRouter} from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
import { Session, Local } from '/@/utils/storage';
import request from "/@/utils/request";
import {initBackEndControlRoutesByPortal} from "/@/router/backEnd";
import avatar from '/@/assets/avatar.png';
export default defineComponent({
name: 'layoutBreadcrumbUser',
components: {
UserNews: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue')),
Search: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue')),
},
setup() {
const { locale, t } = useI18n();
const router = useRouter();
const route = useRoute();
const stores = useUserInfo();
const storesThemeConfig = useThemeConfig();
const { userInfos } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const searchRef = ref();
const state = reactive({
isScreenfull: false,
disabledI18n: 'zh-cn',
disabledSize: 'large',
});
// 设置分割样式
const layoutUserFlexNum = computed(() => {
let num: string | number = '';
const { layout, isClassicSplitMenu } = themeConfig.value;
const layoutArr: string[] = ['defaults', 'columns'];
if (layoutArr.includes(layout) || (layout === 'classic' && !isClassicSplitMenu)) num = '1';
else num = '';
return num;
});
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) state.isScreenfull = true;
else state.isScreenfull = false;
});
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path: string) => {
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: t('message.user.logOutTitle'),
message: t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: t('message.user.logOutConfirm'),
cancelButtonText: t('message.user.logOutCancel'),
buttonSize: 'default',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(async () => {
// 清除缓存/token等
Session.clear();
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/zsvg/vboot-vue');
} else {
router.push(path);
}
};
// 菜单搜索点击
const onSearchClick = () => {
searchRef.value.openSearch();
};
// 组件大小改变
const onComponentSizeChange = (size: string) => {
Local.remove('themeConfig');
themeConfig.value.globalComponentSize = size;
Local.set('themeConfig', themeConfig.value);
initI18nOrSize('globalComponentSize', 'disabledSize');
window.location.reload();
};
// 语言切换
const onLanguageChange = (lang: string) => {
Local.remove('themeConfig');
themeConfig.value.globalI18n = lang;
Local.set('themeConfig', themeConfig.value);
locale.value = lang;
other.useTitle();
initI18nOrSize('globalI18n', 'disabledI18n');
};
// 初始化组件大小/i18n
const initI18nOrSize = (value: string, attr: string) => {
(<any>state)[attr] = Local.get('themeConfig')[value];
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initI18nOrSize('globalComponentSize', 'disabledSize');
initI18nOrSize('globalI18n', 'disabledI18n');
}
});
const portalChange=async (porid:any)=>{
const res= await request({
url: 'getMenuList',
method: 'get',
params:{porid:porid}
}) as any;
await initBackEndControlRoutesByPortal(res);
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 3, ...route }));
location.href='#/home';
}
return {
userInfos,
avatar,
portalChange,
onLayoutSetingClick,
onHandleCommandClick,
onScreenfullClick,
onSearchClick,
onComponentSizeChange,
onLanguageChange,
searchRef,
layoutUserFlexNum,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user {
cursor: pointer;
display: flex;
align-items: center;
justify-content: flex-end;
&-link {
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
&-photo {
width: 25px;
height: 25px;
border-radius: 100%;
}
}
&-icon {
padding: 0 10px;
cursor: pointer;
color: var(--next-bg-topBarColor);
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
&:hover {
background: var(--next-color-user-hover);
i {
display: inline-block;
animation: logoAnimation 0.3s ease-in-out;
}
}
}
:deep(.el-dropdown) {
color: var(--next-bg-topBarColor);
}
:deep(.el-badge) {
height: 40px;
line-height: 40px;
display: flex;
align-items: center;
}
:deep(.el-badge__content.is-fixed) {
top: 12px;
}
}
</style>
+14 -20
View File
@@ -5,29 +5,23 @@
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, computed, defineComponent } from 'vue';
<script setup lang="ts" name="layoutNavBars">
import { defineAsyncComponent, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
export default defineComponent({
name: 'layoutNavBars',
components: {
BreadcrumbIndex: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue')),
TagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
},
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = themeConfig.value;
return layout !== 'classic' && isTagsview;
});
return {
setShowTagsView,
};
},
// 引入组件
const BreadcrumbIndex = defineAsyncComponent(() => import('/@/layout/navBars/topBar/index.vue'));
const TagsView = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue'));
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = themeConfig.value;
return layout !== 'classic' && isTagsview;
});
</script>
+87 -87
View File
@@ -7,10 +7,10 @@
data-popper-placement="bottom"
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
:key="Math.random()"
v-show="isShow"
v-show="state.isShow"
>
<ul class="el-dropdown-menu">
<template v-for="(v, k) in dropdownList">
<template v-for="(v, k) in state.dropdownList">
<li
class="el-dropdown-menu__item"
aria-disabled="false"
@@ -24,101 +24,101 @@
</li>
</template>
</ul>
<div class="el-popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
</div>
</transition>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted, watch } from 'vue';
<script setup lang="ts" name="layoutTagsViewContextmenu">
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
export default defineComponent({
name: 'layoutTagsViewContextmenu',
props: {
dropdown: {
type: Object,
default: () => {
return {
x: 0,
y: 0,
};
},
// 定义父组件传过来的值
const props = defineProps({
dropdown: {
type: Object,
default: () => {
return {
x: 0,
y: 0,
};
},
},
setup(props, { emit }) {
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'ele-RefreshRight' },
{ contextMenuClickId: 1, txt: 'message.tagsView.close', affix: false, icon: 'ele-Close' },
{ contextMenuClickId: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'ele-CircleClose' },
{ contextMenuClickId: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'ele-FolderDelete' },
{
contextMenuClickId: 4,
txt: 'message.tagsView.fullscreen',
affix: false,
icon: 'iconfont icon-fullscreen',
},
],
item: {},
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return props.dropdown;
}
});
// 当前项菜单点击
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item: any) => {
state.item = item;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
{
deep: true,
}
);
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['currentContextmenuClick']);
// 定义变量内容
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'ele-RefreshRight' },
{ contextMenuClickId: 1, txt: 'message.tagsView.close', affix: false, icon: 'ele-Close' },
{ contextMenuClickId: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'ele-CircleClose' },
{ contextMenuClickId: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'ele-FolderDelete' },
{
contextMenuClickId: 4,
txt: 'message.tagsView.fullscreen',
affix: false,
icon: 'iconfont icon-fullscreen',
},
],
item: {},
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
dropdowns,
openContextmenu,
closeContextmenu,
onCurrentContextmenuClick,
...toRefs(state),
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return props.dropdown;
}
});
// 当前项菜单点击
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item: RouteItem) => {
state.item = item;
item.meta?.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
{
deep: true,
}
);
// 暴露变量
defineExpose({
openContextmenu,
});
</script>
File diff suppressed because it is too large Load Diff
+164
View File
@@ -0,0 +1,164 @@
<template>
<div v-if="isShowBreadcrumb" class="layout-navbars-breadcrumb">
<div class='layout-navbars-breadcrumb-icon' style='text-align: center;padding-top: 16px;width: 48px;margin-right: 6px' @click="onThemeConfigChange">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18" :class="['hamburger',{'is-active':!themeConfig.isCollapse}]">
<path class='hamburger-path' data-v-49e15297="" d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"/>
</svg>
</div>
<!-- <SvgIcon-->
<!-- class="layout-navbars-breadcrumb-icon"-->
<!-- :name="themeConfig.isCollapse ? 'ele-Expand' : 'ele-Fold'"-->
<!-- :size="16"-->
<!-- @click="onThemeConfigChange"-->
<!-- />-->
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="!v.meta.tagsViewName ? v.meta.title : v.meta.tagsViewName">
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
<div v-if="!v.meta.tagsViewName">{{ $t(v.meta.title) }}</div>
<div v-else>{{ v.meta.tagsViewName }}</div>
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</template>
<script setup lang="ts" name="layoutBreadcrumb">
import { reactive, computed, onMounted } from 'vue';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
import { Local } from '/@/utils/storage';
import other from '/@/utils/other';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useRoutesList } from '/@/stores/routesList';
// 定义变量内容
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { routesList } = storeToRefs(stores);
const route = useRoute();
const router = useRouter();
const state = reactive<BreadcrumbState>({
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
});
// 动态设置经典、横向布局不显示
const isShowBreadcrumb = computed(() => {
initRouteSplit(route.path);
const { layout, isBreadcrumb } = themeConfig.value;
if (layout === 'classic' || layout === 'transverse') return false;
else return isBreadcrumb ? true : false;
});
// 面包屑点击时
const onBreadcrumbClick = (v: RouteItem) => {
const { redirect, path } = v;
if (redirect) router.push(redirect);
else router.push(path);
};
// 展开/收起左侧菜单点击
const onThemeConfigChange = () => {
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
setLocalThemeConfig();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', themeConfig.value);
};
// 处理面包屑数据
const getBreadcrumbList = (arr: RouteItems) => {
arr.forEach((item: RouteItem) => {
state.routeSplit.forEach((v: string, k: number, arrs: string[]) => {
if (state.routeSplitFirst === item.path) {
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
state.breadcrumbList.push(item);
state.routeSplitIndex++;
if (item.children) getBreadcrumbList(item.children);
}
});
});
};
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (path: string) => {
if (!themeConfig.value.isBreadcrumb) return false;
state.breadcrumbList = [routesList.value[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(routesList.value);
if (route.name === 'home' || (route.name === 'notFound' && state.breadcrumbList.length > 1)) state.breadcrumbList.shift();
if (state.breadcrumbList.length > 0)
state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(<RouteToFrom>route);
};
// 页面加载时
onMounted(() => {
initRouteSplit(route.path);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
initRouteSplit(to.path);
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb {
flex: 1;
height: inherit;
display: flex;
align-items: center;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
color: var(--next-bg-topBarColor);
height: 100%;
width: 40px;
//opacity: 0.8;
&:hover {
//opacity: 1;
background-color: #f6f6f6;
}
}
.layout-navbars-breadcrumb-span {
display: flex;
opacity: 0.7;
color: var(--next-bg-topBarColor);
}
.layout-navbars-breadcrumb-iconfont {
font-size: 14px;
margin-right: 5px;
}
:deep(.el-breadcrumb__separator) {
opacity: 0.7;
color: var(--next-bg-topBarColor);
}
:deep(.el-breadcrumb__inner a, .el-breadcrumb__inner.is-link) {
font-weight: unset !important;
color: var(--next-bg-topBarColor);
&:hover {
color: var(--el-color-primary) !important;
}
}
}
.dark .layout-navbars-breadcrumb .layout-navbars-breadcrumb-icon:hover{
background-color: #111111;
}
.hamburger-path {fill:currentColor;}
.hamburger.is-active {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
</style>
@@ -6,26 +6,18 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script setup lang="ts" name="layoutCloseFull">
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: 'layoutCloseFull',
setup() {
const stores = useTagsViewRoutes();
const { isTagsViewCurrenFull } = storeToRefs(stores);
// 关闭当前全屏
const onCloseFullscreen = () => {
stores.setCurrenFullscreen(false);
};
return {
isTagsViewCurrenFull,
onCloseFullscreen,
};
},
});
// 定义变量内容
const stores = useTagsViewRoutes();
const { isTagsViewCurrenFull } = storeToRefs(stores);
// 关闭当前全屏
const onCloseFullscreen = () => {
stores.setCurrenFullscreen(false);
};
</script>
<style scoped lang="scss">
+121
View File
@@ -0,0 +1,121 @@
<template>
<div class="layout-navbars-breadcrumb-index">
<Logo v-if="setIsShowLogo" />
<Breadcrumb />
<Horizontal :menuList="state.menuList" v-if="isLayoutTransverse" />
<User />
</div>
</template>
<script setup lang="ts" name="layoutBreadcrumbIndex">
import { defineAsyncComponent, computed, reactive, onMounted, onUnmounted,watch} from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt';
import pinia from '/@/stores';
// 引入组件
const Breadcrumb = defineAsyncComponent(() => import('/@/layout/navBars/topBar/breadcrumb.vue'));
const User = defineAsyncComponent(() => import('/@/layout/navBars/topBar/user.vue'));
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
const Horizontal = defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue'));
// 定义变量内容
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { routesList } = storeToRefs(stores);
const route = useRoute();
const state = reactive({
menuList: [] as RouteItems,
});
// 设置 logo 显示/隐藏
const setIsShowLogo = computed(() => {
let { isShowLogo, layout } = themeConfig.value;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
});
// 设置是否显示横向导航菜单
const isLayoutTransverse = computed(() => {
let { layout, isClassicSplitMenu } = themeConfig.value;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
const resData = setSendClassicChildren(route.path);
mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(routesList.value);
}
};
// 设置了分割菜单时,删除底下 children
const delClassicChildren = <T extends ChilType>(arr: T[]): T[] => {
arr.map((v: T) => {
if (v.children) delete v.children;
});
return arr;
};
// 路由过滤递归函数
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: MittMenu = { children: [] };
filterRoutesFun(routesList.value).map((v: RouteItem, 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;
};
// 监听pinia值的变化,动态赋值给菜单中
// 多次执行问题,待后期优化
watch(
pinia.state,
(val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
setFilterRoutes();
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-index {
height: 50px;
display: flex;
align-items: center;
background: var(--next-bg-topBar);
border-bottom: 1px solid var(--next-border-color-light);
}
</style>
+125
View File
@@ -0,0 +1,125 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false">
<template #footer>
<el-autocomplete
v-model="state.menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
:fit-input-width="true"
>
<template #prefix>
<el-icon class="el-input__icon">
<ele-Search />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ $t(item.meta.title) }}
</div>
</template>
</el-autocomplete>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="layoutBreadcrumbSearch">
import { reactive, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
// 定义变量内容
const storesTagsViewRoutes = useTagsViewRoutes();
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
const layoutMenuAutocompleteRef = ref();
const { t } = useI18n();
const router = useRouter();
const state = reactive<SearchState>({
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
});
// 搜索弹窗打开
const openSearch = () => {
state.menuQuery = '';
state.isShowSearch = true;
initTageView();
nextTick(() => {
setTimeout(() => {
layoutMenuAutocompleteRef.value.focus();
});
});
};
// 搜索弹窗关闭
const closeSearch = () => {
state.isShowSearch = false;
};
// 菜单搜索数据过滤
const menuSearch = (queryString: string, cb: Function) => {
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
cb(results);
};
// 菜单搜索过滤
const createFilter = (queryString: string) => {
return (restaurant: RouteItem) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta!.title!.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
t(restaurant.meta!.title!).indexOf(queryString.toLowerCase()) > -1
);
};
};
// 初始化菜单数据
const initTageView = () => {
if (state.tagsViewList.length > 0) return false;
tagsViewRoutes.value.map((v: RouteItem) => {
if (!v.meta?.isHide) state.tagsViewList.push({ ...v });
});
};
// 当前菜单选中时
const onHandleSelect = (item: RouteItem) => {
let { path, redirect } = item;
if (item.meta?.isLink && !item.meta?.isIframe) window.open(item.meta?.isLink);
else if (redirect) router.push(redirect);
else router.push(path);
closeSearch();
};
// 暴露变量
defineExpose({
openSearch,
});
</script>
<style scoped lang="scss">
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {
.el-dialog__header,
.el-dialog__body {
display: none;
}
.el-dialog__footer {
width: 100%;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -53vh;
}
}
:deep(.el-autocomplete) {
width: 560px;
position: absolute;
top: 150px;
left: 50%;
transform: translateX(-50%);
}
}
</style>
@@ -59,6 +59,17 @@
<el-color-picker v-model="getThemeConfig.menuBarColor" size="default" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoMenuBarActiveColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker
v-model="getThemeConfig.menuBarActiveColor"
size="default"
show-alpha
@change="onBgColorPickerChange('menuBarActiveColor')"
/>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsMenuBarColorGradual') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
@@ -227,12 +238,12 @@
<el-switch v-model="getThemeConfig.isCacheTagsView" size="small" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: isMobile ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: state.isMobile ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsSortableTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="getThemeConfig.isSortableTagsView"
:disabled="isMobile ? true : false"
:disabled="state.isMobile ? true : false"
size="small"
@change="onSortableTagsViewChange"
></el-switch>
@@ -271,7 +282,7 @@
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourWartermarkText') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input v-model="getThemeConfig.wartermarkText" size="default" style="width: 90px" @input="onWartermarkTextInput($event)"></el-input>
<el-input v-model="getThemeConfig.wartermarkText" size="default" style="width: 90px" @input="onWartermarkTextInput"></el-input>
</div>
</div>
@@ -418,285 +429,266 @@
</div>
</template>
<script lang="ts">
import { nextTick, onUnmounted, onMounted, defineComponent, computed, reactive, toRefs } from 'vue';
<script setup lang="ts" name="layoutBreadcrumbSeting">
import { nextTick, onUnmounted, onMounted, computed, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { getLightColor, getDarkColor } from '/@/utils/theme';
import { useChangeColor } from '/@/utils/theme';
import { verifyAndSpace } from '/@/utils/toolsValidate';
import { Local } from '/@/utils/storage';
import Watermark from '/@/utils/wartermark';
import Watermark from '/@/utils/watermark';
import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutBreadcrumbSeting',
setup() {
const { locale } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction();
const state = reactive({
isMobile: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 1、全局主题
const onColorPickerChange = () => {
if (!getThemeConfig.value.primary) return ElMessage.warning('全局主题 primary 颜色值不能为空');
// 颜色加深
document.documentElement.style.setProperty('--el-color-primary-dark-2', `${getDarkColor(getThemeConfig.value.primary, 0.1)}`);
document.documentElement.style.setProperty('--el-color-primary', getThemeConfig.value.primary);
// 颜色变浅
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(getThemeConfig.value.primary, i / 10)}`);
}
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏
const onBgColorPickerChange = (bg: string) => {
document.documentElement.style.setProperty(`--next-bg-${bg}`, (<any>getThemeConfig.value)[bg]);
if (bg === 'menuBar') {
document.documentElement.style.setProperty(`--next-bg-menuBar-light-1`, <any>getLightColor(getThemeConfig.value.menuBar, 0.05));
}
onTopBarGradualChange();
onMenuBarGradualChange();
onColumnsMenuBarGradualChange();
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏 --> 顶栏背景渐变
const onTopBarGradualChange = () => {
setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
};
// 2、菜单 / 顶栏 --> 菜单背景渐变
const onMenuBarGradualChange = () => {
setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
};
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
const onColumnsMenuBarGradualChange = () => {
setGraduaFun('.layout-container .layout-columns-aside', getThemeConfig.value.isColumnsMenuBarColorGradual, getThemeConfig.value.columnsMenuBar);
};
// 2、菜单 / 顶栏 --> 背景渐变函数
const setGraduaFun = (el: string, bool: boolean, color: string) => {
setTimeout(() => {
let els = document.querySelector(el);
if (!els) return false;
document.documentElement.style.setProperty('--el-menu-bg-color', document.documentElement.style.getPropertyValue('--next-bg-menuBar'));
if (bool) els.setAttribute('style', `background:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)}) !important;`);
else els.setAttribute('style', ``);
setLocalThemeConfig();
}, 200);
};
// 2、分栏设置 ->
const onColumnsMenuHoverPreloadChange = () => {
mittBus.emit('setHoverPreload');
// 定义变量内容
const { locale } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction();
const { getLightColor, getDarkColor } = useChangeColor();
const state = reactive({
isMobile: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 1、全局主题
const onColorPickerChange = () => {
if (!getThemeConfig.value.primary) return ElMessage.warning('全局主题 primary 颜色值不能为空');
// 颜色加深
document.documentElement.style.setProperty('--el-color-primary-dark-2', `${getDarkColor(getThemeConfig.value.primary, 0.1)}`);
document.documentElement.style.setProperty('--el-color-primary', getThemeConfig.value.primary);
// 颜色变浅
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(getThemeConfig.value.primary, i / 10)}`);
}
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏
const onBgColorPickerChange = (bg: string) => {
document.documentElement.style.setProperty(`--next-bg-${bg}`, themeConfig.value[bg]);
if (bg === 'menuBar') {
document.documentElement.style.setProperty(`--next-bg-menuBar-light-1`, getLightColor(getThemeConfig.value.menuBar, 0.05));
}
onTopBarGradualChange();
onMenuBarGradualChange();
onColumnsMenuBarGradualChange();
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏 --> 顶栏背景渐变
const onTopBarGradualChange = () => {
setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
};
// 2、菜单 / 顶栏 --> 菜单背景渐变
const onMenuBarGradualChange = () => {
setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
};
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
const onColumnsMenuBarGradualChange = () => {
setGraduaFun('.layout-container .layout-columns-aside', getThemeConfig.value.isColumnsMenuBarColorGradual, getThemeConfig.value.columnsMenuBar);
};
// 2、菜单 / 顶栏 --> 背景渐变函数
const setGraduaFun = (el: string, bool: boolean, color: string) => {
nextTick(() => {
setTimeout(() => {
let els = document.querySelector(el);
if (!els) return false;
document.documentElement.style.setProperty('--el-menu-bg-color', document.documentElement.style.getPropertyValue('--next-bg-menuBar'));
if (bool) els.setAttribute('style', `background:linear-gradient(to bottom , ${color}, ${getLightColor(color, 0.5)})`);
else els.setAttribute('style', ``);
setLocalThemeConfig();
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
setDispatchThemeConfig();
};
// 3、界面设置 --> 固定 Header
const onIsFixedHeaderChange = () => {
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
setLocalThemeConfig();
};
// 3、界面设置 --> 经典布局分割菜单
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
setLocalThemeConfig();
};
// 4、界面显示 --> 面包屑 Breadcrumb
const onIsBreadcrumbChange = () => {
if (getThemeConfig.value.layout === 'classic') {
getThemeConfig.value.isClassicSplitMenu = false;
}
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
const onAddFilterChange = (attr: string) => {
if (attr === 'grayscale') {
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
} else {
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
}
const cssAttr =
attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle: any = document.body;
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
};
// 4、界面显示 --> 深色模式
const onAddDarkChange = () => {
const body = document.documentElement as HTMLElement;
// if (getThemeConfig.value.isIsDark) body.setAttribute('data-theme', 'dark');
// else body.setAttribute('data-theme', '');
if (getThemeConfig.value.isIsDark){
body.setAttribute('class', 'dark');
}
else{
body.setAttribute('class', '');
}
};
// 4、界面显示 --> 开启水印
const onWartermarkChange = () => {
getThemeConfig.value.isWartermark ? Watermark.set(getThemeConfig.value.wartermarkText) : Watermark.del();
setLocalThemeConfig();
};
// 4、界面显示 --> 水印文案
const onWartermarkTextInput = (val: any) => {
getThemeConfig.value.wartermarkText = verifyAndSpace(val);
if (getThemeConfig.value.wartermarkText === '') return false;
if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
setLocalThemeConfig();
};
// 5、布局切换
const onSetLayout = (layout: string) => {
Local.set('oldLayout', layout);
if (getThemeConfig.value.layout === layout) return false;
if (layout === 'transverse') getThemeConfig.value.isCollapse = false;
getThemeConfig.value.layout = layout;
}, 300);
});
};
// 2、分栏设置 ->
const onColumnsMenuHoverPreloadChange = () => {
setLocalThemeConfig();
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
setDispatchThemeConfig();
};
// 3、界面设置 --> 固定 Header
const onIsFixedHeaderChange = () => {
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
setLocalThemeConfig();
};
// 3、界面设置 --> 经典布局分割菜单
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
setLocalThemeConfig();
};
// 4、界面显示 --> 面包屑 Breadcrumb
const onIsBreadcrumbChange = () => {
if (getThemeConfig.value.layout === 'classic') {
getThemeConfig.value.isClassicSplitMenu = false;
}
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
const onAddFilterChange = (attr: string) => {
if (attr === 'grayscale') {
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
} else {
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
}
const cssAttr =
attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle = document.body;
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
};
// 4、界面显示 --> 深色模式
const onAddDarkChange = () => {
// const body = document.documentElement as HTMLElement;
// if (getThemeConfig.value.isIsDark) body.setAttribute('data-theme', 'dark');
// else body.setAttribute('data-theme', '');
const body = document.documentElement as HTMLElement;
if (getThemeConfig.value.isIsDark){
body.setAttribute('class', 'dark');
}
else{
body.setAttribute('class', '');
}
};
// 4、界面显示 --> 开启水印
const onWartermarkChange = () => {
getThemeConfig.value.isWartermark ? Watermark.set(getThemeConfig.value.wartermarkText) : Watermark.del();
setLocalThemeConfig();
};
// 4、界面显示 --> 水印文案
const onWartermarkTextInput = (val: string) => {
getThemeConfig.value.wartermarkText = verifyAndSpace(val);
if (getThemeConfig.value.wartermarkText === '') return false;
if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
setLocalThemeConfig();
};
// 5、布局切换
const onSetLayout = (layout: string) => {
Local.set('oldLayout', layout);
if (getThemeConfig.value.layout === layout) return false;
if (layout === 'transverse') getThemeConfig.value.isCollapse = false;
getThemeConfig.value.layout = layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
};
// 设置布局切换函数
const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar');
onBgColorPickerChange('menuBarColor');
onBgColorPickerChange('menuBarActiveColor');
onBgColorPickerChange('topBar');
onBgColorPickerChange('topBarColor');
onBgColorPickerChange('columnsMenuBar');
onBgColorPickerChange('columnsMenuBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 layoutScrollbarRef.value.update() 更新滚动条高度
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
getThemeConfig.value.isDrawer = false;
setLocalThemeConfig();
};
// 布局配置弹窗打开
const openDrawer = () => {
getThemeConfig.value.isDrawer = true;
};
// 触发 store 布局配置更新
const setDispatchThemeConfig = () => {
setLocalThemeConfig();
setLocalThemeConfigStyle();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', getThemeConfig.value);
};
// 存储布局配置全局主题样式(html根标签)
const setLocalThemeConfigStyle = () => {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
};
// 一键复制配置
const onCopyConfigClick = () => {
let copyThemeConfig = Local.get('themeConfig');
copyThemeConfig.isDrawer = false;
copyText(JSON.stringify(copyThemeConfig)).then(() => {
getThemeConfig.value.isDrawer = false;
});
};
// 一键恢复默认
const onResetConfigClick = () => {
Local.clear();
window.location.reload();
// @ts-ignore
Local.set('version', __NEXT_VERSION__);
};
// 初始化菜单样式等
const initSetStyle = () => {
// 2、菜单 / 顶栏 --> 顶栏背景渐变
onTopBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单背景渐变
onMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
onColumnsMenuBarGradualChange();
};
onMounted(() => {
nextTick(() => {
// 判断当前布局是否不相同,不相同则初始化当前布局的样式,防止监听窗口大小改变时,布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
mittBus.on('layoutMobileResize', (res: LayoutMobileResize) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
};
// 设置布局切换函数
const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar');
onBgColorPickerChange('menuBarColor');
onBgColorPickerChange('topBar');
onBgColorPickerChange('topBarColor');
onBgColorPickerChange('columnsMenuBar');
onBgColorPickerChange('columnsMenuBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 layoutScrollbarRef.value.update() 更新滚动条高度
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
getThemeConfig.value.isDrawer = false;
setLocalThemeConfig();
};
// 布局配置弹窗打开
const openDrawer = () => {
getThemeConfig.value.isDrawer = true;
};
// 触发 store 布局配置更新
const setDispatchThemeConfig = () => {
setLocalThemeConfig();
setLocalThemeConfigStyle();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', getThemeConfig.value);
};
// 存储布局配置全局主题样式(html根标签)
const setLocalThemeConfigStyle = () => {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
};
// 一键复制配置
const onCopyConfigClick = () => {
let copyThemeConfig = Local.get('themeConfig');
copyThemeConfig.isDrawer = false;
copyText(JSON.stringify(copyThemeConfig)).then(() => {
getThemeConfig.value.isDrawer = false;
});
};
// 一键恢复默认
const onResetConfigClick = () => {
Local.clear();
window.location.reload();
};
// 初始化菜单样式等
const initSetStyle = () => {
// 2、菜单 / 顶栏 --> 顶栏背景渐变
onTopBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单背景渐变
onMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
onColumnsMenuBarGradualChange();
};
onMounted(() => {
nextTick(() => {
// 判断当前布局是否不相同,不相同则初始化当前布局的样式,防止监听窗口大小改变时,布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
mittBus.on('layoutMobileResize', (res: any) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
state.isMobile = other.isMobile();
});
setTimeout(() => {
// 默认样式
onColorPickerChange();
// 灰色模式
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
// 色弱模式
if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
// 深色模式
if (getThemeConfig.value.isIsDark) onAddDarkChange();
// 开启水印
onWartermarkChange();
// 语言国际化
if (Local.get('themeConfig')) locale.value = Local.get('themeConfig').globalI18n;
// 初始化菜单样式等
initSetStyle();
}, 100);
});
state.isMobile = other.isMobile();
});
onUnmounted(() => {
mittBus.off('layoutMobileResize', () => {});
});
return {
openDrawer,
onColorPickerChange,
onBgColorPickerChange,
onTopBarGradualChange,
onMenuBarGradualChange,
onColumnsMenuBarGradualChange,
onColumnsMenuHoverPreloadChange,
onThemeConfigChange,
onIsFixedHeaderChange,
onIsShowLogoChange,
getThemeConfig,
onDrawerClose,
onAddFilterChange,
onAddDarkChange,
onWartermarkChange,
onWartermarkTextInput,
onSetLayout,
setLocalThemeConfig,
onClassicSplitMenuChange,
onIsBreadcrumbChange,
onSortableTagsViewChange,
onShareTagsViewChange,
onCopyConfigClick,
onResetConfigClick,
...toRefs(state),
};
},
setTimeout(() => {
// 默认样式
onColorPickerChange();
// 灰色模式
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
// 色弱模式
if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
// 深色模式
if (getThemeConfig.value.isIsDark) onAddDarkChange();
// 开启水印
onWartermarkChange();
// 语言国际化
if (Local.get('themeConfig')) locale.value = Local.get('themeConfig').globalI18n;
// 初始化菜单样式等
initSetStyle();
}, 100);
});
});
onUnmounted(() => {
mittBus.off('layoutMobileResize', () => {});
});
// 暴露变量
defineExpose({
openDrawer,
});
</script>
+299
View File
@@ -0,0 +1,299 @@
<template>
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
<!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">-->
<!-- <div class="layout-navbars-breadcrumb-user-icon">-->
<!-- <i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>-->
<!-- </div>-->
<!-- <template #dropdown>-->
<!-- <el-dropdown-menu>-->
<!-- <el-dropdown-item command="large" :disabled="state.disabledSize === 'large'">{{ $t('message.user.dropdownLarge') }}</el-dropdown-item>-->
<!-- <el-dropdown-item command="default" :disabled="state.disabledSize === 'default'">{{ $t('message.user.dropdownDefault') }}</el-dropdown-item>-->
<!-- <el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">{{ $t('message.user.dropdownSmall') }}</el-dropdown-item>-->
<!-- </el-dropdown-menu>-->
<!-- </template>-->
<!-- </el-dropdown>-->
<div>
<el-select v-show="userInfos.portals.length>1" v-model='userInfos.dePortal' placeholder='门户名称' @change="portalChange" style='margin-right: 10px;width: 140px'>
<el-option
v-for='item in userInfos.portals'
:key='item.id'
:label='item.name'
:value='item.id'
/>
</el-select>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i
class="iconfont"
:class="state.disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'"
:title="$t('message.user.title1')"
></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-cn" :disabled="state.disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="state.disabledI18n === 'en'">English</el-dropdown-item>
<el-dropdown-item command="zh-tw" :disabled="state.disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<el-icon :title="$t('message.user.title2')">
<ele-Search />
</el-icon>
</div>
<!-- <div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">-->
<!-- <i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>-->
<!-- </div>-->
<div class="layout-navbars-breadcrumb-user-icon" ref="userNewsBadgeRef" v-click-outside="onUserNewsClick">
<el-badge :is-dot="true">
<el-icon :title="$t('message.user.title4')">
<ele-Bell />
</el-icon>
</el-badge>
</div>
<el-popover
ref="userNewsRef"
:virtual-ref="userNewsBadgeRef"
placement="bottom"
trigger="click"
transition="el-zoom-in-top"
virtual-triggering
:width="300"
:persistent="false"
>
<UserNews />
</el-popover>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i
class="iconfont"
:title="state.isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="userInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ userInfos.userName === '' ? 'common' : userInfos.userName }}
<el-icon class="el-icon--right">
<ele-ArrowDown />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<Search ref="searchRef" />
<div class="layout-navbars-breadcrumb-user-icon" style="width: 42px" @click="onLayoutSetingClick">
<el-icon class="el-icon--right">
<ele-Setting />
</el-icon>
</div>
</div>
</template>
<script setup lang="ts" name="layoutBreadcrumbUser">
import { defineAsyncComponent, ref, unref, computed, reactive, onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router';
import { ElMessageBox, ElMessage, ClickOutside as vClickOutside } from 'element-plus';
import screenfull from 'screenfull';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
import { Session, Local } from '/@/utils/storage';
import request from "/@/utils/request";
import {initBackEndControlRoutesByPortal} from "/@/router/backEnd";
// 引入组件
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/topBar/userNews.vue'));
const Search = defineAsyncComponent(() => import('/@/layout/navBars/topBar/search.vue'));
// 定义变量内容
const userNewsRef = ref();
const userNewsBadgeRef = ref();
const { locale, t } = useI18n();
const router = useRouter();
const route = useRoute();
const stores = useUserInfo();
const storesThemeConfig = useThemeConfig();
const { userInfos } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const searchRef = ref();
const state = reactive({
isScreenfull: false,
disabledI18n: 'zh-cn',
disabledSize: 'large',
});
// 设置分割样式
const layoutUserFlexNum = computed(() => {
let num: string | number = '';
const { layout, isClassicSplitMenu } = themeConfig.value;
const layoutArr: string[] = ['defaults', 'columns'];
if (layoutArr.includes(layout) || (layout === 'classic' && !isClassicSplitMenu)) num = '1';
else num = '';
return num;
});
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) state.isScreenfull = true;
else state.isScreenfull = false;
});
};
// 消息通知点击时
const onUserNewsClick = () => {
unref(userNewsRef).popperRef?.delayHide?.();
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path: string) => {
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: t('message.user.logOutTitle'),
message: t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: t('message.user.logOutConfirm'),
cancelButtonText: t('message.user.logOutCancel'),
buttonSize: 'default',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(async () => {
// 清除缓存/token等
Session.clear();
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/zsvg/vboot-vue');
} else {
router.push(path);
}
};
// 菜单搜索点击
const onSearchClick = () => {
searchRef.value.openSearch();
};
// 组件大小改变
const onComponentSizeChange = (size: string) => {
Local.remove('themeConfig');
themeConfig.value.globalComponentSize = size;
Local.set('themeConfig', themeConfig.value);
initI18nOrSize('globalComponentSize', 'disabledSize');
window.location.reload();
};
// 语言切换
const onLanguageChange = (lang: string) => {
Local.remove('themeConfig');
themeConfig.value.globalI18n = lang;
Local.set('themeConfig', themeConfig.value);
locale.value = lang;
other.useTitle();
initI18nOrSize('globalI18n', 'disabledI18n');
};
// 初始化组件大小/i18n
const initI18nOrSize = (value: string, attr: string) => {
(<any>state)[attr] = Local.get('themeConfig')[value];
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initI18nOrSize('globalComponentSize', 'disabledSize');
initI18nOrSize('globalI18n', 'disabledI18n');
}
});
// 门户切换
const portalChange=async (porid:any)=>{
const res= await request({
url: 'getMenuList',
method: 'get',
params:{porid:porid}
}) as any;
await initBackEndControlRoutesByPortal(res);
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 3, ...route }));
location.href='#/home';
}
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user {
cursor: pointer;
display: flex;
align-items: center;
justify-content: flex-end;
&-link {
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
&-photo {
width: 25px;
height: 25px;
border-radius: 100%;
}
}
&-icon {
padding: 0 10px;
cursor: pointer;
color: var(--next-bg-topBarColor);
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
&:hover {
background: var(--next-color-user-hover);
i {
display: inline-block;
animation: logoAnimation 0.3s ease-in-out;
}
}
}
:deep(.el-dropdown) {
color: var(--next-bg-topBarColor);
}
:deep(.el-badge) {
height: 40px;
line-height: 40px;
display: flex;
align-items: center;
}
:deep(.el-badge__content.is-fixed) {
top: 12px;
}
}
</style>
@@ -2,11 +2,11 @@
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>
<div class="head-box-btn" v-if="state.newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>
</div>
<div class="content-box">
<template v-if="newsList.length > 0">
<div class="content-box-item" v-for="(v, k) in newsList" :key="k">
<template v-if="state.newsList.length > 0">
<div class="content-box-item" v-for="(v, k) in state.newsList" :key="k">
<div>{{ v.label }}</div>
<div class="content-box-msg">
{{ v.value }}
@@ -16,45 +16,37 @@
</template>
<el-empty :description="$t('message.user.newDesc')" v-else></el-empty>
</div>
<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">{{ $t('message.user.newGo') }}</div>
<div class="foot-box" @click="onGoToGiteeClick" v-if="state.newsList.length > 0">{{ $t('message.user.newGo') }}</div>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, defineComponent } from 'vue';
<script setup lang="ts" name="layoutBreadcrumbUserNews">
import { reactive } from 'vue';
export default defineComponent({
name: 'layoutBreadcrumbUserNews',
setup() {
const state = reactive({
newsList: [
{
label: '关于版本发布的通知',
value: 'vue-next-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus,正式发布时间:2021年02月28日!',
time: '2020-12-08',
},
{
label: '关于学习交流的通知',
value: 'QQ群号码 665452019,欢迎小伙伴入群学习交流探讨!',
time: '2020-12-08',
},
],
});
// 全部已读点击
const onAllReadClick = () => {
state.newsList = [];
};
// 前往通知中心点击
const onGoToGiteeClick = () => {
window.open('https://gitee.com/zsvg/vboot-vue');
};
return {
onAllReadClick,
onGoToGiteeClick,
...toRefs(state),
};
},
// 定义变量内容
const state = reactive({
newsList: [
{
label: '关于版本发布的通知',
value: 'vue-next-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus,正式发布时间:2021年02月28日!',
time: '2020-12-08',
},
{
label: '关于学习交流的通知',
value: 'QQ群号码 665452019,欢迎小伙伴入群学习交流探讨!',
time: '2020-12-08',
},
],
});
// 全部已读点击
const onAllReadClick = () => {
state.newsList = [];
};
// 前往通知中心点击
const onGoToGiteeClick = () => {
window.open('https://gitee.com/lyt-top/vue-next-admin');
};
</script>
<style scoped lang="scss">
+103 -134
View File
@@ -1,151 +1,120 @@
<template>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</a>
</template>
</el-menu-item>
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
</template>
</el-menu>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, nextTick, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
<script setup lang="ts" name="navMenuHorizontal">
import { defineAsyncComponent, reactive, computed, onBeforeMount } from 'vue';
import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { verifyUrl } from '/@/utils/toolsValidate';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'navMenuHorizontal',
components: {
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
// 引入组件
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
// 定义父组件传过来的值
const props = defineProps({
// 菜单列表
menuList: {
type: Array<RouteRecordRaw>,
default: () => [],
},
props: {
menuList: {
type: Array,
default: () => [],
},
},
setup(props) {
const elMenuHorizontalScrollRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
defaultActive: null,
});
// 定义变量内容
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive({
defaultActive: '' as string | undefined,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return <RouteItems>props.menuList;
});
// 路由过滤递归函数
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
// 获取父级菜单数据
const menuLists = computed(() => {
return <any>props.menuList;
});
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: any) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = els.offsetLeft;
});
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<string>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
filterRoutesFun(routesList.value).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 设置页面当前路由高亮
const setCurrentRouterHighlight = (currentRoute: any) => {
const { path, meta } = currentRoute;
if (themeConfig.value.layout === 'classic') {
(<any>state.defaultActive) = `/${path.split('/')[1]}`;
} else {
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
};
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
});
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
setCurrentRouterHighlight(to);
// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
return {
elMenuHorizontalScrollRef,
menuLists,
onElMenuHorizontalScroll,
onALinkClick,
...toRefs(state),
};
},
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: MittMenu = { children: [] };
filterRoutesFun(routesList.value).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = { ...v };
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 设置页面当前路由高亮
const setCurrentRouterHighlight = (currentRoute: RouteToFrom) => {
const { path, meta } = currentRoute;
if (themeConfig.value.layout === 'classic') {
state.defaultActive = `/${path?.split('/')[1]}`;
} else {
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
if (pathSplit.length >= 4 && meta?.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
};
// 打开外部链接
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val);
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
setCurrentRouterHighlight(to);
// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
</script>
+20 -30
View File
@@ -16,7 +16,7 @@
<template v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
<span>{{ $t(val.meta.title) }}</span>
</a>
</template>
</el-menu-item>
@@ -24,36 +24,26 @@
</template>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { verifyUrl } from '/@/utils/toolsValidate';
<script setup lang="ts" name="navMenuSubItem">
import { computed } from 'vue';
import { RouteRecordRaw } from 'vue-router';
import other from '/@/utils/other';
export default defineComponent({
name: 'navMenuSubItem',
props: {
chil: {
type: Array,
default: () => [],
},
},
setup(props) {
const router = useRouter();
// 获取父级菜单数据
const chils = computed(() => {
return <any>props.chil;
});
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
return {
chils,
onALinkClick,
};
// 定义父组件传过来的值
const props = defineProps({
// 菜单列表
chil: {
type: Array<RouteRecordRaw>,
default: () => [],
},
});
// 获取父级菜单数据
const chils = computed(() => {
return <RouteItems>props.chil;
});
// 打开外部链接
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val);
};
</script>
-50
View File
@@ -1,50 +0,0 @@
<template>
<el-sub-menu :index='val.path' :key='val.path' v-if='val.children && val.children.length > 0'>
<template #title>
<SvgIcon :name='val.meta.icon' />
<span>{{ $t(val.meta.title) }}</span>
</template>
<!-- <sub-item :chil="val.children" :key="`s-${val.path}`"/>-->
<!-- <BasicSubMenuItem v-bind="$props" :item="childrenItem" />-->
<SubItem :item='childrenItem' v-for='childrenItem in val.children' :key="`s-${childrenItem.path}`"/>
<!-- <sub-item :chil="val.children"/>-->
</el-sub-menu>
<template v-else>
<el-menu-item :index='val.path' :key='val.path'>
<template v-if='!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)'>
<SvgIcon :name='val.meta.icon' />
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href='val.meta.isLink' target='_blank' rel='opener' class='w100'>
<SvgIcon :name='val.meta.icon' />
<span>{{ $t(val.meta.title) }}</span>
</a>
</template>
</el-menu-item>
</template>
</template>
<script lang='ts'>
import { computed, defineComponent } from 'vue';
export default defineComponent({
name: 'navMenuSubItem',
props: {
item: {
type: Object,
default: () => {
},
},
},
setup(props) {
// 获取父级菜单数据
const val = computed(() => {
return <any>props.item;
});
return {
val,
};
},
});
</script>
+66 -76
View File
@@ -1,9 +1,9 @@
<template>
<el-menu
router
:default-active="defaultActive"
:default-active="state.defaultActive"
background-color="transparent"
:collapse="isCollapse"
:collapse="state.isCollapse"
:unique-opened="getThemeConfig.isUniqueOpened"
:collapse-transition="false"
>
@@ -30,83 +30,73 @@
</el-menu>
</template>
<script lang="ts">
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
<script setup lang="ts" name="navMenuVertical">
import { defineAsyncComponent, reactive, computed, onMounted, watch } from 'vue';
import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { verifyUrl } from '/@/utils/toolsValidate';
import other from '/@/utils/other';
export default defineComponent({
name: 'navMenuVertical',
components: {
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
},
props: {
menuList: {
type: Array,
default: () => [],
},
},
setup(props) {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return <any>props.menuList;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute: any) => {
const { path, meta } = currentRoute;
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
// 设置菜单的收起/展开
watch(
themeConfig.value,
() => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse);
},
{
immediate: true,
}
);
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
state.defaultActive = setParentHighlight(to);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
});
return {
menuLists,
getThemeConfig,
onALinkClick,
...toRefs(state),
};
// 引入组件
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
// 定义父组件传过来的值
const props = defineProps({
// 菜单列表
menuList: {
type: Array<RouteRecordRaw>,
default: () => [],
},
});
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive({
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return <RouteItems>props.menuList;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute: RouteToFrom) => {
const { path, meta } = currentRoute;
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
if (pathSplit.length >= 4 && meta?.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 打开外部链接
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val);
};
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
state.defaultActive = setParentHighlight(to);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
});
// 设置菜单的收起/展开
watch(
() => themeConfig.value.isCollapse,
(isCollapse) => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = isCollapse);
},
{
immediate: true,
}
);
</script>
-140
View File
@@ -1,140 +0,0 @@
<template>
<el-menu
router
:default-active='defaultActive'
background-color='transparent'
:collapse='isCollapse'
:unique-opened='getThemeConfig.isUniqueOpened'
:collapse-transition='false'
>
<template v-for='val in menuLists'>
<el-sub-menu :index='val.path' v-if='val.children && val.children.length > 0' :key='val.path'>
<template #title>
<SvgIcon :name='val.meta.icon' />
<span>{{ $t(val.meta.title) }}</span>
</template>
<!-- <SubItem :item='childrenItem' v-for='childrenItem in val.children' :key="`s-${childrenItem.path}`"/>-->
<template v-for='citem in val.children'>
<el-sub-menu :index='citem.path' :key='citem.path' v-if='citem.children && citem.children.length > 0'>
<template #title>
<SvgIcon :name='citem.meta.icon' />
<span>{{ $t(citem.meta.title) }}</span>
</template>
<template v-for='ccitem in citem.children'>
<el-sub-menu :index='ccitem.path' :key='ccitem.path' v-if='ccitem.children && ccitem.children.length > 0'>
<template #title>
<SvgIcon :name='ccitem.meta.icon' />
<span>{{ $t(ccitem.meta.title) }}</span>
</template>
</el-sub-menu>
<el-menu-item v-else :index='ccitem.path' :key='ccitem.path'>
<SvgIcon :name='ccitem.meta.icon' />
<span>{{ $t(ccitem.meta.title) }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else :index='citem.path' :key='citem.path'>
<SvgIcon :name='citem.meta.icon' />
<span>{{ $t(citem.meta.title) }}</span>
</el-menu-item>
</template>
<!-- <SubItem :item='childrenItem' v-for='childrenItem in val.children' :key='`s-${childrenItem.path}`' />-->
<!-- <SubItem :chil='val.children' />-->
</el-sub-menu>
<template v-else>
<el-menu-item :index='val.path' :key='val.path'>
<SvgIcon :name='val.meta.icon' />
<template #title v-if='!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)'>
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a :href='val.meta.isLink' target='_blank' rel='opener' class='w100'>{{ $t(val.meta.title) }}</a>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</template>
<script lang='ts'>
import { toRefs, reactive, computed, defineComponent, onMounted, watch, onUpdated, onBeforeUpdate } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
// import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuVertical',
// components: { SubItem },
props: {
menuList: {
type: Array,
default: () => [],
},
},
setup(props) {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive({
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return <any>props.menuList;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute: any) => {
const { path, meta } = currentRoute;
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 设置菜单的收起/展开
watch(
themeConfig.value,
() => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse);
},
{
immediate: true,
},
);
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
let date1 = 0;
onUpdated(() => {
console.log(new Date().getTime() - date1);
});
onBeforeUpdate(() => {
date1 = new Date().getTime();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
state.defaultActive = setParentHighlight(to);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
});
return {
menuLists,
getThemeConfig,
...toRefs(state),
};
},
});
</script>
+74 -77
View File
@@ -2,7 +2,7 @@
<div class="layout-padding layout-padding-unset layout-iframe">
<div class="layout-padding-auto layout-padding-view">
<div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white">
<transition-group :name="name" mode="out-in">
<transition-group :name="name">
<iframe
:src="v.meta.isLink"
:key="v.path"
@@ -20,85 +20,82 @@
</div>
</template>
<script lang="ts">
import { defineComponent, computed, watch, ref, nextTick } from 'vue';
<script setup lang="ts" name="layoutIframeView">
import { computed, watch, ref, nextTick } from 'vue';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'layoutIframeView',
props: {
refreshKey: {
type: String,
default: () => '',
},
name: {
type: String,
default: () => 'slide-right',
},
list: {
type: Array,
default: () => [],
},
// 定义父组件传过来的值
const props = defineProps({
// 刷新 iframe
refreshKey: {
type: String,
default: () => '',
},
setup(props) {
const iframeRef = ref();
const route = useRoute();
// 处理 list 列表,当打开时,才进行加载
const setIframeList = computed(() => {
return (<any>props.list).filter((v: any) => v.meta.isIframeOpen);
});
// 获取 iframe 当前路由 path
const getRoutePath = computed(() => {
return route.path;
});
// 关闭 iframe loading
const closeIframeLoading = (val: string, item: any) => {
nextTick(() => {
if (!iframeRef.value) return false;
iframeRef.value.forEach((v: any) => {
if (v.dataset.url === val) {
v.onload = () => {
if (item.meta.isIframeOpen && item.meta.loading) item.meta.loading = false;
};
}
});
});
};
// 监听路由变化,初始化 iframe 数据,防止多个 iframe 时,切换不生效
watch(
() => route.fullPath,
(val) => {
const item: any = props.list.find((v: any) => v.path === val);
if (!item) return false;
if (!item.meta.isIframeOpen) item.meta.isIframeOpen = true;
closeIframeLoading(val, item);
},
{
immediate: true,
}
);
// 监听 iframe refreshKey 变化,用于 tagsview 右键菜单刷新
watch(
() => props.refreshKey,
() => {
const item: any = props.list.find((v: any) => v.path === route.path);
if (!item) return false;
if (item.meta.isIframeOpen) item.meta.isIframeOpen = false;
setTimeout(() => {
item.meta.isIframeOpen = true;
item.meta.loading = true;
closeIframeLoading(route.fullPath, item);
});
},
{
deep: true,
}
);
return {
iframeRef,
setIframeList,
getRoutePath,
};
// 过渡动画 name
name: {
type: String,
default: () => 'slide-right',
},
// iframe 列表
list: {
type: Array,
default: () => [],
},
});
// 定义变量内容
const iframeRef = ref();
const route = useRoute();
// 处理 list 列表,当打开时,才进行加载
const setIframeList = computed(() => {
return (<RouteItems>props.list).filter((v: RouteItem) => v.meta?.isIframeOpen);
});
// 获取 iframe 当前路由 path
const getRoutePath = computed(() => {
return route.path;
});
// 关闭 iframe loading
const closeIframeLoading = (val: string, item: RouteItem) => {
nextTick(() => {
if (!iframeRef.value) return false;
iframeRef.value.forEach((v: HTMLElement) => {
if (v.dataset.url === val) {
v.onload = () => {
if (item.meta?.isIframeOpen && item.meta.loading) item.meta.loading = false;
};
}
});
});
};
// 监听路由变化,初始化 iframe 数据,防止多个 iframe 时,切换不生效
watch(
() => route.fullPath,
(val) => {
const item: any = props.list.find((v: any) => v.path === val);
if (!item) return false;
if (!item.meta.isIframeOpen) item.meta.isIframeOpen = true;
closeIframeLoading(val, item);
},
{
immediate: true,
}
);
// 监听 iframe refreshKey 变化,用于 tagsview 右键菜单刷新
watch(
() => props.refreshKey,
() => {
const item: any = props.list.find((v: any) => v.path === route.path);
if (!item) return false;
if (item.meta.isIframeOpen) item.meta.isIframeOpen = false;
setTimeout(() => {
item.meta.isIframeOpen = true;
item.meta.loading = true;
closeIframeLoading(route.fullPath, item);
});
},
{
deep: true,
}
);
</script>
+27 -47
View File
@@ -3,7 +3,7 @@
<div class="layout-padding-auto layout-padding-view">
<div class="layout-link-warp">
<i class="layout-link-icon iconfont icon-xingqiu"></i>
<div class="layout-link-msg">页面 "{{ $t(currentRouteMeta.title) }}" 已在新窗口中打开</div>
<div class="layout-link-msg">页面 "{{ $t(state.title) }}" 已在新窗口中打开</div>
<el-button class="mt30" round size="default" @click="onGotoFullPage">
<i class="iconfont icon-lianjie"></i>
<span>立即前往体验</span>
@@ -13,55 +13,35 @@
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, watch } from 'vue';
import { useRoute, RouteMeta } from 'vue-router';
<script setup lang="ts" name="layoutLinkView">
import { reactive, watch } from 'vue';
import { useRoute } from 'vue-router';
import { verifyUrl } from '/@/utils/toolsValidate';
// 定义接口来定义对象的类型
interface LinkViewState {
currentRouteMeta: {
isLink: string;
title: string;
};
}
interface LinkViewRouteMeta extends RouteMeta {
isLink: string;
title: string;
}
export default defineComponent({
name: 'layoutLinkView',
setup() {
const route = useRoute();
const state = reactive<LinkViewState>({
currentRouteMeta: {
isLink: '',
title: '',
},
});
// 立即前往
const onGotoFullPage = () => {
const { origin, pathname } = window.location;
if (verifyUrl(state.currentRouteMeta.isLink)) window.open(state.currentRouteMeta.isLink);
else window.open(`${origin}${pathname}#${state.currentRouteMeta.isLink}`);
};
// 监听路由的变化,设置内容
watch(
() => route.path,
() => {
state.currentRouteMeta = <LinkViewRouteMeta>route.meta;
},
{
immediate: true,
}
);
return {
onGotoFullPage,
...toRefs(state),
};
},
// 定义变量内容
const route = useRoute();
const state = reactive<LinkViewState>({
title: '',
isLink: '',
});
// 立即前往
const onGotoFullPage = () => {
const { origin, pathname } = window.location;
if (verifyUrl(<string>state.isLink)) window.open(state.isLink);
else window.open(`${origin}${pathname}#${state.isLink}`);
};
// 监听路由的变化,设置内容
watch(
() => route.path,
() => {
state.title = <string>route.meta.title;
state.isLink = <string>route.meta.isLink;
},
{
immediate: true,
}
);
</script>
<style scoped lang="scss">
+85 -99
View File
@@ -3,18 +3,18 @@
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="getKeepAliveNames">
<component :is="Component" :key="refreshRouterViewKey" class="w100" v-show="!isIframePage" />
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
</keep-alive>
</transition>
</router-view>
<transition :name="setTransitionName" mode="out-in">
<Iframes class="w100" v-show="isIframePage" :refreshKey="iframeRefreshKey" :name="setTransitionName" :list="iframeList" />
<Iframes class="w100" v-show="isIframePage" :refreshKey="state.iframeRefreshKey" :name="setTransitionName" :list="state.iframeList" />
</transition>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, computed, defineComponent, toRefs, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
<script setup lang="ts" name="layoutParentView">
import { defineAsyncComponent, computed, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
@@ -22,101 +22,87 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface ParentViewState {
refreshRouterViewKey: string;
iframeRefreshKey: string;
keepAliveNameList: string[];
iframeList: string[];
}
// 引入组件
const Iframes = defineAsyncComponent(() => import('/@/layout/routerView/iframes.vue'));
export default defineComponent({
name: 'layoutParentView',
components: {
Iframes: defineAsyncComponent(() => import('/@/layout/routerView/iframes.vue')),
},
setup() {
const route = useRoute();
const router = useRouter();
const storesKeepAliveNames = useKeepALiveNames();
const storesThemeConfig = useThemeConfig();
const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<ParentViewState>({
refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
keepAliveNameList: [],
iframeList: [],
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
return themeConfig.value.animation;
});
// 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => {
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
});
// 设置 iframe 显示/隐藏
const isIframePage = computed(() => {
return route.meta.isIframe;
});
// 获取 iframe 组件列表(未进行渲染)
const getIframeListRoutes = async () => {
router.getRoutes().forEach((v: any) => {
if (v.meta.isIframe) {
v.meta.isIframeOpen = false;
v.meta.loading = true;
state.iframeList.push({ ...v });
}
});
};
// 页面加载前,处理缓存,页面刷新时路由缓存处理
onBeforeMount(() => {
state.keepAliveNameList = keepAliveNames.value;
mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = '';
state.iframeRefreshKey = '';
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.iframeRefreshKey = fullPath;
state.keepAliveNameList = keepAliveNames.value;
});
});
});
// 页面加载时
onMounted(() => {
getIframeListRoutes();
// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
nextTick(() => {
setTimeout(() => {
if (themeConfig.value.isCacheTagsView) cachedViews.value = Session.get('tagsViewList')?.map((item: any) => item.name);
}, 0);
});
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('onTagsViewRefreshRouterView', () => {});
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = decodeURI(route.fullPath);
},
{
immediate: true,
}
);
return {
route,
setTransitionName,
getKeepAliveNames,
isIframePage,
...toRefs(state),
};
},
// 定义变量内容
const route = useRoute();
const router = useRouter();
const storesKeepAliveNames = useKeepALiveNames();
const storesThemeConfig = useThemeConfig();
const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<ParentViewState>({
refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
keepAliveNameList: [],
iframeList: [],
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
return themeConfig.value.animation;
});
// 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => {
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
});
// 设置 iframe 显示/隐藏
const isIframePage = computed(() => {
return route.meta.isIframe;
});
// 获取 iframe 组件列表(未进行渲染)
const getIframeListRoutes = async () => {
router.getRoutes().forEach((v) => {
if (v.meta.isIframe) {
v.meta.isIframeOpen = false;
v.meta.loading = true;
state.iframeList.push({ ...v });
}
});
};
// 页面加载前,处理缓存,页面刷新时路由缓存处理
onBeforeMount(() => {
state.keepAliveNameList = keepAliveNames.value;
mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = '';
state.iframeRefreshKey = '';
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.iframeRefreshKey = fullPath;
state.keepAliveNameList = keepAliveNames.value;
});
});
});
// 页面加载时
onMounted(() => {
getIframeListRoutes();
// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
// https://gitee.com/lyt-top/vue-next-admin/pulls/40
nextTick(() => {
setTimeout(() => {
if (themeConfig.value.isCacheTagsView) {
let tagsViewArr: RouteItem[] = Session.get('tagsViewList') || [];
cachedViews.value = tagsViewArr.filter((item) => item.meta?.isKeepAlive).map((item) => item.name as string);
}
}, 0);
});
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('onTagsViewRefreshRouterView', () => {});
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = decodeURI(route.fullPath);
},
{
immediate: true,
}
);
</script>
+108
View File
@@ -0,0 +1,108 @@
<template>
<div class="sponsors-container" title="点击前往体验" v-show="state.sponsors.isShow" @click="onSponsorsClick">
<el-carousel height="240px" indicator-position="none" :arrow="setCarouselShow" @change="onCarouselChange">
<el-carousel-item v-for="(v, k) in state.sponsors.list" :key="k">
<img :src="v.url" class="sponsors-img" />
<div class="sponsors-text" v-html="v.text"></div>
</el-carousel-item>
</el-carousel>
<div class="sponsors-close">
<SvgIcon name="ele-Close" :size="12" title="关闭赞助商" @click.stop="onCloseSponsors" />
</div>
</div>
</template>
<script setup lang="ts" name="layoutSponsors">
import { reactive, computed, onMounted } from 'vue';
import sponsorsOne from '/@/assets/ccflowRightNextAdmin.png';
// 定义变量内容
const state = reactive({
sponsors: {
list: [
{
url: sponsorsOne,
text: `驰骋BPM系统包含表单引擎+流程引擎+权限控制,方便集成,配置灵活,功能强大,适合中国国情的工作流引擎.演示:http://demo.ccflow.org。右上角点star方可加群: 1060674395`,
link: 'http://www.ccflow.org/',
},
],
isShow: false,
index: 0,
},
});
// 设置轮播图箭头显示
const setCarouselShow = computed(() => {
return state.sponsors.list.length <= 1 ? 'never' : 'hover';
});
// 关闭赞助商
const onCloseSponsors = () => {
state.sponsors.isShow = false;
};
// 轮播图改变时
const onCarouselChange = (e: number) => {
state.sponsors.index = e;
};
// 当前项内容点击
const onSponsorsClick = () => {
window.open(state.sponsors.list[state.sponsors.index].link);
};
// 延迟显示,防止影响其它界面加载
const delayShow = () => {
setTimeout(() => {
state.sponsors.isShow = true;
}, 3000);
};
// 页面加载时
onMounted(() => {
delayShow();
});
</script>
<style scoped lang="scss">
.sponsors-container {
position: fixed;
right: 15px;
bottom: 15px;
z-index: 3;
width: 200px;
background-color: var(--next-bg-main-color);
box-shadow: var(--el-box-shadow-lighter);
border-radius: 5px;
overflow: hidden;
cursor: pointer;
.sponsors-img {
width: 100%;
height: 80px;
}
.sponsors-text {
padding: 10px;
color: var(--el-text-color-regular);
font-size: var(--el-dialog-content-font-size);
}
.sponsors-close {
width: 60px;
height: 60px;
border-radius: 100%;
background: rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
position: absolute;
right: -35px;
bottom: -35px;
:deep(i) {
position: absolute;
left: 9px;
top: 9px;
color: #afafaf;
transition: all 0.3s ease;
}
&:hover {
transition: all 0.3s ease;
:deep(i) {
color: var(--el-color-primary);
transition: all 0.3s ease;
}
}
}
}
</style>
+151
View File
@@ -0,0 +1,151 @@
<template>
<div class="upgrade-dialog">
<el-dialog
v-model="state.isUpgrade"
width="300px"
destroy-on-close
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="upgrade-title">
<div class="upgrade-title-warp">
<span class="upgrade-title-warp-txt">{{ $t('message.upgrade.title') }}</span>
<span class="upgrade-title-warp-version">v{{ state.version }}</span>
</div>
</div>
<div class="upgrade-content">
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
<div class="mt5">
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
CHANGELOG.md
</el-link>
</div>
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
</div>
<div class="upgrade-btn">
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="layoutUpgrade">
import { reactive, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
// 定义变量内容
const { t } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
isUpgrade: false,
// @ts-ignore
version: __NEXT_VERSION__,
isLoading: false,
btnTxt: '',
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 残忍拒绝
const onCancel = () => {
state.isUpgrade = false;
};
// 马上更新
const onUpgrade = () => {
state.isLoading = true;
state.btnTxt = t('message.upgrade.btnTwoLoading');
setTimeout(() => {
Local.clear();
window.location.reload();
Local.set('version', state.version);
}, 2000);
};
// 延迟显示,防止刷新时界面显示太快
const delayShow = () => {
setTimeout(() => {
state.isUpgrade = true;
}, 2000);
};
// 页面加载时
onMounted(() => {
delayShow();
setTimeout(() => {
state.btnTxt = t('message.upgrade.btnTwo');
}, 200);
});
</script>
<style scoped lang="scss">
.upgrade-dialog {
:deep(.el-dialog) {
.el-dialog__body {
padding: 0 !important;
}
.el-dialog__header {
display: none !important;
}
.upgrade-title {
text-align: center;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&::after {
content: '';
position: absolute;
background-color: var(--el-color-primary-light-1);
width: 130%;
height: 130px;
border-bottom-left-radius: 100%;
border-bottom-right-radius: 100%;
}
.upgrade-title-warp {
z-index: 1;
position: relative;
.upgrade-title-warp-txt {
color: var(--next-color-white);
font-size: 22px;
letter-spacing: 3px;
}
.upgrade-title-warp-version {
color: var(--next-color-white);
background-color: var(--el-color-primary-light-4);
font-size: 12px;
position: absolute;
display: flex;
top: -2px;
right: -50px;
padding: 2px 4px;
border-radius: 2px;
}
}
}
.upgrade-content {
padding: 20px;
line-height: 22px;
.upgrade-content-desc {
color: var(--el-color-info-light-5);
font-size: 12px;
}
}
.upgrade-btn {
border-top: 1px solid var(--el-border-color-lighter, #ebeef5);
display: flex;
justify-content: space-around;
padding: 15px 20px;
.el-button {
width: 100%;
}
}
}
}
</style>
+4 -5
View File
@@ -1,8 +1,8 @@
import { createApp } from 'vue';
import pinia from '/@/stores/index';
import App from './App.vue';
import router from './router';
import { directive } from '/@/utils/directive';
import App from '/@/App.vue';
import router from '/@/router';
import { directive } from '/@/directive/index';
import { i18n } from '/@/i18n/index';
import other from '/@/utils/other';
@@ -23,5 +23,4 @@ other.elSvg(app);
setupVxe(app);
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(VForm3).use(i18n).use(VueGridLayout).mount('#app');
app.use(pinia).use(router).use(ElementPlus).use(VForm3).use(i18n).use(VueGridLayout).mount('#app');
@@ -221,7 +221,7 @@
</el-card>
</template>
<script lang='ts'>
export default { name: 'AssFormTmplEdit' };
export default { name: 'AssFormTempEdit' };
</script>
<script lang='ts' setup>
import { Setting } from '@element-plus/icons-vue';
@@ -241,7 +241,7 @@ const { proxy } = getCurrentInstance() as any;
const activeName = ref('tab1');
const state = reactive({
url: '/ass/form/tmpl', fieldSelectedIds: [] as any,
url: '/ass/form/temp', fieldSelectedIds: [] as any,
params: { path: '', query: '' }, xtab: '' as any, xindex: 0 as any,
form: {
avtag: true, fields: [] as any,links: [],
@@ -50,7 +50,7 @@
<el-tab-pane label='流程配置' name='tab3'>
<div style='width: 100%;height: 720px'>
<BpmTmplEdit ref="bpmTmplEditRef" v-if='state.bpmShowTag' :prxml='form.orxml'/>
<BpmTempEdit ref="bpmTempEditRef" v-if='state.bpmShowTag' :prxml='form.orxml'/>
</div>
</el-tab-pane>
@@ -60,13 +60,13 @@
</el-card>
</template>
<script lang='ts'>
export default { name: 'AssProcTmplEdit' };
export default { name: 'AssProcTempEdit' };
</script>
<script lang='ts' setup>
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import { tabClose, tabSave } from '/@/comps/page/edit';
import { useRoute } from 'vue-router';
import BpmTmplEdit from '/@/comps/bpm/tmpl/edit.vue';
import BpmTempEdit from '/@/comps/bpm/temp/edit.vue';
import { FormInstance } from 'element-plus';
import request from '/@/utils/request';
@@ -76,7 +76,7 @@ const activeName = ref('tab1');
const { proxy } = getCurrentInstance() as any;
const state = reactive({
url: '/bpm/proc/tmpl',bpmShowTag:false,
url: '/bpm/proc/temp',bpmShowTag:false,
params: { path: '', query: {} as any},
form: { avtag: true,orxml:'',type:''} as any,
});
@@ -101,10 +101,10 @@ onMounted(async () => {
state.bpmShowTag=true;
});
const bpmTmplEditRef=ref();
const bpmTempEditRef=ref();
async function save(state: any) {
form.value.orxml =await bpmTmplEditRef.value.getXml();
form.value.orxml =await bpmTempEditRef.value.getXml();
await tabSave({ formRef: formRef.value, state, proxy, route });
}
</script>
+1
View File
@@ -146,6 +146,7 @@ import { Session } from '/@/utils/storage';
import request from '/@/utils/request';
const cropperDialogRef = ref();
const state = reactive({
cropperImg: avatar,userInfo:{} as any,avatar:'',
+4 -4
View File
@@ -268,7 +268,7 @@
<el-tab-pane label='流程配置' name='tab4' v-if='form.prtag'>
<div style='width: 100%;height: 720px'>
<BpmTmplEdit ref="bpmTmplEditRef" v-if='state.bpmShowTag' :prxml='form.prxml'/>
<BpmTempEdit ref="bpmTempEditRef" v-if='state.bpmShowTag' :prxml='form.prxml'/>
</div>
</el-tab-pane>
@@ -294,7 +294,7 @@ import { ElMessage, FormInstance } from 'element-plus';
import { uuid } from '/@/utils/xutil';
import TabEdit from './tab.vue';
import { VxeTableInstance } from 'vxe-table';
import BpmTmplEdit from '/@/comps/bpm/tmpl/edit.vue';
import BpmTempEdit from '/@/comps/bpm/temp/edit.vue';
const route = useRoute();
@@ -342,7 +342,7 @@ onMounted(async () => {
state.bpmShowTag=true;
});
const bpmTmplEditRef=ref();
const bpmTempEditRef=ref();
const editInitx = async () => {
state.params = <any>route;
@@ -650,7 +650,7 @@ const save = async () => {
form.value.ljson = JSON.stringify(form.value.links);
if (form.value.prtag) {
form.value.prxml =await bpmTmplEditRef.value.getXml();
form.value.prxml =await bpmTempEditRef.value.getXml();
}
await tabSave({ formRef: formRef.value, state, proxy, route });
+1 -1
View File
@@ -139,7 +139,7 @@
</div>
</el-tab-pane>
<el-tab-pane label='流程信息' name='tab11' v-if='form.protd'>
<BpmEdit :tmpid='form.protd' ref='bpmRef' />
<BpmEdit :temid='form.protd' ref='bpmRef' />
</el-tab-pane>
</el-tabs>
</el-form>
+1 -1
View File
@@ -111,7 +111,7 @@
</div>
</el-tab-pane>
<el-tab-pane label='流程信息' name='tab11' v-if='state.flowShow&&form.protd'>
<BpmEdit :tmpid='form.protd' ref='bpmRef' />
<BpmEdit :temid='form.protd' ref='bpmRef' />
</el-tab-pane>
</el-tabs>
</el-form>
+2 -2
View File
@@ -96,7 +96,7 @@ async function tabChange(tab: any) {
async function onSearch() {
receItems.value = await request({
url: '/oa/flow/tmpl/list?type=' + p_flow_type,
url: '/oa/flow/temp/list?type=' + p_flow_type,
method: 'get',
params: { name: state.receSearch },
});
@@ -137,7 +137,7 @@ function receItemClick(item: any) {
async function nodeClick(node: any) {
if (node && node.id) {
tierItems.value = await request({
url: '/oa/flow/tmpl/list?type=' + p_flow_type,
url: '/oa/flow/temp/list?type=' + p_flow_type,
method: 'get',
params: { catid: node.id },
});
+4 -4
View File
@@ -31,7 +31,7 @@
<div style='color: green'>注:此页面内容通过在线表单设计器渲染</div>
</el-tab-pane>
<el-tab-pane label='流程处理' name='tab3'>
<BpmEdit v-if='form.protd' :tmpid='form.protd' ref='bpmRef' />
<BpmEdit v-if='form.protd' :temid='form.protd' ref='bpmRef' />
</el-tab-pane>
<el-tab-pane label='权限信息' name='tab4'>
<!-- <img :src='qx' style='width:100%' />-->
@@ -83,13 +83,13 @@ const editInitx = async (state: any, route: any) => {
method: 'get',
});
} else {
let tmpid = state.params.query?.tmpid;
let temid = state.params.query?.temid;
const data: any = await request({
url: '/oa/flow/tmpl/one/' + tmpid,
url: '/oa/flow/temp/one/' + temid,
method: 'get',
});
form.value.protd = data.protd;
form.value.tmpid = tmpid;
form.value.temid = temid;
form.value.avtag = true;
formJson.value = JSON.parse(data.vform);
formData.value = {};
+9 -9
View File
@@ -27,7 +27,7 @@
</div>
</template>
<FlowModal ref='flowModalRef' @close='closeFlowModal' />
<TmplModal url='oa/flow/tmpl/tree' ref='tmplModal' />
<TempModal url='oa/flow/temp/tree' ref='tempModal' />
<el-table height='400' :cell-style="{padding:'2px'}" :row-style="{height: '36px'}" v-loading='state.loading' :data='state.list'
border stripe @selection-change='listSelect($event,state)'>
@@ -76,7 +76,7 @@
import { Search, Plus, Delete, ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import { onMounted, reactive, ref } from 'vue';
import { listQuery, listDelete, listSelect, pageView } from '/@/comps/page/index';
import TmplModal from '/@/comps/gen/GenTreeModal.vue';
import TempModal from '/@/comps/gen/GenTreeModal.vue';
import FlowModal from './FlowModal.vue';
const state = reactive({
@@ -90,13 +90,13 @@ onMounted(() => {
//分类弹框逻辑
// const tmplModal = ref();
// const openTmplModal = () => {
// tmplModal.value.openModal();
// const tempModal = ref();
// const openTempModal = () => {
// tempModal.value.openModal();
// };
// const tmplModalClose=(node:any)=>{
// if(node.type=="tmpl"){
// window.open("#/page/ofme?tmpid="+node.id);
// const tempModalClose=(node:any)=>{
// if(node.type=="temp"){
// window.open("#/page/ofme?temid="+node.id);
// }else if(node.type=="cate"){
// ElMessage.info("当前选择的是流程分类,请选择最下级的流程模板节点,比如请假申请");
// }
@@ -110,7 +110,7 @@ const openFlowModal = () => {
const closeFlowModal = (node: any) => {
setTimeout(() => {
if (node && node.id) {
window.open('#/page/ofme?tmpid=' + node.id);
window.open('#/page/ofme?temid=' + node.id);
}
}, 50);
};
+4 -4
View File
@@ -36,9 +36,9 @@
<BpmView @submit='submit' :proid='form.id' modty="oaFlow"/>
</template>
</el-tab-pane>
<el-tab-pane label='权限信息' name='tab4'>
<img :src='qx' style='width:100%' />
</el-tab-pane>
<!-- <el-tab-pane label='权限信息' name='tab4'>-->
<!-- <img :src='qx' style='width:100%' />-->
<!-- </el-tab-pane>-->
</el-tabs>
</el-form>
</div>
@@ -88,7 +88,7 @@ const viewInitx = async (state: any, route: any) => {
});
const data: any = await request({
url: '/oa/flow/tmpl/one/' + state.form.tmpid,
url: '/oa/flow/temp/one/' + state.form.temid,
method: 'get',
});
formJson.value = JSON.parse(data.vform);
@@ -45,7 +45,7 @@
<el-tab-pane label='流程配置' name='tab3'>
<div style='width: 100%;height: 720px'>
<BpmTmplEdit ref="bpmTmplEditRef" v-if='state.bpmShowTag' :prxml='form.prxml'/>
<BpmTempEdit ref="bpmTempEditRef" v-if='state.bpmShowTag' :prxml='form.prxml'/>
</div>
</el-tab-pane>
@@ -77,7 +77,7 @@
</el-card>
</template>
<script lang='ts'>
export default { name: 'OaFlowTmplEditNew' };
export default { name: 'OaFlowTempEditNew' };
</script>
<script lang='ts' setup>
import { onMounted, reactive, ref, toRefs } from 'vue';
@@ -85,7 +85,7 @@ import { pageSave, pageClose } from '/@/comps/page/edit';
import { useRoute } from 'vue-router';
import { NextLoading } from '/@/utils/loading';
import CateModal from '/@/comps/gen/GenTreeModal.vue';
import BpmTmplEdit from '/@/comps/bpm/tmpl/edit.vue';
import BpmTempEdit from '/@/comps/bpm/temp/edit.vue';
import { FormInstance } from 'element-plus';
import request from '/@/utils/request';
@@ -96,7 +96,7 @@ const activeName = ref('tab1');
const vFormRef = ref(null) as any;
const state = reactive({
url: '/oa/flow/tmpl',bpmShowTag:false,
url: '/oa/flow/temp',bpmShowTag:false,
params: { path: '', query: {} as any},
form: { avtag: true,protd:'',prxml:'' } as any,
vformShow: true
@@ -146,12 +146,12 @@ onMounted(async () => {
state.bpmShowTag=true;
});
const bpmTmplEditRef=ref();
const bpmTempEditRef=ref();
async function save(state: any) {
// xFlow.value.getData();
// form.value.vform = JSON.stringify(vFormRef.value.getFormJson());
form.value.prxml =await bpmTmplEditRef.value.getXml();
form.value.prxml =await bpmTempEditRef.value.getXml();
form.value.vform = JSON.stringify(vFormRef.value.getFormJson());
await pageSave({formRef:formRef.value, state});
}
@@ -89,7 +89,7 @@
</el-card>
</template>
<script lang='ts'>
export default {name: 'OaFlowTmplEdit'};
export default {name: 'OaFlowTempEdit'};
</script>
<script lang='ts' setup>
import {onMounted, reactive, ref, toRefs} from 'vue';
@@ -109,7 +109,7 @@ const formRef = ref<FormInstance>();
const activeName = ref('tab1');
const state = reactive({
url: '/oa/flow/tmpl',
url: '/oa/flow/temp',
params: {path: '', query: {} as any},
form: {avtag: true} as any,
vformShow: true

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