Initial commit

This commit is contained in:
zsvg
2022-05-16 00:10:57 +08:00
commit 4a772eea6c
309 changed files with 46759 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# port 端口号
VITE_PORT = 8888
# open 运行 npm run dev 时自动打开浏览器
VITE_OPEN = false
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
#VITE_PUBLIC_PATH = /vue-next-admin-preview/
VITE_PUBLIC_PATH = ''
+7
View File
@@ -0,0 +1,7 @@
# 本地环境
ENV = 'development'
# 本地环境接口地址
#VITE_API_URL = 'http://localhost:5301/'
VITE_API_URL = 'http://localhost:5000/'
VITE_UI_URL = 'http://localhost:8888/'
+7
View File
@@ -0,0 +1,7 @@
# 线上环境
ENV = 'production'
# 线上环境接口地址
#VITE_API_URL = 'https://lyt-top.gitee.io/vue-next-admin-preview/'
VITE_API_URL = 'http://222.92.228.219:5301/'
+18
View File
@@ -0,0 +1,18 @@
*.sh
node_modules
lib
*.md
*.scss
*.woff
*.ttf
.vscode
.idea
dist
mock
public
bin
build
config
index.html
src/assets
+63
View File
@@ -0,0 +1,63 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
'@type-eslint/ban-ts-ignore': 'off',
'@type-eslint/explicit-function-return-type': 'off',
'@type-eslint/no-explicit-any': 'off',
'@type-eslint/no-var-requires': 'off',
'@type-eslint/no-empty-function': 'off',
'@type-eslint/no-use-before-define': 'off',
'@type-eslint/ban-ts-comment': 'off',
'@type-eslint/ban-types': 'off',
'@type-eslint/no-non-null-assertion': 'off',
'@type-eslint/explicit-module-boundary-types': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/html-self-closing': 'off',
'vue/no-multiple-template-root': 'off',
'vue/require-default-prop': 'off',
'vue/no-v-model-argument': 'off',
'vue/no-arrow-functions-in-watch': 'off',
'vue/no-template-key': 'off',
'vue/no-v-html': 'off',
'vue/comment-directive': 'off',
'vue/no-parsing-error': 'off',
'vue/no-deprecated-v-on-native-modifier': 'off',
'vue/multi-word-component-names': 'off',
'no-useless-escape': 'off',
'no-sparse-arrays': 'off',
'no-prototype-builtins': 'off',
'no-constant-condition': 'off',
'no-use-before-define': 'off',
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'generator-star-spacing': 'off',
'no-unreachable': 'off',
'no-multiple-template-root': 'off',
'no-unused-vars': 'error',
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
},
};
+23
View File
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+39
View File
@@ -0,0 +1,39 @@
module.exports = {
// 一行最多多少个字符
printWidth: 150,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: true,
// 在语句末尾打印分号
semi: true,
// 使用单引号而不是双引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf',
};
+307
View File
@@ -0,0 +1,307 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
## 2.0.2
`2022.03.04`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 Alert 提示添加边框
- 🎯 优化 功能 / 数字滚动 演示界面
- 🐞 修复 全局主题按钮颜色 :active 问题
- 🐞 修复 Dropdown 下拉菜单样式问题
- 🐞 修复 SvgIcon 图标组件动态切换时报警告问题,[SvgIcon 改变 name 时可能导致图像不显示](https://gitee.com/lyt-top/vue-next-admin/issues/I4VGE0),感谢@axcc1234
## 2.0.1
`2022.02.25`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 svgIcon 图标组件
- 🎯 优化 vite.config.ts 打包,感谢群友@YourObjec
- 🐞 修复 tagViews 开启图标不显示问题(风格 5),感谢群友@坏人
- 🐞 修复 [Element Plus 1.2.0-beta.6 以后的版本 el-table 在移动端无法左右滑动](https://gitee.com/lyt-top/vue-next-admin/issues/I4UPTP),感谢@YGDada
## 2.0.0
`2022.02.21`
⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。演示界面建议直接覆盖文件。如需使用之前版本,请前往[gitee 发行版](https://gitee.com/lyt-top/vue-next-admin/releases) 进行对应版本下载。基础版会基于 `master` 分支进行修改
- 🌟 更新 依赖更新最新版本
- 🌟 更新 登录页、首页
- 💔 移除 vue-web-screen-shot
- 💔 移除 城市多级联动,完整 json 数据请去 [vue-next-admin-images/menu](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 仓库查看
- 💔 移除 功能/echartsTree 树图
- 💔 移除 其它设置/Tagsview 风格 2、Tagsview 风格 3
- 💔 移除 功能/验证器
- 🚧 调整 src/api 编写方式
- 🚧 调整 自定义封装公用组件演示,更好的维护
- 🎉 新增 Volar 支持,vs code 配置参考 [Vue Language Features (Volar)](https://lyt-top.gitee.io/vue-next-admin-doc-preview/home/vscode/)
- 🎉 新增 `SvgIcon` 支持本地 svg 图标使用
- 🎉 新增 表单表格验证演示
- 🎯 优化 全局主题(移除 success、info、warning、danger)
- 🎯 优化 工作流(开源)
- 🎯 优化 element plus svg 图标,`elementXXX` 改成 `ele-XXX`
- 🌈 重构 深色模式
- 🌹 合并 [处理 parent 的 h100 由于外层有 min-height 导致失效的问题](https://gitee.com/lyt-top/vue-next-admin/pulls/20),感谢@MaxNull、@21030442-mao
- 🐞 修复 element plus 升级 `^1.3.0-beta.5` 后 组件 size 大小问题(大改:涉及布局、演示界面)
- 🐞 修复 vs code 使用 Vue Language Features (Volar) 插件 代码报红问题(可以把公用的 ts 类型定义封装起来公用)
## 1.2.2
`2021.12.21`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 iframes 滚动条问题
- 🎯 优化 部署后每次都要强制刷新清浏览器缓存问题
- 🎉 新增 工具类百分比验证演示
- 🐞 修复 [tag-view 标签右键会超出浏览器 #I4KN78](https://gitee.com/lyt-top/vue-next-admin/issues/I4KN78)
## 1.2.1
`2021.12.12`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 cropper 裁剪时卡顿问题 [#I4M2VQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4M2VQ)
- 🎯 优化 Wangeditor 富文本编辑器的问题 [#I4LPC1](https://gitee.com/lyt-top/vue-next-admin/issues/I4LPC1)、[#I4LM7I](https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I)
- 🐞 修复 浏览器标题问题
- 🐞 修复 element plus svg 图标引入
- 🐞 修复 工作流不可以拖线连接问题
## 1.2.0
`2021.11.28`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 深色模式
- 🎯 优化 `/@/utils` 文件夹,合并删除单一内容
- 🎯 优化 系统设置:菜单管理(新增、修改)、角色管理(新增菜单权限)、用户管理、部门管理、字典管理
- 🎯 优化 登录界面逻辑、权限管理逻辑
- 🎯 优化 同步 [vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 后端控制菜单模拟数据
- 🎉 新增 适配 Font Icon 向 SVG Icon 迁移(改动大,"element-plus": "^1.2.0-beta.4" 谨慎更新)
- 🐞 修复 热更新问题,感谢@甜蜜蜜
- 🐞 修复 页面/element 字体图标演示
- 🐞 修复 功能/图标选择器演示,新增高级功能 [issues #I4GJXQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4GJXQ)
## 1.1.2
`2021.10.17`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 开启全屏时,刷新界面被还原成未全屏的状态
- 🎯 优化 tagsView 右键菜单关闭逻辑
- 🎯 优化 wangeditor 富文本编辑器(增加双向绑定)
- 🎉 新增 工作流(暂不开源)
- 🎉 新增 基础版 ts(不带国际化),切换 `vue-next-admin-template` 分支
## 1.1.1
`2021.09.25`
- 🌟 更新 依赖更新最新版本(`"element-plus": "^1.1.0-beta.13"` 版本运行错误,`^1.1.0-beta.16`修复横向菜单卡死问题)
- 🐞 修复 Dialog 弹窗位置错误、Drawer 抽屉内边距、el-menu 菜单收起时背景色问题
- 🎯 优化 锁屏界面自动锁屏(s/秒)必须设置至少 1 秒
- 🎉 新增 分栏布局,鼠标移入当前项时,显示当前项菜单内容
- 🎉 新增 工作流(未完成)
## 1.1.0
`2021.09.10`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 小屏模式下登录页二维码遮挡标题问题
- 🎉 新增 图片验证器
- 🎉 新增 动态复杂表单
- 🎉 新增 工作流(未完成)
- 🎉 新增 深色主题(伪深色,样式变动大,谨慎更新)
## 1.0.18
`2021.08.29`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 权限组件去掉顶级 div(`/src/components/auth`)
- 🎉 新增 布局配置添加恢复默认按钮
- 🐞 修复 升级 <a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">element plus 1.1.0-beta.7</a>后项目无法启动、el-menu 菜单
- 🐞 修复 表格固定列时的层级、设置了相对定位时,遮挡左侧导航菜单问题
## 1.0.17
`2021.08.22`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 去除设置布局切换,重置主题样式(initSetLayoutChange),切换布局需手动设置样式,设置的样式自动同步各布局
- 🎯 优化 Dropdown 下拉菜单用户账号靠边时换行问题
- 🎯 优化 左侧导航菜单,共用菜单树,防止 `布局配置` 设置 `菜单 / 顶栏` 时,样式丢失等问题
- 🐞 修复 固定 header 后没有回到顶部的 bug,拉取项目后运行不起来的 bug。<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/14" target="_blank">!14</a>,感谢<a href="https://gitee.com/wjs0509" target="_blank">@wjs0509</a>
- 🐞 修复 tagView 右键全屏后,浏览器窗口大小发生任何变化都会导致左边菜单显示出来,并且可点击打开对应页面。<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I46E6T" target="_blank">I46E6T</a>
- 🐞 修复 默认设置 `菜单 / 顶栏` 样式不生效问题(/@/src/store/modules/themeConfig.ts)
## 1.0.16
`2021.08.14`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 菜单高亮(详情且详情设置了 meta.isHide 时,顶级菜单高亮),感谢群友@YourObject
- 🎯 优化 详情路径写法:如父级(/pages/filtering),那么详情为(/pages/filtering/details?id=1)。这样写可实现(详情时,父级菜单高亮),否则写成(/pages/filteringDetails?id=1)顶级菜单将不会高亮。可参考:`页面/过滤筛选组件`,点击当前图片进行测试
- 🎯 优化 tagsView 右键菜单全屏时,打开的界面高度问题
- 🎯 优化 图表批量 resize 问题
- 🐞 修复 菜单收起时(设置全局主题:primary 且有二级菜单时),文字高亮颜色不对
- 🐞 修复 国际化 <a href="https://gitee.com/lyt-top/vue-next-admin/issues/I43NPE" target="_blank">#I43NPE</a>。可参考:`页面/过滤筛选组件`,点击顶部语言切换,进行底部分页国际化查看
## 1.0.15
`2021.08.06`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 tagsView 右键菜单点击时的字段名(id 已修改成 contextMenuClickId)与路由中返回的 id 名冲突问题,感谢群友@伯牙已遇钟子期
- 🎉 新增 多个 form 表单验证界面演示
## 1.0.14
`2021.07.29`
- 🌟 更新 依赖更新最新版本(vue、vuex、vue-router),出现问题,请手动降级。版本查看:<a href="https://www.npmjs.com/" target="_blank">vnpm</a>
- 🎯 优化 数据可视化图表演示加载卡顿问题、优化有图表的演示界面
- 🎯 优化 路由参数演示界面
- 🎯 优化 tagsView 操作演示界面,由于存在相同路由多标签,必须要传全部参数值(query 或者 params)
- 🎉 新增 开启 TagsView 共用,开启时:(多个路由菜单共用一个详情组件(参数为后点击的覆盖前面点击的),tagsView 中只会出现一个(不支持同时出现多个 tagsView 标签))。关闭时:(多个路由菜单共用一个详情组件,参数不同,会同时出现多个 tagsView 标签)
- 🐞 修复 tagsView 共用(单标签)时,右键菜单功能点击,参数不对的问题(第 2n+个参数未覆盖第一个参数值)
- 🐞 修复 多 tagsView 标签(参数不同)、单个 tagsView 标签公用(参数不同)所带来的刷新功能、横向自动滚动等问题
- 🐞 修复 处理全屏若干问题,<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/12" target="_blank">pr!12</a>,感谢群友@另一个前端
## 1.0.13
`2021.07.25`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示界面(/visualizingDemo1、/visualizingDemo2)
- 🎉 新增 登录页扫码登录
## 1.0.12
`2021.07.16`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示空界面(待完善)
- 🎯 优化 tagsView 动态路由(xxx/:id/:name)时的右键菜单刷新、关闭其它时参数丢失问题(2021.07.15 优化)
- 🐞 修复 路由带参数时,复制路径到登录页,跳转后参数消失的问题
- 🐞 修复 设置多个外链,点击后,页面内容停留在上一个内容(内容未改变)、国际化处理、打开新窗口 sessionStorage 共享等
## 1.0.11
`2021.07.14`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 路由参数、图片懒加载界面演示
- ⚠️ 警告 Form 表单 `binding value must be a string or number`,解决:加上 `label-position="top"` 不报警告(等待官方修复)
- 🎯 优化 锁屏界面动画效果、首页图表显示
- 🎯 优化 tagsView 右键菜单 `关闭` 功能逻辑
- 🐞 修复 开启 TagsView 拖拽报错及小于 `1000px` 时自动设置禁止拖拽(<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI" target="_blank">#I3ZRRI</a>)
- 🐞 修复 `iframe 内嵌、外链` 高度问题,使用 computed 进行计算
- 🐞 修复 默认布局开启 `侧边栏 Logo` 与关闭 `菜单水平折叠`,切换到横向布局时,菜单看不见的问题
- 🐞 修复 切换不同布局时,再去开启 `经典布局分割菜单` 功能不生效问题
- 🐞 修复 浏览器窗口标题中/英文切换不实时生效的问题
- 🐞 修复 切换布局时,某些功能不可以使用。部分界面不需要取消事件监听(proxy.mittBus.off('xxx'))
- 🐞 修复 动态路由带参数,router-link 跳转问题(<a href="hhttps://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G" target="_blank">#I3YX6G</a>)
- 🐞 修复 横向菜单有二级菜单时,点击子级菜单不高亮问题
- 🐞 修复 功能 tagsView 操作演示不生效
## 1.0.10
`2021.07.07`
- 🌟 更新 依赖更新最新版本(字体图标无问题)
- 🎯 优化 内嵌 iframe、外链,解决 tagsView 刷新问题
## 1.0.9
`2021.07.02`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 图标选择器设置宽度、v-model 等问题
- 🎯 优化 滚动通知栏在手机上的体验
- 🎯 优化 系统管理/新增菜单(编辑菜单),使用 `图标选择器` 进行模拟
- 🎯 优化 字体图标(自动载入) 逻辑
- 🐞 修复 screenfull 全屏时,按键盘 esc 键图标不改变问题,感谢群友@伯牙已遇钟子期
## 1.0.8
`2021.06.29`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 表单中英文切换演示
- 🎯 优化 登录页查看密码 icon 图标
- 🎯 优化 图标选择器
- 🎯 优化 拖动指令
- 🐞 修复 form 表单在页面小于 576px 时的排版问题
## 1.0.7
`2021.06.24`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 拖动指令及其演示界面
- 🎯 优化 锁屏界面,解锁提示
- 🎯 优化 登录页在手机上显示的效果
## 1.0.6
`2021.06.23`
- 🎯 优化 去掉内嵌 iframe 内边距(padding)
- 🎯 优化 城市多级联动组件
- 🎯 优化 Tree 树形控件改成表格组件
- 🐞 修复 Cascader 级联选择器高度问题
## 1.0.5
`2021.06.22`
- 🌟 更新 vite 降级为@vite2.3.7,降级方法 `cnpm install vite@2.3.7`,防止 element plus 字体图标消失
- 🐞 修复 开启后端控制路由(isRequestRoutes = true)时,内嵌 iframe、外链不可使用的问题
## 1.0.4
`2021.06.19`
- 🌟 更新 依赖更新最新版本("vite": "^2.3.7")热更新无问题
- 🎉 新增 深克隆工具,方便开发,感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/6" target="_blank">#6</a>)
- 🎯 优化 vuex 模块自动导入。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/4" target="_blank">#4</a>),感谢群友@web 小学生-第五君
- 🎯 优化 类型定义提高编码体验,修复不能将类型“string | undefined”分配给类型“string”的问题。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/5" target="_blank">#5</a>)
- 🎯 优化 `layout` 文件夹移动到与 `views` 文件夹同级(改动较大,`/@/views/layout` 变成 `/@/layout`)
- 🎯 优化 页面有 `console.log` 时 `eslint` 不生效问题
- 🎯 优化 页面、ts 中 `any` 类型问题(改动较大)
- 🎯 优化 登录页在手机上显示的效果
- 🎯 优化 多行注释信息,鼠标放到方法名即可查看,更加直观的知道方法参数等。引入方法时需去掉以 `.ts` 结尾的后缀(改动较大)
- 🎯 优化 移除 `utils/storage.ts` 下的旧写法(改动较大)
- 🎯 优化 拆分 `router` 下内容,路由、前端、后端控制分开写,方便理解
- 🐞 修复 鼠标移入顶部用户信息栏 `开/关全屏` 文字反向问题
- 🐞 修复 热更新时,NextLoading(界面 loading) 不消失问题 `window.nextLoading === undefined`
- 🐞 修复 vuex 中不可以使用 `/@/api/xxx` 下的接口调用问题
## 1.0.3
`2021.06.02`
- ❄️ 删除 G6 思维导图界面
- 🌟 更新 手动更新 vue、vue-router、vuex 到最近最多人使用的版本,出现不可预测的问题请降低版本。版本查看:<a href="https://www.npmjs.com/package/vue" target="_blank">vue 版本查看</a>
- 🐞 修复 开启后端控制路由 `isRequestRoutes` 在非首页刷新页面后,回到首页的问题,感谢群友@伯牙已遇钟子期
## 1.0.2
`2021.06.01`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 菜单搜索中文不可以搜索的问题,感谢群友@逍遥天意
## 1.0.1
`2021.05.31`
- 🎉 新增 更新日志文件 `CHANGELOG.md`,以后每次更新都会在这里显示对应内容
- 🌟 更新 依赖更新最新版本
- 🐞 修复 分栏、经典布局路由设置 `meta.isHide` 为 `true` 时报错问题,感谢群友@29、@芭芭拉
- 🐞 修复 经典布局点击 `tagsView` 左侧菜单数据不变问题
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 lyt-Top
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+135
View File
@@ -0,0 +1,135 @@
<div align="center">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/logo/logo-text.svg">
<p align="center">
<a href="https://v3.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue">
</a>
<a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--plus-%3E1.0.0-blue" alt="element plus">
</a>
<a href="https://www.tslang.cn/" target="_blank">
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
</a>
<a href="https://vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
</a>
<a href="https://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-success" alt="license">
</a>
</p>
<p>&nbsp;</p>
</div>
#### 🌈 介绍
基于 vue3.x + CompositionAPI + typescript + vite + element plus + vue-router-next + next.vuex,适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
#### ⛱️ 线上预览
- vue3.x 版本预览(vue-next-admin)<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-next-admin-preview/#/login</a>
- vue2.x 版本预览(vue-prev-admin)<a href="https://lyt-top.gitee.io/vue-prev-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-prev-admin-preview/#/login</a>
#### 💒 代码仓库
- vue3.x 版本 <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">https://gitee.com/lyt-top/vue-next-admin</a>
- vue2.x 版本 <a href="https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin" target="_blank">https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin</a>
#### 🚧 安装 cnpm、yarn
- 复制代码(桌面 cmd 运行) `npm install -g cnpm --registry=https://registry.npm.taobao.org`
- 复制代码(桌面 cmd 运行) `npm install -g yarn`
#### 🏭 环境支持
| Edge | last 2 versions | last 2 versions | last 2 versions |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| ![Edge](https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_32x32.png) | ![Firefox](https://cdn.jsdelivr.net/npm/@browser-logos/firefox/firefox_32x32.png) | ![Chrome](https://cdn.jsdelivr.net/npm/@browser-logos/chrome/chrome_32x32.png) | ![Safari](https://cdn.jsdelivr.net/npm/@browser-logos/safari/safari_32x32.png) |
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
#### ⚡ 使用说明
建议使用 cnpm,因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 12xx.xx.x</a>
```bash
# 克隆项目
git clone https://gitee.com/lyt-top/vue-next-admin.git
# 进入项目
cd vue-next-admin
# 安装依赖
cnpm install
# 运行项目
cnpm run dev
# 打包发布
cnpm run build
```
#### 💯 学习交流加 QQ 群
- 若加群了没同意(一般秒过),那就是群满了(500 人群),请换一个群试试,3 群未满
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
- 群号码:
1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>
3 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=02EWb5P2JkP-8iwzaDadgFdxA0HSHPpn&jump_from=webapi">795345435</a>
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq1.png" width="220" height="220" alt="vue-next-admin 讨论群1" title="vue-next-admin 讨论群1"/>
</a>
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq2.png" width="220" height="220" alt="vue-next-admin 讨论群2" title="vue-next-admin 讨论群2"/>
</a>
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=02EWb5P2JkP-8iwzaDadgFdxA0HSHPpn&jump_from=webapi">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq3.png" width="220" height="220" alt="vue-next-admin 讨论群3" title="vue-next-admin 讨论群3"/>
</a>
#### 💒 集成后端
- <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a>
- <a target="_blank" href="https://www.gnet.top/public">@甜蜜蜜 GoPro 平台</a>
#### ❤️ 鸣谢列表
- <a href="https://github.com/vuejs/vue" target="_blank">vue</a>
- <a href="https://github.com/vuejs/vue-next" target="_blank">vue-next</a>
- <a href="https://github.com/ElemeFE/element" target="_blank">element-ui</a>
- <a href="https://github.com/element-plus/element-plus" target="_blank">element-plus</a>
- <a href="https://github.com/vuejs/vue-router-next" target="_blank">vue-router-next</a>
- <a href="https://github.com/vuejs/vuex" target="_blank">vuex</a>
- <a href="https://github.com/apache/echarts" target="_blank">echarts</a>
- <a href="https://github.com/axios/axios" target="_blank">axios</a>
- <a href="https://github.com/zenorocha/clipboard.js" target="_blank">clipboard</a>
- <a href="https://github.com/inorganik/countUp.js" target="_blank">countUp</a>
- <a href="https://github.com/developit/mitt" target="_blank">mitt</a>
- <a href="https://github.com/rstacruz/nprogress" target="_blank">nprogress</a>
- <a href="https://github.com/sindresorhus/screenfull.js" target="_blank">screenfull</a>
- <a href="https://github.com/SortableJS/Sortable" target="_blank">sortablejs</a>
- <a href="https://github.com/sass/sass" target="_blank">sass</a>
- <a href="https://github.com/microsoft/TypeScript" target="_blank">typescript</a>
- <a href="https://github.com/vitejs/vite" target="_blank">vite</a>
- <a href="https://github.com/wangeditor-team/wangEditor" target="_blank">wangeditor</a>
- <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a>
- <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">qrcodejs</a>
- <a href="https://github.com/crabbly/Print.js" target="_blank">print-js</a>
- <a href="https://github.com/likaia/screen-shot" target="_blank">vue-web-screen-shot</a>
- <a href="https://github.com/jbaysolutions/vue-grid-layout" target="_blank">vue-grid-layout</a>
- <a href="https://github.com/antoniandre/splitpanes" target="_blank">splitpanes</a>
- <a href="https://github.com/yimijianfang/vue-drag-verify" target="_blank">vue-drag-verify</a>
- <a href="https://github.com/jsplumb/jsplumb" target="_blank">jsplumb</a>
#### 💕 特别感谢
特别感谢老哥们的建议、指导与帮忙。谢谢!
- <a href="https://gitee.com/click33/sa-plus" target="_blank">@省长</a>
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参
- <a href="https://gitee.com/chuange" target="_blank">@川歌</a>
#### 💌 支持作者
如果觉得框架不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/lyt-Top/vue-next-admin">Github</a> 或者
<a target="_blank" href="https://gitee.com/lyt-top/vue-next-admin">Gitee</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
+32
View File
@@ -0,0 +1,32 @@
<!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>Vboot</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();
</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>
</html>
+8306
View File
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
{
"name": "vue-next-admin",
"version": "2.0.2",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
"dev": "vite --force",
"build": "vite build",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^1.0.0",
"axios": "^0.26.0",
"bpmn-js": "^8.1.0",
"bpmn-js-properties-panel": "^0.39.0",
"codemirror": "^5.59.2",
"countup.js": "^2.1.0",
"cropperjs": "^1.5.12",
"echarts": "^5.3.0",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^2.0.4",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"screenfull": "^6.0.1",
"sortablejs": "^1.14.0",
"splitpanes": "^3.1.1",
"vform3-builds": "^3.0.6",
"vue": "^3.2.31",
"vue-clipboard3": "^1.0.1",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.13",
"vuex": "^4.0.2",
"wangeditor": "^4.7.12"
},
"devDependencies": {
"@types/node": "^17.0.21",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"@vitejs/plugin-vue": "^2.2.4",
"@vitejs/plugin-vue-jsx": "^1.1.6",
"@vue/compiler-sfc": "^3.2.31",
"dotenv": "^16.0.0",
"eslint": "^8.10.0",
"eslint-plugin-vue": "^8.5.0",
"prettier": "^2.5.1",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"typescript": "^4.6.2",
"vite": "^2.8.6",
"vue-eslint-parser": "^8.3.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"bugs": {
"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
},
"engines": {
"node": ">=12.0.0",
"npm": ">= 6.0.0"
},
"keywords": [
"vue",
"vue3",
"vuejs/vue-next",
"element-ui",
"element-plus",
"vue-next-admin",
"next-admin"
],
"repository": {
"type": "git",
"url": "https://gitee.com/lyt-top/vue-next-admin.git"
}
}
+3
View File
@@ -0,0 +1,3 @@
declare module 'vue-grid-layout';
declare module 'qrcodejs2-fixes';
declare module 'splitpanes';
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Vendored
+13
View File
@@ -0,0 +1,13 @@
/* eslint-disable */
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx,使用 getCurrentInstance() 来获取
interface Window {
nextLoading: boolean;
}
Vendored
+6
View File
@@ -0,0 +1,6 @@
declare module '*.json';
declare module '*.png';
declare module '*.jpg';
declare module '*.scss';
declare module '*.ts';
declare module '*.js';
+92
View File
@@ -0,0 +1,92 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="i18nLocale">
<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
<LockScreen v-if="getThemeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
<CloseFull />
</el-config-provider>
</template>
<script lang="ts">
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue';
import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
export default defineComponent({
name: 'app',
components: { LockScreen, Setings, CloseFull },
setup() {
const { proxy } = <any>getCurrentInstance();
const setingsRef = ref();
const route = useRoute();
const store = useStore();
const state = reactive({
i18nLocale: null,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 获取全局组件大小
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize;
});
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
});
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 设置 i18n,App.vue 中的 el-config-provider
proxy.mittBus.on('getI18nConfig', (locale: string) => {
(state.i18nLocale as string | null) = locale;
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
// 获取缓存中的全屏配置
if (Session.get('isTagsViewCurrenFull')) {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', Session.get('isTagsViewCurrenFull'));
}
});
});
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
proxy.mittBus.off('openSetingsDrawer', () => {});
proxy.mittBus.off('getI18nConfig', () => {});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
other.useTitle();
}
);
return {
setingsRef,
getThemeConfig,
getGlobalComponentSize,
...toRefs(state),
};
},
});
</script>
+25
View File
@@ -0,0 +1,25 @@
import request from '/@/utils/request';
/**
* 登录api接口集合
* @method signIn 用户登录
* @method signOut 用户退出登录
*/
export function useLoginApi() {
return {
signIn: (params: object) => {
return request({
url: '/user/signIn',
method: 'post',
data: params,
});
},
signOut: (params: object) => {
return request({
url: '/user/signOut',
method: 'post',
data: params,
});
},
};
}
+26
View File
@@ -0,0 +1,26 @@
import request from '/@/utils/request';
/**
* 后端控制菜单模拟json,路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
* @method getMenuAdmin 获取后端动态路由菜单(admin)
* @method getMenuTest 获取后端动态路由菜单(test)
*/
export function useMenuApi() {
return {
getMenuAdmin: (params?: object) => {
return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json',
method: 'get',
params,
});
},
getMenuTest: (params?: object) => {
return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json',
method: 'get',
params,
});
},
};
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 50" width="64" height="50">
<defs>
<image width="64" height="50" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAyCAYAAADsg90UAAAAAXNSR0IB2cksfwAACI5JREFUeJy1WgtsW9UZdvNq0xU6WhZoCCVx7Gunbld1GSsMdYpGREhwr+2IMbQWqRoTk+iWPZi2wWhm1kwEmqV52feRJrbvvd6DiUnbQCAemzY2xh4tSB0wOqZIqAtLYzultIWEpt13btLq+sap/R8nRzpK4vh+5/++85///8851yEk1V+hX6B0tyE3OWwt0x789VR78AKlZ0KBz1oxBE3+BdUW9PNuQ7nJbk/BDQ/v4hi0x44z1R64jyrAVCjQ/fLNN8/ZoUWdgqGc57AFE6I86giH+QRw6XI9QE4RBz3iTAyVW3EyQdEDUmeIIhyeFNtMHO/o0B4e8qYASfVvLk2p4BKgdnioFCAvEQeddhnyFivOpOgvBaFXiAKcygQDrk927S/zjkae5xUA/X3YI/C5AFwHLrSPw+2+YYfCMugiCvDaCbGt0hc9uAXuP1OEAPACZQ+fAGguQ9nGgglx0OfsOAhq24lBcG/YYQa/kWLIz3XlCW4B6mKRCoCMEQdNu3VpnRVnMhBYA2L/LVCATCooXuNJSHXFzv58P+mMyyu5RQDAKNntdLnNjoN0+NMCM0D8901NDkGXuyBAseRZn3Ubagu3AFjTd1GXAdZdpCqurMgSIBS4BwRn8wgwje/d4pV6yz2adGQJyM/bo/ZyC+DSlWqApIiDHqtPyFdlCyBeD4Lv5xHgr5mAWO4ZjTRh9s8tlQDoryIbrOISoLYvvAIALxIHnIHn3GnFmfDfztLhH/IEv/vYd72xyKElJM/6Gdjj4fYCuNA3yYMa6ogdBwQfvIwAk+mguHZz3+MbMPsfLrEALD3v5RfAkH3UOID+Jqqwj2ULIDZeJvhF2He8I0NfX2ry8wI85VSy41LBzaUPsXT4L/Iy0OUbrTgpsXUlyI7lEOAsvGOboPRdKejK28shAPqpek1etxjHArxAiXCo/pAdB2QPLRQg8BLcv8ITi4h4bnaZBGD2+IsQQG0DCDUyv2LfjcHVQyB93iLAecz+bvY/T1x6YrnImwIkVakIAeSrAfIucdCz7qRcY8VJh8RrQDplWfvHxttaVvkGe6vg/h8tpwDoR926wpcOb9CkEhQ4Mbrqym4rTkrcydLhs5bU95Wwwwx+311m8qxPQ4CtvE6AdKjcLlCrQkNJXNXdnYUD0t+eF+AE1v76rV0/qvTEo/8kkkGJq/yFLIKhPsgtgEszl8E4cdAxPHdllgDBwGZW9k6Fguaa9A4PtMEwkrDe0chhCPAZ/E5dNn/kPiWqGe5mVSH1rJCdze2w4rDNDsi/gdn/FPtb0KTfUmeyQenvuE45WCLQd6unsc3fwKeAw0yHX+Zwu247DpbB3emQv3R7x7du8GgybdtrKGc39Ty23rQnqfKk5y9wCwD1XACZJg56xGUMZFVhx1taytjPBnWAfuihyT+3TEiAQwC1Mhzmqwrr9QhbBoeJg36A6Ou0Y/kGe2qQ+mhiGspHDcODTZfs0eRr8fkE0Z4xpyGt4RJgTnX1carq8Jw9dhzv6BB5k+VJyK9t6+y8dNK74bGHMSHK00Scc/CC7fwC6MotAjX6GuqTzsTBkosYW/f9YKUnIb1OFiAW/c4Ce5Lq18jLKKnu5xaApTWBHn0n8Nz6ixhw4zvJ5HXlvU2R3gWui02XizwhrEznbTW9vQ64nUEckBUut13EQOHzFFUA70jErBv+09xcYrXHqUXK8H9qIXXapSs1dm4FN1SF95DdzlD72LMNUt/15CMvXT65NfzIRtQOq5FCv7jAHkPppdqDZ3YvZFZgY8WEQN8dvvFxpB8hLvWT3T8uyWxckG9CEfX38fk0ahGgmboMMIk/2xg5UJKbYZ62sf9RbI7UPxGJzLgT0R2Y/RM0z1HO+QZ6Gl/3+dh2Wmdb6HQwUJc9IfI6fPc40Z5xCLeW2wvw8Pfoy0B5mfoMAuYLn+7oKE0FxZqp+YvWTHugw24PvvtLIjZLh83cAghzmxHqASb5qntz/4EvsfEy7UHroepvJoKtWdUcPPKrVGwsA6k2FuYTAPXAGoC8RfYCStflqRvv3VuB4Hc1SI9bBJjAZ1VWe+p1+TqBHpfG+M8Ksa0EgLacAnhiETNzIPjdbztLPIfPWrME0CSkQ+UfxDFmkQ5v5RPAYcYBckFTcDfUWc+hwZsw06tA+EiOi5RoDns66ctA/Qm3AE4tuhYgZ5Zl9uPR32165PtlINoMwjM5jtPfSvn9WTe/iEufw7PUW+U3a2NSKZ8CTU0s+DyzHAJgq9yuNDaywxNtkQuVWaTDBqs5SIdsQt4hjsXOCvmvzrDu6Fdnebo3Hj3u69xXngoFqkH09KL3ie2BB6y2XCsfZNt1apnOqsL7uY/KoB57k+SDJRUhIZlX2iD4UJ4b5ef+3dqabY9hlunUdPt0XXyIryqs1yKVAv3qbPFuKNNedaAR5FeD4NE8AqRTwZ1ZmxqnHv0EcI4Rxz3hNqSqxThevrEXqpJq31IJgOD3TN3+h0tYmmPpLt9bJex80W4SRBzmEF7kE8Bhbo5aONwutwCx6K4JUVyByu/JfOTn+6EFAiTNN1uoAgxyC1BvqFcAZLJo8pr8rlvuq8wExfpFUl+u/s7/dt6R9UKkS5ergHeaOP6Yc2SQLw7Mqa6SDznsHakvzLAyoeAPCyRvVoXpUGCb1ZZqRWFnhdQXPWcg3Jac5App7C2MImf/5Kb+nupJ0X8FSL1NEOACEyyHPQ9QbWA73CIEkBuEIqrCzUO9CsNB6esCqQ8pAqD/Oe33l9vs2SLQd6svuhJSWW6GeVptLFKGbMD9eltjZ2cjw5m4o60ChF4lCvBeJhjIuntAYF4N3KNEO9Lwgmo+F/D5mNt1cbr/2OeD916qx+HSB4gCsDdNdmXZM7dbjRJtYXeZd/EJ4DDX3Q4uARLSj604yO23UgVA2jRy2EO+OkNX/w+fZNm8pw5QbAAAAABJRU5ErkJggg=="/>
</defs>
<style>
tspan { white-space:pre }
</style>
<use id="Background" href="#img1" x="0" y="0" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+105
View File
@@ -0,0 +1,105 @@
//流程类型名称匹配
import { ModdleElement } from '../type';
export const ProcessNameMapping = {
//流程
'bpmn:Process': '流程',
//子流程
'bpmn:SubProcess': '子流程',
};
// 事件名字匹配
export const EventNameMapping = {
'bpmn:StartEvent': '开始事件',
'bpmn:EndEvent': '结束事件',
'bpmn:MessageEventDefinition': '消息',
'bpmn:TimerEventDefinition': '定时',
'bpmn:ConditionalEventDefinition': '条件',
'bpmn:SignalEventDefinition': '信号',
};
//流名称匹配
export const FlowNameMapping = {
'bpmn:SequenceFlow': (businessObject: ModdleElement) => {
const defaultName = '顺序流';
if (businessObject.conditionExpression) {
return '条件' + defaultName;
}
if (businessObject.sourceRef.default) {
return '默认' + defaultName;
}
return defaultName;
},
};
//网关类型名称匹配
export const GatewayNameMapping = {
//互斥网关
'bpmn:ExclusiveGateway': '互斥网关',
//并行网关
'bpmn:ParallelGateway': '并行网关',
//复杂网关
'bpmn:ComplexGateway': '复杂网关',
//事件网关
'bpmn:EventBasedGateway': '事件网关',
};
//任务类型名称匹配
export const TaskNameMapping = {
//普通任务
'bpmn:Task': '普通任务',
//用户任务
'bpmn:UserTask': '用户任务',
//接收任务
'bpmn:ReceiveTask': '接收任务',
//发送任务
'bpmn:SendTask': '发送任务',
//手工任务
'bpmn:ManualTask': '手工任务',
//业务规则任务
'bpmn:BusinessRuleTask': '业务规则任务',
//服务任务
'bpmn:ServiceTask': '服务任务',
//脚本任务
'bpmn:ScriptTask': '脚本任务',
//调用任务
'bpmn:CallActivity': '调用任务',
};
//其他类型名称匹配
export const OtherNameMapping = {
//池
'bpmn:Participant': '池/参与者',
//分组
'bpmn:Group': '分组',
//数据存储
'bpmn:DataStoreReference': '数据存储',
//数据对象
'bpmn:DataObjectReference': '数据对象',
};
export const NameMapping: { [key: string]: ((obj: any) => string) | string } = {
...ProcessNameMapping,
...EventNameMapping,
...FlowNameMapping,
...GatewayNameMapping,
...TaskNameMapping,
...OtherNameMapping,
};
/**
* 根据流程类型节点业务对象解析业务对象节点类型的名称
* @param businessObject 节点的业务流程对象
*/
export const resolveTypeName = (businessObject: ModdleElement): string => {
const eventDefinitions = businessObject.eventDefinitions;
const nameMappingElement = NameMapping[businessObject.$type];
if (typeof nameMappingElement === 'function') {
return nameMappingElement(businessObject);
}
return eventDefinitions
? NameMapping[eventDefinitions[0].$type] + nameMappingElement
: nameMappingElement;
};
+351
View File
@@ -0,0 +1,351 @@
import { ElFormItem, ElInput, ElOption, ElSelect } from 'element-plus';
import { FieldDefine } from '/@/comps/Activiti/dynamic-binder';
import { PropertiesMap, GroupProperties } from './index';
import SubList from '/@/comps/Activiti/sublist/SubList';
import { SubListState } from '/@/comps/Activiti/sublist/type';
import { ModdleElement } from '../type';
import { BpmnStore } from '../store';
/**
* 所有通用节点的属性(每个节点都有的)
*/
const commonProperties: PropertiesMap<FieldDefine> = {
id: {
component: ElInput,
placeholder: '节点ID',
vSlots: {
prepend: (): JSX.Element => <div>节点ID</div>,
},
setValue(sourceObject: ModdleElement, key: string, value: string) {
const isNotNull = value;
const latestValue = value || ' ';
const shape = BpmnStore.getShape();
BpmnStore.getModeling().updateProperties(shape, {
[key]: isNotNull ? latestValue.trim() : latestValue,
});
},
},
name: {
component: ElInput,
// prefix: '节点名称',
placeholder: '节点名称',
vSlots: {
prepend: (): JSX.Element => <div>节点名称</div>,
},
},
};
/**
* (基础信息)每个节点都有的
*/
export const CommonGroupProperties: GroupProperties = {
name: '基础信息',
icon: 'el-icon-info',
properties: { ...commonProperties },
};
interface Documentation {
text: string;
}
export const DocumentGroupProperties: GroupProperties = {
name: '元素文档',
icon: 'el-icon-document',
properties: {
'documentation.text': {
component: ElInput,
type: 'textarea',
getValue: (obj: { documentation: Array<Documentation> }): string => {
return obj['documentation']?.[0]?.['text'];
},
setValue(businessObject: ModdleElement, key: string, value: unknown): void {
BpmnStore.createElement('bpmn:Documentation', 'documentation', { text: value }, true);
},
},
},
};
interface PropertyElement {
$type: string;
name: string;
value: unknown;
}
/**
* 流程事件类型选项
*/
const EVENT_OPTIONS = [
{ label: '开始', value: 'start' },
{ label: '结束', value: 'end' },
];
/**
* 监听器类型选项
*/
const TYPE_OPTIONS = [
{ label: 'java类', value: 'class' },
{ label: '调用表达式', value: 'expression' },
{ label: '注入表达式', value: 'delegateExpression' },
];
/**
* 获取节点类型的监听器属性配置组
* @param options 参数
*/
import { TaskNameMapping } from './TypeNameMapping';
const taskTags = Object.keys(TaskNameMapping);
export const getElementTypeListenerProperties = function (options: {
name: string;
icon?: string;
//时间类型选项
eventOptions?: Array<{ label: string; value: string }>;
}): GroupProperties {
const eventOptions = options.eventOptions || EVENT_OPTIONS;
return {
name: options.name || '监听器',
icon: options.icon || 'el-icon-bell',
properties: {
'extensionElements.listeners': {
component: SubList,
columns: [
{
type: 'index',
label: '序号',
align: 'center',
},
{
prop: 'event',
label: '事件',
align: 'center',
formatter: (row: any, column: any): string => {
return eventOptions.filter((item) => item.value === row[column.property])[0].label;
},
editComponent: function (scope: any, state: SubListState<any>): JSX.Element {
return (
<ElFormItem
class="sublist-form-item"
label={scope.column.name}
prop={scope.column.property}
>
<ElSelect v-model={state.editItem.event}>
{eventOptions.map((option) => {
return (
<ElOption key={option.value} label={option.label} value={option.value} />
);
})}
</ElSelect>
</ElFormItem>
);
},
},
{
prop: 'type',
label: '执行类型',
align: 'center',
formatter: (row: any, column: any) => {
return TYPE_OPTIONS.filter((item) => item.value === row[column.property])[0].label;
},
editComponent: function (scope: any, state: SubListState<any>): JSX.Element {
return (
<ElFormItem
class="sublist-form-item"
label={scope.column.name}
prop={scope.column.property}
>
<ElSelect v-model={state.editItem.type}>
{TYPE_OPTIONS.map((option) => {
return (
<ElOption key={option.value} label={option.label} value={option.value} />
);
})}
</ElSelect>
</ElFormItem>
);
},
},
{
prop: 'content',
label: '执行内容',
align: 'center',
},
],
rules: {
event: [{ required: true, message: '事件不能为空' }],
type: [{ required: true, message: '类型不能为空' }],
content: [{ required: true, message: '执行内容不能为空' }],
},
getValue: (businessObject: ModdleElement): Array<any> => {
const listenerTagName = taskTags.includes(businessObject.$type)
? 'activiti:TaskListener'
: 'activiti:ExecutionListener';
return businessObject?.extensionElements?.values
?.filter((item: ModdleElement) => item.$type === listenerTagName)
?.map((item: ModdleElement) => {
const type = item.expression
? 'expression'
: item.delegateExpression
? 'delegateExpression'
: 'class';
return {
event: item.event,
type: type,
content: item[type],
};
});
},
setValue(businessObject: ModdleElement, key: string, value: []): void {
const bpmnContext = BpmnStore;
console.warn('activeBusinessObject', businessObject);
const moddle = bpmnContext.getModeler().get('moddle');
//判断当前活动的模型类型,使用不同类型的标签监听器
const listenerTagName = taskTags.includes(businessObject.$type)
? 'activiti:TaskListener'
: 'activiti:ExecutionListener';
bpmnContext.updateExtensionElements(
listenerTagName,
value.map((attr: { event: string; type: string; content: string }) => {
return moddle.create(listenerTagName, {
event: attr.event,
[attr.type]: attr.content,
});
}),
);
},
},
},
};
};
/**
* 扩展属性组配置
*/
export const ExtensionGroupProperties: GroupProperties = {
name: '扩展属性',
icon: 'el-icon-document-add',
properties: {
'extensionElements.properties': {
component: SubList,
columns: [
{
type: 'index',
label: '序号',
align: 'center',
},
{
prop: 'name',
label: '属性名',
align: 'center',
},
{
prop: 'value',
label: '属性值',
align: 'center',
},
],
rules: {
name: [{ required: true, message: '属性名不能为空' }],
value: [{ required: true, message: '属性值不能为空' }],
},
getValue: (businessObject: ModdleElement): Array<any> => {
return businessObject?.extensionElements?.values
?.filter((item: PropertyElement) => item.$type === 'activiti:Properties')[0]
?.values.map((item: PropertyElement) => ({
name: item.name,
value: item.value,
}));
},
setValue(businessObject: ModdleElement, key: string, value: []): void {
const bpmnContext = BpmnStore;
const moddle = bpmnContext.getModeler().get('moddle');
const properties = moddle.create(`activiti:Properties`, {
values: value.map((attr: { name: string; value: unknown }) => {
return moddle.create(`activiti:Property`, { name: attr.name, value: attr.value });
}),
});
bpmnContext.updateExtensionElements('activiti:Properties', properties);
},
},
},
};
interface FromPropertyElement {
$type: string;
id: string;
type: string;
$attrs: FromPropertyAttrsElement;
}
interface FromPropertyAttrsElement {
name: string;
}
/**
* (基础信息)表单
*/
export const FormGroupProperties: GroupProperties = {
name: '表单信息',
icon: 'el-icon-edit',
properties: {
formKey: {
component: ElInput,
placeholder: '表单key',
vSlots: {
prepend: (): JSX.Element => <div>表单key</div>,
},
},
'extensionElements.formProperty': {
component: SubList,
columns: [
{
prop: 'id',
label: '编码',
align: 'center',
},
{
prop: 'type',
label: '类型',
align: 'center',
},
{
prop: 'name',
label: '名称',
align: 'center',
},
],
rules: {
id: [{ required: true, message: '编码不能为空' }],
type: [{ required: true, message: '类型不能为空' }],
name: [{ required: true, message: '名称不能为空' }],
},
getValue: (businessObject: ModdleElement): Array<FromPropertyElement> => {
return businessObject?.extensionElements?.values
?.filter((item: FromPropertyElement) => item.$type === 'activiti:FormProperty')
.map((elem: FromPropertyElement) => {
console.warn('elem', elem);
return { id: elem?.id, type: elem.type, name: elem?.$attrs?.name };
});
},
setValue(businessObject: ModdleElement, key: string, value: []): void {
const bpmnContext = BpmnStore;
const moddle = bpmnContext.getModeler().get('moddle');
//表单数据值对象
const formProperties = value.map((attr: { id: string; type: string; name: string }) => {
return moddle.create('activiti:FormProperty', {
id: attr.id,
name: attr.name,
type: attr.type,
});
});
bpmnContext.updateExtensionElements('activiti:FormProperty', formProperties);
},
},
},
};
// export const FormProperties: GroupProperties = {
// name: '表单属性',
// icon: 'el-icon-document-add',
// properties: {},
// };
+35
View File
@@ -0,0 +1,35 @@
import { FieldDefine } from '/@/comps/Activiti/dynamic-binder';
/**
* 属性Map接口
*/
export interface PropertiesMap<T> {
[key: string]: T;
}
/**
* 组属性(属性分组)
*/
export interface GroupProperties {
name?: string;
icon?: string;
properties: PropertiesMap<FieldDefine>;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const modules = import.meta.glob('./modules/*.tsx');
const getBpmnGroupPropertiesConfig = () => {
const BpmnGroupPropertiesConfig: PropertiesMap<Array<GroupProperties>> = {};
for (const path in modules) {
modules[path]().then((mod: any) => {
const moduleDefuatlExport = mod.default;
for (const moduleKey in moduleDefuatlExport) {
BpmnGroupPropertiesConfig[moduleKey] = moduleDefuatlExport[moduleKey];
}
});
}
return BpmnGroupPropertiesConfig;
};
export default getBpmnGroupPropertiesConfig();
+28
View File
@@ -0,0 +1,28 @@
import {
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
FormGroupProperties,
} from '../common';
const CommonGroupPropertiesArray = [
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
];
export default {
//开始事件、消息开始事件、定时开始事件、条件开始事件(这些都属于开始事件bpmn:StartEvent)
'bpmn:StartEvent': [
CommonGroupProperties,
FormGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
],
//结束事件,也包含类似开始事件的所有结束事件
'bpmn:EndEvent': CommonGroupPropertiesArray,
//中间抛出事件(包含:消息抛出、升级抛出、链接抛出、补偿抛出、信号抛出等)
'bpmn:IntermediateThrowEvent': CommonGroupPropertiesArray,
//中间捕获事件(包含:小心捕获、定时捕获、条件捕获、信号捕获等)
'bpmn:IntermediateCatchEvent': CommonGroupPropertiesArray,
};
+120
View File
@@ -0,0 +1,120 @@
import {
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
getElementTypeListenerProperties,
} from '../common';
import { ElInput, ElOption } from 'element-plus';
import PrefixLabelSelect from '/@/comps/Activiti/prefix-label-select';
import { ModdleElement } from '../../type';
import { BpmnStore } from '../../store';
const FLOW_TYPE_OPTIONS = [
{ label: '普通顺序流', value: 'normal' },
{ label: '默认顺序流', value: 'default' },
{ label: '条件顺序流', value: 'condition' },
];
//获取顺序流的类型
const getSequenceFlowType = (businessObject: ModdleElement) => {
if (businessObject?.conditionExpression) {
return 'condition';
}
if (businessObject?.sourceRef?.default) {
return 'default';
}
return 'normal';
};
/**
* 常规配置
*/
const BaseProperties = {
...CommonGroupProperties,
properties: {
...CommonGroupProperties.properties,
//条件类型
'sequenceFlow.type': {
component: PrefixLabelSelect,
prefixTitle: '顺序流类型',
predicate: (businessObject: ModdleElement): boolean => {
return businessObject?.sourceRef?.$type !== 'bpmn:StartEvent';
},
vSlots: {
default: (): JSX.Element => (
<>
{FLOW_TYPE_OPTIONS.map((item) => {
return <ElOption {...item} />;
})}
</>
),
},
getValue(businessObject: ModdleElement): string {
return getSequenceFlowType(businessObject);
},
setValue(businessObject: ModdleElement, key: string, value: string): void {
const bpmnContext = BpmnStore;
const line = bpmnContext.getShape();
const sourceShape = bpmnContext
.getModeler()
.get('elementRegistry')
.get(line.businessObject.sourceRef.id);
const modeling = bpmnContext.getModeling();
if (!value || value === 'normal') {
modeling.updateProperties(line, { conditionExpression: null });
delete line.businessObject.conditionExpression;
}
if (value === 'default') {
modeling.updateProperties(sourceShape, { default: line });
delete line.businessObject.conditionExpression;
}
if (value === 'condition') {
modeling.updateProperties(line, {
conditionExpression: bpmnContext
.getModeler()
.get('moddle')
.create('bpmn:FormalExpression'),
});
}
},
},
'conditionExpression.body': {
component: ElInput,
placeholder: '条件表达式',
vSlots: {
prepend: (): JSX.Element => <div>条件表达式</div>,
},
predicate: (businessObject: ModdleElement): boolean => {
return 'condition' === getSequenceFlowType(businessObject);
},
getValue(businessObject: ModdleElement): string {
return businessObject?.conditionExpression?.body;
},
setValue(businessObject: ModdleElement, key: string, value: unknown): void {
const bpmnContext = BpmnStore;
const moddle = bpmnContext.getModeler().get('moddle');
bpmnContext.getModeling().updateProperties(bpmnContext.getShape(), {
conditionExpression: moddle.create('bpmn:FormalExpression', { body: value }),
});
},
},
},
};
export default {
//顺序流
'bpmn:SequenceFlow': [
BaseProperties,
getElementTypeListenerProperties({
name: '条件监听器',
eventOptions: [{ label: 'take', value: 'take' }],
}),
ExtensionGroupProperties,
DocumentGroupProperties,
],
};
+22
View File
@@ -0,0 +1,22 @@
import {
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
} from '../common';
const CommonGroupPropertiesArray = [
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
];
export default {
//互斥网关
'bpmn:ExclusiveGateway': CommonGroupPropertiesArray,
//并行网关
'bpmn:ParallelGateway': CommonGroupPropertiesArray,
//复杂网关
'bpmn:ComplexGateway': CommonGroupPropertiesArray,
//事件网关
'bpmn:EventBasedGateway': CommonGroupPropertiesArray,
};
+52
View File
@@ -0,0 +1,52 @@
import {
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
} from '../common';
import { ElInput } from 'element-plus';
const CommonGroupPropertiesArray = [
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
];
interface CategoryValueRef {
value: string;
}
const BpmnGroupBaseProperties = {
name: '基础信息',
icon: 'el-icon-info',
properties: {
id: {
component: ElInput,
placeholder: '节点ID',
vSlots: {
prepend: (): JSX.Element => <div>节点ID</div>,
},
},
name: {
component: ElInput,
// prefix: '节点名称',
placeholder: '节点名称',
vSlots: {
prepend: (): JSX.Element => <div>节点名称</div>,
},
getValue: (obj: { categoryValueRef: CategoryValueRef }): string => {
return obj?.categoryValueRef?.value;
},
},
},
};
export default {
//池
'bpmn:Participant': CommonGroupPropertiesArray,
//分组
'bpmn:Group': [BpmnGroupBaseProperties, ExtensionGroupProperties, DocumentGroupProperties],
//数据存储
'bpmn:DataStoreReference': CommonGroupPropertiesArray,
//数据对象
'bpmn:DataObjectReference': CommonGroupPropertiesArray,
};
+26
View File
@@ -0,0 +1,26 @@
import {
CommonGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
getElementTypeListenerProperties,
} from '../common';
//流程数据属性配置数组
const ProcessGroupPropertiesArray = [
CommonGroupProperties,
/**
* 监听器组
*/
getElementTypeListenerProperties({
name: '全局监听器',
}),
ExtensionGroupProperties,
DocumentGroupProperties,
];
export default {
//流程
'bpmn:Process': ProcessGroupPropertiesArray,
//子流程
'bpmn:SubProcess': ProcessGroupPropertiesArray,
};
+256
View File
@@ -0,0 +1,256 @@
import {
CommonGroupProperties,
FormGroupProperties,
DocumentGroupProperties,
ExtensionGroupProperties,
getElementTypeListenerProperties,
} from '../common';
import { GroupProperties } from '../index';
import PrefixLabelSelect from '/@/comps/Activiti/prefix-label-select';
import { ElInput, ElOption } from 'element-plus';
import { ModdleElement } from '../../type';
import { BpmnStore } from '../../store';
const TASK_EVENT_OPTIONS = [
{ label: '通过', value: 'pass' },
{ label: '驳回', value: 'refuse' },
{ label: '转办', value: 'zhuanhuan' },
{ label: '沟通', value: 'goutong' },
{ label: '废弃', value: 'feiqi' },
];
const TaskListenerProperties = getElementTypeListenerProperties({
name: '任务监听器',
eventOptions: TASK_EVENT_OPTIONS,
});
const USER_OPTIONS = [
{ label: '张三', value: 'z3' },
{ label: '李四', value: 'l4' },
{ label: '王五', value: 'w5' },
{ label: '赵六', value: 'zhao6' },
{ label: '孙七', value: 's7' },
];
const UserOption: JSX.Element = (
<>
{USER_OPTIONS.map((item) => {
return <ElOption {...item} />;
})}
</>
);
/**
* 用户任务属性配置
*/
export const BpmnUserGroupProperties: GroupProperties = {
name: '人员设置',
icon: 'el-icon-user-solid',
properties: {
/**
* 处理人属性
*/
assignee: {
component: PrefixLabelSelect,
prefixTitle: '处理人',
allowCreate: true,
filterable: true,
vSlots: {
default: (): JSX.Element => UserOption,
},
},
/**
* 候选人属性
*/
candidateUsers: {
component: PrefixLabelSelect,
prefixTitle: '候选人',
filterable: true,
multiple: true,
allowCreate: true,
vSlots: {
default: (): JSX.Element => UserOption,
},
getValue(businessObject: ModdleElement): string {
console.warn('businessObject', businessObject);
return 'string' === typeof businessObject.candidateUsers
? businessObject.candidateUsers.split(',')
: businessObject.candidateUsers;
},
},
/**
* 循环基数
*/
loopCardinality: {
component: ElInput,
placeholder: '循环基数',
type: 'number',
vSlots: {
prepend: (): JSX.Element => <div>循环基数</div>,
},
predicate(businessObject: ModdleElement): boolean {
return businessObject.loopCharacteristics;
},
getValue(businessObject: ModdleElement): string {
const loopCharacteristics = businessObject.loopCharacteristics;
if (!loopCharacteristics) {
return '';
}
return loopCharacteristics.loopCardinality?.body;
},
setValue(businessObject: ModdleElement, key: string, value: string): void {
const bpmnContext = BpmnStore;
const moddle = bpmnContext.getModeler().get('moddle');
const loopCharacteristics = businessObject.loopCharacteristics;
loopCharacteristics.loopCardinality = moddle.create('bpmn:FormalExpression', {
body: value,
});
bpmnContext.getModeling().updateProperties(bpmnContext.getShape(), {
loopCharacteristics: loopCharacteristics,
});
},
},
/**
* 多实例完成条件
* nr是number单词缩写
* 1.nrOfInstances 实例总数。
* 2.nrOfCompletedInstances 已经完成的实例个数
* 3.loopCounter 已经循环的次数。
* 4.nrOfActiveInstances 当前还没有完成的实例
*/
completionCondition: {
component: ElInput,
placeholder:
'如:${nrOfCompletedInstances/nrOfInstances >= 0.25} 表示完成数大于等于4分1时任务完成',
vSlots: {
prepend: (): JSX.Element => <div>完成条件</div>,
},
predicate(businessObject: ModdleElement): boolean {
return businessObject.loopCharacteristics;
},
getValue(businessObject: ModdleElement): string {
const loopCharacteristics = businessObject.loopCharacteristics;
if (!loopCharacteristics) {
return '';
}
return loopCharacteristics.completionCondition?.body;
},
setValue(businessObject: ModdleElement, key: string, value: string): void {
const bpmnContext = BpmnStore;
const moddle = bpmnContext.getModeler().get('moddle');
const loopCharacteristics = businessObject.loopCharacteristics;
loopCharacteristics.completionCondition = moddle.create('bpmn:FormalExpression', {
body: value,
});
bpmnContext.getModeling().updateProperties(bpmnContext.getShape(), {
loopCharacteristics: loopCharacteristics,
});
},
},
},
};
const LOOP_OPTIONS = [
{ label: '无', value: 'Null' },
{ label: '并行多重事件', value: 'Parallel' },
{ label: '时序多重事件', value: 'Sequential' },
{ label: '循环事件', value: 'StandardLoop' },
];
const LoopOptions: JSX.Element = (
<>
{LOOP_OPTIONS.map((item) => {
return <ElOption {...item} />;
})}
</>
);
/**
* 任务的基本属性配置
*/
const BaseTaskProperties = {
...CommonGroupProperties,
properties: {
...CommonGroupProperties.properties,
loopCharacteristics: {
component: PrefixLabelSelect,
prefixTitle: '回路特性',
vSlots: {
default: (): JSX.Element => LoopOptions,
},
getValue(businessObject: ModdleElement): string {
const loopCharacteristics = businessObject.loopCharacteristics;
if (!loopCharacteristics) {
return 'Null';
}
if (loopCharacteristics.$type === 'bpmn:MultiInstanceLoopCharacteristics') {
return loopCharacteristics.isSequential ? 'Sequential' : 'Parallel';
} else {
return 'StandardLoop';
}
},
setValue(businessObject: ModdleElement, key: string, value: string): () => void {
const shape = BpmnStore.getShape();
const modeling = BpmnStore.getModeling();
switch (value) {
case 'Null':
modeling.updateProperties(shape, {
loopCharacteristics: null,
});
// delete businessObject.loopCharacteristics;
break;
case 'StandardLoop':
BpmnStore.createElement('bpmn:StandardLoopCharacteristics', 'loopCharacteristics');
break;
default:
BpmnStore.createElement(
'bpmn:MultiInstanceLoopCharacteristics',
'loopCharacteristics',
{
isSequential: value === 'Sequential',
},
);
}
return () => BpmnStore.refresh();
},
},
},
};
const CommonGroupPropertiesArray = [
BaseTaskProperties,
TaskListenerProperties,
FormGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
];
export default {
//普通任务
'bpmn:Task': CommonGroupPropertiesArray,
//用户任务
'bpmn:UserTask': [
BaseTaskProperties,
BpmnUserGroupProperties,
TaskListenerProperties,
FormGroupProperties,
ExtensionGroupProperties,
DocumentGroupProperties,
],
//接收任务
'bpmn:ReceiveTask': CommonGroupPropertiesArray,
//发送任务
'bpmn:SendTask': CommonGroupPropertiesArray,
//手工任务
'bpmn:ManualTask': CommonGroupPropertiesArray,
//业务规则任务
'bpmn:BusinessRuleTask': CommonGroupPropertiesArray,
//服务任务
'bpmn:ServiceTask': CommonGroupPropertiesArray,
//脚本任务
'bpmn:ScriptTask': CommonGroupPropertiesArray,
//调用任务
'bpmn:CallActivity': CommonGroupPropertiesArray,
};
+19
View File
@@ -0,0 +1,19 @@
export default (key: string, name: string): string => {
return `<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
id="sample-diagram"
targetNamespace="http://activiti.org/bpmn">
<bpmn2:process id="Process_${key}" name="${name}" isExecutable="true">
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_${key}">
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>`;
};
+2
View File
@@ -0,0 +1,2 @@
import translate from './translate';
export default translate;
+22
View File
@@ -0,0 +1,22 @@
import i18nTranslate_zh from './zh';
const i18nMap: any = {
zh: i18nTranslate_zh,
};
export default function getI18nTranslate(
i18n: string,
): (template: string, replacements: { [index: string]: string }) => any {
const i18nTranslate: any = i18nMap[i18n];
return function customTranslate(template, replacements) {
replacements = replacements || {};
// Translate
template = i18nTranslate[template] || template;
// Replace
return template.replace(/{([^}]+)}/g, function (_: string, key: string) {
return replacements[key] || '{' + key + '}';
});
};
}
+239
View File
@@ -0,0 +1,239 @@
/**
* This is a sample file that should be replaced with the actual translation.
*
* Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available
* translations and labels to translate.
*/
export default {
// 添加部分
'Append EndEvent': '追加结束事件',
'Append Gateway': '追加网关',
'Append Task': '追加任务',
'Append Intermediate/Boundary Event': '追加中间抛出事件/边界事件',
'Activate the global connect tool': '激活全局连接工具',
'Append {type}': '添加 {type}',
'Add Lane above': '在上面添加道',
'Divide into two Lanes': '分割成两个道',
'Divide into three Lanes': '分割成三个道',
'Add Lane below': '在下面添加道',
'Append compensation activity': '追加补偿活动',
'Change type': '修改类型',
'Connect using Association': '使用关联连接',
'Connect using Sequence/MessageFlow or Association': '使用顺序/消息流或者关联连接',
'Connect using DataInputAssociation': '使用数据输入关联连接',
Remove: '移除',
'Activate the hand tool': '激活抓手工具',
'Activate the lasso tool': '激活套索工具',
'Activate the create/remove space tool': '激活创建/删除空间工具',
'Create expanded SubProcess': '创建扩展子过程',
'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件',
'Create Pool/Participant': '创建池/参与者',
'Parallel Multi Instance': '并行多重事件',
'Sequential Multi Instance': '时序多重事件',
DataObjectReference: '数据对象参考',
DataStoreReference: '数据存储参考',
Loop: '循环',
'Ad-hoc': '即席',
'Create {type}': '创建 {type}',
Task: '任务',
'Send Task': '发送任务',
'Receive Task': '接收任务',
'User Task': '用户任务',
'Manual Task': '手工任务',
'Business Rule Task': '业务规则任务',
'Service Task': '服务任务',
'Script Task': '脚本任务',
'Call Activity': '调用活动',
'Sub Process (collapsed)': '子流程(折叠的)',
'Sub Process (expanded)': '子流程(展开的)',
'Start Event': '开始事件',
StartEvent: '开始事件',
'Intermediate Throw Event': '中间事件',
'End Event': '结束事件',
EndEvent: '结束事件',
'Create StartEvent': '创建开始事件',
'Create EndEvent': '创建结束事件',
'Create Task': '创建任务',
'Create Gateway': '创建网关',
'Create DataObjectReference': '创建数据对象',
'Create DataStoreReference': '创建数据存储',
'Create Group': '创建分组',
'Create Intermediate/Boundary Event': '创建中间/边界事件',
'Message Start Event': '消息开始事件',
'Timer Start Event': '定时开始事件',
'Conditional Start Event': '条件开始事件',
'Signal Start Event': '信号开始事件',
'Error Start Event': '错误开始事件',
'Escalation Start Event': '升级开始事件',
'Compensation Start Event': '补偿开始事件',
'Message Start Event (non-interrupting)': '消息开始事件(非中断)',
'Timer Start Event (non-interrupting)': '定时开始事件(非中断)',
'Conditional Start Event (non-interrupting)': '条件开始事件(非中断)',
'Signal Start Event (non-interrupting)': '信号开始事件(非中断)',
'Escalation Start Event (non-interrupting)': '升级开始事件(非中断)',
'Message Intermediate Catch Event': '消息中间捕获事件',
'Message Intermediate Throw Event': '消息中间抛出事件',
'Timer Intermediate Catch Event': '定时中间捕获事件',
'Escalation Intermediate Throw Event': '升级中间抛出事件',
'Conditional Intermediate Catch Event': '条件中间捕获事件',
'Link Intermediate Catch Event': '链接中间捕获事件',
'Link Intermediate Throw Event': '链接中间抛出事件',
'Compensation Intermediate Throw Event': '补偿中间抛出事件',
'Signal Intermediate Catch Event': '信号中间捕获事件',
'Signal Intermediate Throw Event': '信号中间抛出事件',
'Message End Event': '消息结束事件',
'Escalation End Event': '定时结束事件',
'Error End Event': '错误结束事件',
'Cancel End Event': '取消结束事件',
'Compensation End Event': '补偿结束事件',
'Signal End Event': '信号结束事件',
'Terminate End Event': '终止结束事件',
'Message Boundary Event': '消息边界事件',
'Message Boundary Event (non-interrupting)': '消息边界事件(非中断)',
'Timer Boundary Event': '定时边界事件',
'Timer Boundary Event (non-interrupting)': '定时边界事件(非中断)',
'Escalation Boundary Event': '升级边界事件',
'Escalation Boundary Event (non-interrupting)': '升级边界事件(非中断)',
'Conditional Boundary Event': '条件边界事件',
'Conditional Boundary Event (non-interrupting)': '条件边界事件(非中断)',
'Error Boundary Event': '错误边界事件',
'Cancel Boundary Event': '取消边界事件',
'Signal Boundary Event': '信号边界事件',
'Signal Boundary Event (non-interrupting)': '信号边界事件(非中断)',
'Compensation Boundary Event': '补偿边界事件',
'Exclusive Gateway': '互斥网关',
'Parallel Gateway': '并行网关',
'Inclusive Gateway': '相容网关',
'Complex Gateway': '复杂网关',
'Event based Gateway': '事件网关',
Transaction: '转运',
'Sub Process': '子流程',
'Event Sub Process': '事件子流程',
'Collapsed Pool': '折叠池',
'Expanded Pool': '展开池',
// Errors
'no parent for {element} in {parent}': '在{parent}里,{element}没有父类',
'no shape type specified': '没有指定的形状类型',
'flow elements must be children of pools/participants': '流元素必须是池/参与者的子类',
'out of bounds release': 'out of bounds release',
'more than {count} child lanes': '子道大于{count} ',
'element required': '元素不能为空',
'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范',
'no diagram to display': '没有可展示的流程图',
'no process or collaboration to display': '没有可展示的流程/协作',
'element {element} referenced by {referenced}#{property} not yet drawn':
'由{referenced}#{property}引用的{element}元素仍未绘制',
'already rendered {element}': '{element} 已被渲染',
'failed to import {element}': '导入{element}失败',
//属性面板的参数
Id: '编号',
Name: '名称',
General: '常规',
Details: '详情',
'Message Name': '消息名称',
Message: '消息',
Initiator: '创建者',
'Asynchronous Continuations': '持续异步',
'Asynchronous Before': '异步前',
'Asynchronous After': '异步后',
'Job Configuration': '工作配置',
Exclusive: '排除',
'Job Priority': '工作优先级',
'Retry Time Cycle': '重试时间周期',
Documentation: '文档',
'Element Documentation': '元素文档',
'History Configuration': '历史配置',
'History Time To Live': '历史的生存时间',
Forms: '表单',
'Form Key': '表单key',
'Form Fields': '表单字段',
'Business Key': '业务key',
'Form Field': '表单字段',
ID: '编号',
Type: '类型',
Label: '名称',
'Default Value': '默认值',
'Default Flow': '默认流转路径',
'Conditional Flow': '条件流转路径',
'Sequence Flow': '普通流转路径',
Validation: '校验',
'Add Constraint': '添加约束',
Config: '配置',
Properties: '属性',
'Add Property': '添加属性',
Value: '值',
Listeners: '监听器',
'Execution Listener': '执行监听',
'Event Type': '事件类型',
'Listener Type': '监听器类型',
'Java Class': 'Java类',
Expression: '表达式',
'Must provide a value': '必须提供一个值',
'Delegate Expression': '代理表达式',
Script: '脚本',
'Script Format': '脚本格式',
'Script Type': '脚本类型',
'Inline Script': '内联脚本',
'External Script': '外部脚本',
Resource: '资源',
'Field Injection': '字段注入',
Extensions: '扩展',
'Input/Output': '输入/输出',
'Input Parameters': '输入参数',
'Output Parameters': '输出参数',
Parameters: '参数',
'Output Parameter': '输出参数',
'Timer Definition Type': '定时器定义类型',
'Timer Definition': '定时器定义',
Date: '日期',
Duration: '持续',
Cycle: '循环',
Signal: '信号',
'Signal Name': '信号名称',
Escalation: '升级',
Error: '错误',
'Link Name': '链接名称',
Condition: '条件名称',
'Variable Name': '变量名称',
'Variable Event': '变量事件',
'Specify more than one variable change event as a comma separated list.':
'多个变量事件以逗号隔开',
'Wait for Completion': '等待完成',
'Activity Ref': '活动参考',
'Version Tag': '版本标签',
Executable: '可执行文件',
'External Task Configuration': '扩展任务配置',
'Task Priority': '任务优先级',
External: '外部',
Connector: '连接器',
'Must configure Connector': '必须配置连接器',
'Connector Id': '连接器编号',
Implementation: '实现方式',
'Field Injections': '字段注入',
Fields: '字段',
'Result Variable': '结果变量',
Topic: '主题',
'Configure Connector': '配置连接器',
'Input Parameter': '输入参数',
Assignee: '代理人',
'Candidate Users': '候选用户',
'Candidate Groups': '候选组',
'Due Date': '到期时间',
'Follow Up Date': '跟踪日期',
Priority: '优先级',
'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
Variables: '变量',
'Candidate Starter Configuration': '候选人起动器配置',
'Candidate Starter Groups': '候选人起动器组',
'This maps to the process definition key.': '这映射到流程定义键。',
'Candidate Starter Users': '候选人起动器的用户',
'Specify more than one user as a comma separated list.': '指定多个用户作为逗号分隔的列表。',
'Tasklist Configuration': 'Tasklist配置',
Startable: '启动',
'Specify more than one group as a comma separated list.': '指定多个组作为逗号分隔的列表。',
};
+143
View File
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:yaoqiang="http://bpmn.sourceforge.net" exporter="Yaoqiang BPMN Editor" exporterVersion="5.3" expressionLanguage="http://www.w3.org/1999/XPath" id="m1555922186343" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<process id="com.github.yiuman.citrus.workflow.Leave" isClosed="false" isExecutable="true" processType="None">
<extensionElements>
<yaoqiang:description/>
<yaoqiang:pageFormat height="841.8897637795276" imageableHeight="831.8897637795276" imageableWidth="588.1102362204724" imageableX="5.0" imageableY="5.0" orientation="0" width="598.1102362204724"/>
<yaoqiang:page background="#FFFFFF" horizontalCount="1" verticalCount="1"/>
<yaoqiang:pageFormat height="600.0" imageableHeight="831.8897637795276" imageableWidth="600.0" imageableX="5.0" imageableY="5.0" orientation="0" width="600.0"/>
<yaoqiang:page background="#FFFFFF" horizontalCount="1" verticalCount="1"/>
</extensionElements>
<userTask activiti:assignee="${currentUserId}" activiti:exclusive="true" completionQuantity="1" id="_4" implementation="##unspecified" isForCompensation="false" name="提交请假" startQuantity="1">
<incoming>_7</incoming>
<incoming>_12</incoming>
<incoming>_13</incoming>
<outgoing>_8</outgoing>
</userTask>
<userTask activiti:assignee="${currentUserId}" activiti:exclusive="true" completionQuantity="1" id="_5" implementation="##unspecified" isForCompensation="false" name="部门经理审批" startQuantity="1">
<incoming>_8</incoming>
<outgoing>_9</outgoing>
<outgoing>_12</outgoing>
</userTask>
<userTask activiti:assignee="${currentUserId}" activiti:exclusive="true" completionQuantity="1" id="_6" implementation="##unspecified" isForCompensation="false" name="总经理审批" startQuantity="1">
<incoming>_9</incoming>
<outgoing>_10</outgoing>
<outgoing>_13</outgoing>
</userTask>
<sequenceFlow id="_7" sourceRef="_2" targetRef="_4"/>
<sequenceFlow id="_8" sourceRef="_4" targetRef="_5"/>
<sequenceFlow id="_9" sourceRef="_5" targetRef="_6">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_10" sourceRef="_6" targetRef="_3">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_12" name="驳回" sourceRef="_5" targetRef="_4">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_13" name="驳回" sourceRef="_6" targetRef="_4">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
]]>
</conditionExpression>
</sequenceFlow>
<startEvent id="_2" isInterrupting="true" name="StartEvent" parallelMultiple="false">
<outgoing>_7</outgoing>
<outputSet/>
</startEvent>
<endEvent id="_3" name="EndEvent">
<incoming>_10</incoming>
<inputSet/>
</endEvent>
</process>
<bpmndi:BPMNDiagram id="Yaoqiang_Diagram-com.github.yiuman.citrus.workflow.Leave" name="New Diagram" resolution="96.0">
<bpmndi:BPMNPlane bpmnElement="com.github.yiuman.citrus.workflow.Leave">
<bpmndi:BPMNShape bpmnElement="_4" id="Yaoqiang-_4">
<omgdc:Bounds height="55.0" width="85.0" x="338.0" y="110.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="50.0" x="355.5" y="130.02"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Yaoqiang-_5">
<omgdc:Bounds height="55.0" width="85.0" x="338.0" y="218.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="72.0" x="344.5" y="238.02"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_6" id="Yaoqiang-_6">
<omgdc:Bounds height="55.0" width="85.0" x="338.0" y="319.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="61.0" x="350.0" y="339.02"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_2" id="Yaoqiang-_2">
<omgdc:Bounds height="32.0" width="32.0" x="363.0" y="30.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="60.0" x="349.0" y="70.52"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Yaoqiang-_3">
<omgdc:Bounds height="32.0" width="32.0" x="363.0" y="412.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="55.0" x="351.5" y="452.52"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_13" id="Yaoqiang-_13">
<omgdi:waypoint x="338.0" y="346.0"/>
<omgdi:waypoint x="240.0" y="346.0"/>
<omgdi:waypoint x="240.0" y="300.0"/>
<omgdi:waypoint x="240.0" y="137.0"/>
<omgdi:waypoint x="338.0" y="137.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="28.0" x="226.0" y="232.27"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_12" id="Yaoqiang-_12">
<omgdi:waypoint x="423.0" y="245.0"/>
<omgdi:waypoint x="514.0" y="245.0"/>
<omgdi:waypoint x="514.0" y="137.0"/>
<omgdi:waypoint x="423.0" y="137.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="28.0" x="500.0" y="181.77"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_9" id="Yaoqiang-_9">
<omgdi:waypoint x="380.5" y="273.0"/>
<omgdi:waypoint x="380.5" y="319.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="6.0" x="377.5" y="286.52"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="Yaoqiang-_8">
<omgdi:waypoint x="380.5" y="165.0"/>
<omgdi:waypoint x="380.5" y="218.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="6.0" x="377.5" y="182.02"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_10" id="Yaoqiang-_10">
<omgdi:waypoint x="379.0" y="374.0"/>
<omgdi:waypoint x="379.0" y="412.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="6.0" x="376.0" y="383.52"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_7" id="Yaoqiang-_7">
<omgdi:waypoint x="379.0" y="62.0"/>
<omgdi:waypoint x="379.0" y="110.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="18.96" width="6.0" x="376.0" y="76.52"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
import { reactive, toRaw, nextTick } from 'vue';
import BpmnGroupPropertiesConfig from './config';
import { resolveTypeName } from './config/TypeNameMapping';
import Modeler from 'bpmn-js/lib/Modeler';
import { BpmnState, BpmnContext, ModdleElement } from './type';
const bpmnState = reactive<BpmnState>({
activeElement: null,
businessObject: null,
activeBindDefine: null,
isActive: false,
});
//刷新状态
function refreshState(elementRegistry: any, elementAction: any): void {
if (!bpmnState || !elementAction) {
return;
}
bpmnState.activeElement = elementAction;
const shape = elementRegistry.get(elementAction.element.id);
bpmnState.businessObject = shape ? shape.businessObject : {};
bpmnState.isActive = true;
bpmnState.activeBindDefine = shape ? BpmnGroupPropertiesConfig[elementAction.element.type] : null;
}
export const BpmnStore: BpmnContext = {
modeler: null,
state: bpmnState,
getState() {
return this.state;
},
initModeler(options) {
this.modeler = new Modeler(options);
this.modeler.get('keyboard').bind(document);
const elementRegistry = this.modeler.get('elementRegistry');
//添加click、shapeAdd事件,属性更新后的事件,用于变更当前的业务对象,刷新属性配置栏
['element.click', 'shape.added'].forEach((event) => {
BpmnStore.addEventListener(event, function (elementAction) {
// console.warn('elementAction', elementAction);
const element = elementAction.element || elementAction.context.element;
if (element && (!bpmnState.activeElement || bpmnState.activeElement.id !== element.id)) {
bpmnState.businessObject = null;
nextTick().then(() => {
refreshState(elementRegistry, elementAction);
});
}
});
});
// this.addEventListener('element.changed', function (elementAction: any) {
// 这里是处理修改shape中的label后导致的不及时更新问题
// 现将业务对象至为空对象,视图更新后,再重新进行渲染
// bpmnState.businessObject = {};
// nextTick(() => {
// refreshState(context.modeler.get('elementRegistry'), elementAction);
// });
// });
},
getModeler() {
return this.modeler;
},
refresh() {
bpmnState.businessObject = null;
nextTick().then(() => {
refreshState(this.modeler.get('elementRegistry'), bpmnState.activeElement);
});
// nextTick(() => {
//
// });
},
getShape() {
return this.getShapeById(this.getState().activeElement.element.id);
},
getShapeById(id) {
const elementRegistry = this.getModeler().get('elementRegistry');
return elementRegistry.get(id);
},
getBpmnFactory() {
return this.modeler.get('bpmnFactory');
},
createElement(nodeName, modelName, value, multiple) {
const newElement = this.getBpmnFactory().create(nodeName, value);
this.getModeling().updateProperties(this.getShape(), {
[modelName]: multiple ? [newElement] : newElement,
});
},
importXML(string) {
return this.modeler.importXML(string);
},
getXML() {
return new Promise((resolve, reject) => {
this.getModeler()
.saveXML({ format: true })
.then((response: { xml: string }) => {
resolve(response);
})
.catch((err: unknown) => {
reject(err);
});
});
},
getSVG() {
return new Promise((resolve, reject) => {
this.getModeler()
.saveSVG()
.then((response: { svg: string }) => {
resolve(response);
})
.catch((err: unknown) => {
reject(err);
});
});
},
getModeling() {
return this.getModeler().get('modeling');
},
getActiveElement() {
return this.getState().activeElement;
},
getActiveElementName() {
const businessObject = this.getBusinessObject();
return businessObject ? resolveTypeName(businessObject) : '';
},
getBusinessObject() {
return this.getState().businessObject;
},
addEventListener(string, func) {
this.getModeler()
.get('eventBus')
.on(string, function (e: any) {
func(e);
});
},
updateExtensionElements(elementName, value) {
const moddle = this.getModeler().get('moddle');
const element = this.getShape();
const extensionElements = this.getBusinessObject()?.extensionElements;
// 截取不是扩展属性的属性
const otherExtensions =
extensionElements?.values
?.filter((ex: any) => ex.$type !== elementName)
.map((item: ModdleElement) => toRaw(item)) || [];
// 重建扩展属性
const extensions = moddle.create('bpmn:ExtensionElements', {
values: otherExtensions.concat(value instanceof Array ? value : [value]),
});
this.getModeling().updateProperties(element, { extensionElements: extensions });
},
};
+141
View File
@@ -0,0 +1,141 @@
// 模型节点
import { UnwrapRef } from 'vue';
import Modeler from 'bpmn-js/lib/Modeler';
import { GroupProperties } from './config';
export interface BpmnState {
/**
* 当前活动的节点
*/
activeElement: any;
/**
* 当前活动节点的业务对象
*/
businessObject: any;
/**
* 是否活动
*/
isActive: boolean;
/**
* 当前活动节点的绑定字段配置
*/
activeBindDefine: Array<GroupProperties> | null | never;
}
export interface ModdleElement {
id: string;
$type: string;
value?: [ModdleElement];
$attrs: { [key: string]: any };
[key: string]: any;
}
/**
* 流程管理的上下文
*/
export interface BpmnContext {
/**
* 流程设计器
*/
modeler: any;
/**
* 状态管理
*/
state: UnwrapRef<BpmnState>;
/**
* 获取当前的状态
*/
getState(): UnwrapRef<BpmnState>;
refresh: () => void;
/**
* 初始化流程设计器
* @param options 流程设计器参数
*/
initModeler(options: unknown): void;
/**
*获取设计器
*/
getModeler(): typeof Modeler;
/**
* 导入xml
* @param xml xml字符串
*/
importXML(xml: string): Promise<Array<string> | any>;
/**
* 获取流程xml
*/
getXML(): Promise<{ xml: string }>;
/**
* 获取流程的SVG图
*/
getSVG(): Promise<{ svg: string }>;
/**
* 获取当前节点的Shape对象,此对象用于操作节点与业务流程对象等
*/
getShape(): any;
getShapeById(id: string): any;
/**
* 获取当前的流程的业务对象
*/
getBusinessObject(): any;
/**
* 获取当前的活动节点
*/
getActiveElement(): any;
/**
* 获取当前节点的名称
*/
getActiveElementName(): string;
/**
* 获取当前节点的modeling
*/
getModeling(): any;
/**
* 获取bpmnFactory
*/
getBpmnFactory(): any;
/**
* 创建节点
* @param nodeName 节点名称
* @param modelName 模型名称
* @param value 几点值
* @param multiple 是否是多节点
*/
createElement(
nodeName: string,
modelName: string,
value?: { [key: string]: any } | never,
multiple?: boolean,
): void;
/**
* 添加设计器事件监听
* @param name 事件名称
* @param func 触发事件的回调
*/
addEventListener(name: string, func: (e: any) => void): void;
/**
* 更新扩展节点
* @param elementName 节点名
* @param value 值
*/
updateExtensionElements(elementName: string, value: ModdleElement | Array<ModdleElement>): void;
}
+27
View File
@@ -0,0 +1,27 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'auth',
props: {
value: {
type: String,
default: () => '',
},
},
setup(props) {
const store = useStore();
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
return store.state.userInfos.userInfos.authBtnList.some((v: string) => v === props.value);
});
return {
getUserAuthBtnList,
};
},
});
</script>
+28
View File
@@ -0,0 +1,28 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';
export default defineComponent({
name: 'authAll',
props: {
value: {
type: Array,
default: () => [],
},
},
setup(props) {
const store = useStore();
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
});
return {
getUserAuthBtnList,
};
},
});
</script>
+33
View File
@@ -0,0 +1,33 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'auths',
props: {
value: {
type: Array,
default: () => [],
},
},
setup(props) {
const store = useStore();
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
let flag = false;
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
props.value.map((v) => {
if (val === v) flag = true;
});
});
return flag;
});
return {
getUserAuthBtnList,
};
},
});
</script>
+148
View File
@@ -0,0 +1,148 @@
<template>
<div>
<el-dialog title="更换头像" v-model="isShowDialog" width="769px">
<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>
<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>
<script lang="ts">
import { reactive, toRefs, nextTick, defineComponent } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
export default defineComponent({
name: 'cropperIndex',
setup() {
const state = reactive({
isShowDialog: false,
cropperImg: '',
cropperImgBase64: '',
cropper: null,
});
// 打开弹窗
const openDialog = (imgs: any) => {
state.cropperImg = imgs;
state.isShowDialog = true;
nextTick(() => {
initCropper();
});
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 更换
const onSubmit = () => {
// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
};
// 初始化cropperjs图片裁剪
const initCropper = () => {
const letImg: any = document.querySelector('.cropper-warp-left-img');
(<any>state.cropper) = new Cropper(letImg, {
viewMode: 1,
dragMode: 'none',
initialAspectRatio: 1,
aspectRatio: 1,
preview: '.before',
background: false,
autoCropArea: 0.6,
zoomOnWheel: false,
crop: () => {
state.cropperImgBase64 = (<any>state.cropper).getCroppedCanvas().toDataURL('image/jpeg');
},
});
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
initCropper,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.cropper-warp {
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 350px;
flex: 1;
border: var(--el-border-base);
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>
+79
View File
@@ -0,0 +1,79 @@
<template>
<div class="editor-container">
<div :id="id"></div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, watch, defineComponent } from 'vue';
import wangeditor from 'wangeditor';
// 定义接口来定义对象的类型
interface WangeditorState {
editor: any;
}
export default defineComponent({
name: 'wngEditor',
props: {
// 节点 id
id: {
type: String,
default: () => 'wangeditor',
},
// 是否禁用
isDisable: {
type: Boolean,
default: () => false,
},
// 内容框默认 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,
},
setup(props, { emit }) {
const state = reactive<WangeditorState>({
editor: null,
});
// 初始化富文本
// https://doc.wangeditor.com/
const initWangeditor = () => {
state.editor = new wangeditor(`#${props.id}`);
state.editor.config.zIndex = 1;
state.editor.config.placeholder = props.placeholder;
state.editor.config.uploadImgShowBase64 = true;
state.editor.config.showLinkImg = false;
onWangeditorChange();
state.editor.create();
state.editor.txt.html(props.modelValue);
props.isDisable ? state.editor.disable() : state.editor.enable();
};
// 内容改变时
const onWangeditorChange = () => {
state.editor.config.onchange = (html: string) => {
emit('update:modelValue', html);
};
};
// 页面加载时
onMounted(() => {
initWangeditor();
});
// 监听双向绑定值的改变
// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
watch(
() => props.modelValue,
(value) => {
state.editor.txt.html(value);
}
);
return {
...toRefs(state),
};
},
});
</script>
+242
View File
@@ -0,0 +1,242 @@
<template>
<div class="icon-selector">
<el-popover placement="bottom" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
<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>
<transition name="el-zoom-in-top">
<div class="icon-selector-warp" v-show="fontIconVisible">
<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>
</transition>
</el-popover>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
import initIconfont from '/@/utils/getStyleSheets';
export default defineComponent({
name: 'iconSelector',
emits: ['update:modelValue', 'get', 'clear'],
props: {
// 输入框前置内容
prepend: {
type: String,
default: () => 'ele-Pointer',
},
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'default',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 是否可清空
clearable: {
type: Boolean,
default: () => true,
},
// 自定义空状态描述文字
emptyDescription: {
type: String,
default: () => '无相关图标',
},
// 双向绑定值,字段名为固定,改了之后将不生效
// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
modelValue: String,
},
setup(props, { emit }) {
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state = reactive({
fontIconPrefix: '',
fontIconVisible: false,
fontIconWidth: 0,
fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
fontIconShow: true,
});
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
state.fontIconVisible = true;
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 false;
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
(<string | undefined>state.fontIconPrefix) = props.modelValue;
};
// 图标搜索及图标数据显示
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();
// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
selectorScrollbarRef.value.wrap$.scrollTop = 0;
};
// 图标点击切换
const onIconChange = (type: string) => {
state.fontIconType = type;
initFontIconData(type);
};
// 获取当前点击的 icon 图标
const onColClick = (v: any) => {
state.fontIconPlaceholder = v;
state.fontIconVisible = false;
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);
};
// 页面加载时
onMounted(() => {
// 判断默认进来是什么类型图标,进行 tab 回显
if (props.type === 'all') {
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');
} else {
onIconChange(props.type);
}
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
return {
inputWidthRef,
selectorScrollbarRef,
fontIconSheetsFilterList,
onColClick,
onIconChange,
onClearFontIcon,
onIconFocus,
onIconBlur,
...toRefs(state),
};
},
});
</script>
+194
View File
@@ -0,0 +1,194 @@
<template>
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!isMode">
<div class="notice-bar-warp" :style="{ color, fontSize: `${size}px` }">
<i v-if="leftIcon" class="notice-bar-warp-left-icon" :class="leftIcon"></i>
<div class="notice-bar-warp-text-box" ref="noticeBarWarpRef">
<div class="notice-bar-warp-text" ref="noticeBarTextRef" v-if="!scrollable">{{ text }}</div>
<div class="notice-bar-warp-slot" v-else><slot /></div>
</div>
<SvgIcon :name="rightIcon" v-if="rightIcon" class="notice-bar-warp-right-icon" @click="onRightIconClick" />
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
export default defineComponent({
name: 'noticeBar',
props: {
// 通知栏模式,可选值为 closeable link
mode: {
type: String,
default: () => '',
},
// 通知文本内容
text: {
type: String,
default: () => '',
},
// 通知文本颜色
color: {
type: String,
default: () => 'var(--color-warning)',
},
// 通知背景色
background: {
type: String,
default: () => 'var(--color-warning-light-9)',
},
// 字体大小,单位px
size: {
type: [Number, String],
default: () => 14,
},
// 通知栏高度,单位px
height: {
type: Number,
default: () => 40,
},
// 动画延迟时间 (s)
delay: {
type: Number,
default: () => 1,
},
// 滚动速率 (px/s)
speed: {
type: Number,
default: () => 100,
},
// 是否开启垂直滚动
scrollable: {
type: Boolean,
default: () => false,
},
// 自定义左侧图标
leftIcon: {
type: String,
default: () => '',
},
// 自定义右侧图标
rightIcon: {
type: String,
default: () => '',
},
},
setup(props, { emit }) {
const noticeBarWarpRef = ref();
const noticeBarTextRef = ref();
const state = reactive({
order: 1,
oneTime: 0,
twoTime: 0,
warpOWidth: 0,
textOWidth: 0,
isMode: false,
});
// 初始化 animation 各项参数
const initAnimation = () => {
nextTick(() => {
state.warpOWidth = noticeBarWarpRef.value.offsetWidth;
state.textOWidth = noticeBarTextRef.value.offsetWidth;
document.styleSheets[0].insertRule(`@keyframes oneAnimation {0% {left: 0px;} 100% {left: -${state.textOWidth}px;}}`);
document.styleSheets[0].insertRule(`@keyframes twoAnimation {0% {left: ${state.warpOWidth}px;} 100% {left: -${state.textOWidth}px;}}`);
computeAnimationTime();
setTimeout(() => {
changeAnimation();
}, props.delay * 1000);
});
};
// 计算 animation 滚动时长
const computeAnimationTime = () => {
state.oneTime = state.textOWidth / props.speed;
state.twoTime = (state.textOWidth + state.warpOWidth) / props.speed;
};
// 改变 animation 动画调用
const changeAnimation = () => {
if (state.order === 1) {
noticeBarTextRef.value.style.cssText = `animation: oneAnimation ${state.oneTime}s linear; opactity: 1;}`;
state.order = 2;
} else {
noticeBarTextRef.value.style.cssText = `animation: twoAnimation ${state.twoTime}s linear infinite; opacity: 1;`;
}
};
// 监听 animation 动画的结束
const listenerAnimationend = () => {
noticeBarTextRef.value.addEventListener(
'animationend',
() => {
changeAnimation();
},
false
);
};
// 右侧 icon 图标点击
const onRightIconClick = () => {
if (!props.mode) return false;
if (props.mode === 'closeable') {
state.isMode = true;
emit('close');
} else if (props.mode === 'link') {
emit('link');
}
};
// 页面加载时
onMounted(() => {
if (props.scrollable) return false;
initAnimation();
listenerAnimationend();
});
return {
noticeBarWarpRef,
noticeBarTextRef,
onRightIconClick,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.notice-bar {
padding: 0 15px;
width: 100%;
border-radius: 4px;
.notice-bar-warp {
display: flex;
align-items: center;
width: 100%;
height: inherit;
.notice-bar-warp-text-box {
flex: 1;
height: inherit;
display: flex;
align-items: center;
overflow: hidden;
position: relative;
.notice-bar-warp-text {
white-space: nowrap;
position: absolute;
left: 0;
}
.notice-bar-warp-slot {
width: 100%;
white-space: nowrap;
::v-deep(.el-carousel__item) {
display: flex;
align-items: center;
}
}
}
.notice-bar-warp-left-icon {
width: 24px;
font-size: inherit !important;
}
.notice-bar-warp-right-icon {
width: 24px;
text-align: right;
font-size: inherit !important;
&:hover {
cursor: pointer;
}
}
}
}
</style>
+42
View File
@@ -0,0 +1,42 @@
<script lang="ts">
// 渲染函数:https://v3.cn.vuejs.org/guide/render-function.html
import { h, resolveComponent } from 'vue';
// 定义接口来定义对象的类型
interface SvgIconProps {
name: string;
size: number;
color: string;
}
export default {
name: 'svgIcon',
props: {
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
},
},
setup(props: SvgIconProps) {
// 定义变量
const linesString: any[] = ['https', 'http', '/src', '/assets', import.meta.env.VITE_PUBLIC_PATH];
const onLineStyle: string = `font-size: ${props.size}px;color: ${props.color}`;
const localsStyle: string = `width: ${props.size}px;height: ${props.size}px`;
const eleSetStyle = { class: 'el-icon', style: onLineStyle };
// 逻辑判断
if (props.name?.startsWith('ele-')) return () => h('i', eleSetStyle, [props.name === 'ele-' ? '' : h(resolveComponent(props.name))]);
else if (linesString.find((str) => props.name?.startsWith(str))) return () => h('img', { src: props.name, style: localsStyle });
else return () => h('i', { class: props.name, style: onLineStyle });
},
};
</script>
@@ -0,0 +1,200 @@
import { defineComponent, ref, nextTick } from 'vue';
import ButtonRender, { ButtonRenderProps } from '/@/comps/Activiti/button-render';
import { BpmnStore } from '/@/bpmn/store';
import CodeMirror from 'codemirror';
import 'codemirror/mode/xml/xml.js';
import 'codemirror/addon/hint/xml-hint.js';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
import './bpmn-actions.css';
import { ModdleElement } from '/@/bpmn/type';
export default defineComponent({
name: 'BpmnActions',
setup() {
//放大缩小
const zoom = ref(1);
//预览xml的抽屉控制器
const previewActive = ref(false);
//取到的xml
const xml = ref('');
return {
zoom,
previewActive,
xml,
};
},
render() {
const bpmnContext = BpmnStore;
//codemirror编辑器
let coder: CodeMirror.EditorFromTextArea;
const importFile = function (event: Event) {
const eventTarget = event.target as HTMLInputElement;
if (eventTarget.files) {
const file = eventTarget.files[0];
const reader = new FileReader();
reader.readAsText(file);
reader.onload = function () {
if (this.result) {
bpmnContext.importXML(this.result as string);
}
};
}
};
const buttonRenderProps: ButtonRenderProps = {
buttons: [
{
label: '导入',
icon: 'icon-shangchuan',
action: () => {
document.getElementById('bpmn-upload-element')?.click();
},
},
{
label: '导出SVG',
icon: 'icon-zu920',
action: () => {
const rootElement: ModdleElement = bpmnContext
.getModeler()
.get('canvas')
.getRootElement();
bpmnContext
.getSVG()
.then((response) => {
download(response.svg, rootElement.id || 'process', 'svg');
})
.catch((err: unknown) => {
console.warn(err);
});
},
},
{
label: '导出XML',
icon: 'icon-zu1359',
action: () => {
const rootElement: ModdleElement = bpmnContext
.getModeler()
.get('canvas')
.getRootElement();
bpmnContext
.getXML()
.then((response: { xml: string }) => {
download(response.xml, rootElement.id || 'process', 'bpmn');
})
.catch((err: unknown) => {
console.warn(err);
});
},
},
{
label: '放大',
icon: 'icon-fangda',
action: () => {
this.zoom = Math.floor(this.zoom * 100 + 0.1 * 100) / 100;
bpmnContext.getModeler().get('canvas').zoom(this.zoom);
},
},
{
label: '缩小',
icon: 'icon-suoxiao',
action: () => {
this.zoom = Math.floor(this.zoom * 100 - 0.1 * 100) / 100;
bpmnContext.getModeler().get('canvas').zoom(this.zoom);
},
},
{
label: '还原并居中',
icon: 'icon-quxiaoquanping',
action: () => {
this.zoom = 1;
bpmnContext.getModeler().get('canvas').zoom('fit-viewport', 'auto');
},
},
{
label: '预览',
icon: 'icon-xianshi',
action: () => {
console.warn();
bpmnContext
.getXML()
.then((response) => {
this.xml = response.xml;
this.previewActive = true;
nextTick(() => {
if (!coder) {
coder = CodeMirror.fromTextArea(
document.getElementById('xml-highlight-container') as HTMLTextAreaElement,
{
lineWrapping: true,
mode: 'application/xml', // HMTL混合模式
theme: 'material',
lineNumbers: true,
lint: true,
// theme: 'monokai', // 使用monokai模版
},
);
coder.setSize('100%', '100%');
} else {
coder.setValue(this.xml);
}
});
})
.catch((err: unknown) => {
console.warn(err);
});
},
},
// {
// label: '撤销',
// icon: 'icon-weibiaoti545',
// action: () => {
// bpmnContext.getModeler().get('commandStack').undo();
// },
// },
// {
// label: '恢复',
// icon: 'icon-weibiaoti546',
// action: () => {
// bpmnContext.getModeler().get('commandStack').redo();
// },
// },
],
};
return (
<div class="bpmn-actions">
<ButtonRender {...buttonRenderProps} />
<el-drawer size="35%" direction="ltr" withHeader={false} v-model={this.previewActive}>
<textarea id="xml-highlight-container" v-model={this.xml} />
</el-drawer>
<input
type="file"
id="bpmn-upload-element"
ref="refFile"
style="display: none"
accept=".xml, .bpmn"
onChange={importFile}
/>
</div>
);
},
});
//文本下载
const download = (data: string, filename: string, type: string): void => {
const blob = new Blob([data]);
const tempLink = document.createElement('a'); // 创建a标签
const href = window.URL.createObjectURL(blob); // 创建下载的链接
//filename
const fileName = `${filename}.${type}`;
tempLink.href = href;
tempLink.target = '_blank';
tempLink.download = fileName;
document.body.appendChild(tempLink);
tempLink.click(); // 点击下载
document.body.removeChild(tempLink); // 下载完成移除元素
window.URL.revokeObjectURL(href); // 释放掉blob对象
};
@@ -0,0 +1,56 @@
.bpmn-actions {
position: absolute;
bottom: 5px;
width: 100%;
}
.bpmn-actions button {
margin: 0 20px;
}
.render-icon {
cursor: pointer;
display: inline-block;
}
.render-icon .icon {
margin: 0 10px;
font-size: 30px;
padding: 3px;
}
.render-icon .icon:hover {
position: relative;
bottom: 6px;
}
.render-icon-tooltip {
margin-bottom: 5px;
}
.el-drawer__body {
height: 100%;
}
.CodeMirror {
height: 100%;
font-family: Monaco, Menlo, Consolas, 'COURIER NEW', monospace;
font-size: 12px;
}
.CodeMirror-scroll {
overflow: auto;
}
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
/* stylelint-disable-next-line rule-empty-line-before */
.ant-drawer-body {
height: 100%;
padding: 0;
}
+2
View File
@@ -0,0 +1,2 @@
import BpmnActions from './BpmnActions';
export default BpmnActions;
@@ -0,0 +1,70 @@
import { defineComponent, PropType } from 'vue';
import { Button, ButtonRenderProps } from './index';
import { ElButton, ElTooltip } from 'element-plus';
export default defineComponent({
name: 'ButtonRender',
props: {
buttons: {
type: Array as PropType<Array<Button>>,
require: true,
default: () => [],
},
buttonClick: {
type: Function as PropType<(btn: Button) => void>,
default: () => (item: any) => {
console.log(item);
},
},
},
setup(props: ButtonRenderProps) {
const globalClick = (item: any) => {
if (props.buttonClick) {
props.buttonClick(item);
}
};
return () => (
<div class="button-render">
{props.buttons.map((item) => {
if (!item.icon) {
return (
<ElButton
round
type="info"
{...{
onClick: (): void => (item.action ? item.action() : globalClick(item)),
}}
>
{item.label}
</ElButton>
);
} else {
const tooltipProps = {
effect: 'light',
content: item.label,
placement: 'top',
offset: 10,
'show-after': 300,
};
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<ElTooltip class="render-icon-tooltip" {...tooltipProps}>
<div
class="render-icon"
onClick={() => (item.action ? item.action() : globalClick(item))}
>
<svg class="icon" aria-hidden="true">
<use xlinkHref={`#${item.icon}`} />
</svg>
</div>
</ElTooltip>
);
}
})}
</div>
);
},
});
+13
View File
@@ -0,0 +1,13 @@
import ButtonRender from './ButtonRender';
export interface Button {
label?: string;
icon?: string;
action?: () => void;
}
export interface ButtonRenderProps {
buttons: Array<Button>;
buttonClick?: (btn: Button) => void;
}
export default ButtonRender;
@@ -0,0 +1,172 @@
import { defineComponent, PropType, reactive, toRaw, watchEffect, watch } from 'vue';
import ScriptHelper, { resolve } from '/@/utils/script-helper';
import { FieldDefine } from './index';
/**
* 动态绑定组件
* 传入源对象及数据绑定的定义,根据组件名动态渲染出组件进行数据绑定
*/
export default defineComponent({
name: 'DynamicBinder',
props: {
//传进来的源对象,这里需要通过动态组件修改源对象的值进行数据动态绑定
modelValue: {
type: Object as PropType<unknown>,
default: () => Object.assign({}),
required: true,
},
/**
* 组件的需要绑定数据的定义
*/
fieldDefine: {
type: Object as PropType<FieldDefine>,
default: () => ({}),
required: true,
},
//绑定数据的转换器
bindTransformer: {
type: Function,
default: undefined,
},
},
emits: ['update:modelValue', 'fieldChange'],
setup(props, context) {
const state = reactive({
flatFieldDefine: flatObject(props.fieldDefine || {}, {}),
handingModel: Object.assign({}),
});
watchEffect(() => {
state.handingModel = props.modelValue;
state.flatFieldDefine = flatObject(props.fieldDefine, {});
});
//绑定转换函数赋值,然props有则用props的否则用默认的
const bindTransformer = props.bindTransformer || defaultTransformer;
const dataBindTransformer = function (key: string, value: unknown) {
return bindTransformer(state.handingModel, key, value);
};
return () => (
<div class="dynamic-binder">
{Object.keys(state.flatFieldDefine).map((key) => {
const define = state.flatFieldDefine[key];
if (define && predicate(define, toRaw(props.modelValue))) {
const bindData = dataBindTransformer(key, define);
//组件不能是代理对象,这里直接用目标对象
const Component = toRaw(define.component);
watch(
() => bindData.value,
() => {
// state.handingModel[bindData.bindKey] = bindData.value;
context.emit('update:modelValue', state.handingModel);
//如果有setValue还是则直接使用独立的setValue
if (bindData.setValue) {
//setValue有返回值,值进行赋值后执行
const setValueCallBack = bindData.setValue(
toRaw(props.modelValue),
bindData.bindKey,
bindData.value,
);
if (setValueCallBack) {
setValueCallBack();
}
} else {
context.emit('fieldChange', bindData.bindKey, bindData.value);
}
},
);
return (
<Component
{...bindData}
v-model={bindData.value}
v-slots={bindData.vSlots}
class={`${Component.name}-${key} dynamic-binder-item`}
/>
);
}
return null;
})}
</div>
);
},
});
/**
* 将对象结构扁平化
* 例如:
*
const obj = {
a: {
b: {
c: 'xxx',
},
},
};
出来的对象会变成,{a.b.c:'xxx'}
*
* @param source 源对象
* @param target 目标对象
* @param prefix 前缀
*/
function flatObject(source: FieldDefine, target: FieldDefine, prefix = ''): FieldDefine {
Object.keys(source).forEach((key) => {
const currentKeyObj = source[key];
if (!currentKeyObj || !(typeof currentKeyObj === 'object')) {
return;
}
const component = currentKeyObj.component;
if (component) {
target[prefix + key] = currentKeyObj;
} else {
flatObject(currentKeyObj, target, `${key}.`);
}
});
return target;
}
/**
* 断言
* 若对象中有predicate断言属性,则执行断言 返回true/false
* 1.若属性为string,则使用脚本处理器执行,如 predicate='obj.a ===1' 则会传入当前对象进行判断;
* 2.若属性为function,则直接传入目标对象直接执行
* @param fieldDefine 断言的对象
* @param modelValue 模式值
*/
function predicate(fieldDefine: FieldDefine, modelValue: unknown): boolean {
const bindDefinePredicate = fieldDefine.predicate;
if (bindDefinePredicate) {
if (typeof bindDefinePredicate === 'string') {
return ScriptHelper.executeEl(modelValue, bindDefinePredicate) as boolean;
}
if (typeof bindDefinePredicate === 'function') {
return bindDefinePredicate(modelValue);
}
}
return true;
}
/**
* 默认的转换函数
* @param sourceModel 源模型对象
* @param bindKey 绑定数据的Key
* @param bindDefine 绑定定义
*/
function defaultTransformer(
sourceModel: unknown,
bindKey: string,
bindDefine: FieldDefine,
): unknown {
return reactive({
bindKey,
...bindDefine,
sourceModel,
value: bindDefine.getValue
? bindDefine.getValue(toRaw(sourceModel))
: resolve(bindKey, sourceModel) || '',
});
}
@@ -0,0 +1,22 @@
import { Component } from 'vue';
import DynamicBinder from './DynamicBinder';
/**
* 绑定的定义
*/
export interface FieldDefine extends Map {
//用于断言此绑定的组件是否是否显示
predicate?: string | ((obj: any) => boolean);
component?: string | Component | JSX.Element;
//获取value的值,根据绑定的对象获取对象的值
getValue?: (sourceObject: any) => any;
setValue?: (sourceObject: any, key: string, value: any) => void | (() => void);
}
export interface Map {
[key: string]: any;
[index: number]: any;
}
export default DynamicBinder;
File diff suppressed because one or more lines are too long
+45
View File
@@ -0,0 +1,45 @@
import './modeler.css';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import { defineComponent, onMounted } from 'vue';
import createDefaultBpmnXml from '/@/bpmn/defaultBpmnXml';
import activitiModdel from '/@/bpmn/resources/activiti-moddel.json';
import translate from '/@/bpmn/i18n';
import { BpmnStore } from '/@/bpmn/store';
export default defineComponent({
name: 'Modeler',
emits: ['bpmnMounted'],
setup( props, { emit }) {
const bpmnContext = BpmnStore;
onMounted(() => {
bpmnContext.initModeler({
container: '#modeler-container',
additionalModules: [
//添加翻译
{ translate: ['value', translate('zh')] },
],
moddleExtensions: {
activiti: activitiModdel,
},
});
const defaultProcessIdAndName = '1';
bpmnContext
.importXML(createDefaultBpmnXml(defaultProcessIdAndName, defaultProcessIdAndName))
.then((result: Array<string>) => {
if (result.length) {
console.warn('importSuccess warnings', result);
}
})
.catch((err: any) => {
console.warn('importFail errors ', err);
});
emit("bpmnMounted");
});
return () => <div id="modeler-container" />;
},
});
+2
View File
@@ -0,0 +1,2 @@
import Modeler from './Modeler';
export default Modeler;
+23
View File
@@ -0,0 +1,23 @@
.bjs-powered-by {
display: none;
}
.bpmn-container {
height: 100%;
}
#modeler-container {
flex: 1;
height: 100%;
position: relative;
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat !important;
}
.djs-palette {
border: none !important;
background-color: #fff !important;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
top: 65px !important;
}
+54
View File
@@ -0,0 +1,54 @@
import './modeler.css';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import { defineComponent, onMounted } from 'vue';
import createDefaultBpmnXml from '/@/bpmn/defaultBpmnXml';
import activitiModdel from '/@/bpmn/resources/activiti-moddel.json';
import translate from '/@/bpmn/i18n';
import { BpmnStore } from '/@/bpmn/store';
export default defineComponent({
name: 'Modeler2',
emits: ['bpmnMounted'],
setup( props, { emit }) {
const bpmnContext = BpmnStore;
onMounted(() => {
bpmnContext.initModeler({
container: '#modeler-container',
additionalModules: [
//添加翻译
{
translate: ['value', translate('zh')],
paletteProvider:["value",''],//禁用/清空左侧工具栏
labelEditingProvider:["value",''],//禁用节点编辑
contextPadProvider:["value",''],//禁用图形菜单
bendpoints:["value",{}],//禁用连线拖动
zoomScroll:["value",''],//禁用滚动
// moveCanvas:['value',''],//禁用拖动整个流程图
move:['value','']//禁用单个图形拖动
},
],
moddleExtensions: {
activiti: activitiModdel,
},
});
const defaultProcessIdAndName = '1';
bpmnContext
.importXML(createDefaultBpmnXml(defaultProcessIdAndName, defaultProcessIdAndName))
.then((result: Array<string>) => {
if (result.length) {
console.warn('importSuccess warnings', result);
}
})
.catch((err: any) => {
console.warn('importFail errors ', err);
});
emit("bpmnMounted");
});
return () => <div id="modeler-container" />;
},
});
@@ -0,0 +1,54 @@
import './modeler.css';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import { defineComponent, onMounted } from 'vue';
import createDefaultBpmnXml from '/@/bpmn/defaultBpmnXml';
import activitiModdel from '/@/bpmn/resources/activiti-moddel.json';
import translate from '/@/bpmn/i18n';
import { BpmnStore } from '/@/bpmn/store';
export default defineComponent({
name: 'Modeler2',
emits: ['bpmnMounted'],
setup( props, { emit }) {
const bpmnContext = BpmnStore;
onMounted(() => {
bpmnContext.initModeler({
container: '#modeler-container',
additionalModules: [
//添加翻译
{
translate: ['value', translate('zh')],
paletteProvider:["value",''],//禁用/清空左侧工具栏
labelEditingProvider:["value",''],//禁用节点编辑
contextPadProvider:["value",''],//禁用图形菜单
bendpoints:["value",{}],//禁用连线拖动
// zoomScroll:["value",''],//禁用滚动
// moveCanvas:['value',''],//禁用拖动整个流程图
move:['value','']//禁用单个图形拖动
},
],
moddleExtensions: {
activiti: activitiModdel,
},
});
const defaultProcessIdAndName = '1';
bpmnContext
.importXML(createDefaultBpmnXml(defaultProcessIdAndName, defaultProcessIdAndName))
.then((result: Array<string>) => {
if (result.length) {
console.warn('importSuccess warnings', result);
}
})
.catch((err: any) => {
console.warn('importFail errors ', err);
});
emit("bpmnMounted");
});
return () => <div id="modeler-container" />;
},
});
+2
View File
@@ -0,0 +1,2 @@
import Modeler2 from './Modeler2';
export default Modeler2;
+23
View File
@@ -0,0 +1,23 @@
.bjs-powered-by {
display: none;
}
.bpmn-container {
height: 100%;
}
#modeler-container {
flex: 1;
height: 100%;
position: relative;
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat !important;
}
.djs-palette {
border: none !important;
background-color: #fff !important;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
top: 65px !important;
}
+95
View File
@@ -0,0 +1,95 @@
import { defineComponent, reactive, watch } from 'vue';
import { BpmnStore } from '/@/bpmn/store';
import DynamicBinder from '/@/comps/Activiti/dynamic-binder';
import { ElCollapse, ElCollapseItem } from 'element-plus';
import { GroupProperties } from '/@/bpmn/config';
import { Fold,Expand} from '@element-plus/icons-vue';
import './panel.css';
export default defineComponent({
name: 'Panel',
setup() {
const bpmnContext = BpmnStore;
const contextState = bpmnContext.getState();
//动态数据绑定器的字段变化后更新到xml,视图刷新
//需要注意,如果字段定义里边属性定义了`setValue`方法,则不会进这里了
function onFieldChange(key: string, value: unknown): void {
const shape = bpmnContext.getShape();
bpmnContext.getModeling().updateProperties(shape, { [key]: value });
}
const panelState = reactive({
//活动的数据配置组
elCollapses: Object.assign([]),
//panel面板的开关
shrinkageOff: false,
});
//打开所有抽屉
watch(
() => contextState.activeBindDefine,
() => {
if (contextState.activeBindDefine) {
panelState.elCollapses = contextState.activeBindDefine.map((groupItem) => groupItem.name);
}
},
);
/**
* 获取字段配置组的插槽
* @param groupItem 组对象项
*/
function getSlotObject(groupItem: GroupProperties) {
return {
title: () => (
<div class="group-title-block">
{groupItem.icon && <i class={groupItem.icon} />}
{groupItem.name}
</div>
),
default: () => (
<DynamicBinder
{...{ onFieldChange: onFieldChange }}
fieldDefine={groupItem.properties}
v-model={contextState.businessObject}
/>
),
};
}
return () => (
<>
{contextState.isActive && contextState.businessObject && contextState.activeBindDefine && (
<>
<div
class="bpmn-panel-shrinkage"
onClick={() => (panelState.shrinkageOff = !panelState.shrinkageOff)}
>
{panelState.shrinkageOff ? (
<el-icon>
<Fold/>
</el-icon>
) : (
<el-icon>
<Expand/>
</el-icon>
)}
</div>
<div class="bpmn-panel" v-show={!panelState.shrinkageOff}>
<div class="title">{bpmnContext.getActiveElementName()}</div>
<ElCollapse class="bpmn-panel-collapse" v-model={panelState.elCollapses}>
{contextState.activeBindDefine.map((groupItem) => {
return (
<ElCollapseItem name={groupItem.name} v-slots={getSlotObject(groupItem)} />
);
})}
</ElCollapse>
</div>
</>
)}
</>
);
},
});
+2
View File
@@ -0,0 +1,2 @@
import Pannel from './Panel';
export default Pannel;
+54
View File
@@ -0,0 +1,54 @@
.bpmn-panel {
position: absolute;
top: 0;
right: 0;
background-color: #fff !important;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
width: 45%;
height: 100%;
}
.bpmn-panel .dynamic-binder {
padding: 0 15px;
}
.bpmn-panel .title {
font-size: 18px;
font-weight: bold;
padding: 10px;
color: #303133;
}
.group-title-block {
font-size: 16px;
margin-left: 10px;
}
.group-title-block i {
margin-right: 4px;
}
.dynamic-binder > * {
padding-top: 15px;
}
.bpmn-panel-collapse {
max-height: calc(100% - 60px);
overflow-y: auto;
padding: 5px;
/* padding: 10px; */
}
.bpmn-panel-shrinkage {
z-index: 99;
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
cursor: pointer;
}
.el-collapse{
border-bottom: none !important;
}
@@ -0,0 +1,36 @@
import { ElAutocomplete } from 'element-plus';
import { defineComponent, PropType, computed, toRaw } from 'vue';
import '../prefix-label-select/prefix-label-select.css';
/**
* 带前缀Label的AutoComplete
*/
const PrefixAutoComplete = defineComponent({
props: {
...ElAutocomplete.props,
prefixTitle: {
type: String as PropType<string>,
default: () => '',
},
},
emits: ['update:modelValue'],
setup(props, ctx) {
const computedModelValue = computed({
get: () => props.value,
set: (val) => ctx.emit('update:modelValue', val),
});
return () => (
<div class="prefix-label-select-container">
{props.prefixTitle && <div class="prefix-title ">{props.prefixTitle}</div>}
<ElAutocomplete
class="prefix-label-select"
v-model={computedModelValue.value}
{...props}
v-slots={toRaw(ctx.slots)}
/>
</div>
);
},
});
export default PrefixAutoComplete;
@@ -0,0 +1,3 @@
import PrefixAutoComplete from './PrefixAutoComplete';
export default PrefixAutoComplete;
@@ -0,0 +1,34 @@
import { ElSelect } from 'element-plus';
import { defineComponent, PropType, computed } from 'vue';
import './prefix-label-select.css';
const PrefixLabelSelect = defineComponent({
props: {
...ElSelect.props,
prefixTitle: {
type: String as PropType<string>,
default: () => '',
},
},
emits: ['update:modelValue'],
setup(props, { emit, slots }) {
const computedModelValue = computed({
get: () => props.value,
set: (val) => emit('update:modelValue', val),
});
return () => (
<div class="prefix-label-select-container">
{props.prefixTitle && <div class="prefix-title ">{props.prefixTitle}</div>}
<ElSelect
class="prefix-label-select"
v-model={computedModelValue.value}
{...props}
v-slots={slots}
/>
</div>
);
},
});
export default PrefixLabelSelect;
@@ -0,0 +1,3 @@
import PrefixLabelSelect from './PrefixLabelSelect';
export default PrefixLabelSelect;
@@ -0,0 +1,29 @@
.prefix-label-select-container {
display: inline-table;
width: 100%;
}
.prefix-title {
display: table-cell;
padding: 0 20px;
border: 1px solid #DCDFE6;
border-right: 0;
border-radius: 4px 0 0 4px;
color: #909399;
background-color: #F5F7FA;
width: 1px;
white-space: nowrap
}
.prefix-label-select {
display: table-cell;
}
.prefix-label-select .el-input, .el-select {
line-height: normal;
}
.prefix-label-select .el-input__inner {
border-radius: 0 4px 4px 0 !important;
line-height: normal;
}
+290
View File
@@ -0,0 +1,290 @@
import { defineComponent, PropType, reactive, watch, ref, onMounted, toRaw } from 'vue';
import { ElInput, ElTable, ElTableColumn, ElForm, ElFormItem } from 'element-plus';
import { TableProps } from 'element-plus/lib/el-table/src/table/defaults';
import { TableColumn, SubListState } from './type';
import './sublist.css';
import { SetupContext } from '@vue/runtime-core';
/**
* 深拷贝
* @param prototype 原型
*/
const deepCopy = (prototype: any): typeof prototype => {
return JSON.parse(JSON.stringify(prototype));
};
export default defineComponent({
props: {
/**
* v-model
*/
modelValue: {
type: Array,
default: () => [],
required: true,
},
/**
* 列的描述信息
* @see TableColumn
*/
columns: {
type: Array as PropType<Array<TableColumn>>,
required: true,
},
/**
* 原型对象(用于new)
*/
model: {
type: Object as PropType<{ [key: string]: string }>,
default: () => Object.assign({}),
},
rules: {
type: Object as PropType<{ [key: string]: Array<ObjectConstructor> }>,
default: () => null,
},
/**
* 表格的参数
* @see TableProps
*/
tableProps: {
type: Object as PropType<TableProps<any>>,
default: () => ({
stripe: true,
border: true,
size: 'small',
'empty-text': '没有数据',
}),
},
/**
* 添加按钮的标题
*/
addTitle: {
type: String as PropType<string>,
default: () => '+ 添加',
},
},
emits: ['update:modelValue'],
setup(props, ctx) {
const sublistState: SubListState<any> = reactive({
data: props.modelValue ? JSON.parse(JSON.stringify(props.modelValue)) : [],
editing: false,
editItem: {},
editIndex: undefined,
isNew: false,
sublistForm: null,
});
// SubList表单
const form = ref();
onMounted(() => {
sublistState.sublistForm = form.value;
});
//重置状态
const restoreState = () => {
sublistState.data = props.modelValue ? JSON.parse(JSON.stringify(props.modelValue)) : [];
sublistState.editing = false;
sublistState.editItem = undefined;
sublistState.editIndex = undefined;
sublistState.isNew = false;
};
watch(
() => props.modelValue,
() => {
restoreState();
},
);
/**
* 添加数据项并进行编辑
*/
const addData = (): void => {
sublistState.data.push(deepCopy(props.model));
sublistState.editIndex = sublistState.data.length - 1;
sublistState.editing = true;
sublistState.editItem = deepCopy(deepCopy(props.model));
sublistState.isNew = true;
};
//创建操作列
const actionColumnProps = buildActionColumnProps(sublistState, ctx);
return {
sublistState,
addData,
form,
actionColumnProps,
};
},
render() {
const props = this.$props;
const sublistState = this.sublistState;
const tableProps = deepCopy(props.tableProps);
const formProps = {
size: 'default',
inline: true,
'inline-message': true,
'show-message': true,
rules: props.rules,
model: sublistState.editItem,
};
return (
<div class="sublist-div">
{sublistState.data && (
<ElForm ref="form" {...formProps}>
<ElTable {...tableProps} data={sublistState.data}>
{props.columns.map((column) => {
const rawColum = toRaw(column);
if (sublistState.editing && rawColum.type !== 'index') {
const editComponentBuilder = rawColum.editComponent || getDefaultEditComponent();
const slots = {
default: (scope: any) => {
//获取列的像是方式,如果是正在编辑的行,则使用编辑组件,
const cellValue = scope.row[scope.column.property];
const getRowColumnValue = () => {
return scope.column.formatter
? scope.column.formatter(scope.row, scope.column, cellValue, scope.$index)
: cellValue;
};
return sublistState.editIndex === scope.$index
? editComponentBuilder(scope, sublistState)
: getRowColumnValue();
},
};
return <ElTableColumn v-slots={slots} {...rawColum} />;
} else {
return <ElTableColumn {...rawColum} />;
}
})}
<ElTableColumn {...this.actionColumnProps} v-slots={this.actionColumnProps.vSlots} />
</ElTable>
</ElForm>
)}
{/*新增按钮*/}
{!sublistState.editing && (
<div class="sublist-add" onClick={() => this.addData()}>
{props.addTitle}
</div>
)}
</div>
);
},
});
/**
* 创建执行操作的列(编辑、删除)
*/
const buildActionColumnProps = (state: SubListState<any>, ctx: SetupContext<any>): any => {
//编辑
function actionEdit(scope: any): void {
state.editIndex = scope.$index;
state.editing = true;
state.editItem = deepCopy(scope.row);
}
//移除
function actionRemove(index: number): void {
state?.data?.splice(index, 1);
ctx.emit('update:modelValue', state.data);
}
//确认
function actionConfirm() {
state.sublistForm.validate((valid: boolean): void | boolean => {
if (valid) {
if (typeof state?.editIndex === 'number') {
state.data.splice(state?.editIndex, 1, deepCopy(toRaw(state.editItem)));
}
state.editIndex = undefined;
state.editItem = undefined;
state.editing = false;
ctx.emit('update:modelValue', state.data);
} else {
return false;
}
});
}
//取消
function actionCancel() {
if (state.isNew) {
state.data.splice(state.data.length - 1, 1);
}
state.editItem = undefined;
state.editing = false;
state.editIndex = undefined;
}
function isReadonly(): string {
return state.editing ? 'readonly' : '';
}
return {
align: 'center',
label: '操作',
vSlots: {
default: (scope: any) => (
<div class="sublist-actions">
{state.editing && scope.$index === state.editIndex ? (
<div>
<span class="sublist-confirm sublist-btn" onClick={() => actionConfirm()}>
确认
</span>
<span class="sublist-split">|</span>
<span class="sublist-cancel sublist-btn" onClick={() => actionCancel()}>
取消
</span>
</div>
) : (
<div>
<span
class={`${isReadonly()} sublist-edit sublist-btn`}
onClick={() => {
if (!state.editing) {
actionEdit(scope);
}
}}
>
编辑
</span>
<span class={`${isReadonly()} `}>|</span>
<span
class={`${isReadonly()} sublist-delete sublist-btn`}
onClick={() => {
if (!state.editing) {
actionRemove(scope.$index);
}
}}
>
删除
</span>
</div>
)}
</div>
),
},
};
};
/**
* 默认的编辑组件,ElInput 用于编辑或新增
*/
function getDefaultEditComponent(): (scope: any, state: SubListState<any>) => JSX.Element {
return function (scope, state) {
return (
<ElFormItem
class="sublist-form-item"
label={scope.column.name}
prop={scope.column.property}
>
<ElInput
label={scope.column.label}
v-model={state.editItem[scope.column.property]}
/>
</ElFormItem>
);
};
}
+3
View File
@@ -0,0 +1,3 @@
import SubList from './SubList';
export default SubList;
+67
View File
@@ -0,0 +1,67 @@
.sublist-add {
margin-top: 8px;
border: 1px solid #409eff;
text-align: center;
cursor: pointer;
color: #fff;
font-weight: bold;
border-radius: 4px;
background-color: #409eff;
}
.sublist-add:hover {
border-color: #66b1ff;
background-color: #66b1ff;
}
.sublist-actions .sublist-btn {
margin: 0 5px;
}
.sublist-edit, .sublist-confirm {
color: #409eff;
}
.sublist-split {
color: #bbbec3;
}
.sublist-btn {
cursor: pointer;
}
.sublist-btn:hover {
border-bottom: 2px solid;
}
.sublist-edit :hover, .sublist-confirm:hover {
color: #66b1ff;
}
.sublist-delete {
color: #fa3b3b;
}
.sublist-delete:hover {
color: #f78989;
}
.sublist-actions .readonly {
color: #bbbec3;
cursor: default;
}
.sublist-actions .readonly:hover {
border-bottom: none;
}
.sublist-form-item {
width: 100%;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
.sublist-form-item .el-form-item__content {
width: 100%;
}
+20
View File
@@ -0,0 +1,20 @@
import { TableColumnCtx } from 'element-plus/lib/el-table/src/table-column/defaults';
/**
* 状态
*/
export interface SubListState<T> {
data: Array<T>;
editing: boolean;
editItem: T | undefined | null;
editIndex: number | undefined;
isNew: boolean;
sublistForm: any | null;
}
/**
* 扩展的的可编辑列
*/
export interface TableColumn extends TableColumnCtx<any> {
editComponent?: (scope: any | undefined | null) => JSX.Element;
}
+145
View File
@@ -0,0 +1,145 @@
<template>
<el-dialog v-model='dialogVisible' title='弹框选择' draggable width='500px' :before-close='handleClose'>
<el-card class='box-card' style='height: 500px;' 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: 10px;width: 30px'>
<el-icon class='el-icon--right'>
<more-filled />
</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='refresh'>刷新</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<div style='margin-bottom: 45px'>
<el-tree @node-click='nodeClick' ref='treeRef' class='filter-tree' :data='state.data' :props='defaultProps' :filter-node-method='filterNode' />
</div>
</el-card>
<template #footer>
<span class='dialog-footer'>
<el-button @click='dialogVisible = false'>取消</el-button>
<el-button type='primary' @click='handleConfirm'>确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang='ts' setup>
import { defineExpose, onMounted, reactive, ref, watch } from 'vue';
import type { ElTree } from 'element-plus';
import { Search, MoreFilled } from '@element-plus/icons-vue';
import request from '/@/utils/request';
import { ElMessage } from 'element-plus';
interface Tree {
id: number;
name: string;
children?: Tree[];
}
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
const defaultProps = {
children: 'children',
label: 'name',
};
watch(filterText, (val) => {
treeRef.value!.filter(val);
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.name.includes(value);
};
const state = reactive({
data: [] as any,
});
const handleCommand = (command: string | number | object) => {
if ('expandAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = true;
}
} else if ('collapseAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = false;
}
}
ElMessage(`click on item ${command}`);
};
// // 初始化表格数据
const initTreeData = async () => {
state.data = await request({
url: '/te/feat/screen/tree',
method: 'get',
});
};
const dialogVisible = ref(false);
const openModal = async (id: string) => {
dialogVisible.value = true;
};
defineExpose({ openModal });
onMounted(() => {
initTreeData();
});
const emits = defineEmits(['close']);
const handleConfirm=()=>{
emits('close',{id:state.id,name:state.name,pid:state.pid});
dialogVisible.value = false;
}
const nodeClick=(node:any)=>{
state.id=node.id;
state.name=node.name;
state.pid=node.pid;
}
</script>
<style scoped>
.tree-h-flex {
display: flex;
}
.tree-h-left {
flex: 1;
width: 100%;
}
.tree-h-right {
width: 40px;
min-width: 40px;
}
</style>
+118
View File
@@ -0,0 +1,118 @@
<template>
<el-form class='zform' :model='form' label-width='180px'>
<el-row style='border-top: 1px solid #d2d2d2;'>
<el-col :span='24'>
<el-form-item label='流程说明:'>
<div class='zinput'>
xxxxxx
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='紧急程度:'>
<div class='zinput'>
<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>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='即将流向:'>
<div class='zinput'>
N5.部门负责人(张三)
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='处理意见:'>
<div class='zinput' style='height: auto'>
<el-input v-model='form.opnot' type='textarea' :rows='5' placeholder=' ' />
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='附件:'>
<div class='zinput' style='height: auto'>
<el-upload class='upload-demo' action='https://jsonplaceholder.typicode.com/posts/' :file-list='fileList'>
<el-button type='primary'>上 传</el-button>
</el-upload>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<div style='padding-left: 105px;border-right: 1px solid #ccc'>
<el-checkbox v-model="checked2">流程图</el-checkbox>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
</el-col>
</el-row>
</el-form>
</template>
<script lang='ts' setup>
import { onMounted, reactive, toRaw, toRefs } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const state = reactive({
form: {
},
params: {
path: '',
query: '',
},
});
const {
form, params,
} = toRefs(state);
onMounted(() => {
state.params = <any>route;
if (params.value.query?.id) {
}
});
const getOperateInfo = () => {
return toRaw(form.value);
};
defineExpose({ getOperateInfo });
// const fileList = ref<UploadUserFile[]>([
// {
// name: 'food.jpeg',
// url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
// },
// {
// name: 'food2.jpeg',
// url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
// },
// ])
</script>
<style scoped>
</style>
+357
View File
@@ -0,0 +1,357 @@
<template>
<el-form class='zform' :model='form' label-width='160px' label-position='left'>
<el-row style='border-top: 1px solid #d2d2d2;'>
<el-col :span='24'>
<el-form-item label='流程说明' style='margin-left: 10px;'>
<div class='zinput'>
xxxxxx
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<div style='padding-left: 10px;border-right: 1px solid #ccc'>
<el-checkbox v-model='state.autag'>显示审批记录</el-checkbox>
</div>
</el-col>
</el-row>
<el-row v-show='state.autag'>
<el-col :span='24' style='background-color: white;'>
<div style='border-right: 1px solid #ccc'>
<el-table :data='audits' style='width: 100%'>
<el-table-column prop='crtim' label='时间' width='160' />
<el-table-column prop='facno' label='节点编号' width='80' />
<el-table-column prop='facna' label='节点名称' width='200' />
<el-table-column prop='haman' label='操作者' width='90' />
<el-table-column prop='opinf' label='操作' width='300' />
<el-table-column prop='opnot' label='处理意见' />
</el-table>
</div>
</el-col>
</el-row>
<div v-if='state.cutag'>
<el-row>
<el-col :span='24'>
<el-form-item label='通知紧急程度' style='margin-left: 10px'>
<div class='zinput'>
<el-radio-group v-model='form.opurg'>
<el-radio label='a'><span>一般</span></el-radio>
<el-radio label='b'><span style='color:blue'>急</span></el-radio>
<el-radio label='c'><span style='color:red'>紧急</span></el-radio>
</el-radio-group>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='操作' style='margin-left: 10px'>
<div class='zinput'>
<el-radio-group v-model='form.opkey'>
<el-radio label='pass'>通过</el-radio>
<el-radio label='refuse'>驳回</el-radio>
<el-radio label='commission'>转办</el-radio>
<el-radio label='communicate'>沟通</el-radio>
<el-radio label='abandon'>废弃</el-radio>
</el-radio-group>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.opkey=='refuse'">
<el-col :span='24'>
<el-form-item label='驳回到' style='margin-left: 10px'>
<div class='zinput'>
<el-select v-model='form.refno' placeholder='请选择' style='width:300px;margin-right: 8px' @change='refChange'>
<el-option v-for='item in state.refNodes' :key='item.id' :value='item.id' :label="item.id+'.'+item.name"></el-option>
</el-select>
<el-checkbox v-model='form.retag'>驳回的节点通过后直接返回本节点</el-checkbox>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.opkey=='pass'">
<el-col :span='24'>
<el-form-item label='即将流向' style='margin-left: 10px'>
<div class='zinput'>
{{ state.toExmen }}
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='处理意见' style='margin-left: 10px'>
<div class='zinput' style='height: auto'>
<div class='au-flex'>
<div class='au-msg' style='padding:3px'>
<el-input v-model='form.opnot' type='textarea' :rows='4' placeholder=' ' />
</div>
<div class='au-button'>
<el-button type='primary' @click='bpmSubmit()' style='width: 94px;height: 94px;margin-top: 3px;margin-left: 6px'>提 交</el-button>
</div>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='附件' style='margin-left: 10px'>
<div class='zinput' style='height: auto'>
<el-upload class='upload-demo' action='https://jsonplaceholder.typicode.com/posts/' :file-list='fileList'>
<el-button type='primary'>上 传</el-button>
</el-upload>
</div>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row>
<el-col :span='24'>
<el-form-item label='当前处理人' style='margin-left: 10px'>
<div class='zinput'>
{{ form.facno + '.' + form.facna + '(' + state.cuExmen + ')' }}
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<el-form-item label='已经处理人' style='margin-left: 10px'>
<div class='zinput'>
{{ state.hiHamen }}
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span='24'>
<div style='padding-left: 10px;border-right: 1px solid #ccc'>
<el-checkbox v-model='state.chtag' @change='toggleFlowChart'>流程图</el-checkbox>
</div>
</el-col>
</el-row>
<el-row v-show='state.chtag'>
<el-col :span='24' style='background-color: white;'>
<div style='padding: 10px;border-right: 1px solid #ccc;height:1000px'>
<Modeler2 />
</div>
</el-col>
</el-row>
</el-form>
</template>
<script lang='ts' setup>
import { onMounted, reactive, toRaw, toRefs } from 'vue';
import request from '/@/utils/request';
// import { useRoute } from 'vue-router';
// import { Cookie, Session } from '/@/utils/storage';
// import { store } from '/@/store';
// const route = useRoute();
import Modeler2 from '/@/comps/Activiti/modeler2/index';
import { BpmnStore } from '/@/bpmn/store';
// import Viewer from 'bpmn-js/lib/NavigatedViewer';
// import Viewer from 'bpmn-js/lib/Modeler';
// import Viewer from 'bpmn-js/lib/Viewer';
const state = reactive({
form: {
items: [],
} as any,
params: {
path: '',
query: '',
},
audits: [] as any,
chtag: false,
autag: true,
xml: null as any,
refNodes: [] as any,
cutag: false,
hiHamen: '',
cuExmen: '',
toExmen: '',
});
const {
form, params, audits,
} = toRefs(state);
state.refNodes = [
{ id: 'N2', name: '起草节点', exman: 'sa' },
{ id: 'N4', name: '张三', exman: 'z3' },
{ id: 'N5', name: '王五', exman: 'w5' },
];
const props = defineProps({
proid: String,
});
onMounted(async () => {
await bpmInit();
// state.params = <any>route;
// if (params.value.query?.id) {
//也可以拿到alert(params.value.query?.id);
// }
});
const bpmInit = async () => {
// form.value.proid = props.proid;
const result: any = await request({
url: '/bpm/proc/main/zbpm/' + props.proid,
method: 'get',
});
audits.value = result.audits;
state.hiHamen = result.hiHamen;
state.cuExmen = result.cuExmen;
state.cutag = result.cutag;
form.value = result.zbpm;
if (state.cutag) {
const target: any = await request({
url: '/bpm/proc/main/target',
method: 'get',
params: { proid: form.value.proid, facno: form.value.facno },
});
form.value.tarno = target.tarno;
form.value.tarna = target.tarna;
form.value.tamen = target.tamen;
form.value.bacid = target.bacid;
if (target.tarno == 'N3') {
state.toExmen = target.tarno + '.' + target.tarna;
} else {
state.toExmen = target.tarno + '.' + target.tarna + '(' + target.tamen + ')';
}
form.value.opkey = 'pass';
form.value.opurg = 'a';
}
// audits.value = await request({
// url: '/bpm/audit/main/list/' + props.proid,
// method: 'get',
// });
// form.value = await request({
// url: '/bpm/proc/main/zbpm/' + props.proid,
// method: 'get',
// });
};
const emit = defineEmits(['submit']);
const bpmSubmit = async () => {
console.log('86811');
emit('submit');
console.log('86822');
if (form.value.opkey == 'pass') {
await request({
url: '/bpm/proc/main/hpass',
method: 'post',
data: form.value,
});
} else if (form.value.opkey == 'refuse') {
const refInfo = toRaw(form.value);
refInfo.tarno=refInfo.refno;
refInfo.tarna=refInfo.refna;
refInfo.exman=refInfo.reman;
await request({
url: '/bpm/proc/main/hrefuse',
method: 'post',
data: refInfo,
});
}
};
const toggleFlowChart = async () => {
if (!state.xml) {
state.xml = await request({
url: '/bpm/proc/main/xml/' + props.proid,
method: 'get',
});
await BpmnStore.importXML(state.xml);
// const bpmnViewer = new Viewer({
// container: '#modeler-container',
// }) as any;
// try {
// const { warnings } = await bpmnViewer.importXML(state.xml);
// // bpmnViewer.get("canvas").zoom("fit-viewport", "auto");
// console.log('rendered');
// } catch (err) {
// console.log('error rendering', err);
// }
const elementRegistry = BpmnStore.getModeler().get('elementRegistry');
// const userTaskList = elementRegistry.filter(
// (item) => item.type === 'bpmn:Task'
// );
console.log(elementRegistry);
const list1 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'L7' || item.id == 'N1' || item.id == 'N2') {
list1.push(item);
}
});
const list2 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'N4') {
list2.push(item);
}
});
const list3 = [] as any;
elementRegistry.forEach((item: any) => {
if (item.id == 'L1' || item.id == 'L3') {
list3.push(item);
}
});
let modeling = BpmnStore.modeler.get('modeling');
modeling.setColor(list1, { fill: '#e4feef' });
modeling.setColor(list2, { fill: '#fed6d6' });
modeling.setColor(list3, { stroke: '#009900' });
}
};
const refChange = (id: any) => {
for (let i = 0; i < state.refNodes.length; i++) {
if (state.refNodes[i].id == id) {
form.value.refna = state.refNodes[i].name;
form.value.reman = state.refNodes[i].exman;
break;
}
}
};
</script>
<style scoped>
.au-flex {
display: flex;
background-color: white;
}
.au-msg {
flex: 1;
width: 100%;
}
.au-button {
width: 105px;
min-width: 105px;
}
</style>
+147
View File
@@ -0,0 +1,147 @@
<template>
<el-dialog v-model='dialogVisible' title='弹框选择' draggable width='500px' :before-close='handleClose'>
<el-card class='box-card' style='height: 500px;' 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: 10px;width: 30px'>
<el-icon class='el-icon--right'>
<more-filled />
</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='refresh'>刷新</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<div style='margin-bottom: 45px'>
<el-tree @node-click='nodeClick' ref='treeRef' class='filter-tree' :data='state.data' :props='defaultProps' :filter-node-method='filterNode' />
</div>
</el-card>
<template #footer>
<span class='dialog-footer'>
<el-button @click='dialogVisible = false'>取消</el-button>
<el-button type='primary' @click='handleConfirm'>确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang='ts' setup>
import { defineExpose, onMounted, reactive, ref, watch } from 'vue';
import type { ElTree } from 'element-plus';
import { Search, MoreFilled } from '@element-plus/icons-vue';
import request from '/@/utils/request';
import { ElMessage } from 'element-plus';
const props = defineProps({
url: String,
});
interface Tree {
id: number;
name: string;
children?: Tree[];
}
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
const defaultProps = {
children: 'children',
label: 'name',
};
watch(filterText, (val) => {
treeRef.value!.filter(val);
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.name.includes(value);
};
const state = reactive({
data: [] as any,
});
const handleCommand = (command: string | number | object) => {
if ('expandAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = true;
}
} else if ('collapseAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = false;
}
}
ElMessage(`click on item ${command}`);
};
// // 初始化表格数据
const initTreeData = async () => {
state.data = await request({
url: props.url,
method: 'get',
});
};
const dialogVisible = ref(false);
const openModal = async (id: string) => {
dialogVisible.value = true;
};
defineExpose({ openModal });
onMounted(() => {
initTreeData();
});
const emits = defineEmits(['close']);
const handleConfirm = () => {
emits('close', { id: state.id, name: state.name, pid: state.pid });
dialogVisible.value = false;
};
const nodeClick = (node: any) => {
state.id = node.id;
state.name = node.name;
state.pid = node.pid;
};
</script>
<style scoped>
.tree-h-flex {
display: flex;
}
.tree-h-left {
flex: 1;
width: 100%;
}
.tree-h-right {
width: 40px;
min-width: 40px;
}
</style>
+120
View File
@@ -0,0 +1,120 @@
<template>
<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: 10px;width: 30px'>
<el-icon class='el-icon--right'>
<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='refresh'>刷新</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<div style='margin-bottom: 45px'>
<el-tree ref='treeRef' class='filter-tree' :data='state.data' :props='defaultProps' :filter-node-method='filterNode' />
</div>
</el-card>
</template>
<script lang='ts' setup>
import { onMounted, reactive, ref, watch } from 'vue';
import type { ElTree } from 'element-plus';
import { Search, MoreFilled } from '@element-plus/icons-vue';
import request from '/@/utils/request';
import { ElMessage } from 'element-plus';
const props = defineProps({
url: String,
});
interface Tree {
id: number;
name: string;
children?: Tree[];
}
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
const defaultProps = {
children: 'children',
label: 'name',
};
watch(filterText, (val) => {
treeRef.value!.filter(val);
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.name.includes(value);
};
const state = reactive({
data: [] as any,
});
const handleCommand = (command: string | number | object) => {
if ('expandAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = true;
}
} else if ('collapseAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = false;
}
}
ElMessage(`click on item ${command}`);
};
// // 初始化表格数据
const initTreeData = async () => {
state.data = await request({
url: props.url,
method: 'get',
});
// for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
// treeRef.value!.store._getAllNodes()[i].expanded = true;
// }
};
onMounted(() => {
initTreeData();
});
</script>
<style scoped>
.tree-h-flex {
display: flex;
}
.tree-h-left {
flex: 1;
width: 100%;
}
.tree-h-right {
width: 40px;
min-width: 40px;
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class='p-flex' :style='{ height: `calc(100vh - ${initTagViewHeight}`}'>
<slot></slot>
</div>
</template>
<script lang='ts' setup>
//设置高度为100%
import { useStore } from '/@/store';
import { computed } from 'vue';
const store = useStore();
const initTagViewHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
if (isTagsViewCurrenFull) {
return `13px`;
} else {
if (isTagsview) return `97px`;
else return `63px`;
}
});
</script>
+88
View File
@@ -0,0 +1,88 @@
import request from '/@/utils/request';
export const editInit = async (state: any, route: any) => {
state.params = <any>route;
let id = state.params.query?.id;
if (id) {
state.form = await request({
url: state.url + '/one/' + id,
method: 'get',
});
} else {
state.form.avtag = true;
}
};
export const tabSave = async (state: any, proxy: any, route: any) => {
if (!state.form.crtim) {
await request({
url: state.url,
method: 'post',
data: state.form,
});
} else {
await request({
url: state.url,
method: 'put',
data: state.form,
});
}
tabClose(proxy, route);
};
export const tabClose = (proxy: any, route: any) => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
};
export const pageSave = async (state: any) => {
if (!state.form.crtim) {
await request({
url: state.url,
method: 'post',
data: state.form,
});
} else {
await request({
url: state.url,
method: 'put',
data: state.form,
});
}
window.close();
};
export const pageClose = () => {
window.close();
};
export const drawerSave = async (state: any) => {
if (!state.form.crtim) {
await request({
url: state.url,
method: 'post',
data: state.form,
});
} else {
await request({
url: state.url,
method: 'put',
data: state.form,
});
}
state.show = false;
};
export const drawerOpen = async (state: any, id: string) => {
if (id) {
state.form = await request({
url: state.url + '/one/' + id,
method: 'get',
});
} else {
state.form={avtag:true};
}
state.show = true;
};
+86
View File
@@ -0,0 +1,86 @@
import request from '/@/utils/request';
import { ElMessage, ElMessageBox } from 'element-plus';
import router from '/@/router';
import { uuid } from '/@/utils/xutil';
export async function listQuery(state: any) {
const data: any = await request({
url: state.url,
method: 'get',
params: state.form,
});
state.list = data.items;
state.total = data.total;
state.loading = false;
}
export function listSelect(selection: any, state: any) {
state.ids = selection.map((item: any) => item.id);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
export async function listDelete(state: any) {
const delIds = state.ids.join(',');
if (delIds.length <= 0) {
ElMessage.warning('请选择后再进行删除');
return;
}
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
await request({
url: state.url + '/' + delIds,
method: 'delete',
});
await listQuery(state);
})
.catch(() => ElMessage.info('已取消删除'));
}
export const tabAdd = (url: string) => {
router.push({
path: url + '/edit',
query: { uuid: uuid() },
});
};
export const tabEdit = (url: string, id: string) => {
router.push({
path: url + '/edit',
query: { id: id },
});
};
export const tabView = (url: string, id: string) => {
router.push({
path: url + '/view',
query: { id: id },
});
};
export const pageAdd = (url: string) => {
// if(url.indexOf("?")>0){
// window.open('/#/page/'+url+'&type=add');
// }else{
// window.open('/#/page/'+url+'?type=add');
// }
window.open('/#/page/'+url);
};
export const pageEdit = (url: string) => {
window.open('/#/page/'+url);
};
export const pageView = (url: string) => {
window.open('/#/page/'+url);
};
+28
View File
@@ -0,0 +1,28 @@
import request from '/@/utils/request';
import router from '/@/router';
export const viewClose = (proxy: any, route: any) => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
};
export const viewInit = async (state: any, route: any) => {
state.params = <any>route;
let id = state.params.query?.id;
if (id) {
state.form = await request({
url: state.url + '/one/' + id,
method: 'get',
});
} else {
state.form = { avtag: true };
}
};
export const viewEdit = async (state: any, proxy: any, route: any) => {
viewClose(proxy, route);
router.push({
path: state.url + '/edit',
query: { id: state.form.id },
});
};
+135
View File
@@ -0,0 +1,135 @@
<template>
<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: 10px;width: 30px'>
<el-icon class='el-icon--right'>
<more-filled />
</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='refresh'>刷新</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<div style='margin-bottom: 45px'>
<el-tree ref='treeRef' class='filter-tree' :data='state.data' :props='defaultProps' :filter-node-method='filterNode' @node-click="nodeClick"/>
</div>
</el-card>
</template>
<script lang='ts' setup>
import { defineExpose, onMounted, reactive, ref, watch } from 'vue';
import type { ElTree } from 'element-plus';
import { Search, MoreFilled } from '@element-plus/icons-vue';
import request from '/@/utils/request';
import { ElMessage } from 'element-plus';
interface Tree {
id: number;
name: string;
children?: Tree[];
}
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
const defaultProps = {
children: 'children',
label: 'name',
};
watch(filterText, (val) => {
treeRef.value!.filter(val);
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.name.includes(value);
};
const state = reactive({
data: [] as any,
willInit:true
});
const handleCommand = (command: string | number | object) => {
if ('expandAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = true;
}
} else if ('collapseAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
treeRef.value!.store._getAllNodes()[i].expanded = false;
}
}
ElMessage(`click on item ${command}`);
};
// // 初始化表格数据
const initTreeData = async () => {
state.data = await request({
url: '/gen/org/dept/tree',
method: 'get',
});
};
const props = defineProps({
maInit: Boolean,
});
onMounted(() => {
if(props.maInit==false){
initTreeData();
}
});
//region 与父组件的交互逻辑
const emits = defineEmits(['node-click']);
const nodeClick = (node:any) => {
emits('node-click', { id: node.id, name: node.name });
};
const init = async () => {
await initTreeData();
};
defineExpose({ init });
//endregion
</script>
<style scoped>
.tree-h-flex {
display: flex;
}
.tree-h-left {
flex: 1;
width: 100%;
}
.tree-h-right {
width: 40px;
min-width: 40px;
}
</style>
+511
View File
@@ -0,0 +1,511 @@
<template>
<el-dialog v-model='state.isShow' title='组织架构选择' width='1000px' draggable>
<div style='height: 440px'>
<el-row>
<el-col :span='16'>
<el-tabs v-model='activeName' type='card' @tab-click='tabChange'>
<el-tab-pane label='快捷选择' name='k1'>
<div>
<el-input v-model='state.receSearch' placeholder='请输入关键字查询' style='width: 200px;' @keyup.enter='onSearch' />
<el-button type='primary' style='margin-left: 10px' @click='onSearch'>查询</el-button>
</div>
<div style='margin-top: 5px;overflow: auto;height:353px;border: 1px solid #ccc'>
<ul class='z-org-search' id='zOrgSearch' style='padding: 2px'>
<li v-for='item in receItems' :key='item.id' class='f-user' @click='receItemClick(item)'>
<el-checkbox v-model='item.checked' />
<img src='https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&amp;fm=26&amp;gp=0.jpg'>
<span class='layui-elip f-user-name'>{{ item.name }}</span>
<span class='layui-elip f-user-dept'>{{ item.dept }}</span>
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane label='层级架构' name='k2'>
<el-row>
<el-col :span='12'>
<div style='height: 390px;'>
<DeptTree @node-click='nodeClick' :maInit='true' ref='deptTreeRef' />
</div>
</el-col>
<el-col :span='12'>
<div style='border: 1px solid #ccc;height: 390px;margin-left: 5px;overflow: auto'>
<ul class='z-org-tree'>
<li v-for='item in tierItems' :key='item.id' class='f-user' title='444444444' @click='tierItemClick(item)'>
<el-checkbox v-model='item.checked' />
<img src='https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&amp;fm=26&amp;gp=0.jpg'>
<span class='layui-elip f-user-name'>{{ item.name }}</span>
<span class='layui-elip f-user-dept'>{{ item.dept }}</span>
</li>
</ul>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label='常用群组' name='k3'>暂不支持,待开发</el-tab-pane>
<el-tab-pane label='我的收藏' name='k4'>这个也暂不支持,待开发</el-tab-pane>
</el-tabs>
</el-col>
<el-col :span='8'>
<div style='margin-left: 5px;'>
<div style='height: 36px;'>
<div style='margin-top: 20px'>已 选</div>
</div>
<div style='border: 1px solid #ccc;height: 390px;overflow: auto;'>
<ul class='z-org-selected' style='position: relative'>
<li v-for='item in seldItems' :key='item.id' class='f-user' style='overflow: hidden;'>
<el-icon @click='selectedItemClick(item)'>
<circle-close />
</el-icon>
<img src='https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&amp;fm=26&amp;gp=0.jpg'>
<span class='layui-elip f-user-name2'>{{ item.name }}</span>
</li>
</ul>
</div>
</div>
</el-col>
</el-row>
</div>
<template #footer>
<span class='dialog-footer'>
<el-button type='primary' @click='closeModal'>确认</el-button>
<el-button @click='state.isShow = false'>取消</el-button>
<el-button @click='clearAndcloseModal' style='margin-left: 20px'>清空选择</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang='ts' setup>
import { defineExpose, reactive, ref, toRaw, toRefs } from 'vue';
import { CircleClose } from '@element-plus/icons-vue';
import DeptTree from '/@/comps/sys/DeptTree.vue';
import request from '/@/utils/request';
let p_org_type = -1;//0为综合,1为机构,2为部门,4为岗位,8为用户,16为常用群组,32为角色线
let p_tab_key = 'k1';
let p_select_mode = 1;//1单选,2多选
let p_opener = null as any;//弹框打开者标记
// let p_tier_init_flag = false;//层级架构页签初始化标记
let p_tier_init_flag = false;//层级架构页签初始化标记
const deptTreeRef = ref();
const state = reactive({
isShow: false,
receSearch: '' as string,
receItems: [] as any,
tierItems: [] as any,
seldItems: [] as any,
});
const { receItems, tierItems, seldItems } = toRefs(state);
const activeName = ref('k1');
//region z Tab切换
async function tabChange(tab: any) {
p_tab_key = tab.paneName;
if (p_tab_key === 'k1') {
for (const rItem of receItems.value) {
let flag = false;
for (const sItem of seldItems.value) {
if (sItem.id == rItem.id) {
rItem.checked = true;
flag = true;
break;
}
}
if (!flag) {
rItem.checked = false;
}
}
} else if (p_tab_key === 'k2') {
if (!p_tier_init_flag) {
await deptTreeRef.value.init();
p_tier_init_flag = true;
}
for (const tItem of tierItems.value) {
let flag = false;
for (const sItem of seldItems.value) {
if (sItem.id == tItem.id) {
tItem.checked = true;
flag = true;
break;
}
}
if (!flag) {
tItem.checked = false;
}
}
}
}
//endregion
//region x.1 最近使用
async function onSearch() {
receItems.value = await request({
url: '/gen/org/main?type=' + p_org_type,
method: 'get',
params: { name: state.receSearch },
});
for (const rItem of receItems.value) {
let flag = false;
for (const sItem of seldItems.value) {
if (sItem.id == rItem.id) {
rItem.checked = true;
flag = true;
break;
}
}
if (!flag) {
rItem.checked = false;
}
}
}
const receInit = async () => {
receItems.value = await request({
url: '/gen/org/rece?type=' + p_org_type,
method: 'get',
});
};
function receItemClick(item: any) {
item.checked = !item.checked;
if (p_select_mode == 1) {//单选
if (seldItems.value.length > 0) {
seldItems.value.splice(0, 1);
}
seldItems.value.push(item);
for (let i = 0; i < receItems.value.length; i++) {
if (receItems.value[i].id !== item.id) {
receItems.value[i].checked = false;
}
}
// closeOrgModal();改成不自动关
} else {//多选
if (item.checked) {
let flag = false;
for (const sItem of seldItems.value) {
if (sItem.id === item.id) {
flag = true;
break;
}
}
if (!flag) {
seldItems.value.push(item);
}
} else {
for (let i = 0; i < seldItems.value.length; i++) {
if (seldItems.value[i].id === item.id) {
seldItems.value.splice(i, 1);
break;
}
}
}
}
}
//endregion
//region x.2 层级架构
async function nodeClick(node: any) {
if (node && node.id) {
tierItems.value = await request({
url: '/gen/org/main?type=' + p_org_type,
method: 'get',
params: { deptid: node.id },
});
for (const tItem of tierItems.value) {
for (const sItem of seldItems.value) {
if (sItem.id === tItem.id) {
tItem.checked = true;
break;
}
}
}
}
}
function tierItemClick(item: any) {
item.checked = !item.checked;
if (p_select_mode == 1) {//单选
if (seldItems.value.length > 0) {
seldItems.value.splice(0, 1);
}
seldItems.value.push(item);
for (let i = 0; i < tierItems.value.length; i++) {
if (tierItems.value[i].id !== item.id) {
tierItems.value[i].checked = false;
}
}
// closeOrgModal();改成不自动关
} else {//多选
if (item.checked) {
let flag = false;
for (const sItem of seldItems.value) {
if (sItem.id === item.id) {
flag = true;
break;
}
}
if (!flag) {
seldItems.value.push(item);
}
} else {
for (let i = 0; i < seldItems.value.length; i++) {
if (seldItems.value[i].id === item.id) {
seldItems.value.splice(i, 1);
break;
}
}
}
}
}
//endregion
//region x.3 常用群组
//endregion
//region x.4 我的收藏
//endregion
//region c.1 已选逻辑
function selectedItemClick(item: any) {
if (p_tab_key === 'k1') {
for (let i = 0; i < receItems.value.length; i++) {
if (receItems.value[i].id === item.id) {
receItems.value[i].checked = false;
break;
}
}
} else if (p_tab_key === 'k2') {
for (let i = 0; i < tierItems.value.length; i++) {
if (tierItems.value[i].id === item.id) {
tierItems.value[i].checked = false;
break;
}
}
}
for (let j = 0; j < seldItems.value.length; j++) {
if (seldItems.value[j].id === item.id) {
seldItems.value.splice(j, 1);
break;
}
}
}
//endregion
//region 与父组件的交互逻辑
const openModal = async (data: any) => {
if (!data.orgType) {
data.orgType = 8;
}
if (!data.selectMode) {
data.selectMode = 1;
}
if (p_org_type != data.orgType) {
p_org_type = data.orgType;
await receInit();
tierItems.value.splice(0, tierItems.value.length);
}
p_select_mode = data.selectMode;
p_opener = data.opener;
//1.初始化
seldItems.value = data.orgs ? data.orgs : [];
// //2.设置最近使用与层级架构里item的check状态
if (p_tab_key === 'k1') {
for (const rItem of receItems.value) {
for (const sItem of seldItems.value) {
let flag = false;
if (sItem.id == rItem.id) {
rItem.checked = true;
flag = true;
break;
}
if (!flag) {
rItem.checked = false;
}
}
}
} else if (p_tab_key === 'k2') {
for (const tItem of tierItems.value) {
for (const sItem of seldItems.value) {
let flag = false;
if (sItem.id == tItem.id) {
tItem.checked = true;
flag = true;
break;
}
if (!flag) {
tItem.checked = false;
}
}
}
}
state.isShow = true;
};
defineExpose({ openModal });
const emits = defineEmits(['close']);
const closeModal = () => {
const rawSeldItems = toRaw(seldItems.value);
if (rawSeldItems && rawSeldItems.length > 0) {
//更新后台数据库最近使用的ORGS
request({
url: '/gen/org/rece',
method: 'post',
data: rawSeldItems,
});
}
emits('close', { opener: p_opener, orgs: toRaw(rawSeldItems) });
state.isShow = false;
};
const clearAndcloseModal = () => {
emits('close', { opener: p_opener, orgs: null });
state.isShow = false;
};
//endregion
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
.layui-elip {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.f-user:before,
.f-user:after {
content: " ";
display: table;
}
.f-user:after {
clear: both;
}
.f-user input, .f-user label {
float: left;
margin-top: 10px;
margin-right: 8px;
}
.f-user img {
width: 50px;
height: 50px;
float: left;
border-radius: 100%;
margin-right: 8px;
}
.f-user .f-user-name,
.f-user .f-user-dept {
display: block;
}
.f-user .f-user-name {
margin-top: 6px;
}
.f-user .f-user-dept {
color: #a599b2;
}
/*最近*/
.z-org-recently li {
width: 33%;
float: left;
cursor: pointer;
padding: 5px;
list-style-type: none;
}
.z-org-recently li:hover {
background-color: #f7efec;
}
/*搜索查询*/
.z-org-search li {
/*width: 280px;*/
width: 50%;
float: left;
cursor: pointer;
padding: 2px;
list-style-type: none;
/*margin: 3px;*/
}
.z-org-search li:hover {
background-color: #f7efec;
}
/*!*组织架构树*!*/
.z-org-tree li {
margin: 3px;
cursor: pointer;
padding: 3px;
list-style-type: none;
}
.z-org-tree li:hover {
background-color: #f7efec;
}
/*已选的*/
.z-org-selected li {
margin: 3px;
cursor: pointer;
padding: 3px;
list-style-type: none;
}
.z-org-selected li:hover {
background-color: #f7efec;
}
.z-org-selected .f-user .el-icon {
/*float: left;*/
/*position:relative;*/
position: absolute;
/*position:fixed;*/
color: #f26b96;
font-size: 20px;
display: none;
margin-left: 42px;
}
.z-org-selected li:hover .el-icon {
display: inline;
}
.z-org-selected .f-user img {
display: inline;
/*float: none;*/
}
.z-org-selected .f-user-name2 {
display: block;
margin-top: 14px;
}
</style>
+56
View File
@@ -0,0 +1,56 @@
import { createI18n } from 'vue-i18n';
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 { store } from '/@/store/index';
import nextZhcn from '/@/i18n/lang/zh-cn';
import nextEn from '/@/i18n/lang/en';
import nextZhtw from '/@/i18n/lang/zh-tw';
import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn';
import pagesLoginEn from '/@/i18n/pages/login/en';
import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw';
import pagesFormI18nZhcn from '/@/i18n/pages/formI18n/zh-cn';
import pagesFormI18nEn from '/@/i18n/pages/formI18n/en';
import pagesFormI18nZhtw from '/@/i18n/pages/formI18n/zh-tw';
// 定义语言国际化内容
/**
* 说明:
* /src/i18n/lang 下的 ts 为框架的国际化内容
* /src/i18n/pages 下的 ts 为各界面的国际化内容
*/
const messages = {
[zhcnLocale.name]: {
...zhcnLocale,
message: {
...nextZhcn,
...pagesLoginZhcn,
...pagesFormI18nZhcn,
},
},
[enLocale.name]: {
...enLocale,
message: {
...nextEn,
...pagesLoginEn,
...pagesFormI18nEn,
},
},
[zhtwLocale.name]: {
...zhtwLocale,
message: {
...nextZhtw,
...pagesLoginZhtw,
...pagesFormI18nZhtw,
},
},
};
// 导出语言国际化
export const i18n = createI18n({
locale: store.state.themeConfig.themeConfig.globalI18n,
fallbackLocale: zhcnLocale.name,
messages,
});
+181
View File
@@ -0,0 +1,181 @@
// 定义内容
export default {
router: {
home: 'home',
system: 'system',
systemMenu: 'systemMenu',
systemRole: 'systemRole',
systemUser: 'systemUser',
systemDept: 'systemDept',
systemDic: 'systemDic',
limits: 'limits',
limitsFrontEnd: 'FrontEnd',
limitsFrontEndPage: 'FrontEndPage',
limitsFrontEndBtn: 'FrontEndBtn',
limitsBackEnd: 'BackEnd',
limitsBackEndEndPage: 'BackEndEndPage',
menu: 'menu',
menu1: 'menu1',
menu11: 'menu11',
menu12: 'menu12',
menu121: 'menu121',
menu122: 'menu122',
menu13: 'menu13',
menu2: 'menu2',
funIndex: 'function',
funTagsView: 'funTagsView',
funCountup: 'countup',
funWangEditor: 'wangEditor',
funCropper: 'cropper',
funQrcode: 'qrcode',
funEchartsMap: 'EchartsMap',
funPrintJs: 'PrintJs',
funClipboard: 'Copy cut',
funGridLayout: 'Drag layout',
funSplitpanes: 'Pane splitter',
funDragVerify: 'Validator',
pagesIndex: 'pages',
pagesFiltering: 'Filtering',
pagesFilteringDetails: 'FilteringDetails',
pagesFilteringDetails1: 'FilteringDetails1',
pagesIocnfont: 'iconfont icon',
pagesElement: 'element icon',
pagesAwesome: 'awesome icon',
pagesFormAdapt: 'FormAdapt',
pagesTableRules: 'pagesTableRules',
pagesFormI18n: 'FormI18n',
pagesFormRules: 'Multi form validation',
pagesDynamicForm: 'Dynamic complex form',
pagesWorkflow: 'Workflow',
pagesListAdapt: 'ListAdapt',
pagesWaterfall: 'Waterfall',
pagesSteps: 'Steps',
pagesPreview: 'Large preview',
pagesWaves: 'Wave effect',
pagesTree: 'tree alter table',
pagesDrag: 'Drag command',
pagesLazyImg: 'Image lazy loading',
makeIndex: 'makeIndex',
makeSelector: 'Icon selector',
makeNoticeBar: 'notification bar',
makeSvgDemo: 'Svgicon demo',
paramsIndex: 'Routing parameters',
paramsCommon: 'General routing',
paramsDynamic: 'Dynamic routing',
paramsCommonDetails: 'General routing details',
paramsDynamicDetails: 'Dynamic routing details',
chartIndex: 'chartIndex',
visualizingIndex: 'visualizingIndex',
visualizingLinkDemo1: 'visualizingLinkDemo1',
visualizingLinkDemo2: 'visualizingLinkDemo2',
personal: 'personal',
tools: 'tools',
layoutLinkView: 'LinkView',
layoutIfameView: 'IfameView',
},
staticRoutes: {
signIn: 'signIn',
notFound: 'notFound',
noPower: 'noPower',
},
user: {
title0: 'Component size',
title1: 'Language switching',
title2: 'Menu search',
title3: 'Layout configuration',
title4: 'news',
title5: 'Full screen on',
title6: 'Full screen off',
dropdownLarge: 'large',
dropdownDefault: 'default',
dropdownSmall: 'small',
dropdown1: 'home page',
dropdown2: 'Personal Center',
dropdown3: '404',
dropdown4: '401',
dropdown5: 'Log out',
dropdown6: 'Code warehouse',
searchPlaceholder: 'Menu search: support Chinese, routing path',
newTitle: 'notice',
newBtn: 'All read',
newGo: 'Go to the notification center',
newDesc: 'No notice',
logOutTitle: 'Tips',
logOutMessage: 'This operation will log out. Do you want to continue?',
logOutConfirm: 'determine',
logOutCancel: 'cancel',
logOutExit: 'Exiting',
logOutSuccess: 'Exit successfully!',
},
tagsView: {
refresh: 'refresh',
close: 'close',
closeOther: 'closeOther',
closeAll: 'closeAll',
fullscreen: 'fullscreen',
closeFullscreen: 'closeFullscreen',
},
notFound: {
foundTitle: 'Wrong address input, please re-enter the address~',
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
foundBtn: 'Back to home page',
},
noAccess: {
accessTitle: 'You are not authorized to operate~',
accessMsg: 'Contact information: add QQ group discussion 665452019',
accessBtn: 'Reauthorization',
},
layout: {
configTitle: 'Layout configuration',
oneTitle: 'Global Themes',
twoTopTitle: 'top bar set up',
twoMenuTitle: 'Menu set up',
twoColumnsTitle: 'Columns set up',
twoTopBar: 'Top bar background',
twoTopBarColor: 'Top bar default font color',
twoIsTopBarColorGradual: 'Top bar gradient',
twoMenuBar: 'Menu background',
twoMenuBarColor: 'Menu default font color',
twoIsMenuBarColorGradual: 'Menu gradient',
twoColumnsMenuBar: 'Column menu background',
twoColumnsMenuBarColor: 'Default font color bar menu',
twoIsColumnsMenuBarColorGradual: 'Column gradient',
threeTitle: 'Interface settings',
threeIsCollapse: 'Menu horizontal collapse',
threeIsUniqueOpened: 'Menu accordion',
threeIsFixedHeader: 'Fixed header',
threeIsClassicSplitMenu: 'Classic layout split menu',
threeIsLockScreen: 'Open the lock screen',
threeLockScreenTime: 'screen locking(s/s)',
fourTitle: 'Interface display',
fourIsShowLogo: 'Sidebar logo',
fourIsBreadcrumb: 'Open breadcrumb',
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
fourIsTagsview: 'Open tagsview',
fourIsTagsviewIcon: 'Open tagsview Icon',
fourIsCacheTagsView: 'Enable tagsview cache',
fourIsSortableTagsView: 'Enable tagsview drag',
fourIsShareTagsView: 'Enable tagsview sharing',
fourIsFooter: 'Open footer',
fourIsGrayscale: 'Grey model',
fourIsInvert: 'Color weak mode',
fourIsDark: 'Dark Mode',
fourIsWartermark: 'Turn on watermark',
fourWartermarkText: 'Watermark copy',
fiveTitle: 'Other settings',
fiveTagsStyle: 'Tagsview style',
fiveAnimation: 'page animation',
fiveColumnsAsideStyle: 'Column style',
fiveColumnsAsideLayout: 'Column layout',
sixTitle: 'Layout switch',
sixDefaults: 'One',
sixClassic: 'Two',
sixTransverse: 'Three',
sixColumns: 'Four',
tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.ts` It has been modified in.',
copyText: 'replication configuration',
resetText: 'restore default',
copyTextSuccess: 'Copy succeeded!',
copyTextError: 'Copy failed!',
},
};
+181
View File
@@ -0,0 +1,181 @@
// 定义内容
export default {
router: {
home: '首页',
system: '系统设置',
systemMenu: '菜单管理',
systemRole: '角色管理',
systemUser: '用户管理',
systemDept: '部门管理',
systemDic: '字典管理',
limits: '权限管理',
limitsFrontEnd: '前端控制',
limitsFrontEndPage: '页面权限',
limitsFrontEndBtn: '按钮权限',
limitsBackEnd: '后端控制',
limitsBackEndEndPage: '页面权限',
menu: '菜单嵌套',
menu1: '菜单1',
menu11: '菜单11',
menu12: '菜单12',
menu121: '菜单121',
menu122: '菜单122',
menu13: '菜单13',
menu2: '菜单2',
funIndex: '功能',
funTagsView: 'tagsView 操作',
funCountup: '数字滚动',
funWangEditor: 'Editor 编辑器',
funCropper: '图片裁剪',
funQrcode: '二维码生成',
funEchartsMap: '地理坐标/地图',
funPrintJs: '页面打印',
funClipboard: '复制剪切',
funGridLayout: '拖拽布局',
funSplitpanes: '窗格拆分器',
funDragVerify: '验证器',
pagesIndex: '页面',
pagesFiltering: '过滤筛选组件',
pagesFilteringDetails: '过滤筛选组件详情',
pagesFilteringDetails1: '过滤筛选组件详情111',
pagesIocnfont: 'ali 字体图标',
pagesElement: 'ele 字体图标',
pagesAwesome: 'awe 字体图标',
pagesFormAdapt: '表单自适应',
pagesTableRules: '表单表格验证',
pagesFormI18n: '表单国际化',
pagesFormRules: '多表单验证',
pagesDynamicForm: '动态复杂表单',
pagesWorkflow: '工作流',
pagesListAdapt: '列表自适应',
pagesWaterfall: '瀑布屏',
pagesSteps: '步骤条',
pagesPreview: '大图预览',
pagesWaves: '波浪效果',
pagesTree: '树形改表格',
pagesDrag: '拖动指令',
pagesLazyImg: '图片懒加载',
makeIndex: '组件封装',
makeSelector: '图标选择器',
makeNoticeBar: '滚动通知栏',
makeSvgDemo: 'svgIcon 演示',
paramsIndex: '路由参数',
paramsCommon: '普通路由',
paramsDynamic: '动态路由',
paramsCommonDetails: '普通路由详情',
paramsDynamicDetails: '动态路由详情',
chartIndex: '大数据图表',
visualizingIndex: '数据可视化',
visualizingLinkDemo1: '数据可视化演示1',
visualizingLinkDemo2: '数据可视化演示2',
personal: '个人中心',
tools: '工具类集合',
layoutLinkView: '外链',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登录',
notFound: '找不到此页面',
noPower: '没有权限',
},
user: {
title0: '组件大小',
title1: '语言切换',
title2: '菜单搜索',
title3: '布局配置',
title4: '消息',
title5: '开全屏',
title6: '关全屏',
dropdownLarge: '大型',
dropdownDefault: '默认',
dropdownSmall: '小型',
dropdown1: '首页',
dropdown2: '个人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '退出登录',
dropdown6: '代码仓库',
searchPlaceholder: '菜单搜索:支持中文、路由路径',
newTitle: '通知',
newBtn: '全部已读',
newGo: '前往通知中心',
newDesc: '暂无通知',
logOutTitle: '提示',
logOutMessage: '此操作将退出登录, 是否继续?',
logOutConfirm: '确定',
logOutCancel: '取消',
logOutExit: '退出中',
logOutSuccess: '安全退出成功!',
},
tagsView: {
refresh: '刷新',
close: '关闭',
closeOther: '关闭其它',
closeAll: '全部关闭',
fullscreen: '当前页全屏',
closeFullscreen: '关闭全屏',
},
notFound: {
foundTitle: '地址输入错误,请重新输入地址~',
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
foundBtn: '返回首页',
},
noAccess: {
accessTitle: '您未被授权,没有操作权限~',
accessMsg: '联系方式:加QQ群探讨 665452019',
accessBtn: '重新授权',
},
layout: {
configTitle: '布局配置',
oneTitle: '全局主题',
twoTopTitle: '顶栏设置',
twoMenuTitle: '菜单设置',
twoColumnsTitle: '分栏设置',
twoTopBar: '顶栏背景',
twoTopBarColor: '顶栏默认字体颜色',
twoIsTopBarColorGradual: '顶栏背景渐变',
twoMenuBar: '菜单背景',
twoMenuBarColor: '菜单默认字体颜色',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
threeIsUniqueOpened: '菜单手风琴',
threeIsFixedHeader: '固定 Header',
threeIsClassicSplitMenu: '经典布局分割菜单',
threeIsLockScreen: '开启锁屏',
threeLockScreenTime: '自动锁屏(s/秒)',
fourTitle: '界面显示',
fourIsShowLogo: '侧边栏 Logo',
fourIsBreadcrumb: '开启 Breadcrumb',
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
fourIsTagsview: '开启 Tagsview',
fourIsTagsviewIcon: '开启 Tagsview 图标',
fourIsCacheTagsView: '开启 TagsView 缓存',
fourIsSortableTagsView: '开启 TagsView 拖拽',
fourIsShareTagsView: '开启 TagsView 共用',
fourIsFooter: '开启 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
fourIsDark: '深色模式',
fourIsWartermark: '开启水印',
fourWartermarkText: '水印文案',
fiveTitle: '其它设置',
fiveTagsStyle: 'Tagsview 风格',
fiveAnimation: '主页面切换动画',
fiveColumnsAsideStyle: '分栏高亮风格',
fiveColumnsAsideLayout: '分栏布局风格',
sixTitle: '布局切换',
sixDefaults: '默认',
sixClassic: '经典',
sixTransverse: '横向',
sixColumns: '分栏',
tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.ts` 中修改。',
copyText: '一键复制配置',
resetText: '一键恢复默认',
copyTextSuccess: '复制成功!',
copyTextError: '复制失败!',
},
};
+181
View File
@@ -0,0 +1,181 @@
// 定义内容
export default {
router: {
home: '首頁',
system: '系統設置',
systemMenu: '選單管理',
systemRole: '角色管理',
systemUser: '用戶管理',
systemDept: '部門管理',
systemDic: '字典管理',
limits: '許可權管理',
limitsFrontEnd: '前端控制',
limitsFrontEndPage: '頁面許可權',
limitsFrontEndBtn: '按鈕許可權',
limitsBackEnd: '後端控制',
limitsBackEndEndPage: '頁面許可權',
menu: '選單嵌套',
menu1: '選單1',
menu11: '選單11',
menu12: '選單12',
menu121: '選單121',
menu122: '選單122',
menu13: '選單13',
menu2: '選單2',
funIndex: '功能',
funTagsView: 'tagsView 操作',
funCountup: '數位滾動',
funWangEditor: 'Editor 編輯器',
funCropper: '圖片裁剪',
funQrcode: '二維碼生成',
funEchartsMap: '地理座標/地圖',
funPrintJs: '頁面列印',
funClipboard: '複製剪切',
funGridLayout: '拖拽佈局',
funSplitpanes: '窗格折開器',
funDragVerify: '驗證器',
pagesIndex: '頁面',
pagesFiltering: '過濾篩選組件',
pagesFilteringDetails: '過濾篩選組件詳情',
pagesFilteringDetails1: '過濾篩選組件詳情111',
pagesIocnfont: 'ali 字體圖標',
pagesElement: 'ele 字體圖標',
pagesAwesome: 'awe 字體圖標',
pagesFormAdapt: '表單自我調整',
pagesTableRules: '表單表格驗證',
pagesFormI18n: '表單國際化',
pagesFormRules: '多表單驗證',
pagesDynamicForm: '動態複雜表單',
pagesWorkflow: '工作流',
pagesListAdapt: '清單自我調整',
pagesWaterfall: '瀑布屏',
pagesSteps: '步驟條',
pagesPreview: '大圖預覽',
pagesWaves: '波浪效果',
pagesTree: '樹形改表格',
pagesDrag: '拖動指令',
pagesLazyImg: '圖片懶加載',
makeIndex: '組件封裝',
makeSelector: '圖標選擇器',
makeNoticeBar: '滾動通知欄',
makeSvgDemo: 'svgIcon 演示',
paramsIndex: '路由參數',
paramsCommon: '普通路由',
paramsDynamic: '動態路由',
paramsCommonDetails: '普通路由詳情',
paramsDynamicDetails: '動態路由詳情',
chartIndex: '大資料圖表',
visualizingIndex: '數據視覺化',
visualizingLinkDemo1: '數據視覺化演示1',
visualizingLinkDemo2: '數據視覺化演示2',
personal: '個人中心',
tools: '工具類集合',
layoutLinkView: '外鏈',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登入',
notFound: '找不到此頁面',
noPower: '沒有許可權',
},
user: {
title0: '組件大小',
title1: '語言切換',
title2: '選單蒐索',
title3: '佈局配寘',
title4: '消息',
title5: '開全屏',
title6: '關全屏',
dropdownLarge: '大型',
dropdownDefault: '默認',
dropdownSmall: '小型',
dropdown1: '首頁',
dropdown2: '個人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '登出',
dropdown6: '程式碼倉庫',
searchPlaceholder: '選單蒐索:支援中文、路由路徑',
newTitle: '通知',
newBtn: '全部已讀',
newGo: '前往通知中心',
newDesc: '暫無通知',
logOutTitle: '提示',
logOutMessage: '此操作將登出,是否繼續?',
logOutConfirm: '確定',
logOutCancel: '取消',
logOutExit: '退出中',
logOutSuccess: '安全退出成功!',
},
tagsView: {
refresh: '重繪',
close: '關閉',
closeOther: '關閉其它',
closeAll: '全部關閉',
fullscreen: '當前頁全屏',
closeFullscreen: '關閉全屏',
},
notFound: {
foundTitle: '地址輸入錯誤,請重新輸入地址~',
foundMsg: '您可以先檢查網址,然後重新輸入或給我們迴響問題。',
foundBtn: '返回首頁',
},
noAccess: {
accessTitle: '您未被授權,沒有操作許可權~',
accessMsg: '聯繫方式:加QQ群探討665452019',
accessBtn: '重新授權',
},
layout: {
configTitle: '佈局配寘',
oneTitle: '全域主題',
twoTopTitle: '頂欄設定',
twoMenuTitle: '選單設定',
twoColumnsTitle: '分欄設定',
twoTopBar: '頂欄背景',
twoTopBarColor: '頂欄默認字體顏色',
twoIsTopBarColorGradual: '頂欄背景漸變',
twoMenuBar: '選單背景',
twoMenuBarColor: '選單默認字體顏色',
twoIsMenuBarColorGradual: '選單背景漸變',
twoColumnsMenuBar: '分欄選單背景',
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
threeTitle: '介面設定',
threeIsCollapse: '選單水准折疊',
threeIsUniqueOpened: '選單手風琴',
threeIsFixedHeader: '固定 Header',
threeIsClassicSplitMenu: '經典佈局分割選單',
threeIsLockScreen: '開啟鎖屏',
threeLockScreenTime: '自動鎖屏(s/秒)',
fourTitle: '介面顯示',
fourIsShowLogo: '側邊欄 Logo',
fourIsBreadcrumb: '開啟 Breadcrumb',
fourIsBreadcrumbIcon: '開啟 Breadcrumb 圖標',
fourIsTagsview: '開啟 Tagsview',
fourIsTagsviewIcon: '開啟 Tagsview 圖標',
fourIsCacheTagsView: '開啟 TagsView 緩存',
fourIsSortableTagsView: '開啟 TagsView 拖拽',
fourIsShareTagsView: '開啟 TagsView 共用',
fourIsFooter: '開啟 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
fourIsDark: '深色模式',
fourIsWartermark: '開啟浮水印',
fourWartermarkText: '浮水印文案',
fiveTitle: '其它設定',
fiveTagsStyle: 'Tagsview 風格',
fiveAnimation: '主頁面切換動畫',
fiveColumnsAsideStyle: '分欄高亮風格',
fiveColumnsAsideLayout: '分欄佈局風格',
sixTitle: '佈局切換',
sixDefaults: '默認',
sixClassic: '經典',
sixTransverse: '橫向',
sixColumns: '分欄',
tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.ts`中修改。',
copyText: '一鍵複製配寘',
resetText: '一鍵恢復默認',
copyTextSuccess: '複製成功!',
copyTextError: '複製失敗!',
},
};
+13
View File
@@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: 'name',
email: 'email',
autograph: 'autograph',
},
formI18nPlaceholder: {
name: 'Please enter your name',
email: 'Please enter the users Department',
autograph: 'Please enter the login account name',
},
};
+13
View File
@@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: '姓名',
email: '用户归属部门',
autograph: '登陆账户名',
},
formI18nPlaceholder: {
name: '请输入姓名',
email: '请输入用户归属部门',
autograph: '请输入登陆账户名',
},
};
+13
View File
@@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: '姓名',
email: '用戶歸屬部門',
autograph: '登入帳戶名',
},
formI18nPlaceholder: {
name: '請輸入姓名',
email: '請輸入用戶歸屬部門',
autograph: '請輸入登入帳戶名',
},
};
+29
View File
@@ -0,0 +1,29 @@
// 定义内容
export default {
label: {
one1: 'User name login',
two2: 'Mobile number',
},
link: {
one3: 'Third party login',
two4: 'Links',
},
account: {
accountPlaceholder1: 'The user name admin or not is common',
accountPlaceholder2: 'Password: 123456',
accountPlaceholder3: 'Please enter the verification code',
accountBtnText: 'Sign in',
},
mobile: {
placeholder1: 'Please input mobile phone number',
placeholder2: 'Please enter the verification code',
codeText: 'Get code',
btnText: 'Sign in',
msgText:
'Warm tip: it is recommended to use Google, Microsoft edge, version 79.0.1072.62 and above browsers, and 360 browser, please use speed mode',
},
scan: {
text: 'Open the mobile phone to scan and quickly log in / register',
},
signInText: 'welcome back!',
};
+28
View File
@@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: '用户名登录',
two2: '手机号登录',
},
link: {
one3: '第三方登录',
two4: '友情链接',
},
account: {
accountPlaceholder1: '用户名 admin 或不输均为 common',
accountPlaceholder2: '密码:123456',
accountPlaceholder3: '请输入验证码',
accountBtnText: '登 录',
},
mobile: {
placeholder1: '请输入手机号',
placeholder2: '请输入验证码',
codeText: '获取验证码',
btnText: '登 录',
msgText: '* 温馨提示:建议使用谷歌、Microsoft Edge,版本 79.0.1072.62 及以上浏览器,360浏览器请使用极速模式',
},
scan: {
text: '打开手机扫一扫,快速登录/注册',
},
signInText: '欢迎回来!',
};
+28
View File
@@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: '用戶名登入',
two2: '手機號登入',
},
link: {
one3: '協力廠商登入',
two4: '友情連結',
},
account: {
accountPlaceholder1: '用戶名admin或不輸均為common',
accountPlaceholder2: '密碼:123456',
accountPlaceholder3: '請輸入驗證碼',
accountBtnText: '登入',
},
mobile: {
placeholder1: '請輸入手機號',
placeholder2: '請輸入驗證碼',
codeText: '獲取驗證碼',
btnText: '登入',
msgText: '* 溫馨提示:建議使用穀歌、Microsoft Edge,版本79.0.1072.62及以上瀏覽器,360瀏覽器請使用極速模式',
},
scan: {
text: '打開手機掃一掃,快速登錄/注册',
},
signInText: '歡迎回來!',
};
+1
View File
File diff suppressed because one or more lines are too long
+157
View File
@@ -0,0 +1,157 @@
<template>
<div class="h100" v-show="!isTagsViewCurrenFull">
<el-aside class="layout-aside" :class="setCollapseStyle">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
<Vertical :menuList="menuList" />
</el-scrollbar>
</el-aside>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
import Logo from '/@/layout/logo/index.vue';
import Vertical from '/@/layout/navMenu/vertical.vue';
export default defineComponent({
name: 'layoutAside',
components: { Logo, Vertical },
setup() {
const { proxy } = <any>getCurrentInstance();
const store = useStore();
const state = reactive({
menuList: [],
clientWidth: 0,
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 设置菜单展开/收起时的宽度
const setCollapseStyle = computed(() => {
const { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
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) store.state.themeConfig.themeConfig.isCollapse = false;
document.body.setAttribute('class', '');
};
// 设置显示/隐藏 logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
(state.menuList as any) = filterRoutesFun(store.state.routesList.routesList);
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
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 } = store.state.themeConfig.themeConfig;
if (layout !== 'columns') return false;
if (!bool) proxy.mittBus.emit('restoreDefault');
store.dispatch('routesList/setColumnsMenuHover', bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
proxy.$refs.layoutAsideScrollbarRef.update();
}
});
// 监听vuex值的变化,动态赋值给菜单中
watch(store.state, (val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
});
// 页面加载前
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
proxy.mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
return {
setCollapseStyle,
setShowLogo,
isTagsViewCurrenFull,
onAsideEnterLeave,
...toRefs(state),
};
},
});
</script>
+294
View File
@@ -0,0 +1,294 @@
<template>
<div class="layout-columns-aside">
<el-scrollbar>
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
<li
v-for="(v, k) in columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v, k)"
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
:ref="
(el) => {
if (el) columnsAsideOffsetTopRefs[k] = el;
}
"
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
:title="$t(v.meta.title)"
>
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
: $t(v.meta.title)
}}
</div>
</div>
<div :class="setColumnsAsidelayout" v-else>
<a :href="v.meta.isLink" target="_blank">
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
: $t(v.meta.title)
}}
</div>
</a>
</div>
</li>
<div ref="columnsAsideActiveRef" :class="setColumnsAsideStyle"></div>
</ul>
</el-scrollbar>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { useStore } from '/@/store/index';
// 定义接口来定义对象的类型
interface ColumnsAsideState {
columnsAsideList: any[];
liIndex: number;
liOldIndex: null | number;
liHoverIndex: null | number;
liOldPath: null | string;
difference: number;
routeSplit: string[];
isNavHover: boolean;
}
export default defineComponent({
name: 'layoutColumnsAside',
setup() {
const columnsAsideOffsetTopRefs: any = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = <any>getCurrentInstance();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state = reactive<ColumnsAsideState>({
columnsAsideList: [],
liIndex: 0,
liOldIndex: null,
liHoverIndex: null,
liOldPath: null,
difference: 0,
routeSplit: [],
isNavHover: false,
});
// 设置分栏高亮风格
const setColumnsAsideStyle = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideStyle;
});
// 设置分栏布局风格
const setColumnsAsidelayout = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideLayout;
});
// 设置菜单高亮位置移动
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) => {
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
store.dispatch('routesList/setColumnsMenuHover', false);
store.dispatch('routesList/setColumnsNavHover', true);
state.isNavHover = true;
};
// 鼠标移走时,显示原来的子级菜单
const onColumnsAsideMenuMouseleave = async () => {
await store.dispatch('routesList/setColumnsNavHover', false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
}, 100);
// state.isNavHover = false;
};
// 设置高亮动态位置
const onColumnsAsideDown = (k: number) => {
nextTick(() => {
setColumnsAsideMove(k);
});
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
const resData: any = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
proxy.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<object>) => {
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(store.state, (val) => {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
state.liHoverIndex = null;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
});
// 页面加载时
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
proxy.mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
return {
columnsAsideOffsetTopRefs,
columnsAsideActiveRef,
onColumnsAsideDown,
setColumnsAsideStyle,
setColumnsAsidelayout,
onColumnsAsideMenuClick,
onColumnsAsideMenuMouseenter,
onColumnsAsideMenuMouseleave,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-columns-aside {
width: 70px;
height: 100%;
background: var(--next-bg-columnsMenuBar);
ul {
position: relative;
li {
color: var(--next-bg-columnsMenuBarColor);
width: 100%;
height: 50px;
text-align: center;
display: flex;
cursor: pointer;
position: relative;
z-index: 1;
.columns-vertical {
margin: auto;
.columns-vertical-title {
padding-top: 1px;
}
}
.columns-horizontal {
display: flex;
height: 50px;
width: 100%;
align-items: center;
padding: 0 5px;
i {
margin-right: 3px;
}
a {
display: flex;
.columns-horizontal-title {
padding-top: 1px;
}
}
}
a {
text-decoration: none;
color: var(--next-bg-columnsMenuBarColor);
}
}
.layout-columns-active {
color: var(--el-color-white);
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
.columns-round {
background: var(--el-color-primary);
color: var(--el-color-white);
position: absolute;
left: 50%;
top: 2px;
height: 44px;
width: 65px;
transform: translateX(-50%);
z-index: 0;
transition: 0.3s ease-in-out;
border-radius: 5px;
}
.columns-card {
@extend .columns-round;
top: 0;
height: 50px;
width: 100%;
border-radius: 0;
}
}
}
</style>
+32
View File
@@ -0,0 +1,32 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
import NavBarsIndex from '/@/layout/navBars/index.vue';
export default defineComponent({
name: 'layoutHeader',
components: { NavBarsIndex },
setup() {
const store = useStore();
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
return {
setHeaderHeight,
isTagsViewCurrenFull,
};
},
});
</script>
+84
View File
@@ -0,0 +1,84 @@
<template>
<el-main class="layout-main">
<el-scrollbar
class="layout-scrollbar"
ref="layoutScrollbarRef"
:style="{ padding: currentRouteMeta.isLink && currentRouteMeta.isIframe ? 0 : '', transition: 'padding 0.3s ease-in-out' }"
>
<LayoutParentView :style="{ minHeight: `calc(100vh - ${headerHeight})` }" />
<Footer v-if="getThemeConfig.isFooter" />
</el-scrollbar>
</el-main>
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted } from 'vue';
import { useStore } from '/@/store/index';
import { useRoute } from 'vue-router';
import LayoutParentView from '/@/layout/routerView/parent.vue';
import Footer from '/@/layout/footer/index.vue';
// 定义接口来定义对象的类型
interface MainState {
headerHeight: string | number;
currentRouteMeta: any;
}
export default defineComponent({
name: 'layoutMain',
components: { LayoutParentView, Footer },
setup() {
const { proxy } = <any>getCurrentInstance();
const route = useRoute();
const store = useStore();
const state = reactive<MainState>({
headerHeight: '',
currentRouteMeta: {},
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 main 的高度
const initHeaderHeight = () => {
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return (state.headerHeight = bool ? `85px` : `114px`);
else return (state.headerHeight = `51px`);
};
// 初始化获取当前路由 meta,用于设置 iframes padding
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 页面加载前
onMounted(async () => {
await initGetMeta();
initHeaderHeight();
});
// 监听路由变化
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
state.headerHeight = bool ? `85px` : `114px`;
proxy.$refs.layoutScrollbarRef.update();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
state.currentRouteMeta = route.meta;
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
state.headerHeight = val.isTagsview ? (bool ? `85px` : `114px`) : '51px';
if (val.isFixedHeaderChange !== val.isFixedHeader) {
if (!proxy.$refs.layoutScrollbarRef) return false;
proxy.$refs.layoutScrollbarRef.update();
}
});
return {
getThemeConfig,
...toRefs(state),
};
},
});
</script>

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