Browse Source

feat: flowable前端改造

master
tony 3 years ago
parent
commit
ae0226a8f4
  1. 2
      ruoyi-admin/src/main/resources/application.yml
  2. 0
      ruoyi-system/src/main/resources/mapper/flowable/FlowDeployMapper.xml
  3. 0
      ruoyi-system/src/main/resources/mapper/flowable/SysTaskFormMapper.xml
  4. 5
      ruoyi-ui/package.json
  5. 122
      ruoyi-ui/src/api/flowable/definition.js
  6. 80
      ruoyi-ui/src/api/flowable/finished.js
  7. 61
      ruoyi-ui/src/api/flowable/form.js
  8. 98
      ruoyi-ui/src/api/flowable/process.js
  9. 116
      ruoyi-ui/src/api/flowable/todo.js
  10. 68
      ruoyi-ui/src/components/Process/BpmData.js
  11. 169
      ruoyi-ui/src/components/Process/PropertyPanel.vue
  12. 20
      ruoyi-ui/src/components/Process/common/customTranslate.js
  13. 24
      ruoyi-ui/src/components/Process/common/mixinExecutionListener.js
  14. 70
      ruoyi-ui/src/components/Process/common/mixinPanel.js
  15. 22
      ruoyi-ui/src/components/Process/common/mixinXcrud.js
  16. 53
      ruoyi-ui/src/components/Process/common/parseElement.js
  17. 24
      ruoyi-ui/src/components/Process/components/custom/customContextPad.vue
  18. 81
      ruoyi-ui/src/components/Process/components/nodePanel/gateway.vue
  19. 113
      ruoyi-ui/src/components/Process/components/nodePanel/process.vue
  20. 194
      ruoyi-ui/src/components/Process/components/nodePanel/property/executionListener.vue
  21. 96
      ruoyi-ui/src/components/Process/components/nodePanel/property/listenerParam.vue
  22. 117
      ruoyi-ui/src/components/Process/components/nodePanel/property/multiInstance.vue
  23. 124
      ruoyi-ui/src/components/Process/components/nodePanel/property/signal.vue
  24. 196
      ruoyi-ui/src/components/Process/components/nodePanel/property/taskListener.vue
  25. 92
      ruoyi-ui/src/components/Process/components/nodePanel/sequenceFlow.vue
  26. 94
      ruoyi-ui/src/components/Process/components/nodePanel/startEnd.vue
  27. 426
      ruoyi-ui/src/components/Process/components/nodePanel/task.vue
  28. 1194
      ruoyi-ui/src/components/Process/flowable/flowable.json
  29. 24
      ruoyi-ui/src/components/Process/flowable/init.js
  30. 55
      ruoyi-ui/src/components/Process/flowable/showConfig.js
  31. 5
      ruoyi-ui/src/components/Process/index.js
  32. 463
      ruoyi-ui/src/components/Process/index.vue
  33. 227
      ruoyi-ui/src/components/Process/lang/zh.js
  34. 12
      ruoyi-ui/src/components/customBpmn/index.js
  35. 197
      ruoyi-ui/src/components/parser/Parser.vue
  36. 17
      ruoyi-ui/src/components/parser/README.md
  37. 324
      ruoyi-ui/src/components/parser/example/Index.vue
  38. 3
      ruoyi-ui/src/components/parser/index.js
  39. 25
      ruoyi-ui/src/components/parser/package.json
  40. 19
      ruoyi-ui/src/components/render/package.json
  41. 122
      ruoyi-ui/src/components/render/render.js
  42. 5
      ruoyi-ui/src/components/render/slots/el-button.js
  43. 13
      ruoyi-ui/src/components/render/slots/el-checkbox-group.js
  44. 8
      ruoyi-ui/src/components/render/slots/el-input.js
  45. 13
      ruoyi-ui/src/components/render/slots/el-radio-group.js
  46. 9
      ruoyi-ui/src/components/render/slots/el-select.js
  47. 17
      ruoyi-ui/src/components/render/slots/el-upload.js
  48. 3
      ruoyi-ui/src/components/tinymce/README.md
  49. 8
      ruoyi-ui/src/components/tinymce/config.js
  50. 38
      ruoyi-ui/src/components/tinymce/example/Index.vue
  51. 3
      ruoyi-ui/src/components/tinymce/index.js
  52. 88
      ruoyi-ui/src/components/tinymce/index.vue
  53. 28
      ruoyi-ui/src/components/tinymce/package.json
  54. 420
      ruoyi-ui/src/components/tinymce/zh_CN.js
  55. 9
      ruoyi-ui/src/icons/index.js
  56. 1
      ruoyi-ui/src/icons/svg/button.svg
  57. 1
      ruoyi-ui/src/icons/svg/cascader.svg
  58. 1
      ruoyi-ui/src/icons/svg/checkbox.svg
  59. 1
      ruoyi-ui/src/icons/svg/color.svg
  60. 1
      ruoyi-ui/src/icons/svg/component.svg
  61. 1
      ruoyi-ui/src/icons/svg/date-range.svg
  62. 1
      ruoyi-ui/src/icons/svg/date.svg
  63. 1
      ruoyi-ui/src/icons/svg/input.svg
  64. 1
      ruoyi-ui/src/icons/svg/number.svg
  65. 1
      ruoyi-ui/src/icons/svg/password.svg
  66. 1
      ruoyi-ui/src/icons/svg/radio.svg
  67. 1
      ruoyi-ui/src/icons/svg/rate.svg
  68. 1
      ruoyi-ui/src/icons/svg/rich-text.svg
  69. 1
      ruoyi-ui/src/icons/svg/row.svg
  70. 1
      ruoyi-ui/src/icons/svg/select.svg
  71. 1
      ruoyi-ui/src/icons/svg/slider.svg
  72. 1
      ruoyi-ui/src/icons/svg/switch.svg
  73. 1
      ruoyi-ui/src/icons/svg/table.svg
  74. 1
      ruoyi-ui/src/icons/svg/textarea.svg
  75. 1
      ruoyi-ui/src/icons/svg/time-range.svg
  76. 1
      ruoyi-ui/src/icons/svg/time.svg
  77. 1
      ruoyi-ui/src/icons/svg/upload.svg
  78. 39
      ruoyi-ui/src/router/index.js
  79. 271
      ruoyi-ui/src/styles/home.scss
  80. 141
      ruoyi-ui/src/styles/index.scss
  81. 33
      ruoyi-ui/src/styles/mixin.scss
  82. 56
      ruoyi-ui/src/utils/db.js
  83. 517
      ruoyi-ui/src/utils/generator/config.js
  84. 37
      ruoyi-ui/src/utils/generator/drawingDefalut.js
  85. 29
      ruoyi-ui/src/utils/generator/drawingDefault.js
  86. 73
      ruoyi-ui/src/utils/index.js
  87. 28
      ruoyi-ui/src/utils/loadBeautifier.js
  88. 40
      ruoyi-ui/src/utils/loadMonaco.js
  89. 60
      ruoyi-ui/src/utils/loadScript.js
  90. 29
      ruoyi-ui/src/utils/loadTinymce.js
  91. 13
      ruoyi-ui/src/utils/pluginsConfig.js
  92. 548
      ruoyi-ui/src/views/flowable/definition/index.vue
  93. 145
      ruoyi-ui/src/views/flowable/definition/model.vue
  94. 285
      ruoyi-ui/src/views/flowable/task/finished/index.vue
  95. 308
      ruoyi-ui/src/views/flowable/task/form/index.vue
  96. 403
      ruoyi-ui/src/views/flowable/task/process/index.vue
  97. 36
      ruoyi-ui/src/views/flowable/task/record/flow.vue
  98. 243
      ruoyi-ui/src/views/flowable/task/record/flowview.vue
  99. 607
      ruoyi-ui/src/views/flowable/task/record/index.vue
  100. 237
      ruoyi-ui/src/views/flowable/task/todo/index.vue

2
ruoyi-admin/src/main/resources/application.yml

@ -133,6 +133,6 @@ xss:
# flowable相关表
flowable:
# true 会对数据库中所有表进行更新操作。如果表不存在,则自动创建(建议开发时使用)
database-schema-update: true
database-schema-update: false
# 关闭定时任务JOB
async-executor-activate: false

0
ruoyi-system/src/main/resources/FlowDeployMapper.xml → ruoyi-system/src/main/resources/mapper/flowable/FlowDeployMapper.xml

0
ruoyi-system/src/main/resources/SysTaskFormMapper.xml → ruoyi-system/src/main/resources/mapper/flowable/SysTaskFormMapper.xml

5
ruoyi-ui/package.json

@ -58,7 +58,10 @@
"vue-meta": "2.4.0",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
"vuex": "3.6.0",
"workflow-bpmn-modeler": "^0.2.8",
"diagram-js": "^5.0.0",
"vkbeautify": "^0.99.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",

122
ruoyi-ui/src/api/flowable/definition.js

@ -0,0 +1,122 @@
import request from '@/utils/request'
// 查询流程定义列表
export function listDefinition(query) {
return request({
url: '/flowable/definition/list',
method: 'get',
params: query
})
}
// 部署流程实例
export function definitionStart(procDefId, data) {
return request({
url: '/flowable/definition/start/' + procDefId,
method: 'post',
data: data
})
}
// 获取流程变量
export function getProcessVariables(taskId) {
return request({
url: '/flowable/task/processVariables/' + taskId,
method: 'get'
})
}
// 激活/挂起流程
export function updateState(params) {
return request({
url: '/flowable/definition/updateState',
method: 'put',
params: params
})
}
// 指定流程办理人员列表
export function userList(query) {
return request({
url: '/flowable/definition/userList',
method: 'get',
params: query
})
}
// 指定流程办理组列表
export function roleList(query) {
return request({
url: '/flowable/definition/roleList',
method: 'get',
params: query
})
}
// 读取xml文件
export function readXml(deployId) {
return request({
url: '/flowable/definition/readXml/' + deployId,
method: 'get'
})
}
// 读取image文件
export function readImage(deployId) {
return request({
url: '/flowable/definition/readImage/' + deployId,
method: 'get'
})
}
// 读取image文件
export function getFlowViewer(procInsId, executionId) {
return request({
url: '/flowable/task/flowViewer/' + procInsId + '/' + executionId,
method: 'get'
})
}
// 读取xml文件
export function saveXml(data) {
return request({
url: '/flowable/definition/save',
method: 'post',
data: data
})
}
// 新增流程定义
export function addDeployment(data) {
return request({
url: '/system/deployment',
method: 'post',
data: data
})
}
// 修改流程定义
export function updateDeployment(data) {
return request({
url: '/system/deployment',
method: 'put',
data: data
})
}
// 删除流程定义
export function delDeployment(deployId) {
return request({
url: '/flowable/definition/' + deployId,
method: 'delete',
})
}
// 导出流程定义
export function exportDeployment(query) {
return request({
url: '/system/deployment/export',
method: 'get',
params: query
})
}

80
ruoyi-ui/src/api/flowable/finished.js

@ -0,0 +1,80 @@
import request from '@/utils/request'
import da from "element-ui/src/locale/lang/da";
// 查询已办任务列表
export function finishedList(query) {
return request({
url: '/flowable/task/finishedList',
method: 'get',
params: query
})
}
// 任务流转记录
export function flowRecord(query) {
return request({
url: '/flowable/task/flowRecord',
method: 'get',
params: query
})
}
// 撤回任务
export function revokeProcess(data) {
return request({
url: '/flowable/task/revokeProcess',
method: 'post',
data: data
})
}
// 部署流程实例
export function deployStart(deployId) {
return request({
url: '/flowable/process/startFlow/' + deployId,
method: 'get',
})
}
// 查询流程定义详细
export function getDeployment(id) {
return request({
url: '/system/deployment/' + id,
method: 'get'
})
}
// 新增流程定义
export function addDeployment(data) {
return request({
url: '/system/deployment',
method: 'post',
data: data
})
}
// 修改流程定义
export function updateDeployment(data) {
return request({
url: '/system/deployment',
method: 'put',
data: data
})
}
// 删除流程定义
export function delDeployment(id) {
return request({
url: '/flowable/instance/delete/?instanceId=' + id,
method: 'delete'
})
}
// 导出流程定义
export function exportDeployment(query) {
return request({
url: '/system/deployment/export',
method: 'get',
params: query
})
}

61
ruoyi-ui/src/api/flowable/form.js

@ -0,0 +1,61 @@
import request from '@/utils/request'
// 查询流程表单列表
export function listForm(query) {
return request({
url: '/flowable/form/list',
method: 'get',
params: query
})
}
// 查询流程表单详细
export function getForm(formId) {
return request({
url: '/flowable/form/' + formId,
method: 'get'
})
}
// 新增流程表单
export function addForm(data) {
return request({
url: '/flowable/form',
method: 'post',
data: data
})
}
// 修改流程表单
export function updateForm(data) {
return request({
url: '/flowable/form',
method: 'put',
data: data
})
}
// 挂载表单
export function addDeployForm(data) {
return request({
url: '/flowable/form/addDeployForm',
method: 'post',
data: data
})
}
// 删除流程表单
export function delForm(formId) {
return request({
url: '/flowable/form/' + formId,
method: 'delete'
})
}
// 导出流程表单
export function exportForm(query) {
return request({
url: '/flowable/form/export',
method: 'get',
params: query
})
}

98
ruoyi-ui/src/api/flowable/process.js

@ -0,0 +1,98 @@
import request from '@/utils/request'
import da from "element-ui/src/locale/lang/da";
// 我的发起的流程
export function myProcessList(query) {
return request({
url: '/flowable/task/myProcess',
method: 'get',
params: query
})
}
// 完成任务
export function complete(data) {
return request({
url: '/flowable/task/complete',
method: 'post',
data: data
})
}
// 取消申请
export function stopProcess(data) {
return request({
url: '/flowable/task/stopProcess',
method: 'post',
data: data
})
}
// 驳回任务
export function rejectTask(data) {
return request({
url: '/flowable/task/reject',
method: 'post',
data: data
})
}
// 可退回任务列表
export function returnList(data) {
return request({
url: '/flowable/task/returnList',
method: 'post',
data: data
})
}
// 部署流程实例
export function deployStart(deployId) {
return request({
url: '/flowable/process/startFlow/' + deployId,
method: 'get',
})
}
// 查询流程定义详细
export function getDeployment(id) {
return request({
url: '/system/deployment/' + id,
method: 'get'
})
}
// 新增流程定义
export function addDeployment(data) {
return request({
url: '/system/deployment',
method: 'post',
data: data
})
}
// 修改流程定义
export function updateDeployment(data) {
return request({
url: '/system/deployment',
method: 'put',
data: data
})
}
// 删除流程定义
export function delDeployment(id) {
return request({
url: '/system/deployment/' + id,
method: 'delete'
})
}
// 导出流程定义
export function exportDeployment(query) {
return request({
url: '/system/deployment/export',
method: 'get',
params: query
})
}

116
ruoyi-ui/src/api/flowable/todo.js

@ -0,0 +1,116 @@
import request from '@/utils/request'
import da from "element-ui/src/locale/lang/da";
// 查询待办任务列表
export function todoList(query) {
return request({
url: '/flowable/task/todoList',
method: 'get',
params: query
})
}
// 完成任务
export function complete(data) {
return request({
url: '/flowable/task/complete',
method: 'post',
data: data
})
}
// 委派任务
export function delegate(data) {
return request({
url: '/flowable/task/delegate',
method: 'post',
data: data
})
}
// 退回任务
export function returnTask(data) {
return request({
url: '/flowable/task/return',
method: 'post',
data: data
})
}
// 驳回任务
export function rejectTask(data) {
return request({
url: '/flowable/task/reject',
method: 'post',
data: data
})
}
// 可退回任务列表
export function returnList(data) {
return request({
url: '/flowable/task/returnList',
method: 'post',
data: data
})
}
// 下一节点
export function getNextFlowNode(data) {
return request({
url: '/flowable/task/nextFlowNode',
method: 'post',
data: data
})
}
// 部署流程实例
export function deployStart(deployId) {
return request({
url: '/flowable/process/startFlow/' + deployId,
method: 'get',
})
}
// 查询流程定义详细
export function getDeployment(id) {
return request({
url: '/system/deployment/' + id,
method: 'get'
})
}
// 新增流程定义
export function addDeployment(data) {
return request({
url: '/system/deployment',
method: 'post',
data: data
})
}
// 修改流程定义
export function updateDeployment(data) {
return request({
url: '/system/deployment',
method: 'put',
data: data
})
}
// 删除流程定义
export function delDeployment(id) {
return request({
url: '/system/deployment/' + id,
method: 'delete'
})
}
// 导出流程定义
export function exportDeployment(query) {
return request({
url: '/system/deployment/export',
method: 'get',
params: query
})
}

68
ruoyi-ui/src/components/Process/BpmData.js

@ -0,0 +1,68 @@
/**
* 存储流程设计相关参数
*/
export default class BpmData {
constructor() {
this.controls = [] // 设计器控件
this.init()
}
init() {
this.controls = [
{
action: 'create.start-event',
title: '开始'
},
{
action: 'create.intermediate-event',
title: '中间'
},
{
action: 'create.end-event',
title: '结束'
},
{
action: 'create.exclusive-gateway',
title: '网关'
},
{
action: 'create.task',
title: '任务'
},
{
action: 'create.user-task',
title: '用户任务'
},
{
action: 'create.user-sign-task',
title: '会签任务'
},
{
action: 'create.subprocess-expanded',
title: '子流程'
},
{
action: 'create.data-object',
title: '数据对象'
},
{
action: 'create.data-store',
title: '数据存储'
},
{
action: 'create.participant-expanded',
title: '扩展流程'
},
{
action: 'create.group',
title: '分组'
}
]
}
// 获取控件配置信息
getControl(action) {
const result = this.controls.filter(item => item.action === action)
return result[0] || {}
}
}

169
ruoyi-ui/src/components/Process/PropertyPanel.vue

@ -0,0 +1,169 @@
<template>
<div ref="propertyPanel" class="property-panel">
<div v-if="nodeName" class="node-name">{{ nodeName }}</div>
<component
:is="getComponent"
v-if="element"
:element="element"
:modeler="modeler"
:users="users"
:groups="groups"
:categorys="categorys"
@dataType="dataType"
/>
</div>
</template>
<script>
import taskPanel from './components/nodePanel/task'
import startEndPanel from './components/nodePanel/startEnd'
import processPanel from './components/nodePanel/process'
import sequenceFlowPanel from './components/nodePanel/sequenceFlow'
import gatewayPanel from './components/nodePanel/gateway'
import { NodeName } from './lang/zh'
export default {
name: 'PropertyPanel',
components: { processPanel, taskPanel, startEndPanel, sequenceFlowPanel, gatewayPanel },
props: {
users: {
type: Array,
required: true
},
groups: {
type: Array,
required: true
},
categorys: {
type: Array,
required: true
},
modeler: {
type: Object,
required: true
}
},
data() {
return {
element: null,
form: {
id: '',
name: '',
color: null
},
roles: [
{ value: 'manager', label: '经理' },
{ value: 'personnel', label: '人事' },
{ value: 'charge', label: '主管' }
]
}
},
computed: {
getComponent() {
const type = this.element?.type
if (['bpmn:IntermediateThrowEvent', 'bpmn:StartEvent', 'bpmn:EndEvent'].includes(type)) {
return 'startEndPanel'
}
if ([
'bpmn:UserTask',
'bpmn:Task',
'bpmn:SendTask',
'bpmn:ReceiveTask',
'bpmn:ManualTask',
'bpmn:BusinessRuleTask',
'bpmn:ServiceTask',
'bpmn:ScriptTask'
// 'bpmn:CallActivity',
// 'bpmn:SubProcess'
].includes(type)) {
return 'taskPanel'
}
if (type === 'bpmn:SequenceFlow') {
return 'sequenceFlowPanel'
}
if ([
'bpmn:InclusiveGateway',
'bpmn:ExclusiveGateway',
'bpmn:ParallelGateway',
'bpmn:EventBasedGateway'
].includes(type)) {
return 'gatewayPanel'
}
if (type === 'bpmn:Process') {
return 'processPanel'
}
return null
},
nodeName() {
if (this.element) {
const bizObj = this.element.businessObject
const type = bizObj?.eventDefinitions
? bizObj.eventDefinitions[0].$type
: bizObj.$type
return NodeName[type] || type
}
return ''
}
},
mounted() {
this.handleModeler()
},
methods: {
handleModeler() {
this.modeler.on('root.added', e => {
if (e.element.type === 'bpmn:Process') {
this.element = null
this.$nextTick().then(() => {
this.element = e.element
})
}
})
this.modeler.on('element.click', e => {
const { element } = e
console.log(element)
if (element.type === 'bpmn:Process') {
this.element = element
}
})
this.modeler.on('selection.changed', e => {
// hack
this.element = null
const element = e.newSelection[0]
if (element) {
this.$nextTick().then(() => {
this.element = element
})
}
})
},
/** 获取数据类型 */
dataType(data){
this.$emit('dataType', data)
}
}
}
</script>
<style lang="scss">
.property-panel {
padding: 20px 20px;
// reset element css
.el-form--label-top .el-form-item__label {
padding: 0;
}
.el-form-item {
margin-bottom: 6px;
}
.tab-table .el-form-item {
margin-bottom: 16px;
}
.node-name{
border-bottom: 1px solid #ccc;
padding: 0 0 10px 20px;
margin-bottom: 10px;
font-size: 16px;
font-weight: bold;
color: #444;
}
}
</style>

20
ruoyi-ui/src/components/Process/common/customTranslate.js

@ -0,0 +1,20 @@
import translations from '../lang/zh'
export default function customTranslate(template, replacements) {
replacements = replacements || {}
// Translate
template = translations[template] || template
// Replace
return template.replace(/{([^}]+)}/g, function(_, key) {
var str = replacements[key]
if (
translations[replacements[key]] !== null &&
translations[replacements[key]] !== 'undefined'
) {
str = translations[replacements[key]]
}
return str || '{' + key + '}'
})
}

24
ruoyi-ui/src/components/Process/common/mixinExecutionListener.js

@ -0,0 +1,24 @@
import executionListenerDialog from '../components/nodePanel/property/executionListener'
export default {
components: {
executionListenerDialog
},
data() {
return {
executionListenerLength: 0,
dialogName: null
}
},
methods: {
computedExecutionListenerLength() {
this.executionListenerLength = this.element.businessObject.extensionElements?.values?.length ?? 0
},
finishExecutionListener() {
if (this.dialogName === 'executionListenerDialog') {
this.computedExecutionListenerLength()
}
this.dialogName = ''
}
}
}

70
ruoyi-ui/src/components/Process/common/mixinPanel.js

@ -0,0 +1,70 @@
import xcrud from 'xcrud'
import golbalConfig from 'xcrud/package/common/config'
import showConfig from '../flowable/showConfig'
golbalConfig.set({
input: {
// size: 'mini'
},
select: {
// size: 'mini'
},
colorPicker: {
showAlpha: true
},
xform: {
form: {
labelWidth: 'auto'
// size: 'mini'
}
}
})
export default {
components: { xForm: xcrud.xForm },
props: {
modeler: {
type: Object,
required: true
},
element: {
type: Object,
required: true
},
categorys: {
type: Array,
default: () => []
}
},
watch: {
'formData.id': function(val) {
this.updateProperties({ id: val })
},
'formData.name': function(val) {
this.updateProperties({ name: val })
},
'formData.documentation': function(val) {
if (!val) {
this.updateProperties({ documentation: [] })
return
}
const documentationElement = this.modeler.get('moddle').create('bpmn:Documentation', { text: val })
this.updateProperties({ documentation: [documentationElement] })
}
},
methods: {
updateProperties(properties) {
const modeling = this.modeler.get('modeling')
modeling.updateProperties(this.element, properties)
}
},
computed: {
elementType() {
const bizObj = this.element.businessObject
return bizObj.eventDefinitions
? bizObj.eventDefinitions[0].$type
: bizObj.$type
},
showConfig() {
return showConfig[this.elementType] || {}
}
}
}

22
ruoyi-ui/src/components/Process/common/mixinXcrud.js

@ -0,0 +1,22 @@
import xcrud from 'xcrud'
import golbalConfig from 'xcrud/package/common/config'
golbalConfig.set({
input: {
// size: 'mini'
},
select: {
// size: 'mini'
},
colorPicker: {
showAlpha: true
},
xform: {
form: {
labelWidth: 'auto'
// size: 'mini'
}
}
})
export default {
components: { xForm: xcrud.xForm }
}

53
ruoyi-ui/src/components/Process/common/parseElement.js

@ -0,0 +1,53 @@
export function commonParse(element) {
const result = {
...element.businessObject,
...element.businessObject.$attrs
}
return formatJsonKeyValue(result)
}
export function formatJsonKeyValue(result) {
// 移除flowable前缀,格式化数组
for (const key in result) {
if (key.indexOf('flowable:') === 0) {
const newKey = key.replace('flowable:', '')
result[newKey] = result[key]
delete result[key]
}
}
result = documentationParse(result)
return result
}
export function documentationParse(obj) {
if ('documentation' in obj) {
let str = ''
obj.documentation.forEach(item => {
str += item.text
})
obj.documentation = str
}
return obj
}
export function conditionExpressionParse(obj) {
if ('conditionExpression' in obj) {
obj.conditionExpression = obj.conditionExpression.body
}
return obj
}
export function userTaskParse(obj) {
for (const key in obj) {
if (key === 'candidateUsers') {
obj.userType = 'candidateUsers'
obj[key] = obj[key]?.split(',') || []
} else if (key === 'candidateGroups') {
obj.userType = 'candidateGroups'
obj[key] = obj[key]?.split(',') || []
} else if (key === 'assignee') {
obj.userType = 'assignee'
}
}
return obj
}

24
ruoyi-ui/src/components/Process/components/custom/customContextPad.vue

@ -0,0 +1,24 @@
export default class CustomContextPad {
constructor(config, contextPad, create, elementFactory, injector, translate) {
this.create = create;
this.elementFactory = elementFactory;
this.translate = translate;
if (config.autoPlace !== false) {
this.autoPlace = injector.get('autoPlace', false);
}
contextPad.registerProvider(this); // contextPad
}
getContextPadEntries(element) {}
}
CustomContextPad.$inject = [
'config',
'contextPad',
'create',
'elementFactory',
'injector',
'translate'
];

81
ruoyi-ui/src/components/Process/components/nodePanel/gateway.vue

@ -0,0 +1,81 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import { commonParse } from '../../common/parseElement'
export default {
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
formData: {}
}
},
computed: {
formConfig() {
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'switch',
name: 'async',
label: '异步',
activeText: '是',
inactiveText: '否'
}
]
}
}
},
watch: {
'formData.async': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:async': val })
}
},
created() {
this.formData = commonParse(this.element)
}
}
</script>
<style>
</style>

113
ruoyi-ui/src/components/Process/components/nodePanel/process.vue

@ -0,0 +1,113 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
<template #signal>
<el-badge :value="signalLength">
<el-button size="small" @click="dialogName = 'signalDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
<signalDialog
v-if="dialogName === 'signalDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import signalDialog from './property/signal'
import { commonParse } from '../../common/parseElement'
export default {
components: {
signalDialog
},
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
signalLength: 0,
formData: {}
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'select',
name: 'processCategory',
label: '流程分类',
dic: { data: _this.categorys, label: 'dictLabel', value: 'dictValue' }
},
{
xType: 'input',
name: 'id',
label: '流程标识key',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '流程名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'slot',
name: 'signal',
label: '信号定义'
}
]
}
}
},
watch: {
'formData.processCategory': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:processCategory': val })
}
},
created() {
this.formData = commonParse(this.element)
},
methods: {
computedSignalLength() {
this.signalLength = this.element.businessObject.extensionElements?.values?.length ?? 0
},
finishSignal() {
if (this.dialogName === 'signalDialog') {
this.computedSignalLength()
}
this.dialogName = ''
}
}
}
</script>
<style>
</style>

194
ruoyi-ui/src/components/Process/components/nodePanel/property/executionListener.vue

@ -0,0 +1,194 @@
<template>
<div>
<el-dialog
title="执行监听器"
:visible.sync="dialogVisible"
width="900px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close')"
>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #params="scope">
<el-badge :value="scope.row.params ? scope.row.params.length : 0" type="primary">
<el-button size="small" @click="configParam(scope.$index)">配置</el-button>
</el-badge>
</template>
</x-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
</el-dialog>
<listenerParam v-if="showParamDialog" :value="formData.executionListener[nowIndex].params" @close="finishConfigParam" />
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
import listenerParam from './listenerParam'
export default {
components: { listenerParam },
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
showParamDialog: false,
nowIndex: null,
formData: {
executionListener: []
}
}
},
computed: {
formConfig() {
// const _this = this
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '执行监听器',
name: 'executionListener',
column: [
{
label: '事件',
name: 'event',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: 'start', value: 'start' },
{ label: 'end', value: 'end' },
{ label: 'take', value: 'take' }
]
},
{
label: '类型',
name: 'type',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '类', value: 'class' },
{ label: '表达式', value: 'expression' },
{ label: '委托表达式', value: 'delegateExpression' }
],
tooltip: `类:示例 com.company.MyCustomListener,自定义类必须实现 org.flowable.engine.delegate.TaskListener 接口 <br />
表达式示例 \${myObject.callMethod(task, task.eventName)} <br />
委托表达式示例 \${myListenerSpringBean} springBean 需要实现 org.flowable.engine.delegate.TaskListener 接口
`
},
{
label: 'java 类名',
name: 'className',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
},
{
xType: 'slot',
label: '参数',
width: 120,
slot: true,
name: 'params'
}
]
}
]
}
]
}
}
},
mounted() {
this.formData.executionListener = this.element.businessObject.extensionElements?.values
.filter(item => item.$type === 'flowable:ExecutionListener')
.map(item => {
let type
if ('class' in item) type = 'class'
if ('expression' in item) type = 'expression'
if ('delegateExpression' in item) type = 'delegateExpression'
return {
event: item.event,
type: type,
className: item[type],
params: item.fields?.map(field => {
let fieldType
if ('stringValue' in field) fieldType = 'stringValue'
if ('expression' in field) fieldType = 'expression'
return {
name: field.name,
type: fieldType,
value: field[fieldType]
}
}) ?? []
}
}) ?? []
},
methods: {
configParam(index) {
this.nowIndex = index
const nowObj = this.formData.executionListener[index]
if (!nowObj.params) {
nowObj.params = []
}
this.showParamDialog = true
},
finishConfigParam(param) {
this.showParamDialog = false
// hack
const cache = this.formData.executionListener[this.nowIndex]
cache.params = param
this.$set(this.formData.executionListener[this.nowIndex], this.nowIndex, cache)
this.nowIndex = null
},
updateElement() {
if (this.formData.executionListener?.length) {
let extensionElements = this.element.businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
}
//
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener') ?? []
this.formData.executionListener.forEach(item => {
const executionListener = this.modeler.get('moddle').create('flowable:ExecutionListener')
executionListener['event'] = item.event
executionListener[item.type] = item.className
if (item.params && item.params.length) {
item.params.forEach(field => {
const fieldElement = this.modeler.get('moddle').create('flowable:Field')
fieldElement['name'] = field.name
fieldElement[field.type] = field.value
// flowable.json stringexpressionStringhack
// const valueElement = this.modeler.get('moddle').create(`flowable:${field.type}`, { body: field.value })
// fieldElement[field.type] = valueElement
executionListener.get('fields').push(fieldElement)
})
}
extensionElements.get('values').push(executionListener)
})
this.updateProperties({ extensionElements: extensionElements })
} else {
const extensionElements = this.element.businessObject[`extensionElements`]
if (extensionElements) {
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener') ?? []
}
}
},
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e))
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

96
ruoyi-ui/src/components/Process/components/nodePanel/property/listenerParam.vue

@ -0,0 +1,96 @@
<template>
<div>
<el-dialog
title="监听器参数"
:visible.sync="dialogVisible"
width="700px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close', formData.paramList)"
>
<x-form ref="xForm" v-model="formData" :config="formConfig" />
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import mixinXcrud from '../../../common/mixinXcrud'
export default {
mixins: [mixinXcrud],
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
dialogVisible: true,
formData: {
paramList: this.value
}
}
},
computed: {
formConfig() {
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '监听器参数',
name: 'paramList',
column: [
{
label: '类型',
name: 'type',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '字符串', value: 'stringValue' },
{ label: '表达式', value: 'expression' }
]
},
{
label: '名称',
name: 'name',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'input'
},
{
label: '值',
name: 'value',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
}
]
}
]
}
]
}
}
},
methods: {
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.dialogVisible = false
}).catch(e => console.error(e))
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

117
ruoyi-ui/src/components/Process/components/nodePanel/property/multiInstance.vue

@ -0,0 +1,117 @@
<template>
<div>
<el-dialog
title="多实例配置"
:visible.sync="dialogVisible"
width="500px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
class="muti-instance"
@closed="$emit('close')"
>
<el-alert
type="info"
:closable="false"
show-icon
style="margin-bottom: 20px"
>
<template #title>
按照BPMN2.0规范的要求用于为每个实例创建执行的父执行会提供下列变量:<br>
nrOfInstances实例总数<br>
nrOfActiveInstances当前活动的即未完成的实例数量对于顺序多实例这个值总为1<br>
nrOfCompletedInstances已完成的实例数量<br>
loopCounter给定实例在for-each循环中的index<br>
</template>
</el-alert>
<x-form ref="xForm" v-model="formData" :config="formConfig" />
</el-dialog>
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
import { formatJsonKeyValue } from '../../../common/parseElement'
export default {
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
formData: {}
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'input',
name: 'collection',
label: '集合',
tooltip: '属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,<br />不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,<br />这个字符串都会被当做变量名,并从流程变量中用于获取实际的集合。'
},
{
xType: 'input',
name: 'elementVariable',
label: '元素变量',
tooltip: '每创建一个用户任务前,先以该元素变量为label,集合中的一项为value,<br />创建(局部)流程变量,该局部流程变量被用于指派用户任务。<br />一般来说,该字符串应与指定人员变量相同。'
},
{
xType: 'radio',
name: 'isSequential',
label: '执行方式',
dic: [{ label: '串行', value: true }, { label: '并行', value: false }]
},
{
xType: 'input',
name: 'completionCondition',
label: '完成条件',
tooltip: '多实例活动在所有实例都完成时结束,然而也可以指定一个表达式,在每个实例<br />结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例<br />活动,继续执行流程。例如 ${nrOfCompletedInstances/nrOfInstances >= 0.6 },<br />表示当任务完成60%时,该节点就算完成'
}
],
operate: [
{ text: '确定', show: true, click: _this.save },
{ text: '清空', show: true, click: () => { _this.formData = {} } }
]
}
}
},
mounted() {
const cache = JSON.parse(JSON.stringify(this.element.businessObject.loopCharacteristics ?? {}))
cache.completionCondition = cache.completionCondition?.body
this.formData = formatJsonKeyValue(cache)
},
methods: {
updateElement() {
if (this.formData.isSequential !== null && this.formData.isSequential !== undefined) {
let loopCharacteristics = this.element.businessObject.get('loopCharacteristics')
if (!loopCharacteristics) {
loopCharacteristics = this.modeler.get('moddle').create('bpmn:MultiInstanceLoopCharacteristics')
}
loopCharacteristics['isSequential'] = this.formData.isSequential
loopCharacteristics['collection'] = this.formData.collection
loopCharacteristics['elementVariable'] = this.formData.elementVariable
if (this.formData.completionCondition) {
const completionCondition = this.modeler.get('moddle').create('bpmn:Expression', { body: this.formData.completionCondition })
loopCharacteristics['completionCondition'] = completionCondition
}
this.updateProperties({ loopCharacteristics: loopCharacteristics })
} else {
delete this.element.businessObject.loopCharacteristics
}
},
save() {
this.updateElement()
this.dialogVisible = false
}
}
}
</script>
<style>
.muti-instance .el-form-item {
margin-bottom: 22px;
}
</style>

124
ruoyi-ui/src/components/Process/components/nodePanel/property/signal.vue

@ -0,0 +1,124 @@
<template>
<div>
<el-dialog
title="信号定义"
:visible.sync="dialogVisible"
width="700px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close')"
>
<x-form ref="xForm" v-model="formData" :config="formConfig" />
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
export default {
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
formData: {
signal: []
}
}
},
computed: {
formConfig() {
// const _this = this
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '信号定义',
name: 'signal',
column: [
{
label: 'scope',
name: 'scope',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '全局', value: 'start' },
{ label: '流程实例', value: 'end' }
]
},
{
label: 'id',
name: 'id',
width: 200,
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }],
xType: 'input'
},
{
label: '名称',
name: 'name',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
}
]
}
]
}
]
}
}
},
mounted() {
// this.formData.signal = this.element.businessObject.extensionElements?.values.map(item => {
// let type
// if ('class' in item.$attrs) type = 'class'
// if ('expression' in item.$attrs) type = 'expression'
// if ('delegateExpression' in item.$attrs) type = 'delegateExpression'
// return {
// event: item.$attrs.event,
// type: type,
// className: item.$attrs[type]
// }
// }) ?? []
},
methods: {
updateElement() {
if (this.formData.signal?.length) {
let extensionElements = this.element.businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = this.modeler.get('moddle').create('bpmn:signal')
}
const length = extensionElements.get('values').length
for (let i = 0; i < length; i++) {
//
extensionElements.get('values').pop()
}
this.updateProperties({ extensionElements: extensionElements })
} else {
const extensionElements = this.element.businessObject[`extensionElements`]
if (extensionElements) {
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener')
}
}
},
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e))
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

196
ruoyi-ui/src/components/Process/components/nodePanel/property/taskListener.vue

@ -0,0 +1,196 @@
<template>
<div>
<el-dialog
title="任务监听器"
:visible.sync="dialogVisible"
width="900px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close')"
>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #params="scope">
<el-badge :value="scope.row.params ? scope.row.params.length : 0" type="primary">
<el-button size="small" @click="configParam(scope.$index)">配置</el-button>
</el-badge>
</template>
</x-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
</el-dialog>
<listenerParam v-if="showParamDialog" :value="formData.taskListener[nowIndex].params" @close="finishConfigParam" />
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
import listenerParam from './listenerParam'
export default {
components: { listenerParam },
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
showParamDialog: false,
nowIndex: null,
formData: {
taskListener: []
}
}
},
computed: {
formConfig() {
// const _this = this
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '任务监听器',
name: 'taskListener',
column: [
{
label: '事件',
name: 'event',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: 'create', value: 'create' },
{ label: 'assignment', value: 'assignment' },
{ label: 'complete', value: 'complete' },
{ label: 'delete', value: 'delete' }
],
tooltip: `create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。<br />
assignment指派当任务已经指派给某人时触发请注意当流程执行到达用户任务时在触发create事件之前会首先触发assignment事件<br />
complete完成当任务已经完成从运行时数据中删除前触发<br />
delete删除在任务即将被删除前触发请注意任务由completeTask正常完成时也会触发
`
},
{
label: '类型',
name: 'type',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '类', value: 'class' },
{ label: '表达式', value: 'expression' },
{ label: '委托表达式', value: 'delegateExpression' }
]
},
{
label: 'java 类名',
name: 'className',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
},
{
xType: 'slot',
label: '参数',
width: 120,
slot: true,
name: 'params'
}
]
}
]
}
]
}
}
},
mounted() {
this.formData.taskListener = this.element.businessObject.extensionElements?.values
.filter(item => item.$type === 'flowable:TaskListener')
.map(item => {
let type
if ('class' in item) type = 'class'
if ('expression' in item) type = 'expression'
if ('delegateExpression' in item) type = 'delegateExpression'
return {
event: item.event,
type: type,
className: item[type],
params: item.fields?.map(field => {
let fieldType
if ('stringValue' in field) fieldType = 'stringValue'
if ('expression' in field) fieldType = 'expression'
return {
name: field.name,
type: fieldType,
value: field[fieldType]
}
}) ?? []
}
}) ?? []
},
methods: {
configParam(index) {
this.nowIndex = index
const nowObj = this.formData.taskListener[index]
if (!nowObj.params) {
nowObj.params = []
}
this.showParamDialog = true
},
finishConfigParam(param) {
this.showParamDialog = false
// hack
const cache = this.formData.taskListener[this.nowIndex]
cache.params = param
this.$set(this.formData.taskListener[this.nowIndex], this.nowIndex, cache)
this.nowIndex = null
},
updateElement() {
if (this.formData.taskListener?.length) {
let extensionElements = this.element.businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
}
//
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:TaskListener') ?? []
this.formData.taskListener.forEach(item => {
const taskListener = this.modeler.get('moddle').create('flowable:TaskListener')
taskListener['event'] = item.event
taskListener[item.type] = item.className
if (item.params && item.params.length) {
item.params.forEach(field => {
const fieldElement = this.modeler.get('moddle').create('flowable:Field')
fieldElement['name'] = field.name
fieldElement[field.type] = field.value
// flowable.json stringexpressionStringhack
// const valueElement = this.modeler.get('moddle').create(`flowable:${field.type}`, { body: field.value })
// fieldElement[field.type] = valueElement
taskListener.get('fields').push(fieldElement)
})
}
extensionElements.get('values').push(taskListener)
})
this.updateProperties({ extensionElements: extensionElements })
} else {
const extensionElements = this.element.businessObject[`extensionElements`]
if (extensionElements) {
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:TaskListener') ?? []
}
}
},
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e))
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

92
ruoyi-ui/src/components/Process/components/nodePanel/sequenceFlow.vue

@ -0,0 +1,92 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import { commonParse, conditionExpressionParse } from '../../common/parseElement'
export default {
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
formData: {}
}
},
computed: {
formConfig() {
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'input',
name: 'conditionExpression',
label: '跳转条件'
},
{
xType: 'input',
name: 'skipExpression',
label: '跳过表达式'
}
]
}
}
},
watch: {
'formData.conditionExpression': function(val) {
if (val) {
const newCondition = this.modeler.get('moddle').create('bpmn:FormalExpression', { body: val })
this.updateProperties({ conditionExpression: newCondition })
} else {
this.updateProperties({ conditionExpression: null })
}
},
'formData.skipExpression': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:skipExpression': val })
}
},
created() {
let cache = commonParse(this.element)
cache = conditionExpressionParse(cache)
this.formData = cache
}
}
</script>
<style></style>

94
ruoyi-ui/src/components/Process/components/nodePanel/startEnd.vue

@ -0,0 +1,94 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import { commonParse } from '../../common/parseElement'
export default {
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
formData: {}
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'input',
name: 'initiator',
label: '发起人',
show: !!_this.showConfig.initiator
},
{
xType: 'input',
name: 'formKey',
label: '表单标识key',
show: !!_this.showConfig.formKey
}
]
}
}
},
watch: {
'formData.initiator': function(val) {
if (val === '') val = null
//
// if (val === '') val = 'INITIATOR'
this.updateProperties({ 'flowable:initiator': val })
},
'formData.formKey': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:formKey': val })
}
},
created() {
// this.updateProperties({ 'flowable:initiator': 'INITIATOR' })
this.formData = commonParse(this.element)
}
}
</script>
<style>
</style>

426
ruoyi-ui/src/components/Process/components/nodePanel/task.vue

@ -0,0 +1,426 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
<template #taskListener>
<el-badge :value="taskListenerLength">
<el-button size="small" @click="dialogName = 'taskListenerDialog'">编辑</el-button>
</el-badge>
</template>
<template #multiInstance>
<el-badge :is-dot="hasMultiInstance">
<el-button size="small" @click="dialogName = 'multiInstanceDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
<taskListenerDialog
v-if="dialogName === 'taskListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishTaskListener"
/>
<multiInstanceDialog
v-if="dialogName === 'multiInstanceDialog'"
:element="element"
:modeler="modeler"
@close="finishMultiInstance"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import executionListenerDialog from './property/executionListener'
import taskListenerDialog from './property/taskListener'
import multiInstanceDialog from './property/multiInstance'
import { commonParse, userTaskParse } from '../../common/parseElement'
export default {
components: {
executionListenerDialog,
taskListenerDialog,
multiInstanceDialog
},
mixins: [mixinPanel],
props: {
users: {
type: Array,
required: true
},
groups: {
type: Array,
required: true
}
},
data() {
return {
userTypeOption: [
{ label: '指定人员', value: 'assignee' },
{ label: '候选人员', value: 'candidateUsers' },
{ label: '候选组', value: 'candidateGroups' }
],
dataTypeOption: [
{ label: '固定', value: 'fixed' },
{ label: '动态', value: 'dynamic' }
],
dialogName: '',
executionListenerLength: 0,
taskListenerLength: 0,
hasMultiInstance: false,
formData: {}
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称',
rules: [{ required: true, message: '节点名称不能为空' }]
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'slot',
name: 'taskListener',
label: '任务监听器',
show: !!_this.showConfig.taskListener
},
{
xType: 'select',
name: 'userType',
label: '人员类型',
dic: _this.userTypeOption,
show: !!_this.showConfig.userType
},
{
xType: 'radio',
name: 'dataType',
label: '指定方式',
dic: _this.dataTypeOption,
show: !!_this.showConfig.dataType,
rules: [{ required: true, message: '请指定方式' }]
},
// {
// xType: 'input',
// name: 'assigneeFixed',
// label: '()',
// show: !!_this.showConfig.assigneeFixed && _this.formData.userType === 'assignee' && _this.formData.dataType === 'fixed'
// },
// {
// xType: 'input',
// name: 'candidateUsersFixed',
// label: '()',
// show: !!_this.showConfig.candidateUsersFixed && _this.formData.userType === 'candidateUsers' && _this.formData.dataType === 'fixed'
// },
// {
// xType: 'input',
// name: 'candidateGroupsFixed',
// label: '()',
// show: !!_this.showConfig.candidateGroupsFixed && _this.formData.userType === 'candidateGroups' && _this.formData.dataType === 'fixed'
// },
{
xType: 'select',
name: 'assignee',
label: '指定人员',
allowCreate: true,
filterable: true,
dic: { data: _this.users, label: 'nickName', value: 'userId' },
show: !!_this.showConfig.assignee && _this.formData.userType === 'assignee'
},
{
xType: 'select',
name: 'candidateUsers',
label: '候选人员',
multiple: true,
allowCreate: true,
filterable: true,
dic: { data: _this.users, label: 'nickName', value: 'userId' },
show: !!_this.showConfig.candidateUsers && _this.formData.userType === 'candidateUsers'
},
{
xType: 'select',
name: 'candidateGroups',
label: '候选组',
multiple: true,
allowCreate: true,
filterable: true,
dic: { data: _this.groups, label: 'roleName', value: 'roleId' },
show: !!_this.showConfig.candidateGroups && _this.formData.userType === 'candidateGroups'
},
{
xType: 'slot',
name: 'multiInstance',
label: '多实例'
},
{
xType: 'switch',
name: 'async',
label: '异步',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.async
},
{
xType: 'input',
name: 'priority',
label: '优先级',
show: !!_this.showConfig.priority
},
{
xType: 'input',
name: 'formKey',
label: '表单标识key',
show: !!_this.showConfig.formKey
},
{
xType: 'input',
name: 'skipExpression',
label: '跳过表达式',
show: !!_this.showConfig.skipExpression
},
{
xType: 'switch',
name: 'isForCompensation',
label: '是否为补偿',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.isForCompensation
},
{
xType: 'switch',
name: 'triggerable',
label: '服务任务可触发',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.triggerable
},
{
xType: 'switch',
name: 'autoStoreVariables',
label: '自动存储变量',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.autoStoreVariables
},
{
xType: 'input',
name: 'ruleVariablesInput',
label: '输入变量',
show: !!_this.showConfig.ruleVariablesInput
},
{
xType: 'input',
name: 'rules',
label: '规则',
show: !!_this.showConfig.rules
},
{
xType: 'input',
name: 'resultVariable',
label: '结果变量',
show: !!_this.showConfig.resultVariable
},
{
xType: 'switch',
name: 'exclude',
label: '排除',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.exclude
},
{
xType: 'input',
name: 'class',
label: '类',
show: !!_this.showConfig.class
},
{
xType: 'datePicker',
type: 'datetime',
name: 'dueDate',
label: '到期时间',
show: !!_this.showConfig.dueDate
}
]
}
}
},
watch: {
'formData.userType': function(val, oldVal) {
if (oldVal) {
const types = ['assignee', 'candidateUsers', 'candidateGroups']
types.forEach(type => {
delete this.element.businessObject.$attrs[`flowable:${type}`]
delete this.formData[type]
})
}
},
//
'formData.dataType': function(val) {
const that = this
this.updateProperties({'flowable:dataType': val})
if (val === 'dynamic') {
this.updateProperties({'flowable:userType': that.formData.userType})
}
//
const types = ['assignee', 'candidateUsers', 'candidateGroups']
types.forEach(type => {
delete this.element.businessObject.$attrs[`flowable:${type}`]
delete this.formData[type]
})
//
const params = {
dataType: val,
userType: this.formData.userType
}
this.$emit('dataType', params)
},
'formData.assignee': function(val) {
if (this.formData.userType !== 'assignee') {
delete this.element.businessObject.$attrs[`flowable:assignee`]
return
}
this.updateProperties({'flowable:assignee': val})
},
'formData.candidateUsers': function(val) {
if (this.formData.userType !== 'candidateUsers') {
delete this.element.businessObject.$attrs[`flowable:candidateUsers`]
return
}
this.updateProperties({'flowable:candidateUsers': val?.join(',')})
},
'formData.candidateGroups': function(val) {
if (this.formData.userType !== 'candidateGroups') {
delete this.element.businessObject.$attrs[`flowable:candidateGroups`]
return
}
this.updateProperties({'flowable:candidateGroups': val?.join(',')})
},
'formData.async': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:async': val })
},
'formData.dueDate': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:dueDate': val })
},
'formData.formKey': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:formKey': val })
},
'formData.priority': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:priority': val })
},
'formData.skipExpression': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:skipExpression': val })
},
'formData.isForCompensation': function(val) {
if (val === '') val = null
this.updateProperties({ 'isForCompensation': val })
},
'formData.triggerable': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:triggerable': val })
},
'formData.class': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:class': val })
},
'formData.autoStoreVariables': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:autoStoreVariables': val })
},
'formData.exclude': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:exclude': val })
},
'formData.ruleVariablesInput': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:ruleVariablesInput': val })
},
'formData.rules': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:rules': val })
},
'formData.resultVariable': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:resultVariable': val })
}
},
created() {
let cache = commonParse(this.element)
cache = userTaskParse(cache)
this.formData = cache
this.computedExecutionListenerLength()
this.computedTaskListenerLength()
this.computedHasMultiInstance()
},
methods: {
computedExecutionListenerLength() {
this.executionListenerLength = this.element.businessObject.extensionElements?.values
?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
},
computedTaskListenerLength() {
this.taskListenerLength = this.element.businessObject.extensionElements?.values
?.filter(item => item.$type === 'flowable:TaskListener').length ?? 0
},
computedHasMultiInstance() {
if (this.element.businessObject.loopCharacteristics) {
this.hasMultiInstance = true
} else {
this.hasMultiInstance = false
}
},
finishExecutionListener() {
if (this.dialogName === 'executionListenerDialog') {
this.computedExecutionListenerLength()
}
this.dialogName = ''
},
finishTaskListener() {
if (this.dialogName === 'taskListenerDialog') {
this.computedTaskListenerLength()
}
this.dialogName = ''
},
finishMultiInstance() {
if (this.dialogName === 'multiInstanceDialog') {
this.computedHasMultiInstance()
}
this.dialogName = ''
}
}
}
</script>
<style></style>

1194
ruoyi-ui/src/components/Process/flowable/flowable.json

File diff suppressed because it is too large

24
ruoyi-ui/src/components/Process/flowable/init.js

@ -0,0 +1,24 @@
function randomStr() {
return Math.random().toString(36).slice(-8)
}
export default function() {
return `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/processdef">
<process id="process_${randomStr()}" name="name_${randomStr()}">
<startEvent id="startNode1" name="开始" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_flow">
<bpmndi:BPMNPlane id="BPMNPlane_flow" bpmnElement="T-2d89e7a3-ba79-4abd-9f64-ea59621c258c">
<bpmndi:BPMNShape id="BPMNShape_startNode1" bpmnElement="startNode1" bioc:stroke="">
<omgdc:Bounds x="240" y="200" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="242" y="237" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
`
}

55
ruoyi-ui/src/components/Process/flowable/showConfig.js

@ -0,0 +1,55 @@
export default {
'bpmn:EndEvent': {},
'bpmn:StartEvent': {
initiator: true,
formKey: true
},
'bpmn:UserTask': {
userType: true,
dataType: true,
assignee: true,
candidateUsers: true,
candidateGroups: true,
// assigneeFixed: true,
// candidateUsersFixed: true,
// candidateGroupsFixed: true,
async: true,
priority: true,
formKey: true,
skipExpression: true,
dueDate: true,
taskListener: true
},
'bpmn:ServiceTask': {
async: true,
skipExpression: true,
isForCompensation: true,
triggerable: true,
class: true
},
'bpmn:ScriptTask': {
async: true,
isForCompensation: true,
autoStoreVariables: true
},
'bpmn:ManualTask': {
async: true,
isForCompensation: true
},
'bpmn:ReceiveTask': {
async: true,
isForCompensation: true
},
'bpmn:SendTask': {
async: true,
isForCompensation: true
},
'bpmn:BusinessRuleTask': {
async: true,
isForCompensation: true,
ruleVariablesInput: true,
rules: true,
resultVariable: true,
exclude: true
}
}

5
ruoyi-ui/src/components/Process/index.js

@ -0,0 +1,5 @@
import workflowBpmnModeler from './index.vue'
workflowBpmnModeler.install = Vue => Vue.component(workflowBpmnModeler.name, workflowBpmnModeler) // 给组件配置install方法
export default workflowBpmnModeler

463
ruoyi-ui/src/components/Process/index.vue

@ -0,0 +1,463 @@
<template>
<div v-loading="isView" class="flow-containers" :class="{ 'view-mode': isView }">
<el-container style="height: 100%">
<el-header style="border-bottom: 1px solid rgb(218 218 218);height: auto;">
<div style="display: flex; padding: 10px 0px; justify-content: space-between;">
<div>
<el-upload action="" :before-upload="openBpmn" style="margin-right: 10px; display:inline-block;">
<el-tooltip effect="dark" content="加载xml" placement="bottom">
<el-button size="mini" icon="el-icon-folder-opened" />
</el-tooltip>
</el-upload>
<el-tooltip effect="dark" content="新建" placement="bottom">
<el-button size="mini" icon="el-icon-circle-plus" @click="newDiagram" />
</el-tooltip>
<el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
<el-button size="mini" icon="el-icon-rank" @click="fitViewport" />
</el-tooltip>
<el-tooltip effect="dark" content="放大" placement="bottom">
<el-button size="mini" icon="el-icon-zoom-in" @click="zoomViewport(true)" />
</el-tooltip>
<el-tooltip effect="dark" content="缩小" placement="bottom">
<el-button size="mini" icon="el-icon-zoom-out" @click="zoomViewport(false)" />
</el-tooltip>
<el-tooltip effect="dark" content="后退" placement="bottom">
<el-button size="mini" icon="el-icon-back" @click="modeler.get('commandStack').undo()" />
</el-tooltip>
<el-tooltip effect="dark" content="前进" placement="bottom">
<el-button size="mini" icon="el-icon-right" @click="modeler.get('commandStack').redo()" />
</el-tooltip>
</div>
<div>
<el-button size="mini" icon="el-icon-view" @click="showXML">查看xml</el-button>
<el-button size="mini" icon="el-icon-download" @click="saveXML(true)">下载xml</el-button>
<el-button size="mini" icon="el-icon-picture" @click="saveImg('svg', true)">下载svg</el-button>
<el-button size="mini" type="primary" @click="save">保存模型</el-button>
</div>
</div>
</el-header>
<el-container style="align-items: stretch">
<el-main style="padding: 0;">
<div ref="canvas" class="canvas" />
</el-main>
<el-aside style="width: 400px; min-height: 650px; background-color: #f0f2f5">
<panel v-if="modeler" :modeler="modeler" :users="users" :groups="groups" :categorys="categorys" @dataType="dataType" />
</el-aside>
</el-container>
</el-container>
</div>
</template>
<script>
//
import customTranslate from './common/customTranslate'
import Modeler from 'bpmn-js/lib/Modeler'
import panel from './PropertyPanel'
import BpmData from './BpmData'
import getInitStr from './flowable/init'
// flowable
import flowableModdle from './flowable/flowable.json'
export default {
name: 'WorkflowBpmnModeler',
components: {
panel
},
props: {
xml: {
type: String,
default: ''
},
users: {
type: Array,
default: () => []
},
groups: {
type: Array,
default: () => []
},
categorys: {
type: Array,
default: () => []
},
isView: {
type: Boolean,
default: false
},
taskList: {
type: Array,
default: () => []
}
},
data() {
return {
modeler: null,
// taskList: [],
zoom: 1
}
},
watch: {
xml: function(val) {
if (val) {
this.createNewDiagram(val)
}
}
},
mounted() {
//
this.modeler = new Modeler({
container: this.$refs.canvas,
additionalModules: [
{
translate: ['value', customTranslate]
}
],
moddleExtensions: {
flowable: flowableModdle
}
})
//
if (!this.xml) {
this.newDiagram()
} else {
this.createNewDiagram(this.xml)
}
},
methods: {
newDiagram() {
this.createNewDiagram(getInitStr())
},
//
fitViewport() {
this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
const bbox = document.querySelector('.flow-containers .viewport').getBBox()
const currentViewbox = this.modeler.get('canvas').viewbox()
const elementMid = {
x: bbox.x + bbox.width / 2 - 65,
y: bbox.y + bbox.height / 2
}
this.modeler.get('canvas').viewbox({
x: elementMid.x - currentViewbox.width / 2,
y: elementMid.y - currentViewbox.height / 2,
width: currentViewbox.width,
height: currentViewbox.height
})
this.zoom = bbox.width / currentViewbox.width * 1.8
},
//
zoomViewport(zoomIn = true) {
this.zoom = this.modeler.get('canvas').zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
this.modeler.get('canvas').zoom(this.zoom)
},
async createNewDiagram(data) {
//
// data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
return str.replace(/</g, '&lt;')
})
try {
await this.modeler.importXML(data)
this.adjustPalette()
this.fitViewport()
if (this.taskList !==undefined && this.taskList.length > 0 ) {
this.fillColor()
}
} catch (err) {
console.error(err.message, err.warnings)
}
},
//
adjustPalette() {
try {
// bpmn
const canvas = this.$refs.canvas
const djsPalette = canvas.children[0].children[1].children[4]
const djsPalStyle = {
width: '130px',
padding: '5px',
background: 'white',
left: '20px',
borderRadius: 0
}
for (var key in djsPalStyle) {
djsPalette.style[key] = djsPalStyle[key]
}
const palette = djsPalette.children[0]
const allGroups = palette.children
allGroups[0].style['display'] = 'none'
//
for (var gKey in allGroups) {
const group = allGroups[gKey]
for (var cKey in group.children) {
const control = group.children[cKey]
const controlStyle = {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
width: '100%',
padding: '5px'
}
if (
control.className &&
control.dataset &&
control.className.indexOf('entry') !== -1
) {
const controlProps = new BpmData().getControl(
control.dataset.action
)
control.innerHTML = `<div style='font-size: 14px;font-weight:500;margin-left:15px;'>${
controlProps['title']
}</div>`
for (var csKey in controlStyle) {
control.style[csKey] = controlStyle[csKey]
}
}
}
}
} catch (e) {
console.log(e)
}
},
fillColor() {
const canvas = this.modeler.get('canvas')
this.modeler._definitions.rootElements[0].flowElements.forEach(n => {
const completeTask = this.taskList.find(m => m.key === n.id)
const todoTask = this.taskList.find(m => !m.completed)
const endTask = this.taskList[this.taskList.length - 1]
if (n.$type === 'bpmn:UserTask') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
}else {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
}
})
}
}
//
else if (n.$type === 'bpmn:ExclusiveGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
//
else if (n.$type === 'bpmn:ParallelGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
debugger
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
else if (n.$type === 'bpmn:StartEvent') {
n.outgoing.forEach(nn => {
const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (completeTask) {
canvas.addMarker(nn.id, 'highlight')
canvas.addMarker(n.id, 'highlight')
return
}
})
}
else if (n.$type === 'bpmn:EndEvent') {
if (endTask.key === n.id && endTask.completed) {
canvas.addMarker(n.id, 'highlight')
return
}
}
})
},
// api
getProcess() {
const element = this.getProcessElement()
return {
id: element.id,
name: element.name,
category: element.$attrs['flowable:processCategory']
}
},
getProcessElement() {
const rootElements = this.modeler.getDefinitions().rootElements
for (let i = 0; i < rootElements.length; i++) {
if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]
}
},
async saveXML(download = false) {
try {
const { xml } = await this.modeler.saveXML({ format: true })
if (download) {
this.downloadFile(`${this.getProcessElement().name}.bpmn20.xml`, xml, 'application/xml')
}
return xml
} catch (err) {
console.log(err)
}
},
async showXML() {
try {
const { xml } = await this.modeler.saveXML({ format: true })
debugger
this.$emit('showXML',xml)
} catch (err) {
console.log(err)
}
},
async saveImg(type = 'svg', download = false) {
try {
const { svg } = await this.modeler.saveSVG({ format: true })
if (download) {
this.downloadFile(this.getProcessElement().name, svg, 'image/svg+xml')
}
return svg
} catch (err) {
console.log(err)
}
},
async save() {
const process = this.getProcess()
const xml = await this.saveXML()
const svg = await this.saveImg()
const result = { process, xml, svg }
this.$emit('save', result)
window.parent.postMessage(result, '*')
},
openBpmn(file) {
const reader = new FileReader()
reader.readAsText(file, 'utf-8')
reader.onload = () => {
this.createNewDiagram(reader.result)
}
return false
},
downloadFile(filename, data, type) {
var a = document.createElement('a')
var url = window.URL.createObjectURL(new Blob([data], { type: type }))
a.href = url
a.download = filename
a.click()
window.URL.revokeObjectURL(url)
},
/** 获取数据类型 */
dataType(data){
this.$emit('dataType', data)
}
}
}
</script>
<style lang="scss">
/*左边工具栏以及编辑节点的样式*/
@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";
.view-mode {
.el-header, .el-aside, .djs-palette, .bjs-powered-by {
display: none;
}
.el-loading-mask {
background-color: initial;
}
.el-loading-spinner {
display: none;
}
}
.flow-containers {
// background-color: #ffffff;
width: 100%;
height: 100%;
.canvas {
width: 100%;
height: 100%;
}
.panel {
position: absolute;
right: 0;
top: 50px;
width: 300px;
}
.load {
margin-right: 10px;
}
.el-form-item__label{
font-size: 13px;
}
.djs-palette{
left: 0px!important;
top: 0px;
border-top: none;
}
.djs-container svg {
min-height: 650px;
}
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: green !important;
}
.highlight.djs-shape .djs-visual > path {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: green !important;
}
// .djs-connection > .djs-visual > path {
// stroke: orange !important;
// stroke-dasharray: 4px !important;
// fill-opacity: 0.2 !important;
// }
// .djs-shape .djs-visual > :nth-child(1) {
// fill: orange !important;
// stroke: orange !important;
// stroke-dasharray: 4px !important;
// fill-opacity: 0.2 !important;
// }
.highlight-todo.djs-connection > .djs-visual > path {
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
fill: orange !important;
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
}
</style>

227
ruoyi-ui/src/components/Process/lang/zh.js

@ -0,0 +1,227 @@
export default {
// Labels
'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 Gateway': '创建网关',
'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': '默认值',
'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': '变量'
}
export const NodeName = {
'bpmn:Process': '流程',
'bpmn:StartEvent': '开始事件',
'bpmn:IntermediateThrowEvent': '中间事件',
'bpmn:Task': '任务',
'bpmn:SendTask': '发送任务',
'bpmn:ReceiveTask': '接收任务',
'bpmn:UserTask': '用户任务',
'bpmn:ManualTask': '手工任务',
'bpmn:BusinessRuleTask': '业务规则任务',
'bpmn:ServiceTask': '服务任务',
'bpmn:ScriptTask': '脚本任务',
'bpmn:EndEvent': '结束事件',
'bpmn:SequenceFlow': '流程线',
'bpmn:ExclusiveGateway': '互斥网关',
'bpmn:ParallelGateway': '并行网关',
'bpmn:InclusiveGateway': '相容网关',
'bpmn:ComplexGateway': '复杂网关',
'bpmn:EventBasedGateway': '事件网关'
}

12
ruoyi-ui/src/components/customBpmn/index.js

@ -0,0 +1,12 @@
import inherits from "inherits";
import Viewer from "bpmn-js/lib/Viewer";
import ZoomScrollModule from "diagram-js/lib/navigation/zoomscroll";
import MoveCanvasModule from "diagram-js/lib/navigation/movecanvas";
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._modules = [].concat(Viewer.prototype._modules, [ZoomScrollModule, MoveCanvasModule]);
export {
CustomViewer
};

197
ruoyi-ui/src/components/parser/Parser.vue

@ -0,0 +1,197 @@
<script>
import { deepClone } from '@/utils/index'
import render from '@/components/render/render.js'
const ruleTrigger = {
'el-input': 'blur',
'el-input-number': 'blur',
'el-select': 'change',
'el-radio-group': 'change',
'el-checkbox-group': 'change',
'el-cascader': 'change',
'el-time-picker': 'change',
'el-date-picker': 'change',
'el-rate': 'change'
}
const layouts = {
colFormItem(h, scheme) {
const config = scheme.__config__
const listeners = buildListeners.call(this, scheme)
let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
if (config.showLabel === false) labelWidth = '0'
return (
<el-col span={config.span}>
<el-form-item label-width={labelWidth} prop={scheme.__vModel__}
label={config.showLabel ? config.label : ''}>
<render conf={scheme} on={listeners} />
</el-form-item>
</el-col>
)
},
rowFormItem(h, scheme) {
let child = renderChildren.apply(this, arguments)
if (scheme.type === 'flex') {
child = <el-row type={scheme.type} justify={scheme.justify} align={scheme.align}>
{child}
</el-row>
}
return (
<el-col span={scheme.span}>
<el-row gutter={scheme.gutter}>
{child}
</el-row>
</el-col>
)
}
}
function renderFrom(h) {
const { formConfCopy } = this
return (
<el-row gutter={formConfCopy.gutter}>
<el-form
size={formConfCopy.size}
label-position={formConfCopy.labelPosition}
disabled={formConfCopy.disabled}
label-width={`${formConfCopy.labelWidth}px`}
ref={formConfCopy.formRef}
// model https://github.com/vuejs/jsx/issues/49#issuecomment-472013664
props={{ model: this[formConfCopy.formModel] }}
rules={this[formConfCopy.formRules]}
>
{renderFormItem.call(this, h, formConfCopy.fields)}
{formConfCopy.formBtns && formBtns.call(this, h)}
</el-form>
</el-row>
)
}
function formBtns(h) {
return <el-col>
<el-form-item size="large">
<el-button type="primary" onClick={this.submitForm}>提交</el-button>
<el-button onClick={this.resetForm}>重置</el-button>
</el-form-item>
</el-col>
}
function renderFormItem(h, elementList) {
return elementList.map(scheme => {
const config = scheme.__config__
const layout = layouts[config.layout]
if (layout) {
return layout.call(this, h, scheme)
}
throw new Error(`没有与${config.layout}匹配的layout`)
})
}
function renderChildren(h, scheme) {
const config = scheme.__config__
if (!Array.isArray(config.children)) return null
return renderFormItem.call(this, h, config.children)
}
function setValue(event, config, scheme) {
this.$set(config, 'defaultValue', event)
this.$set(this[this.formConf.formModel], scheme.__vModel__, event)
}
function buildListeners(scheme) {
const config = scheme.__config__
const methods = this.formConf.__methods__ || {}
const listeners = {}
// __methods__thisevent
Object.keys(methods).forEach(key => {
listeners[key] = event => methods[key].call(this, event)
})
// render.js vModel $emit('input', val)
listeners.input = event => setValue.call(this, event, config, scheme)
return listeners
}
export default {
components: {
render
},
props: {
formConf: {
type: Object,
required: true
}
},
data() {
const data = {
formConfCopy: deepClone(this.formConf),
[this.formConf.formModel]: {},
[this.formConf.formRules]: {}
}
this.initFormData(data.formConfCopy.fields, data[this.formConf.formModel])
this.buildRules(data.formConfCopy.fields, data[this.formConf.formRules])
return data
},
methods: {
initFormData(componentList, formData) {
componentList.forEach(cur => {
const config = cur.__config__
if (cur.__vModel__) formData[cur.__vModel__] = config.defaultValue
if (config.children) this.initFormData(config.children, formData)
})
},
buildRules(componentList, rules) {
componentList.forEach(cur => {
const config = cur.__config__
if (Array.isArray(config.regList)) {
if (config.required) {
const required = { required: config.required, message: cur.placeholder }
if (Array.isArray(config.defaultValue)) {
required.type = 'array'
required.message = `请至少选择一个${config.label}`
}
required.message === undefined && (required.message = `${config.label}不能为空`)
config.regList.push(required)
}
rules[cur.__vModel__] = config.regList.map(item => {
item.pattern && (item.pattern = eval(item.pattern))
item.trigger = ruleTrigger && ruleTrigger[config.tag]
return item
})
}
if (config.children) this.buildRules(config.children, rules)
})
},
resetForm() {
this.formConfCopy = deepClone(this.formConf)
this.$refs[this.formConf.formRef].resetFields()
},
submitForm() {
this.$refs[this.formConf.formRef].validate(valid => {
if (!valid) return false
// sumit
// this.$emit('submit', this[this.formConf.formModel])
const params = {
formData: this.formConfCopy,
valData: this[this.formConf.formModel]
}
this.$emit('submit', params)
return true
})
},
//
getData(){
debugger
this.$emit('getData', this[this.formConf.formModel])
// this.$emit('getData',this.formConfCopy)
}
},
render(h) {
return renderFrom.call(this, h)
}
}
</script>

17
ruoyi-ui/src/components/parser/README.md

@ -0,0 +1,17 @@
## form-generator JSON 解析器
>用于将form-generator导出的JSON解析成一个表单。
### 安装组件
```
npm i form-gen-parser
```
或者
```
yarn add form-gen-parser
```
### 使用示例
> [查看在线示例](https://mrhj.gitee.io/form-generator/#/parser)
示例代码:
> [src\components\parser\example\Index.vue](https://github.com/JakHuang/form-generator/blob/dev/src/components/parser/example/Index.vue)

324
ruoyi-ui/src/components/parser/example/Index.vue

@ -0,0 +1,324 @@
<template>
<div class="test-form">
<parser :form-conf="formConf" @submit="sumbitForm1" />
<parser :key="key2" :form-conf="formConf" @submit="sumbitForm2" />
<el-button @click="change">
change
</el-button>
</div>
</template>
<script>
import Parser from '../Parser'
// parsernpm使
// import Parser from 'form-gen-parser'
export default {
components: {
Parser
},
props: {},
data() {
return {
key2: +new Date(),
formConf: {
fields: [
{
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
regList: [
{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}
]
},
__slot__: {
prepend: '',
append: ''
},
__vModel__: 'mobile',
placeholder: '请输入手机号',
style: {
width: '100%'
},
clearable: true,
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false
},
{
__config__: {
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
showLabel: true,
labelWidth: null,
required: true,
layout: 'colFormItem',
regList: [],
changeTag: true,
document:
'https://element.eleme.cn/#/zh-CN/component/date-picker',
formId: 101,
renderKey: 1585980082729
},
style: {
width: '100%'
},
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false,
__vModel__: 'field101'
},
{
__config__: {
layout: 'rowFormItem',
tagIcon: 'row',
label: '行容器',
layoutTree: true,
children: [
{
__config__: {
label: '评分',
tag: 'el-rate',
tagIcon: 'rate',
defaultValue: 0,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/rate',
formId: 102,
renderKey: 1586839671259
},
style: {},
max: 5,
'allow-half': false,
'show-text': false,
'show-score': false,
disabled: false,
__vModel__: 'field102'
}
],
document: 'https://element.eleme.cn/#/zh-CN/component/layout',
formId: 101,
span: 24,
renderKey: 1586839668999,
componentName: 'row101',
gutter: 15
},
type: 'default',
justify: 'start',
align: 'top'
},
{
__config__: {
label: '按钮',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
span: 24,
layout: 'colFormItem',
document: 'https://element.eleme.cn/#/zh-CN/component/button',
renderKey: 1594288459289
},
__slot__: {
default: '测试按钮1'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium',
plain: false,
circle: false,
disabled: false,
on: {
click: 'clickTestButton1'
}
}
],
__methods__: {
clickTestButton1() {
console.log(
`%c【测试按钮1】点击事件里可以访问当前表单:
1) formModel='formData', 所以this.formData可以拿到当前表单的model
2) formRef='elForm', 所以this.$refs.elForm可以拿到当前表单的ref(vue组件)
`,
'color:#409EFF;font-size: 15px'
)
console.log('表单的Model:', this.formData)
console.log('表单的ref:', this.$refs.elForm)
}
},
formRef: 'elForm',
formModel: 'formData',
size: 'small',
labelPosition: 'right',
labelWidth: 100,
formRules: 'rules',
gutter: 15,
disabled: false,
span: 24,
formBtns: true,
unFocusedComponentBorder: false
},
formConf2: {
fields: [
{
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
regList: [
{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}
]
},
__slot__: {
prepend: '',
append: ''
},
__vModel__: 'mobile',
placeholder: '请输入手机号',
style: {
width: '100%'
},
clearable: true,
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false
},
{
__config__: {
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
showLabel: true,
labelWidth: null,
required: true,
layout: 'colFormItem',
regList: [],
changeTag: true,
document:
'https://element.eleme.cn/#/zh-CN/component/date-picker',
formId: 101,
renderKey: 1585980082729
},
style: {
width: '100%'
},
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false,
__vModel__: 'field101'
}
],
formRef: 'elForm',
formModel: 'formData',
size: 'small',
labelPosition: 'right',
labelWidth: 100,
formRules: 'rules',
gutter: 15,
disabled: false,
span: 24,
formBtns: true,
unFocusedComponentBorder: false
}
}
},
computed: {},
watch: {},
created() {},
mounted() {
//
setTimeout(() => {
//
const data = {
mobile: '18836662555'
}
//
this.fillFormData(this.formConf, data)
//
this.key2 = +new Date()
}, 2000)
},
methods: {
fillFormData(form, data) {
form.fields.forEach(item => {
const val = data[item.__vModel__]
if (val) {
item.__config__.defaultValue = val
}
})
},
change() {
this.key2 = +new Date()
const t = this.formConf
this.formConf = this.formConf2
this.formConf2 = t
},
sumbitForm1(data) {
console.log('sumbitForm1提交数据:', data)
},
sumbitForm2(data) {
console.log('sumbitForm2提交数据:', data)
}
}
}
</script>
<style lang="scss" scoped>
.test-form {
margin: 15px auto;
width: 800px;
padding: 15px;
}
</style>

3
ruoyi-ui/src/components/parser/index.js

@ -0,0 +1,3 @@
import Parser from './Parser'
export default Parser

25
ruoyi-ui/src/components/parser/package.json

@ -0,0 +1,25 @@
{
"name": "form-gen-parser",
"version": "1.0.3",
"description": "表单json解析器",
"main": "lib/form-gen-parser.umd.js",
"directories": {
"example": "example"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/JakHuang/form-generator.git"
},
"dependencies": {
"form-gen-render": "^1.0.0"
},
"author": "jakHuang",
"license": "MIT",
"bugs": {
"url": "https://github.com/JakHuang/form-generator/issues"
},
"homepage": "https://github.com/JakHuang/form-generator/blob/dev/src/components/parser"
}

19
ruoyi-ui/src/components/render/package.json

@ -0,0 +1,19 @@
{
"name": "form-gen-render",
"version": "1.0.4",
"description": "表单核心render",
"main": "lib/form-gen-render.umd.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/JakHuang/form-generator.git"
},
"author": "jakhuang",
"license": "MIT",
"bugs": {
"url": "https://github.com/JakHuang/form-generator/issues"
},
"homepage": "https://github.com/JakHuang/form-generator#readme"
}

122
ruoyi-ui/src/components/render/render.js

@ -0,0 +1,122 @@
import { deepClone } from '@/utils/index'
const componentChild = {}
/**
* ./slots中的文件挂载到对象componentChild上
* 文件名为key对应JSON配置中的__config__.tag
* 文件内容为value解析JSON配置中的__slot__
*/
const slotsFiles = require.context('./slots', false, /\.js$/)
const keys = slotsFiles.keys() || []
keys.forEach(key => {
const tag = key.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = slotsFiles(key).default
componentChild[tag] = value
})
function vModel(dataObject, defaultValue) {
dataObject.props.value = defaultValue
dataObject.on.input = val => {
this.$emit('input', val)
}
}
function mountSlotFiles(h, confClone, children) {
const childObjs = componentChild[confClone.__config__.tag]
if (childObjs) {
Object.keys(childObjs).forEach(key => {
const childFunc = childObjs[key]
if (confClone.__slot__ && confClone.__slot__[key]) {
children.push(childFunc(h, confClone, key))
}
})
}
}
function emitEvents(confClone) {
['on', 'nativeOn'].forEach(attr => {
const eventKeyList = Object.keys(confClone[attr] || {})
eventKeyList.forEach(key => {
const val = confClone[attr][key]
if (typeof val === 'string') {
confClone[attr][key] = event => this.$emit(val, event)
}
})
})
}
function buildDataObject(confClone, dataObject) {
Object.keys(confClone).forEach(key => {
const val = confClone[key]
if (key === '__vModel__') {
vModel.call(this, dataObject, confClone.__config__.defaultValue)
} else if (dataObject[key] !== undefined) {
if (dataObject[key] === null
|| dataObject[key] instanceof RegExp
|| ['boolean', 'string', 'number', 'function'].includes(typeof dataObject[key])) {
dataObject[key] = val
} else if (Array.isArray(dataObject[key])) {
dataObject[key] = [...dataObject[key], ...val]
} else {
dataObject[key] = { ...dataObject[key], ...val }
}
} else {
dataObject.attrs[key] = val
}
})
// 清理属性
clearAttrs(dataObject)
}
function clearAttrs(dataObject) {
delete dataObject.attrs.__config__
delete dataObject.attrs.__slot__
delete dataObject.attrs.__methods__
}
function makeDataObject() {
// 深入数据对象:
// https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1
return {
class: {},
attrs: {},
props: {},
domProps: {},
nativeOn: {},
on: {},
style: {},
directives: [],
scopedSlots: {},
slot: null,
key: null,
ref: null,
refInFor: true
}
}
export default {
props: {
conf: {
type: Object,
required: true
}
},
render(h) {
const dataObject = makeDataObject()
const confClone = deepClone(this.conf)
const children = this.$slots.default || []
// 如果slots文件夹存在与当前tag同名的文件,则执行文件中的代码
mountSlotFiles.call(this, h, confClone, children)
// 将字符串类型的事件,发送为消息
emitEvents.call(this, confClone)
// 将json表单配置转化为vue render可以识别的 “数据对象(dataObject)”
buildDataObject.call(this, confClone, dataObject)
return h(this.conf.__config__.tag, dataObject, children)
}
}

5
ruoyi-ui/src/components/render/slots/el-button.js

@ -0,0 +1,5 @@
export default {
default(h, conf, key) {
return conf.__slot__[key]
}
}

13
ruoyi-ui/src/components/render/slots/el-checkbox-group.js

@ -0,0 +1,13 @@
export default {
options(h, conf, key) {
const list = []
conf.__slot__.options.forEach(item => {
if (conf.__config__.optionType === 'button') {
list.push(<el-checkbox-button label={item.value}>{item.label}</el-checkbox-button>)
} else {
list.push(<el-checkbox label={item.value} border={conf.border}>{item.label}</el-checkbox>)
}
})
return list
}
}

8
ruoyi-ui/src/components/render/slots/el-input.js

@ -0,0 +1,8 @@
export default {
prepend(h, conf, key) {
return <template slot="prepend">{conf.__slot__[key]}</template>
},
append(h, conf, key) {
return <template slot="append">{conf.__slot__[key]}</template>
}
}

13
ruoyi-ui/src/components/render/slots/el-radio-group.js

@ -0,0 +1,13 @@
export default {
options(h, conf, key) {
const list = []
conf.__slot__.options.forEach(item => {
if (conf.__config__.optionType === 'button') {
list.push(<el-radio-button label={item.value}>{item.label}</el-radio-button>)
} else {
list.push(<el-radio label={item.value} border={conf.border}>{item.label}</el-radio>)
}
})
return list
}
}

9
ruoyi-ui/src/components/render/slots/el-select.js

@ -0,0 +1,9 @@
export default {
options(h, conf, key) {
const list = []
conf.__slot__.options.forEach(item => {
list.push(<el-option label={item.label} value={item.value} disabled={item.disabled}></el-option>)
})
return list
}
}

17
ruoyi-ui/src/components/render/slots/el-upload.js

@ -0,0 +1,17 @@
export default {
'list-type': (h, conf, key) => {
const list = []
const config = conf.__config__
if (conf['list-type'] === 'picture-card') {
list.push(<i class="el-icon-plus"></i>)
} else {
list.push(<el-button size="small" type="primary" icon="el-icon-upload">{config.buttonText}</el-button>)
}
if (config.showTip) {
list.push(
<div slot="tip" class="el-upload__tip">只能上传不超过 {config.fileSize}{config.sizeUnit} {conf.accept}文件</div>
)
}
return list
}
}

3
ruoyi-ui/src/components/tinymce/README.md

@ -0,0 +1,3 @@
## 简介
富文本编辑器tinymce的一个vue版本封装。使用cdn动态脚本引入的方式加载。

8
ruoyi-ui/src/components/tinymce/config.js

@ -0,0 +1,8 @@
/* eslint-disable max-len */
export const plugins = [
'advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount'
]
export const toolbar = [
'code searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote removeformat subscript superscript codesample hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'
]

38
ruoyi-ui/src/components/tinymce/example/Index.vue

@ -0,0 +1,38 @@
<template>
<div>
<Tinymce v-model="defaultValue" :height="300" placeholder="在这里输入文字" />
</div>
</template>
<script>
import Tinymce from '../index.vue'
export default {
components: {
Tinymce
},
props: {
},
data() {
return {
defaultValue: '<p>配置文档参阅:http://tinymce.ax-z.cn</p>'
}
},
computed: {
},
watch: {
},
created() {
},
mounted() {
},
methods: {
}
}
</script>

3
ruoyi-ui/src/components/tinymce/index.js

@ -0,0 +1,3 @@
import Index from './index.vue'
export default Index

88
ruoyi-ui/src/components/tinymce/index.vue

@ -0,0 +1,88 @@
<template>
<textarea :id="tinymceId" style="visibility: hidden" />
</template>
<script>
import loadTinymce from '@/utils/loadTinymce'
import { plugins, toolbar } from './config'
import { debounce } from 'throttle-debounce'
let num = 1
export default {
props: {
id: {
type: String,
default: () => {
num === 10000 && (num = 1)
return `tinymce${+new Date()}${num++}`
}
},
value: {
default: ''
}
},
data() {
return {
tinymceId: this.id
}
},
mounted() {
loadTinymce(tinymce => {
// eslint-disable-next-line global-require
require('./zh_CN')
let conf = {
selector: `#${this.tinymceId}`,
language: 'zh_CN',
menubar: 'file edit insert view format table',
plugins,
toolbar,
height: 300,
branding: false,
object_resizing: false,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true
}
conf = Object.assign(conf, this.$attrs)
conf.init_instance_callback = editor => {
if (this.value) editor.setContent(this.value)
this.vModel(editor)
}
tinymce.init(conf)
})
},
destroyed() {
this.destroyTinymce()
},
methods: {
vModel(editor) {
// setContent
const debounceSetContent = debounce(250, editor.setContent)
this.$watch('value', (val, prevVal) => {
if (editor && val !== prevVal && val !== editor.getContent()) {
if (typeof val !== 'string') val = val.toString()
debounceSetContent.call(editor, val)
}
})
editor.on('change keyup undo redo', () => {
this.$emit('input', editor.getContent())
})
},
destroyTinymce() {
if (!window.tinymce) return
const tinymce = window.tinymce.get(this.tinymceId)
if (tinymce) {
tinymce.destroy()
}
}
}
}
</script>

28
ruoyi-ui/src/components/tinymce/package.json

@ -0,0 +1,28 @@
{
"name": "form-gen-tinymce",
"version": "1.0.0",
"description": "富文本编辑器tinymce的一个vue版本封装。使用cdn动态脚本引入的方式加载。",
"main": "lib/form-gen-tinymce.umd.js",
"directories": {
"example": "example"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/JakHuang/form-generator.git"
},
"keywords": [
"tinymce-vue"
],
"dependencies": {
"throttle-debounce": "^2.1.0"
},
"author": "jakHuang",
"license": "MIT",
"bugs": {
"url": "https://github.com/JakHuang/form-generator/issues"
},
"homepage": "https://github.com/JakHuang/form-generator/blob/dev/src/components/tinymce"
}

420
ruoyi-ui/src/components/tinymce/zh_CN.js

@ -0,0 +1,420 @@
/* eslint-disable */
tinymce.addI18n('zh_CN',{
"Redo": "\u91cd\u505a",
"Undo": "\u64a4\u9500",
"Cut": "\u526a\u5207",
"Copy": "\u590d\u5236",
"Paste": "\u7c98\u8d34",
"Select all": "\u5168\u9009",
"New document": "\u65b0\u6587\u4ef6",
"Ok": "\u786e\u5b9a",
"Cancel": "\u53d6\u6d88",
"Visual aids": "\u7f51\u683c\u7ebf",
"Bold": "\u7c97\u4f53",
"Italic": "\u659c\u4f53",
"Underline": "\u4e0b\u5212\u7ebf",
"Strikethrough": "\u5220\u9664\u7ebf",
"Superscript": "\u4e0a\u6807",
"Subscript": "\u4e0b\u6807",
"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
"Justify": "\u4e24\u7aef\u5bf9\u9f50",
"Bullet list": "\u9879\u76ee\u7b26\u53f7",
"Numbered list": "\u7f16\u53f7\u5217\u8868",
"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
"Close": "\u5173\u95ed",
"Formats": "\u683c\u5f0f",
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
"Headers": "\u6807\u9898",
"Header 1": "\u6807\u98981",
"Header 2": "\u6807\u98982",
"Header 3": "\u6807\u98983",
"Header 4": "\u6807\u98984",
"Header 5": "\u6807\u98985",
"Header 6": "\u6807\u98986",
"Headings": "\u6807\u9898",
"Heading 1": "\u6807\u98981",
"Heading 2": "\u6807\u98982",
"Heading 3": "\u6807\u98983",
"Heading 4": "\u6807\u98984",
"Heading 5": "\u6807\u98985",
"Heading 6": "\u6807\u98986",
"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
"Div": "Div",
"Pre": "Pre",
"Code": "\u4ee3\u7801",
"Paragraph": "\u6bb5\u843d",
"Blockquote": "\u5f15\u6587\u533a\u5757",
"Inline": "\u6587\u672c",
"Blocks": "\u57fa\u5757",
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
"Fonts": "\u5b57\u4f53",
"Font Sizes": "\u5b57\u53f7",
"Class": "\u7c7b\u578b",
"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
"OR": "\u6216",
"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
"Upload": "\u4e0a\u4f20",
"Block": "\u5757",
"Align": "\u5bf9\u9f50",
"Default": "\u9ed8\u8ba4",
"Circle": "\u7a7a\u5fc3\u5706",
"Disc": "\u5b9e\u5fc3\u5706",
"Square": "\u65b9\u5757",
"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
"Anchor...": "\u951a\u70b9...",
"Name": "\u540d\u79f0",
"Id": "\u6807\u8bc6\u7b26",
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
"Special character...": "\u7279\u6b8a\u5b57\u7b26...",
"Source code": "\u6e90\u4ee3\u7801",
"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
"Language": "\u8bed\u8a00",
"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
"Color Picker": "\u9009\u8272\u5668",
"R": "R",
"G": "G",
"B": "B",
"Left to right": "\u4ece\u5de6\u5230\u53f3",
"Right to left": "\u4ece\u53f3\u5230\u5de6",
"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
"Title": "\u6807\u9898",
"Keywords": "\u5173\u952e\u8bcd",
"Description": "\u63cf\u8ff0",
"Robots": "\u673a\u5668\u4eba",
"Author": "\u4f5c\u8005",
"Encoding": "\u7f16\u7801",
"Fullscreen": "\u5168\u5c4f",
"Action": "\u64cd\u4f5c",
"Shortcut": "\u5feb\u6377\u952e",
"Help": "\u5e2e\u52a9",
"Address": "\u5730\u5740",
"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
"Plugins": "\u63d2\u4ef6",
"Handy Shortcuts": "\u5feb\u6377\u952e",
"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
"Image description": "\u56fe\u7247\u63cf\u8ff0",
"Source": "\u5730\u5740",
"Dimensions": "\u5927\u5c0f",
"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
"General": "\u666e\u901a",
"Advanced": "\u9ad8\u7ea7",
"Style": "\u6837\u5f0f",
"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
"Border": "\u8fb9\u6846",
"Insert image": "\u63d2\u5165\u56fe\u7247",
"Image...": "\u56fe\u7247...",
"Image list": "\u56fe\u7247\u5217\u8868",
"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
"Edit image": "\u7f16\u8f91\u56fe\u7247",
"Image options": "\u56fe\u7247\u9009\u9879",
"Zoom in": "\u653e\u5927",
"Zoom out": "\u7f29\u5c0f",
"Crop": "\u88c1\u526a",
"Resize": "\u8c03\u6574\u5927\u5c0f",
"Orientation": "\u65b9\u5411",
"Brightness": "\u4eae\u5ea6",
"Sharpen": "\u9510\u5316",
"Contrast": "\u5bf9\u6bd4\u5ea6",
"Color levels": "\u989c\u8272\u5c42\u6b21",
"Gamma": "\u4f3d\u9a6c\u503c",
"Invert": "\u53cd\u8f6c",
"Apply": "\u5e94\u7528",
"Back": "\u540e\u9000",
"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
"Text to display": "\u663e\u793a\u6587\u5b57",
"Url": "\u5730\u5740",
"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
"Current window": "\u5f53\u524d\u7a97\u53e3",
"None": "\u65e0",
"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
"Remove link": "\u5220\u9664\u94fe\u63a5",
"Anchors": "\u951a\u70b9",
"Link...": "\u94fe\u63a5...",
"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
"Link list": "\u94fe\u63a5\u5217\u8868",
"Insert video": "\u63d2\u5165\u89c6\u9891",
"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
"Alternative source": "\u955c\u50cf",
"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
"Embed": "\u5185\u5d4c",
"Media...": "\u591a\u5a92\u4f53...",
"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
"Page break": "\u5206\u9875\u7b26",
"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
"Preview": "\u9884\u89c8",
"Print...": "\u6253\u5370...",
"Save": "\u4fdd\u5b58",
"Find": "\u67e5\u627e",
"Replace with": "\u66ff\u6362\u4e3a",
"Replace": "\u66ff\u6362",
"Replace all": "\u5168\u90e8\u66ff\u6362",
"Previous": "\u4e0a\u4e00\u4e2a",
"Next": "\u4e0b\u4e00\u4e2a",
"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
"Find whole words only": "\u5168\u5b57\u5339\u914d",
"Spell check": "\u62fc\u5199\u68c0\u67e5",
"Ignore": "\u5ffd\u7565",
"Ignore all": "\u5168\u90e8\u5ffd\u7565",
"Finish": "\u5b8c\u6210",
"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
"Insert table": "\u63d2\u5165\u8868\u683c",
"Table properties": "\u8868\u683c\u5c5e\u6027",
"Delete table": "\u5220\u9664\u8868\u683c",
"Cell": "\u5355\u5143\u683c",
"Row": "\u884c",
"Column": "\u5217",
"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
"Delete row": "\u5220\u9664\u884c",
"Row properties": "\u884c\u5c5e\u6027",
"Cut row": "\u526a\u5207\u884c",
"Copy row": "\u590d\u5236\u884c",
"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
"Delete column": "\u5220\u9664\u5217",
"Cols": "\u5217",
"Rows": "\u884c",
"Width": "\u5bbd",
"Height": "\u9ad8",
"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
"Show caption": "\u663e\u793a\u6807\u9898",
"Left": "\u5de6\u5bf9\u9f50",
"Center": "\u5c45\u4e2d",
"Right": "\u53f3\u5bf9\u9f50",
"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
"Scope": "\u8303\u56f4",
"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
"H Align": "\u6c34\u5e73\u5bf9\u9f50",
"V Align": "\u5782\u76f4\u5bf9\u9f50",
"Top": "\u9876\u90e8\u5bf9\u9f50",
"Middle": "\u5782\u76f4\u5c45\u4e2d",
"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
"Header cell": "\u8868\u5934\u5355\u5143\u683c",
"Row group": "\u884c\u7ec4",
"Column group": "\u5217\u7ec4",
"Row type": "\u884c\u7c7b\u578b",
"Header": "\u8868\u5934",
"Body": "\u8868\u4f53",
"Footer": "\u8868\u5c3e",
"Border color": "\u8fb9\u6846\u989c\u8272",
"Insert template...": "\u63d2\u5165\u6a21\u677f...",
"Templates": "\u6a21\u677f",
"Template": "\u6a21\u677f",
"Text color": "\u6587\u5b57\u989c\u8272",
"Background color": "\u80cc\u666f\u8272",
"Custom...": "\u81ea\u5b9a\u4e49...",
"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
"No color": "\u65e0",
"Remove color": "\u79fb\u9664\u989c\u8272",
"Table of Contents": "\u5185\u5bb9\u5217\u8868",
"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
"Word count": "\u5b57\u6570",
"Count": "\u8ba1\u6570",
"Document": "\u6587\u6863",
"Selection": "\u9009\u62e9",
"Words": "\u5355\u8bcd",
"Words: {0}": "\u5b57\u6570\uff1a{0}",
"{0} words": "{0} \u5b57",
"File": "\u6587\u4ef6",
"Edit": "\u7f16\u8f91",
"Insert": "\u63d2\u5165",
"View": "\u89c6\u56fe",
"Format": "\u683c\u5f0f",
"Table": "\u8868\u683c",
"Tools": "\u5de5\u5177",
"Powered by {0}": "\u7531{0}\u9a71\u52a8",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
"Image title": "\u56fe\u7247\u6807\u9898",
"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
"Border style": "\u8fb9\u6846\u6837\u5f0f",
"Error": "\u9519\u8bef",
"Warn": "\u8b66\u544a",
"Valid": "\u6709\u6548",
"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
"System Font": "\u7cfb\u7edf\u5b57\u4f53",
"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
"example": "\u793a\u4f8b",
"Search": "\u641c\u7d22",
"All": "\u5168\u90e8",
"Currency": "\u8d27\u5e01",
"Text": "\u6587\u5b57",
"Quotations": "\u5f15\u7528",
"Mathematical": "\u6570\u5b66",
"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
"Symbols": "\u7b26\u53f7",
"Arrows": "\u7bad\u5934",
"User Defined": "\u81ea\u5b9a\u4e49",
"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
"currency sign": "\u8d27\u5e01\u7b26\u53f7",
"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
"colon sign": "\u5192\u53f7",
"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
"lira sign": "\u91cc\u62c9\u7b26\u53f7",
"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
"naira sign": "\u5948\u62c9\u7b26\u53f7",
"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
"won sign": "\u97e9\u5143\u7b26\u53f7",
"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
"austral sign": "\u6fb3\u5143\u7b26\u53f7",
"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
"cedi sign": "\u585e\u5730\u7b26\u53f7",
"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
"spesmilo sign": "spesmilo\u7b26\u53f7",
"tenge sign": "\u575a\u6208\u7b26\u53f7",
"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
"ruble sign": "\u5362\u5e03\u7b26\u53f7",
"yen character": "\u65e5\u5143\u5b57\u6837",
"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
"People": "\u4eba\u7c7b",
"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
"Activity": "\u6d3b\u52a8",
"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
"Objects": "\u7269\u4ef6",
"Flags": "\u65d7\u5e1c",
"Characters": "\u5b57\u7b26",
"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
"{0} characters": "{0} \u4e2a\u5b57\u7b26",
"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
"Update": "\u66f4\u65b0",
"Color swatch": "\u989c\u8272\u6837\u672c",
"Turquoise": "\u9752\u7eff\u8272",
"Green": "\u7eff\u8272",
"Blue": "\u84dd\u8272",
"Purple": "\u7d2b\u8272",
"Navy Blue": "\u6d77\u519b\u84dd",
"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
"Dark Green": "\u6df1\u7eff\u8272",
"Medium Blue": "\u4e2d\u84dd\u8272",
"Medium Purple": "\u4e2d\u7d2b\u8272",
"Midnight Blue": "\u6df1\u84dd\u8272",
"Yellow": "\u9ec4\u8272",
"Orange": "\u6a59\u8272",
"Red": "\u7ea2\u8272",
"Light Gray": "\u6d45\u7070\u8272",
"Gray": "\u7070\u8272",
"Dark Yellow": "\u6697\u9ec4\u8272",
"Dark Orange": "\u6df1\u6a59\u8272",
"Dark Red": "\u6df1\u7ea2\u8272",
"Medium Gray": "\u4e2d\u7070\u8272",
"Dark Gray": "\u6df1\u7070\u8272",
"Light Green": "\u6d45\u7eff\u8272",
"Light Yellow": "\u6d45\u9ec4\u8272",
"Light Red": "\u6d45\u7ea2\u8272",
"Light Purple": "\u6d45\u7d2b\u8272",
"Light Blue": "\u6d45\u84dd\u8272",
"Dark Purple": "\u6df1\u7d2b\u8272",
"Dark Blue": "\u6df1\u84dd\u8272",
"Black": "\u9ed1\u8272",
"White": "\u767d\u8272",
"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
"history": "\u5386\u53f2",
"styles": "\u6837\u5f0f",
"formatting": "\u683c\u5f0f\u5316",
"alignment": "\u5bf9\u9f50",
"indentation": "\u7f29\u8fdb",
"permanent pen": "\u8bb0\u53f7\u7b14",
"comments": "\u5907\u6ce8",
"Format Painter": "\u683c\u5f0f\u5237",
"Insert\/edit iframe": "\u63d2\u5165\/\u7f16\u8f91\u6846\u67b6",
"Capitalization": "\u5927\u5199",
"lowercase": "\u5c0f\u5199",
"UPPERCASE": "\u5927\u5199",
"Title Case": "\u9996\u5b57\u6bcd\u5927\u5199",
"Permanent Pen Properties": "\u6c38\u4e45\u7b14\u5c5e\u6027",
"Permanent pen properties...": "\u6c38\u4e45\u7b14\u5c5e\u6027...",
"Font": "\u5b57\u4f53",
"Size": "\u5b57\u53f7",
"More...": "\u66f4\u591a...",
"Spellcheck Language": "\u62fc\u5199\u68c0\u67e5\u8bed\u8a00",
"Select...": "\u9009\u62e9...",
"Preferences": "\u9996\u9009\u9879",
"Yes": "\u662f",
"No": "\u5426",
"Keyboard Navigation": "\u952e\u76d8\u6307\u5f15",
"Version": "\u7248\u672c",
"Anchor": "\u951a\u70b9",
"Special character": "\u7279\u6b8a\u7b26\u53f7",
"Code sample": "\u4ee3\u7801\u793a\u4f8b",
"Color": "\u989c\u8272",
"Emoticons": "\u8868\u60c5",
"Document properties": "\u6587\u6863\u5c5e\u6027",
"Image": "\u56fe\u7247",
"Insert link": "\u63d2\u5165\u94fe\u63a5",
"Target": "\u6253\u5f00\u65b9\u5f0f",
"Link": "\u94fe\u63a5",
"Poster": "\u5c01\u9762",
"Media": "\u5a92\u4f53",
"Print": "\u6253\u5370",
"Prev": "\u4e0a\u4e00\u4e2a",
"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
"Whole words": "\u5168\u5b57\u5339\u914d",
"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
"Caption": "\u6807\u9898",
"Insert template": "\u63d2\u5165\u6a21\u677f"
});

9
ruoyi-ui/src/icons/index.js

@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

1
ruoyi-ui/src/icons/svg/button.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588670460195" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1314" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M230.4 307.712c13.824 0 25.088-11.264 25.088-25.088 0-100.352 81.92-182.272 182.272-182.272s182.272 81.408 182.272 182.272c0 13.824 11.264 25.088 25.088 25.088s25.088-11.264 24.576-25.088c0-127.488-103.936-231.936-231.936-231.936S205.824 154.624 205.824 282.624c-0.512 14.336 10.752 25.088 24.576 25.088z m564.736 234.496c-11.264 0-21.504 2.048-31.232 6.144 0-44.544-40.448-81.92-88.064-81.92-14.848 0-28.16 3.584-39.936 10.24-13.824-28.16-44.544-48.128-78.848-48.128-12.288 0-24.576 2.56-35.328 7.68V284.16c0-45.568-37.888-81.92-84.48-81.92s-84.48 36.864-84.48 81.92v348.672l-69.12-112.64c-18.432-28.16-58.368-36.864-91.136-19.968-26.624 14.336-46.592 47.104-30.208 88.064 3.072 8.192 76.8 205.312 171.52 311.296 0 0 28.16 24.576 43.008 58.88 4.096 9.728 13.312 15.36 22.528 15.36 3.072 0 6.656-0.512 9.728-2.048 12.288-5.12 18.432-19.968 12.8-32.256-19.456-44.544-53.76-74.752-53.76-74.752C281.6 768 209.408 573.44 208.384 570.88c-5.12-12.8-2.56-20.992 7.168-26.112 9.216-4.608 21.504-4.608 26.112 2.56l113.152 184.32c4.096 8.704 12.8 14.336 22.528 14.336 13.824 0 25.088-10.752 25.088-25.088V284.16c0-17.92 15.36-32.256 34.816-32.256s34.816 14.336 34.816 32.256v284.16c0 13.824 10.24 25.088 24.576 25.088 13.824 0 25.088-11.264 25.088-25.088v-57.344c0-17.92 15.36-32.768 34.816-32.768 19.968 0 37.376 15.36 37.376 32.768v95.232c0 7.168 3.072 13.312 7.68 17.92 4.608 4.608 10.752 7.168 17.92 7.168 13.824 0 24.576-11.264 24.576-25.088V547.84c0-18.432 13.824-32.256 32.256-32.256 20.48 0 38.912 15.36 38.912 32.256v95.232c0 13.824 11.264 25.088 25.088 25.088s24.576-11.264 25.088-25.088v-18.944c0-18.944 12.8-32.256 30.72-32.256 18.432 0 22.528 18.944 22.528 31.744 0 1.024-11.776 99.84-50.688 173.056-30.72 58.368-45.056 112.128-51.2 146.944-2.56 13.312 6.656 26.112 19.968 28.672 1.536 0 3.072 0.512 4.608 0.512 11.776 0 22.016-8.192 24.064-20.48 5.632-31.232 18.432-79.36 46.08-132.608 43.52-81.92 55.808-186.88 56.32-193.536-0.512-50.688-29.696-83.968-72.704-83.968z"></path></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

1
ruoyi-ui/src/icons/svg/cascader.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
ruoyi-ui/src/icons/svg/checkbox.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575982282951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="902" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M828.40625 90.125H195.59375C137.375 90.125 90.125 137.375 90.125 195.59375v632.8125c0 58.21875 47.25 105.46875 105.46875 105.46875h632.8125c58.21875 0 105.46875-47.25 105.46875-105.46875V195.59375c0-58.21875-47.25-105.46875-105.46875-105.46875z m52.734375 738.28125c0 29.16-23.57015625 52.734375-52.734375 52.734375H195.59375c-29.109375 0-52.734375-23.574375-52.734375-52.734375V195.59375c0-29.109375 23.625-52.734375 52.734375-52.734375h632.8125c29.16 0 52.734375 23.625 52.734375 52.734375v632.8125z" p-id="903"></path><path d="M421.52890625 709.55984375a36.28125 36.28125 0 0 1-27.55265625-12.66890625L205.17453125 476.613125a36.28546875 36.28546875 0 0 1 55.10109375-47.22890625l164.986875 192.4846875 342.16171875-298.48078125a36.2896875 36.2896875 0 0 1 47.70984375 54.68765625L445.3859375 700.6203125a36.3234375 36.3234375 0 0 1-23.85703125 8.93953125z" p-id="904"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
ruoyi-ui/src/icons/svg/color.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577252187056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M747.59340925 691.12859384c11.51396329 0.25305413 22.43746719-0.21087818 40.74171707-1.51832482 29.35428085-2.10878421 35.84933734-2.36183835 46.47761114-0.8856895 24.71495444 3.37405491 41.12129828 21.76265671 32.47528161 47.95376084-85.57447632 258.19957947-442.00123984 249.76444099-628.67084683 50.73735554-153.47733892-159.33976008-153.09775772-414.41833795 0.92786545-573.42069196 159.71934128-162.67163983 424.03439521-166.59397897 565.78689185 0.63263534 80.38686649 94.81095318 108.34934958 169.16669549 89.11723508 230.57450162-15.01454608 47.99593598-50.61082928 77.68762207-119.77896259 114.63352789-4.89237973 2.65706845-29.35428085 15.52065436-35.84933652 19.02123633-46.94154346 25.30541465-63.51659033 41.20565021-62.20914449 58.45550757 2.95229856 39.13904114 24.16667102 52.7196135 70.98168823 53.81618115z m44.41100207 50.10472101c-19.82257471 1.43397372-32.05352527 1.940082-45.63409763 1.6448519-70.34905207-1.60267593-115.98314969-30.91478165-121.38163769-101.64341492-3.45840683-46.05585397 24.7571304-73.13264758 89.24376132-107.96976837 6.7902866-3.66928501 31.37871396-16.57504688 36.06021551-19.06341229 57.69634516-30.83042972 85.15271997-53.73183005 94.76877722-84.47790866 12.77923398-40.78389304-9.10994898-98.94417051-79.24812286-181.6507002-121.17075953-142.97559219-350.14258521-139.60153647-489.2380134 2.06660824-134.49827774 138.84237405-134.79350784 362.12048163-0.42175717 501.637667 158.53842169 168.99799328 451.9968783 181.18676788 534.57688175-11.80919339-4.68150156 0.2952301-10.71262573 0.67481131-18.72600705 1.26527069z" p-id="2509"></path><path d="M346.03865637 637.18588562a78.82636652 78.82636652 0 0 0 78.32025825-79.29029883c0-43.69401562-35.005823-79.29029883-78.32025825-79.29029882a78.82636652 78.82636652 0 0 0-78.36243338 79.29029882c0 43.69401562 35.005823 79.29029883 78.36243338 79.29029883z m0-51.7495729a27.07679361 27.07679361 0 0 1-26.5706845-27.54072593c0-15.30977536 11.97789643-27.54072593 26.5706845-27.54072592 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072592a27.07679361 27.07679361 0 0 1-26.57068533 27.54072593zM475.7289063 807.11174353a78.82636652 78.82636652 0 0 0 78.3624334-79.29029882c0-43.69401562-34.96364785-79.29029883-78.32025825-79.29029883a78.82636652 78.82636652 0 0 0-78.32025742 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029882z m0-51.74957208a27.07679361 27.07679361 0 0 1-26.57068532-27.54072674c0-15.30977536 12.06224753-27.54072593 26.57068532-27.54072593 14.59278892 0 26.57068533 12.23095057 26.57068453 27.54072593a27.07679361 27.07679361 0 0 1-26.57068453 27.54072674zM601.24376214 377.21492718a78.82636652 78.82636652 0 0 0 78.32025742-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025742-79.29029882a78.82636652 78.82636652 0 0 0-78.32025823 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025824 79.29029883z m1e-8-51.74957208a27.07679361 27.07679361 0 0 1-26.57068534-27.54072675c0-15.30977536 11.97789643-27.54072593 26.57068534-27.54072591 14.55061295 0 26.57068533 12.23095057 26.57068451 27.54072592a27.07679361 27.07679361 0 0 1-26.57068451 27.54072674zM378.80916809 433.85687983a78.82636652 78.82636652 0 0 0 78.32025824-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025824-79.29029802a78.82636652 78.82636652 0 0 0-78.32025742 79.29029802c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029883z m0-51.74957209a27.07679361 27.07679361 0 0 1-26.57068451-27.54072674c0-15.30977536 11.97789643-27.54072593 26.57068451-27.54072593 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072593a27.07679361 27.07679361 0 0 1-26.57068533 27.54072674z" p-id="2510"></path></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

1
ruoyi-ui/src/icons/svg/component.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575804206892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3145" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M826.56 470.016c-32.896 0-64.384 12.288-89.984 35.52l0-104.96c0-62.208-50.496-112.832-112.64-113.088L623.936 287.04 519.552 287.104C541.824 262.72 554.56 230.72 554.56 197.12c0-73.536-59.904-133.44-133.504-133.44-73.472 0-133.376 59.904-133.376 133.44 0 32.896 12.224 64.256 35.52 89.984L175.232 287.104l0 0.576C113.728 288.704 64 338.88 64 400.576l0.32 0 0.32 116.48C60.864 544.896 70.592 577.728 100.8 588.48c12.736 4.608 37.632 7.488 60.864-25.28 12.992-18.368 34.24-29.248 56.64-29.248 38.336 0 69.504 31.104 69.504 69.312 0 38.4-31.168 69.504-69.504 69.504-22.656 0-44.032-11.264-57.344-30.4C138.688 610.112 112.576 615.36 102.464 619.136c-29.824 10.752-39.104 43.776-38.144 67.392l0 160.384L64 846.912C64 909.248 114.752 960 177.216 960l446.272 0c62.4 0 113.152-50.752 113.152-113.152l0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.536 0 133.44-59.904 133.44-133.504C960 529.92 900.096 470.016 826.56 470.016zM826.56 672.896c-22.72 0-44.032-11.264-57.344-30.4-22.272-32.384-48.448-27.136-58.56-23.36-29.824 10.752-39.04 43.776-38.08 67.392l0 160.384c0 27.136-22.016 49.152-49.152 49.152L177.216 896.064C150.08 896 128 873.984 128 846.848l0.32 0 0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.6 0 133.504-59.904 133.504-133.504 0-73.472-59.904-133.376-133.504-133.376-32.896 0-64.32 12.288-89.984 35.52l0-104.96L128 400.512c0-27.072 22.08-49.152 49.216-49.152L177.216 351.04 334.656 350.72c3.776 0.512 7.616 0.832 11.52 0.832 24.896 0 50.752-10.816 60.032-37.056 4.544-12.736 7.424-37.568-25.344-60.736C362.624 240.768 351.68 219.52 351.68 197.12c0-38.272 31.104-69.44 69.376-69.44 38.336 0 69.504 31.168 69.504 69.44 0 22.72-11.264 44.032-30.528 57.472C427.968 276.736 433.088 302.784 436.8 313.024c10.752 29.888 43.072 39.232 67.392 38.08l119.232 0 0 0.384c27.136 0 49.152 22.08 49.152 49.152l0.256 116.48c-3.776 27.84 6.016 60.736 36.224 71.488 12.736 4.608 37.632 7.488 60.8-25.28 13.056-18.368 34.24-29.248 56.704-29.248C864.832 534.016 896 565.12 896 603.392 896 641.728 864.832 672.896 826.56 672.896z" p-id="3146"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

1
ruoyi-ui/src/icons/svg/date-range.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774833889" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.466667 192.853333h-100.693334V119.466667c0-10.24-6.826667-17.066667-17.066666-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H303.786667V119.466667c0-10.24-6.826667-17.066667-17.066667-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H168.96c-46.08 0-85.333333 37.546667-85.333333 85.333334V836.266667c0 46.08 37.546667 85.333333 85.333333 85.333333H887.466667c46.08 0 85.333333-37.546667 85.333333-85.333333V278.186667c0-47.786667-37.546667-85.333333-85.333333-85.333334z m-718.506667 34.133334h100.693333v66.56c0 10.24 6.826667 17.066667 17.066667 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56h450.56v66.56c0 10.24 6.826667 17.066667 17.066666 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56H887.466667c27.306667 0 51.2 22.186667 51.2 51.2v88.746666H117.76v-88.746666c0-29.013333 22.186667-51.2 51.2-51.2zM887.466667 887.466667H168.96c-27.306667 0-51.2-22.186667-51.2-51.2V401.066667H938.666667V836.266667c0 27.306667-22.186667 51.2-51.2 51.2z" p-id="1377"></path><path d="M858.453333 493.226667H327.68c-10.24 0-17.066667 6.826667-17.066667 17.066666v114.346667h-116.053333c-10.24 0-17.066667 6.826667-17.066667 17.066667v133.12c0 10.24 6.826667 17.066667 17.066667 17.066666H460.8c10.24 0 17.066667-6.826667 17.066667-17.066666v-114.346667h380.586666c10.24 0 17.066667-6.826667 17.066667-17.066667v-133.12c0-10.24-6.826667-17.066667-17.066667-17.066666z m-413.013333 34.133333v97.28h-98.986667v-97.28h98.986667z m-230.4 131.413333h98.986667v98.986667h-98.986667v-98.986667z m131.413333 97.28v-97.28h98.986667v97.28h-98.986667z m133.12-228.693333h97.28v98.986667h-97.28v-98.986667z m131.413334 0h98.986666v98.986667h-98.986666v-98.986667z m230.4 97.28h-98.986667v-98.986667h98.986667v98.986667z" p-id="1378"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

1
ruoyi-ui/src/icons/svg/date.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577186573535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1068" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M479.85714249 608.42857168h64.28571502c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285751s-12.85714249-32.14285751-32.14285664-32.14285664h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285662s12.85714249 32.14285751 32.14285664 32.14285753z m-2e-8 122.14285665h64.28571504c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285665s-12.85714249-32.14285751-32.14285664-32.14285751h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285751s12.85714249 32.14285751 32.14285664 32.14285664z m353.57142921-559.28571416h-128.57142921v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285664-32.14285753s-32.14285751 12.85714249-32.14285751 32.14285753v32.14285664h-257.14285665v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285752-32.14285753s-32.14285751 12.85714249-32.14285664 32.14285753v32.14285664h-128.57142919c-70.71428585 0-128.57142832 57.85714249-128.57142832 122.14285751v501.42857081c0 70.71428585 57.85714249 128.57142832 128.57142832 122.14285751h642.85714335c70.71428585 0 128.57142832-57.85714249 128.57142833-122.14285751v-501.42857081c0-70.71428585-57.85714249-122.14285753-128.57142833-122.14285751z m64.28571415 623.57142832c0 32.14285751-32.14285751 64.28571415-64.28571416 64.28571504h-642.85714335c-32.14285751 0-64.28571415-25.71428583-64.28571417-64.28571504v-372.85714249h771.42857168v372.85714249z m0-437.14285664h-771.42857168v-64.28571417c0-32.14285751 32.14285751-64.28571415 64.28571417-64.28571415h128.57142919v32.14285664c0 19.28571417 12.85714249 32.14285751 32.14285664 32.14285751s32.14285751-12.85714249 32.14285753-32.14285751v-32.14285664h257.14285665v32.14285664c0 19.28571417 12.85714249 32.14285751 32.1428575 32.14285751s32.14285751-12.85714249 32.14285664-32.14285751v-32.14285664h128.57142921c32.14285751 0 64.28571415 25.71428583 64.28571415 64.28571415v64.28571417z m-610.71428583 372.85714247h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285664s-12.85714249-32.14285751-32.14285753-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285751s12.85714249 32.14285751 32.14285751 32.14285665z m385.71428583-122.14285664h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285751s-12.85714249-32.14285751-32.14285751-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285664s12.85714249 32.14285751 32.14285753 32.14285751z m-385.71428583 0h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285751s-12.85714249-32.14285751-32.14285753-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285664s12.85714249 32.14285751 32.14285751 32.14285751z m385.71428583 122.14285665h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285665s-12.85714249-32.14285751-32.14285751-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285751s12.85714249 32.14285751 32.14285753 32.14285665z" p-id="1069"></path></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

1
ruoyi-ui/src/icons/svg/input.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802859706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="3103"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="3104"></path></svg>

After

Width:  |  Height:  |  Size: 744 B

1
ruoyi-ui/src/icons/svg/number.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802851180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2867" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M279.272727 791.272727h512a46.545455 46.545455 0 0 1 0 93.090909H279.272727a46.545455 46.545455 0 0 1 0-93.090909z m33.838546-617.984V651.636364H193.722182V395.170909c0-37.003636-0.884364-59.298909-2.653091-66.746182a24.948364 24.948364 0 0 0-14.615273-16.989091c-8.005818-3.863273-25.786182-5.771636-53.341091-5.771636h-11.822545v-55.854545c57.716364-12.381091 101.562182-37.888 131.490909-76.520728h70.283636z m303.709091 396.8V651.636364H354.164364v-68.235637c77.777455-127.255273 124.043636-206.010182 138.705454-236.218182 14.661818-30.254545 22.016-53.853091 22.016-70.74909 0-13.032727-2.234182-22.714182-6.656-29.137455-4.421818-6.376727-11.170909-9.588364-20.247273-9.588364a22.248727 22.248727 0 0 0-20.200727 10.612364c-4.468364 7.121455-6.656 21.178182-6.656 42.263273v45.521454H354.164364v-17.454545c0-26.763636 1.396364-47.941818 4.142545-63.348364 2.746182-15.499636 9.541818-30.72 20.386909-45.661091 10.798545-14.987636 24.901818-26.298182 42.216727-33.978182 17.361455-7.68 38.167273-11.543273 62.37091-11.543272 47.476364 0 83.316364 11.776 107.706181 35.328 24.296727 23.552 36.445091 53.341091 36.445091 89.367272 0 27.368727-6.842182 56.32-20.48 86.853819-13.730909 30.533818-54.039273 95.325091-121.018182 194.420363h130.885819z m270.615272-189.393454c18.152727 6.097455 31.650909 16.104727 40.494546 29.975272 8.843636 13.917091 13.312 46.452364 13.312 97.652364 0 38.027636-4.328727 67.490909-13.032727 88.529455-8.657455 20.945455-23.598545 36.910545-44.869819 47.848727-21.271273 10.938182-48.593455 16.384-81.873454 16.384-37.794909 0-67.490909-6.330182-89.088-19.083636-21.550545-12.660364-35.746909-28.253091-42.542546-46.638546-6.795636-18.432-10.193455-50.362182-10.193454-95.883636v-37.841455h119.389091v77.730909c0 20.666182 1.210182 33.838545 3.723636 39.424 2.420364 5.585455 7.912727 8.424727 16.337455 8.424728 9.309091 0 15.36-3.537455 18.338909-10.612364 2.932364-7.121455 4.421818-25.6 4.421818-55.575273v-33.047273c0-18.338909-2.048-31.744-6.190546-40.215272a30.72 30.72 0 0 0-18.338909-16.709818c-8.052364-2.653091-23.738182-4.189091-46.964363-4.561455V357.050182c28.392727 0 45.893818-1.070545 52.596363-3.258182a22.946909 22.946909 0 0 0 14.475637-14.149818c2.932364-7.307636 4.421818-18.711273 4.421818-34.257455v-26.624c0-16.756364-1.722182-27.741091-5.12-33.047272-3.490909-5.352727-8.843636-8.005818-16.151273-8.005819-8.285091 0-13.963636 2.792727-16.989091 8.378182-3.025455 5.632-4.561455 17.640727-4.561454 35.933091v39.284364h-119.389091v-40.773818c0-45.661091 10.472727-76.567273 31.325091-92.625455 20.898909-16.058182 54.085818-24.064 99.607272-24.064 56.878545 0 95.511273 11.170909 115.805091 33.373091 20.293818 22.248727 30.394182 53.201455 30.394182 92.765091 0 26.810182-3.630545 46.173091-10.891636 58.088727-7.307636 11.915636-20.107636 22.807273-38.446546 32.628364z" p-id="2868"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

1
ruoyi-ui/src/icons/svg/password.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802846045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2750" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M868.593046 403.832442c-30.081109-28.844955-70.037123-44.753273-112.624057-44.753273L265.949606 359.079168c-42.554188 0-82.510202 15.908318-112.469538 44.690852-30.236652 28.782533-46.857191 67.222007-46.857191 108.198258l0 294.079782c0 40.977273 16.619516 79.414701 46.702672 108.136859 29.959336 28.844955 70.069869 44.814672 112.624057 44.814672l490.019383 0c42.585911 0 82.696444-15.969717 112.624057-44.814672 30.082132-28.844955 46.579875-67.222007 46.579875-108.136859L915.172921 511.968278C915.171897 471.053426 898.675178 432.677397 868.593046 403.832442zM841.821309 806.049083c0 22.098297-8.882298 42.772152-25.099654 58.306964-16.154935 15.661701-37.81935 24.203238-60.752666 24.203238L265.949606 888.559285c-22.934339 0-44.567032-8.54256-60.877509-24.264637-16.186657-15.474436-25.067932-36.148291-25.067932-58.246589L180.004165 511.968278c0-22.035876 8.881274-42.772152 25.192775-58.307987 16.186657-15.536858 37.81935-24.139793 60.753689-24.139793l490.019383 0c22.933315 0 44.597731 8.602935 60.752666 24.139793 16.21838 15.535835 25.099654 36.272112 25.099654 58.307987L841.822332 806.049083zM510.974136 135.440715c114.914216 0 208.318536 89.75214 208.318536 200.055338l73.350588 0c0-149.113109-126.366036-270.496667-281.669124-270.496667-155.333788 0-281.699824 121.383558-281.699824 270.496667l73.350588 0C302.623877 225.193879 396.059919 135.440715 510.974136 135.440715zM474.299865 747.244792l73.350588 0L547.650453 629.576859l-73.350588 0L474.299865 747.244792z" p-id="2751"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

1
ruoyi-ui/src/icons/svg/radio.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575966775973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="879" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M507.39346659 71.84873358c241.53533667 0 437.39770766 195.85422109 437.39770767 437.37442191 0 241.53766571-195.86237099 437.38955776-437.39770767 437.38955776-241.50040803 0-437.34997219-195.85189205-437.34997219-437.38955776C70.0434944 267.70295467 265.89189347 71.84873358 507.39346659 71.84873358L507.39346659 71.84873358zM507.39346659 282.81899805c-125.00686734 0-226.37039389 101.38914133-226.37039388 226.41813048 0 125.01268821 101.36352768 226.39717262 226.37039388 226.39717262 125.04295993 0 226.42395136-101.38448441 226.42395136-226.39717262C733.81625401 384.20813938 632.43642653 282.81899805 507.39346659 282.81899805L507.39346659 282.81899805zM507.39346659 120.78172615c-214.46664192 0-388.42047261 173.95150279-388.4204726 388.44026539 0 214.51204949 173.95499463 388.46122325 388.4204726 388.46122325 214.52369237 0 388.46005817-173.94800981 388.46005818-388.46122325C895.85236082 294.73322894 721.91715897 120.78172615 507.39346659 120.78172615z" p-id="880"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
ruoyi-ui/src/icons/svg/rate.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577246781606" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1098" xmlns:xlink="http://www.w3.org/1999/xlink" width="84.5595703125" height="81"><defs><style type="text/css"></style></defs><path d="M633.72929961 378.02038203l9.49872568 18.68789795 20.78025469 2.79745225 206.61592412 27.33248408a11.46496817 11.46496817 0 0 1 6.6095543 19.47324902l-147.2675168 147.35350284-14.89299345 14.89299345 3.8006376 20.68280244 37.84585956 204.89044571a11.46496817 11.46496817 0 0 1-16.4808914 12.2961788L554.68980898 751.84713388l-18.68789794-9.49299345-18.48726123 9.99171915-183.23885392 99.34968163a11.46496817 11.46496817 0 0 1-16.78471347-11.8662416l32.5433127-205.79617881 3.29617793-20.78598692-15.19108243-14.49172002-151.03375839-143.48407587a11.46496817 11.46496817 0 0 1 6.09936328-19.63949062l205.79617881-32.63503185 20.78598691-3.2961788L428.87898125 380.72038203 518.59235674 192.64331182a11.46496817 11.46496817 0 0 1 20.56815264-0.26369385l94.56879023 185.63503183zM496.64840732 85.52038203l-121.75796162 254.98089229L95.76433145 384.76178369A34.3949045 34.3949045 0 0 0 77.46050938 443.66879023l204.87324901 194.66369385-44.16879023 279.1146498a34.3949045 34.3949045 0 0 0 50.36560489 35.61592325l248.4-134.67898038 251.84522285 128.27579591a34.3949045 34.3949045 0 0 0 49.43694287-36.89426777l-51.30573223-277.85350284 199.73120977-199.90891758a34.3949045 34.3949045 0 0 0-19.82866201-58.40827998l-280.11783428-37.03184736L558.32993633 84.71210205a34.3949045 34.3949045 0 0 0-61.68152901 0.80254775z" p-id="1099"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
ruoyi-ui/src/icons/svg/rich-text.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588552949749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1802" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M834.2654461 933.87476599H189.7345539A99.37494442 99.37494442 0 0 1 90.12523401 834.2654461V189.7345539A99.37494442 99.37494442 0 0 1 189.7345539 90.12523401h644.53089221A99.37494442 99.37494442 0 0 1 933.87476599 189.7345539v644.53089221A99.37494442 99.37494442 0 0 1 834.2654461 933.87476599zM189.7345539 140.04708127a49.68747262 49.68747262 0 0 0-49.68747263 49.68747263v644.53089221a49.68747262 49.68747262 0 0 0 49.68747262 49.68747262h644.53089221a49.68747262 49.68747262 0 0 0 49.68747263-49.68747262V189.7345539a49.68747262 49.68747262 0 0 0-49.68747263-49.68747263z" p-id="1803"></path><path d="M561.68747262 239.18765188h247.73423676a23.43748728 23.43748728 0 0 1 24.84373673 24.84373591 23.43748728 23.43748728 0 0 1-24.84373673 24.84373589H561.68747262a23.43748728 23.43748728 0 0 1-24.84373672-24.84373589 23.43748728 23.43748728 0 0 1 24.84373672-24.84373591z m0 123.9843057h247.73423676a24.84373591 24.84373591 0 0 1 0 49.68747262H561.68747262a24.84373591 24.84373591 0 1 1 0-49.68747262z m0 123.98430652h247.73423676a24.84373591 24.84373591 0 0 1 0 49.68747181H561.68747262a24.84373591 24.84373591 0 0 1 0-49.68747181zM214.57829062 611.1405698h594.84341876a24.84373591 24.84373591 0 0 1 0 49.68747263H214.57829062a24.84373591 24.84373591 0 0 1 0-49.68747263z m0 123.98430652h594.84341876a24.84373591 24.84373591 0 0 1 0 49.6874718H214.57829062a24.84373591 24.84373591 0 1 1 0-49.6874718z m52.03122061-280.07797001h133.82805103l32.10935696 81.79682959h46.87497372l-123.51555642-297.65608402H311.14073697l-121.40618308 297.65608403h46.87497373z m61.87496594-156.32803812a171.56240497 171.56240497 0 0 0 4.92187226-19.68748901 72.18745972 72.18745972 0 0 1 5.15624688 19.68748901l49.45309717 123.98430652H279.03137918z" p-id="1804"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

1
ruoyi-ui/src/icons/svg/row.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579339929870" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M152 854.856875h325.7146875V237.715625H134.856875v600q0 6.99375 5.0746875 12.0684375T152 854.856875z m737.143125-17.1421875v-600H546.284375v617.1421875H872q6.99375 0 12.0684375-5.07375t5.0746875-12.0684375z m68.5715625-651.429375V837.715625q0 35.3821875-25.16625 60.5484375T872 923.4284375H152q-35.383125 0-60.5484375-25.1653125T66.284375 837.7146875V186.284375q0-35.3821875 25.16625-60.5484375T152 100.5715625h720q35.383125 0 60.5484375 25.1653125t25.16625 60.5484375z" p-id="1183"></path></svg>

After

Width:  |  Height:  |  Size: 873 B

1
ruoyi-ui/src/icons/svg/select.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575803481213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="804" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62 511.97954521C62 263.86590869 263.90681826 62 511.97954521 62s449.97954521 201.825 449.97954521 449.97954521c0 248.19545479-201.90681826 449.97954521-449.97954521 449.97954521C263.90681826 962 62 760.175 62 511.97954521M901.98636348 511.97954521c0-215.24318174-175.00909131-390.41590869-390.00681827-390.41590869-215.03863652 0-389.96590869 175.17272695-389.96590868 390.41590869 0 215.28409131 175.00909131 390.45681826 389.96590868 390.45681826C727.01818174 902.47727305 901.98636348 727.30454521 901.98636348 511.97954521M264.17272695 430.28409131c0-5.76818174 2.12727305-11.51590869 6.64772696-15.87272696 8.71363652-8.75454521 22.88863652-8.75454521 31.725 0l209.4340913 208.22727305L721.45454521 414.53409131c8.75454521-8.71363652 22.97045479-8.71363652 31.90909132 0 8.71363652 8.75454521 8.71363652 22.88863652 0 31.60227304L511.97954521 685.74090869 270.71818174 446.01363653C266.27954521 441.77954521 264.17272695 436.05227305 264.17272695 430.28409131" p-id="805"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
ruoyi-ui/src/icons/svg/slider.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577185310368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M951.453125 476.84375H523.671875a131.8359375 131.8359375 0 0 0-254.1796875 0H72.546875v70.3125h196.9453125a131.8359375 131.8359375 0 0 0 254.1796875 0H951.453125z" p-id="1239"></path></svg>

After

Width:  |  Height:  |  Size: 564 B

1
ruoyi-ui/src/icons/svg/switch.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576042673958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1110" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M692 792H332c-150 0-270-120-270-270s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="1111"></path><path d="M341 522m-150 0a150 150 0 1 0 300 0 150 150 0 1 0-300 0Z" p-id="1112"></path></svg>

After

Width:  |  Height:  |  Size: 679 B

1
ruoyi-ui/src/icons/svg/table.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595774196464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4269" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M856.187 65.711H167.215c-56.054 0-101.554 45.5-101.554 101.554v688.972c0 56.054 45.5 101.554 101.554 101.554h688.972c56.054 0 101.554-45.5 101.554-101.554V167.265c0-56.054-45.5-101.554-101.554-101.554z m-677.024 51.773H844.24c34.05 0 61.729 27.678 61.729 61.728v183.594H117.434V179.212c0-34.05 27.678-61.728 61.729-61.728z m217.046 297.094H634.66v219.934H396.209V414.578z m-51.773 219.834H117.434V414.578h227.002v219.834z m341.997-219.834H905.97v219.934H686.433V414.578z m157.807 491.44H179.163c-34.05 0-61.73-27.678-61.73-61.728V686.185h227.003v219.833h51.773V686.185H634.66v219.833h51.772V686.185H905.97V844.29c0 34.05-27.679 61.728-61.729 61.728z" p-id="4270"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
ruoyi-ui/src/icons/svg/textarea.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802855098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2984" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64z m0 608c0 16-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V256c0-16 12.8-32 32-32h704c19.2 0 32 12.8 32 32v512z" p-id="2985"></path><path d="M224 288c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V320c0-16-12.8-32-32-32z m608 480c19.2 0 32-12.8 32-32V608L704 768h128z" p-id="2986"></path></svg>

After

Width:  |  Height:  |  Size: 787 B

1
ruoyi-ui/src/icons/svg/time-range.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774825624" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1248" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.595712 482.290351 345.420077 482.290351l0 57.307194 210.477712 0L555.897789 274.196942l-57.301054 0L498.596735 482.290351zM498.595712 482.290351" p-id="1249"></path><path d="M577.685002 644.98478l379.879913 0 0 57.302077L577.685002 702.286858 577.685002 644.98478 577.685002 644.98478zM577.685002 644.98478" p-id="1250"></path><path d="M577.685002 773.764795l379.879913 0 0 57.307194L577.685002 831.071989 577.685002 773.764795 577.685002 773.764795zM577.685002 773.764795" p-id="1251"></path><path d="M577.685002 902.549927l379.879913 0 0 57.307194L577.685002 959.857121 577.685002 902.549927 577.685002 902.549927zM577.685002 902.549927" p-id="1252"></path><path d="M102.523001 382.290823c4.450359 2.615571 9.470699 3.954055 14.530948 3.954055 2.969635 0 5.952572-0.461511 8.836249-1.394766l190.809767-61.886489c15.052834-4.882194 23.297612-21.040199 18.415418-36.08894-4.882194-15.052834-21.040199-23.297612-36.093033-18.415418L175.676092 308.458257c15.994276-26.115797 35.170011-50.537 57.370639-72.743768 73.767074-73.767074 171.845857-114.388237 276.16783-114.388237 104.32095 0 202.39564 40.622186 276.16169 114.388237s114.393353 171.845857 114.393353 276.16783c0 26.427906-2.615571 52.449559-7.709589 77.780481l58.302871 0c4.464685-25.499767 6.708795-51.470255 6.708795-77.780481 0-60.449767-11.845793-119.102608-35.204803-174.336584-22.559808-53.334719-54.850236-101.226472-95.968725-142.349055-41.122583-41.122583-89.017406-73.408917-142.348032-95.968725C628.317169 75.866898 569.659211 64.021106 509.215584 64.021106c-60.448744 0-119.106702 11.845793-174.336584 35.207873-53.334719 22.559808-101.230566 54.846142-142.349055 95.968725-23.980157 23.980157-44.934398 50.278103-62.727647 78.601172l-20.738323-105.655342c-3.043313-15.527648-18.105357-25.642007-33.631982-22.599717-15.527648 3.048429-25.64303 18.105357-22.599717 33.637098l36.102243 183.932126C90.51348 371.153158 95.460142 378.13313 102.523001 382.290823L102.523001 382.290823zM102.523001 382.290823" p-id="1253"></path><path d="M126.020158 587.9416 67.768453 587.9416c5.759167 33.679054 15.368012 66.544579 28.789697 98.278327 22.559808 53.333696 54.850236 101.225449 95.971795 142.348032 41.122583 41.122583 89.014336 73.408917 142.349055 95.968725 54.112432 22.88829 111.517863 34.71157 170.668031 35.18229L505.547031 902.395408c-102.94972-0.941442-199.594851-41.445948-272.499277-114.349351C177.545672 732.543975 140.810003 663.275355 126.020158 587.9416L126.020158 587.9416zM126.020158 587.9416" p-id="1254"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

1
ruoyi-ui/src/icons/svg/time.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577099827399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M520 559h204c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H488c-17.673 0-32-14.327-32-32 0-0.167 0.001-0.334 0.004-0.5a32.65 32.65 0 0 1-0.004-0.5V277c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32v282z m-8 401C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-64c212.077 0 384-171.923 384-384S724.077 128 512 128 128 299.923 128 512s171.923 384 384 384z" p-id="1009"></path></svg>

After

Width:  |  Height:  |  Size: 805 B

1
ruoyi-ui/src/icons/svg/upload.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

39
ruoyi-ui/src/router/index.js

@ -87,7 +87,46 @@ export const constantRoutes = [
meta: { title: '个人中心', icon: 'user' }
}
]
},
{
path: '/flowable',
component: Layout,
hidden: true,
children: [
{
path: 'definition/model/',
component: () => import('@/views/flowable/definition/model'),
name: 'Model',
meta: { title: '流程设计', icon: '' }
}
]
},
{
path: '/flowable',
component: Layout,
hidden: true,
children: [
{
path: 'task/record/index',
component: () => import('@/views/flowable/task/record/index'),
name: 'Record',
meta: { title: '流程处理', icon: '' }
}
]
},
{
path: '/tool',
component: Layout,
hidden: true,
children: [
{
path: 'build/index',
component: () => import('@/views/tool/build/index'),
name: 'FormBuild',
meta: { title: '表单配置', icon: '' }
}
]
},
]
// 动态路由,基于用户权限动态去加载

271
ruoyi-ui/src/styles/home.scss

@ -0,0 +1,271 @@
$selectedColor: #f6f7ff;
$lighterBlue: #409EFF;
.container {
position: relative;
width: 100%;
height: 100%;
}
.components-list {
padding: 8px;
box-sizing: border-box;
height: 100%;
.components-item {
display: inline-block;
width: 48%;
margin: 1%;
transition: transform 0ms !important;
}
}
.components-draggable{
padding-bottom: 20px;
}
.components-title{
font-size: 14px;
color: #222;
margin: 6px 2px;
.svg-icon{
color: #666;
font-size: 18px;
}
}
.components-body {
padding: 8px 10px;
background: $selectedColor;
font-size: 12px;
cursor: move;
border: 1px dashed $selectedColor;
border-radius: 3px;
.svg-icon{
color: #777;
font-size: 15px;
}
&:hover {
border: 1px dashed #787be8;
color: #787be8;
.svg-icon {
color: #787be8;
}
}
}
.left-board {
width: 260px;
position: absolute;
left: 0;
top: 0;
height: 100vh;
}
.left-scrollbar{
height: calc(100vh - 42px);
overflow: hidden;
}
.center-scrollbar {
height: calc(100vh - 42px);
overflow: hidden;
border-left: 1px solid #f1e8e8;
border-right: 1px solid #f1e8e8;
box-sizing: border-box;
}
.center-board {
height: 100vh;
width: auto;
margin: 0 350px 0 260px;
box-sizing: border-box;
}
.empty-info{
position: absolute;
top: 46%;
left: 0;
right: 0;
text-align: center;
font-size: 18px;
color: #ccb1ea;
letter-spacing: 4px;
}
.action-bar{
position: relative;
height: 42px;
text-align: right;
padding: 0 15px;
box-sizing: border-box;;
border: 1px solid #f1e8e8;
border-top: none;
border-left: none;
.delete-btn{
color: #F56C6C;
}
}
.logo-wrapper{
position: relative;
height: 42px;
background: #fff;
border-bottom: 1px solid #f1e8e8;
box-sizing: border-box;
}
.logo{
position: absolute;
left: 12px;
top: 6px;
line-height: 30px;
color: #00afff;
font-weight: 600;
font-size: 17px;
white-space: nowrap;
> img{
width: 30px;
height: 30px;
vertical-align: top;
}
.github{
display: inline-block;
vertical-align: sub;
margin-left: 15px;
> img{
height: 22px;
}
}
}
.center-board-row {
padding: 12px 12px 15px 12px;
box-sizing: border-box;
& > .el-form {
// 69 = 12+15+42
height: calc(100vh - 69px);
}
}
.drawing-board {
height: 100%;
position: relative;
.components-body {
padding: 0;
margin: 0;
font-size: 0;
}
.sortable-ghost {
position: relative;
display: block;
overflow: hidden;
&::before {
content: " ";
position: absolute;
left: 0;
right: 0;
top: 0;
height: 3px;
background: rgb(89, 89, 223);
z-index: 2;
}
}
.components-item.sortable-ghost {
width: 100%;
height: 60px;
background-color: $selectedColor;
}
.active-from-item {
& > .el-form-item{
background: $selectedColor;
border-radius: 6px;
}
& > .drawing-item-copy, & > .drawing-item-delete{
display: initial;
}
& > .component-name{
color: $lighterBlue;
}
}
.el-form-item{
margin-bottom: 15px;
}
}
.drawing-item{
position: relative;
cursor: move;
&.unfocus-bordered:not(.active-from-item) > div:first-child {
border: 1px dashed #ccc;
}
.el-form-item{
padding: 12px 10px;
}
}
.drawing-row-item{
position: relative;
cursor: move;
box-sizing: border-box;
border: 1px dashed #ccc;
border-radius: 3px;
padding: 0 2px;
margin-bottom: 15px;
.drawing-row-item {
margin-bottom: 2px;
}
.el-col{
margin-top: 22px;
}
.el-form-item{
margin-bottom: 0;
}
.drag-wrapper{
min-height: 80px;
}
&.active-from-item{
border: 1px dashed $lighterBlue;
}
.component-name{
position: absolute;
top: 0;
left: 0;
font-size: 12px;
color: #bbb;
display: inline-block;
padding: 0 6px;
}
}
.drawing-item, .drawing-row-item{
&:hover {
& > .el-form-item{
background: $selectedColor;
border-radius: 6px;
}
& > .drawing-item-copy, & > .drawing-item-delete{
display: initial;
}
}
& > .drawing-item-copy, & > .drawing-item-delete{
display: none;
position: absolute;
top: -10px;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
border-radius: 50%;
font-size: 12px;
border: 1px solid;
cursor: pointer;
z-index: 1;
}
& > .drawing-item-copy{
right: 56px;
border-color: $lighterBlue;
color: $lighterBlue;
background: #fff;
&:hover{
background: $lighterBlue;
color: #fff;
}
}
& > .drawing-item-delete{
right: 24px;
border-color: #F56C6C;
color: #F56C6C;
background: #fff;
&:hover{
background: #F56C6C;
color: #fff;
}
}
}

141
ruoyi-ui/src/styles/index.scss

@ -0,0 +1,141 @@
$editorTabsborderColor: #121315;
body, html{
margin: 0;
padding: 0;
background: #fff;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
}
input, textarea{
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
}
.editor-tabs{
background: $editorTabsborderColor;
.el-tabs__header{
margin: 0;
border-bottom-color: $editorTabsborderColor;
.el-tabs__nav{
border-color: $editorTabsborderColor;
}
}
.el-tabs__item{
height: 32px;
line-height: 32px;
color: #888a8e;
border-left: 1px solid $editorTabsborderColor!important;
background: #363636;
margin-right: 5px;
user-select: none;
}
.el-tabs__item.is-active{
background: #1e1e1e;
border-bottom-color: #1e1e1e!important;
color: #fff;
}
.el-icon-edit{
color: #f1fa8c;
}
.el-icon-document{
color: #a95812;
}
:focus.is-active.is-focus:not(:active) {
box-shadow: none;
border-radius: 0;
}
}
// home
.right-scrollbar {
.el-scrollbar__view {
padding: 12px 18px 15px 15px;
}
}
.el-scrollbar__wrap {
box-sizing: border-box;
overflow-x: hidden !important;
margin-bottom: 0 !important;
}
.center-tabs{
.el-tabs__header{
margin-bottom: 0!important;
}
.el-tabs__item{
width: 50%;
text-align: center;
}
.el-tabs__nav{
width: 100%;
}
}
.reg-item{
padding: 12px 6px;
background: #f8f8f8;
position: relative;
border-radius: 4px;
.close-btn{
position: absolute;
right: -6px;
top: -6px;
display: block;
width: 16px;
height: 16px;
line-height: 16px;
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
color: #fff;
text-align: center;
z-index: 1;
cursor: pointer;
font-size: 12px;
&:hover{
background: rgba(210, 23, 23, 0.5)
}
}
& + .reg-item{
margin-top: 18px;
}
}
.action-bar{
& .el-button+.el-button {
margin-left: 15px;
}
& i {
font-size: 20px;
vertical-align: middle;
position: relative;
top: -1px;
}
}
.custom-tree-node{
width: 100%;
font-size: 14px;
.node-operation{
float: right;
}
i[class*="el-icon"] + i[class*="el-icon"]{
margin-left: 6px;
}
.el-icon-plus{
color: #409EFF;
}
.el-icon-delete{
color: #157a0c;
}
}
.el-scrollbar__view{
overflow-x: hidden;
}
.el-rate{
display: inline-block;
vertical-align: text-top;
}
.el-upload__tip{
line-height: 1.2;
}

33
ruoyi-ui/src/styles/mixin.scss

@ -0,0 +1,33 @@
@mixin action-bar {
.action-bar {
height: 33px;
background: #f2fafb;
padding: 0 15px;
box-sizing: border-box;
.bar-btn {
display: inline-block;
padding: 0 6px;
line-height: 32px;
color: #8285f5;
cursor: pointer;
font-size: 14px;
user-select: none;
& i {
font-size: 20px;
}
&:hover {
color: #4348d4;
}
}
.bar-btn + .bar-btn {
margin-left: 8px;
}
.delete-btn {
color: #f56c6c;
&:hover {
color: #ea0b30;
}
}
}
}

56
ruoyi-ui/src/utils/db.js

@ -0,0 +1,56 @@
import de from "element-ui/src/locale/lang/de";
const DRAWING_ITEMS = 'drawingItems'
const DRAWING_ITEMS_VERSION = '1.2'
const DRAWING_ITEMS_VERSION_KEY = 'DRAWING_ITEMS_VERSION'
const DRAWING_ID = 'idGlobal'
const TREE_NODE_ID = 'treeNodeId'
const FORM_CONF = 'formConf'
export function getDrawingList() {
// 加入缓存版本的概念,保证缓存数据与程序匹配
const version = localStorage.getItem(DRAWING_ITEMS_VERSION_KEY)
if (version !== DRAWING_ITEMS_VERSION) {
localStorage.setItem(DRAWING_ITEMS_VERSION_KEY, DRAWING_ITEMS_VERSION)
saveDrawingList([])
return null
}
const str = localStorage.getItem(DRAWING_ITEMS)
if (str) return JSON.parse(str)
return null
}
export function saveDrawingList(list) {
localStorage.setItem(DRAWING_ITEMS, JSON.stringify(list))
}
export function getIdGlobal() {
const str = localStorage.getItem(DRAWING_ID)
if (str) return parseInt(str, 10)
return 100
}
export function saveIdGlobal(id) {
localStorage.setItem(DRAWING_ID, `${id}`)
}
export function getTreeNodeId() {
const str = localStorage.getItem(TREE_NODE_ID)
if (str) return parseInt(str, 10)
return 100
}
export function saveTreeNodeId(id) {
localStorage.setItem(TREE_NODE_ID, `${id}`)
}
export function getFormConf() {
const str = localStorage.getItem(FORM_CONF)
if (str) return JSON.parse(str)
return null
}
export function saveFormConf(obj) {
localStorage.setItem(FORM_CONF, JSON.stringify(obj))
}

517
ruoyi-ui/src/utils/generator/config.js

@ -1,3 +1,4 @@
// 表单属性【右面板】
export const formConf = {
formRef: 'elForm',
formModel: 'formData',
@ -11,144 +12,195 @@ export const formConf = {
formBtns: true
}
// 输入型组件 【左面板】
export const inputComponents = [
{
// 组件的自定义配置
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
placeholder: '请输入',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
labelWidth: null,
style: { width: '100%' },
clearable: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
// 正则校验规则
regList: []
},
// 组件的插槽属性
__slot__: {
prepend: '',
append: '',
append: ''
},
// 其余的为可直接写在组件标签上的属性
placeholder: '请输入',
style: {width: '100%'},
clearable: true,
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
disabled: false
},
{
__config__: {
label: '多行文本',
labelWidth: null,
showLabel: true,
tag: 'el-input',
tagIcon: 'textarea',
type: 'textarea',
placeholder: '请输入',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
labelWidth: null,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
type: 'textarea',
placeholder: '请输入',
autosize: {
minRows: 4,
maxRows: 4
},
style: { width: '100%' },
style: {width: '100%'},
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
disabled: false
},
{
__config__: {
label: '密码',
showLabel: true,
labelWidth: null,
changeTag: true,
tag: 'el-input',
tagIcon: 'password',
placeholder: '请输入',
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
required: true,
regList: [],
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
__slot__: {
prepend: '',
append: ''
},
placeholder: '请输入',
'show-password': true,
labelWidth: null,
style: { width: '100%' },
style: {width: '100%'},
clearable: true,
prepend: '',
append: '',
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
disabled: false
},
{
__config__: {
label: '计数器',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-input-number',
tagIcon: 'number',
placeholder: '',
defaultValue: undefined,
span: 24,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
document: 'https://element.eleme.cn/#/zh-CN/component/input-number'
},
placeholder: '',
min: undefined,
max: undefined,
step: undefined,
step: 1,
'step-strictly': false,
precision: undefined,
'controls-position': '',
disabled: false,
disabled: false
},
{
__config__: {
label: '编辑器',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'tinymce',
tagIcon: 'rich-text',
defaultValue: null,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input-number'
document: 'http://tinymce.ax-z.cn'
},
placeholder: '请输入',
height: 300, // 编辑器高度
branding: false // 隐藏右下角品牌烙印
}
]
// 选择型组件 【左面板】
export const selectComponents = [
{
__config__: {
label: '下拉选择',
showLabel: true,
labelWidth: null,
tag: 'el-select',
tagIcon: 'select',
placeholder: '请选择',
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
labelWidth: null,
style: { width: '100%' },
clearable: true,
disabled: false,
required: true,
filterable: false,
multiple: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/select'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}],
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/select'
}]
},
placeholder: '请选择',
style: {width: '100%'},
clearable: true,
disabled: false,
filterable: false,
multiple: false
},
{
__config__: {
label: '级联选择',
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/cascaderList',
method: 'get',
dataPath: 'list',
dataConsumer: 'options',
showLabel: true,
labelWidth: null,
tag: 'el-cascader',
tagIcon: 'cascader',
placeholder: '请选择',
layout: 'colFormItem',
defaultValue: [],
dataType: 'dynamic',
span: 24,
labelWidth: null,
style: { width: '100%' },
props: {
props: {
multiple: false
}
},
'show-all-levels': true,
disabled: false,
clearable: true,
filterable: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/cascader'
},
options: [{
id: 1,
value: 1,
@ -159,280 +211,419 @@ export const selectComponents = [
label: '选项1-1'
}]
}],
dataType: 'dynamic',
labelKey: 'label',
valueKey: 'value',
childrenKey: 'children',
separator: '/',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/cascader'
placeholder: '请选择',
style: {width: '100%'},
props: {
props: {
multiple: false,
label: 'label',
value: 'value',
children: 'children'
}
},
'show-all-levels': true,
disabled: false,
clearable: true,
filterable: false,
separator: '/'
},
{
__config__: {
label: '单选框组',
labelWidth: null,
showLabel: true,
tag: 'el-radio-group',
tagIcon: 'radio',
changeTag: true,
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
labelWidth: null,
style: {},
optionType: 'default',
border: false,
size: 'medium',
disabled: false,
regList: [],
required: true,
border: false,
document: 'https://element.eleme.cn/#/zh-CN/component/radio'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}],
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/radio'
}]
},
style: {},
size: 'medium',
disabled: false
},
{
__config__: {
label: '多选框组',
tag: 'el-checkbox-group',
tagIcon: 'checkbox',
defaultValue: [],
span: 24,
showLabel: true,
labelWidth: null,
style: {},
layout: 'colFormItem',
optionType: 'default',
border: false,
size: 'medium',
disabled: false,
required: true,
regList: [],
changeTag: true,
border: false,
document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}],
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'
}]
},
style: {},
size: 'medium',
min: null,
max: null,
disabled: false
},
{
__config__: {
label: '开关',
tag: 'el-switch',
tagIcon: 'switch',
defaultValue: false,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/switch'
},
style: {},
disabled: false,
required: true,
'active-text': '',
'inactive-text': '',
'active-color': null,
'inactive-color': null,
'active-value': true,
'inactive-value': false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/switch'
'inactive-value': false
},
{
__config__: {
label: '滑块',
tag: 'el-slider',
tagIcon: 'slider',
defaultValue: null,
span: 24,
showLabel: true,
layout: 'colFormItem',
labelWidth: null,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/slider'
},
disabled: false,
min: 0,
max: 100,
step: 1,
'show-stops': false,
range: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/slider'
range: false
},
{
__config__: {
label: '时间选择',
tag: 'el-time-picker',
tagIcon: 'time',
placeholder: '请选择',
defaultValue: null,
span: 24,
showLabel: true,
layout: 'colFormItem',
labelWidth: null,
style: { width: '100%' },
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
placeholder: '请选择',
style: {width: '100%'},
disabled: false,
clearable: true,
required: true,
'picker-options': {
selectableRange: '00:00:00-23:59:59'
},
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
'value-format': 'HH:mm:ss'
},
{
__config__: {
label: '时间范围',
tag: 'el-time-picker',
tagIcon: 'time-range',
defaultValue: null,
span: 24,
showLabel: true,
labelWidth: null,
style: { width: '100%' },
layout: 'colFormItem',
defaultValue: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
style: {width: '100%'},
disabled: false,
clearable: true,
required: true,
'is-range': true,
'range-separator': '至',
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
'value-format': 'HH:mm:ss'
},
{
__config__: {
label: '日期选择',
tag: 'el-date-picker',
tagIcon: 'date',
placeholder: '请选择',
defaultValue: null,
type: 'date',
span: 24,
showLabel: true,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
span: 24,
layout: 'colFormItem',
required: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
placeholder: '请选择',
type: 'date',
style: {width: '100%'},
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false
},
{
__config__: {
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
showLabel: true,
labelWidth: null,
style: { width: '100%' },
required: true,
layout: 'colFormItem',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
style: {width: '100%'},
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
required: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
readonly: false
},
{
__config__: {
label: '评分',
tag: 'el-rate',
tagIcon: 'rate',
defaultValue: 0,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/rate'
},
style: {},
max: 5,
'allow-half': false,
'show-text': false,
'show-score': false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/rate'
disabled: false
},
{
__config__: {
label: '颜色选择',
tag: 'el-color-picker',
tagIcon: 'color',
span: 24,
defaultValue: null,
showLabel: true,
labelWidth: null,
'show-alpha': false,
'color-format': '',
disabled: false,
layout: 'colFormItem',
required: true,
size: 'medium',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/color-picker'
},
'show-alpha': false,
'color-format': '',
disabled: false,
size: 'medium'
},
{
__config__: {
label: '上传',
tag: 'el-upload',
tagIcon: 'upload',
action: 'https://jsonplaceholder.typicode.com/posts/',
layout: 'colFormItem',
defaultValue: null,
showLabel: true,
labelWidth: null,
disabled: false,
required: true,
accept: '',
name: 'file',
'auto-upload': true,
span: 24,
showTip: false,
buttonText: '点击上传',
fileSize: 2,
sizeUnit: 'MB',
'list-type': 'text',
multiple: false,
regList: [],
changeTag: true,
fileSize: 2,
sizeUnit: 'MB',
document: 'https://element.eleme.cn/#/zh-CN/component/upload'
},
__slot__: {
'list-type': true
},
action: 'https://jsonplaceholder.typicode.com/posts/',
disabled: false,
accept: '',
name: 'file',
'auto-upload': true,
'list-type': 'text',
multiple: false
}
]
// 布局型组件 【左面板】
export const layoutComponents = [
{
__config__: {
layout: 'rowFormItem',
tagIcon: 'row',
type: 'default',
justify: 'start',
align: 'top',
label: '行容器',
layoutTree: true,
children: [],
document: 'https://element.eleme.cn/#/zh-CN/component/layout'
document: 'https://element.eleme.cn/#/zh-CN/component/layout#row-attributes'
},
type: 'default',
justify: 'start',
align: 'top'
},
{
layout: 'colFormItem',
__config__: {
label: '按钮',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
span: 24,
default: '主要按钮',
layout: 'colFormItem',
document: 'https://element.eleme.cn/#/zh-CN/component/button'
},
__slot__: {
default: '主要按钮'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium',
disabled: false,
document: 'https://element.eleme.cn/#/zh-CN/component/button'
plain: false,
circle: false,
disabled: false
},
{
__config__: {
layout: 'colFormItem',
tagIcon: 'table',
tag: 'el-table',
document: 'https://element.eleme.cn/#/zh-CN/component/table',
span: 24,
formId: 101,
renderKey: 1595761764203,
componentName: 'row101',
showLabel: true,
changeTag: true,
labelWidth: null,
label: '表格[开发中]',
dataType: 'dynamic',
method: 'get',
dataPath: 'list',
dataConsumer: 'data',
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/tableData',
children: [{
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 15957617660153
},
prop: 'date',
label: '日期'
}, {
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 15957617660152
},
prop: 'address',
label: '地址'
}, {
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 15957617660151
},
prop: 'name',
label: '名称'
}, {
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 1595774496335,
children: [
{
__config__: {
label: '按钮',
tag: 'el-button',
tagIcon: 'button',
layout: 'raw',
renderKey: 1595779809901
},
__slot__: {
default: '主要按钮'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium'
}
]
},
label: '操作'
}]
},
data: [],
directives: [{
name: 'loading',
value: true
}],
border: true,
type: 'default',
justify: 'start',
align: 'top'
}
]
// 组件rule的触发方式,无触发方式的组件不生成rule
export const trigger = {
'el-input': 'blur',
'el-input-number': 'blur',
'el-select': 'change',
'el-radio-group': 'change',
'el-checkbox-group': 'change',
'el-cascader': 'change',
'el-time-picker': 'change',
'el-date-picker': 'change',
'el-rate': 'change'
}

37
ruoyi-ui/src/utils/generator/drawingDefalut.js

@ -0,0 +1,37 @@
export default [
{
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
// 正则校验规则
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
},
// 组件的插槽属性
__slot__: {
prepend: '',
append: ''
},
__vModel__: 'mobile',
placeholder: '请输入手机号',
style: { width: '100%' },
clearable: true,
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false
}
]

29
ruoyi-ui/src/utils/generator/drawingDefault.js

@ -1,29 +0,0 @@
export default [
{
layout: 'colFormItem',
tagIcon: 'input',
label: '手机号',
vModel: 'mobile',
formId: 6,
tag: 'el-input',
placeholder: '请输入手机号',
defaultValue: '',
span: 24,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false,
required: true,
changeTag: true,
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
}
]

73
ruoyi-ui/src/utils/index.js

@ -257,20 +257,20 @@ export function debounce(func, wait, immediate) {
* @param {Object} source
* @returns {Object}
*/
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
// export function deepClone(source) {
// if (!source && typeof source !== 'object') {
// throw new Error('error arguments', 'deepClone')
// }
// const targetObj = source.constructor === Array ? [] : {}
// Object.keys(source).forEach(keys => {
// if (source[keys] && typeof source[keys] === 'object') {
// targetObj[keys] = deepClone(source[keys])
// } else {
// targetObj[keys] = source[keys]
// }
// })
// return targetObj
// }
/**
* @param {Array} arr
@ -388,3 +388,48 @@ export function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
}
// 深拷贝对象
export function deepClone(obj) {
const _toString = Object.prototype.toString
// null, undefined, non-object, function
if (!obj || typeof obj !== 'object') {
return obj
}
// DOM Node
if (obj.nodeType && 'cloneNode' in obj) {
return obj.cloneNode(true)
}
// Date
if (_toString.call(obj) === '[object Date]') {
return new Date(obj.getTime())
}
// RegExp
if (_toString.call(obj) === '[object RegExp]') {
const flags = []
if (obj.global) { flags.push('g') }
if (obj.multiline) { flags.push('m') }
if (obj.ignoreCase) { flags.push('i') }
return new RegExp(obj.source, flags.join(''))
}
const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}
for (const key in obj) {
result[key] = deepClone(obj[key])
}
return result
}
const toStr = Function.prototype.call.bind(Object.prototype.toString)
export function isObjectObject(t) {
return toStr(t) === '[object Object]'
}

28
ruoyi-ui/src/utils/loadBeautifier.js

@ -0,0 +1,28 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
let beautifierObj
export default function loadBeautifier(cb) {
const { beautifierUrl } = pluginsConfig
if (beautifierObj) {
cb(beautifierObj)
return
}
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '格式化资源加载中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
loadScript(beautifierUrl, () => {
loading.close()
// eslint-disable-next-line no-undef
beautifierObj = beautifier
cb(beautifierObj)
})
}

40
ruoyi-ui/src/utils/loadMonaco.js

@ -0,0 +1,40 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
// monaco-editor单例
let monacoEidtor
/**
* 动态加载monaco-editor cdn资源
* @param {Function} cb 回调必填
*/
export default function loadMonaco(cb) {
if (monacoEidtor) {
cb(monacoEidtor)
return
}
const { monacoEditorUrl: vs } = pluginsConfig
// 使用element ui实现加载提示
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '编辑器资源初始化中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
!window.require && (window.require = {})
!window.require.paths && (window.require.paths = {})
window.require.paths.vs = vs
loadScript(`${vs}/loader.js`, () => {
window.require(['vs/editor/editor.main'], () => {
loading.close()
monacoEidtor = window.monaco
cb(monacoEidtor)
})
})
}

60
ruoyi-ui/src/utils/loadScript.js

@ -0,0 +1,60 @@
const callbacks = {}
/**
* 加载一个远程脚本
* @param {String} src 一个远程脚本
* @param {Function} callback 回调
*/
function loadScript(src, callback) {
const existingScript = document.getElementById(src)
const cb = callback || (() => {})
if (!existingScript) {
callbacks[src] = []
const $script = document.createElement('script')
$script.src = src
$script.id = src
$script.async = 1
document.body.appendChild($script)
const onEnd = 'onload' in $script ? stdOnEnd.bind($script) : ieOnEnd.bind($script)
onEnd($script)
}
callbacks[src].push(cb)
function stdOnEnd(script) {
script.onload = () => {
this.onerror = this.onload = null
callbacks[src].forEach(item => {
item(null, script)
})
delete callbacks[src]
}
script.onerror = () => {
this.onerror = this.onload = null
cb(new Error(`Failed to load ${src}`), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = () => {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
callbacks[src].forEach(item => {
item(null, script)
})
delete callbacks[src]
}
}
}
/**
* 顺序加载一组远程脚本
* @param {Array} list 一组远程脚本
* @param {Function} cb 回调
*/
export function loadScriptQueue(list, cb) {
const first = list.shift()
list.length ? loadScript(first, () => loadScriptQueue(list, cb)) : loadScript(first, cb)
}
export default loadScript

29
ruoyi-ui/src/utils/loadTinymce.js

@ -0,0 +1,29 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
let tinymceObj
export default function loadTinymce(cb) {
const { tinymceUrl } = pluginsConfig
if (tinymceObj) {
cb(tinymceObj)
return
}
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '富文本资源加载中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
loadScript(tinymceUrl, () => {
loading.close()
// eslint-disable-next-line no-undef
tinymceObj = tinymce
cb(tinymceObj)
})
}

13
ruoyi-ui/src/utils/pluginsConfig.js

@ -0,0 +1,13 @@
const CDN = 'https://lib.baomitu.com/' // CDN Homepage: https://cdn.baomitu.com/
const publicPath = process.env.BASE_URL
function splicingPluginUrl(PluginName, version, fileName) {
return `${CDN}${PluginName}/${version}/${fileName}`
}
export default {
beautifierUrl: splicingPluginUrl('js-beautify', '1.13.5', 'beautifier.min.js'),
// monacoEditorUrl: splicingPluginUrl('monaco-editor', '0.19.3', 'min/vs'), // 使用 monaco-editor CDN 链接
monacoEditorUrl: `${publicPath}libs/monaco-editor/vs`, // 使用 monaco-editor 本地代码
tinymceUrl: splicingPluginUrl('tinymce', '5.7.0', 'tinymce.min.js')
}

548
ruoyi-ui/src/views/flowable/definition/index.vue

@ -0,0 +1,548 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="开始时间" prop="deployTime">
<el-date-picker clearable size="small"
v-model="queryParams.deployTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-upload"
size="mini"
@click="handleImport"
>导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-plus"
size="mini"
@click="handleLoadXml"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:deployment:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:deployment:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" fit :data="definitionList" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="流程编号" align="center" prop="deploymentId" :show-overflow-tooltip="true"/>
<el-table-column label="流程标识" align="center" prop="flowKey" :show-overflow-tooltip="true" />
<el-table-column label="流程分类" align="center" prop="category" />
<el-table-column label="流程名称" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
<el-button type="text" @click="handleReadImage(scope.row.deploymentId)">
<span>{{ scope.row.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="业务表单" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
<el-button v-if="scope.row.formId" type="text" @click="handleForm(scope.row.formId)">
<span>{{ scope.row.formName }}</span>
</el-button>
<label v-else>暂无表单</label>
</template>
</el-table-column>
<el-table-column label="流程版本" align="center">
<template slot-scope="scope">
<el-tag size="medium" >v{{ scope.row.version }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
<el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
</template>
</el-table-column>
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="180"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-dropdown>
<span class="el-dropdown-link">
更多操作<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-edit-outline" @click.native="handleLoadXml(scope.row)">
编辑
</el-dropdown-item>
<el-dropdown-item icon="el-icon-connection" @click.native="handleAddForm(scope.row)" v-if="scope.row.formId == null">
配置表单
</el-dropdown-item>
<el-dropdown-item icon="el-icon-video-pause" @click.native="handleUpdateSuspensionState(scope.row)" v-if="scope.row.suspensionState === 1">
挂起
</el-dropdown-item>
<el-dropdown-item icon="el-icon-video-play" @click.native="handleUpdateSuspensionState(scope.row)" v-if="scope.row.suspensionState === 2">
激活
</el-dropdown-item>
<el-dropdown-item icon="el-icon-delete" @click.native="handleDelete(scope.row)" v-hasPermi="['system:deployment:remove']">
删除
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改流程定义对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="看看" prop="name">
<el-input v-model="form.name" placeholder="请输入看看" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- bpmn20.xml导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload
ref="upload"
:limit="1"
accept=".xml"
:headers="upload.headers"
:action="upload.url + '?name=' + upload.name+'&category='+ upload.category"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
<div class="el-upload__tip" slot="tip">
流程名称<el-input v-model="upload.name"/>
流程分类<el-input v-model="upload.category"/>
</div>
<div class="el-upload__tip" style="color:red" slot="tip">提示仅允许导入bpmn20.xml格式文件</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
<!-- 流程图 -->
<el-dialog :title="readImage.title" :visible.sync="readImage.open" width="70%" append-to-body>
<!-- <el-image :src="readImage.src"></el-image> -->
<flow :xmlData="xmlData"/>
</el-dialog>
<!--表单配置详情-->
<el-dialog :title="formTitle" :visible.sync="formConfOpen" width="50%" append-to-body>
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="formConf" />
</div>
</el-dialog>
<!--挂载表单-->
<el-dialog :title="formDeployTitle" :visible.sync="formDeployOpen" width="60%" append-to-body>
<el-row :gutter="24">
<el-col :span="10" :xs="24">
<el-table
ref="singleTable"
:data="formList"
border
highlight-current-row
@current-change="handleCurrentChange"
style="width: 100%">
<el-table-column label="表单编号" align="center" prop="formId" />
<el-table-column label="表单名称" align="center" prop="formName" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="submitFormDeploy(scope.row)">确定</el-button>
</template>
</el-table-column>
</el-table>
<pagination
small
layout="prev, pager, next"
v-show="formTotal>0"
:total="formTotal"
:page.sync="formQueryParams.pageNum"
:limit.sync="formQueryParams.pageSize"
@pagination="ListFormDeploy"
/>
</el-col>
<el-col :span="14" :xs="24">
<div v-if="currentRow">
<parser :key="new Date().getTime()" :form-conf="currentRow" />
</div>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script>
import { listDefinition, updateState, delDeployment, addDeployment, updateDeployment, exportDeployment, definitionStart, readXml} from "@/api/flowable/definition";
import { getToken } from "@/utils/auth";
import { getForm, addDeployForm ,listForm } from "@/api/flowable/form";
import Parser from '@/components/parser/Parser'
import flow from '@/views/flowable/task/record/flow'
export default {
name: "Definition",
components: {
Parser,
flow
},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
definitionList: [],
//
title: "",
//
open: false,
formConfOpen: false,
formTitle: "",
formDeployOpen: false,
formDeployTitle: "",
formList: [],
formTotal:0,
formConf: {}, //
readImage:{
open: false,
src: "",
},
// bpmn.xml
upload: {
// xml
open: false,
// xml
title: "",
//
isUploading: false,
name: null,
category: null,
//
headers: { Authorization: "Bearer " + getToken() },
//
url: process.env.VUE_APP_BASE_API + "/flowable/definition/import"
},
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
},
formQueryParams:{
pageNum: 1,
pageSize: 10,
},
//
formDeployParam:{
formId: null,
deployId: null
},
currentRow: null,
// xml
xmlData:"",
//
form: {},
//
rules: {
}
};
},
created() {
this.getList();
},
methods: {
/** 查询流程定义列表 */
getList() {
this.loading = true;
listDefinition(this.queryParams).then(response => {
this.definitionList = response.data.records;
this.total = response.data.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.deploymentId)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加流程定义";
},
/** 跳转到流程设计页面 */
handleLoadXml(row){
this.$router.push({ path: '/flowable/definition/model',query: { deployId: row.deploymentId }})
},
/** 流程图查看 */
handleReadImage(deploymentId){
this.readImage.title = "流程图";
this.readImage.open = true;
// this.readImage.src = process.env.VUE_APP_BASE_API + "/flowable/definition/readImage/" + deploymentId;
// xml
readXml(deploymentId).then(res =>{
this.xmlData = res.data
})
},
/** 表单查看 */
handleForm(formId){
getForm(formId).then(res =>{
this.formTitle = "表单详情";
this.formConfOpen = true;
this.formConf = JSON.parse(res.data.formContent)
})
},
/** 启动流程 */
handleDefinitionStart(row){
definitionStart(row.id).then(res =>{
this.msgSuccess(res.msg);
})
},
/** 挂载表单弹框 */
handleAddForm(row){
this.formDeployParam.deployId = row.deploymentId
this.ListFormDeploy()
},
/** 挂载表单列表 */
ListFormDeploy(){
listForm(this.formQueryParams).then(res =>{
this.formList = res.rows;
this.formTotal = res.total;
this.formDeployOpen = true;
this.formDeployTitle = "挂载表单";
})
},
// /** */
// handleEditForm(row){
// this.formDeployParam.deployId = row.deploymentId
// const queryParams = {
// pageNum: 1,
// pageSize: 10
// }
// listForm(queryParams).then(res =>{
// this.formList = res.rows;
// this.formDeployOpen = true;
// this.formDeployTitle = "";
// })
// },
/** 挂载表单 */
submitFormDeploy(row){
this.formDeployParam.formId = row.formId;
addDeployForm(this.formDeployParam).then(res =>{
this.msgSuccess(res.msg);
this.formDeployOpen = false;
this.getList();
})
},
handleCurrentChange(data) {
if (data) {
this.currentRow = JSON.parse(data.formContent);
}
},
/** 挂起/激活流程 */
handleUpdateSuspensionState(row){
let state = 1;
if (row.suspensionState === 1) {
state = 2
}
const params = {
deployId: row.deploymentId,
state: state
}
updateState(params).then(res => {
this.msgSuccess(res.msg);
this.getList();
});
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.deploymentId || this.ids
getDeployment(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改流程定义";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateDeployment(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addDeployment(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const deploymentIds = row.deploymentId || this.ids;
this.$confirm('是否确认删除流程定义编号为"' + deploymentIds + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delDeployment(deploymentIds);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportDeployment(queryParams);
}).then(response => {
this.download(response.msg);
})
},
/** 导入bpmn.xml文件 */
handleImport(){
this.upload.title = "bpmn20.xml文件导入";
this.upload.open = true;
},
//
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
//
handleFileSuccess(response, file, fileList) {
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
this.$message(response.msg);
this.getList();
},
//
submitFileForm() {
this.$refs.upload.submit();
}
}
};
</script>

145
ruoyi-ui/src/views/flowable/definition/model.vue

@ -0,0 +1,145 @@
<template>
<div>
<bpmn-modeler
ref="refNode"
:xml="xml"
:users="users"
:groups="groups"
:categorys="categorys"
:is-view="false"
@save="save"
@showXML="showXML"
@dataType="dataType"
/>
<!--在线查看xml-->
<el-dialog :title="xmlTitle" :visible.sync="xmlOpen" width="60%" append-to-body>
<div>
<pre v-highlight>
<code class="xml">
{{xmlContent}}
</code>
</pre>
</div>
</el-dialog>
</div>
</template>
<script>
import {readXml, roleList, saveXml, userList} from "@/api/flowable/definition";
import bpmnModeler from '@/components/Process/index'
import vkbeautify from 'vkbeautify'
import Hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
export default {
name: "Model",
components: {
bpmnModeler,
vkbeautify
},
//
directives: {
highlight:(el) => {
let blocks = el.querySelectorAll('pre code');
blocks.forEach((block) => {
Hljs.highlightBlock(block)
})
}
},
data() {
return {
xml: "", // xml
modeler:"",
xmlOpen: false,
xmlTitle: '',
xmlContent: '',
users: [],
groups: [],
categorys: [],
};
},
created () {
const deployId = this.$route.query && this.$route.query.deployId;
// xml
if (deployId) {
this.getModelDetail(deployId);
}
this.getDicts("sys_process_category").then(res => {
this.categorys = res.data;
});
this.getDataList()
},
methods: {
/** xml 文件 */
getModelDetail(deployId) {
// xml
readXml(deployId).then(res =>{
this.xml = res.data;
this.modeler = res.data
})
},
/** 保存xml */
save(data) {
const params = {
name: data.process.name,
category: data.process.category,
xml: data.xml
}
saveXml(params).then(res => {
this.$message(res.msg)
//
this.$store.dispatch("tagsView/delView", this.$route);
this.$router.go(-1)
})
},
/** 指定流程办理人员列表 */
getDataList() {
// todo
// const params = {
//
// }
userList().then(res =>{
res.data.forEach(val =>{
val.userId = val.userId.toString();
})
this.users = res.data;
let arr = {nickName: "流程发起人", userId: "${INITIATOR}"}
this.users.push(arr)
});
roleList().then(res =>{
res.data.forEach(val =>{
val.roleId = val.roleId.toString();
})
this.groups = res.data;
});
},
/** 展示xml */
showXML(data){
this.xmlTitle = 'xml查看';
this.xmlOpen = true;
debugger
this.xmlContent = vkbeautify.xml(data);
},
/** 获取数据类型 */
dataType(data){
this.users = [];
this.groups = [];
if (data) {
if (data.dataType === 'dynamic') {
if (data.userType === 'assignee') {
this.users = [{nickName: "${INITIATOR}", userId: "${INITIATOR}"},
{nickName: "#{approval}", userId: "#{approval}"}
]
} else if (data.userType === 'candidateUsers') {
this.users = [ {nickName: "#{approval}", userId: "#{approval}"}]
} else {
this.groups = [{roleName: "#{approval}", roleId: "#{approval}"}]
}
} else {
this.getDataList()
}
}
}
},
};
</script>

285
ruoyi-ui/src/views/flowable/task/finished/index.vue

@ -0,0 +1,285 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="开始时间" prop="deployTime">
<el-date-picker clearable size="small"
v-model="queryParams.deployTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:deployment:remove']"
>删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="finishedList" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true"/>
<el-table-column label="流程名称" align="center" prop="procDefName" :show-overflow-tooltip="true"/>
<el-table-column label="任务节点" align="center" prop="taskName" />
<el-table-column label="流程发起人" align="center">
<template slot-scope="scope">
<label>{{scope.row.startUserName}} <el-tag type="info" size="mini">{{scope.row.startDeptName}}</el-tag></label>
</template>
</el-table-column>
<el-table-column label="接收时间" align="center" prop="createTime" width="180"/>
<el-table-column label="审批时间" align="center" prop="finishTime" width="180"/>
<el-table-column label="耗时" align="center" prop="duration" width="180"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-tickets"
@click="handleFlowRecord(scope.row)"
>流转记录</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-tickets"
@click="handleRevoke(scope.row)"
>撤回
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { finishedList, getDeployment, delDeployment, addDeployment, updateDeployment, exportDeployment, revokeProcess } from "@/api/flowable/finished";
export default {
name: "Deploy",
components: {
},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
finishedList: [],
//
title: "",
//
open: false,
src: "",
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
},
//
form: {},
//
rules: {
}
};
},
created() {
this.getList();
},
methods: {
/** 查询流程定义列表 */
getList() {
this.loading = true;
finishedList(this.queryParams).then(response => {
this.finishedList = response.data.records;
this.total = response.data.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
};
this.resetForm("form");
},
setIcon(val){
if (val){
return "el-icon-check";
}else {
return "el-icon-time";
}
},
setColor(val){
if (val){
return "#2bc418";
}else {
return "#b3bdbb";
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加流程定义";
},
/** 流程流转记录 */
handleFlowRecord(row){
this.$router.push({ path: '/flowable/task/record/index',
query: {
procInsId: row.procInsId,
deployId: row.deployId,
taskId: row.taskId,
finished: false
}})
},
/** 撤回任务 */
handleRevoke(row){
const params = {
instanceId: row.procInsId
}
revokeProcess(params).then( res => {
this.msgSuccess(res.msg);
this.getList();
});
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getDeployment(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改流程定义";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateDeployment(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addDeployment(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delDeployment(ids);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportDeployment(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>

308
ruoyi-ui/src/views/flowable/task/form/index.vue

@ -0,0 +1,308 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="表单名称" prop="formName">
<el-input
v-model="queryParams.formName"
placeholder="请输入表单名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['flowable:form:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['flowable:form:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['flowable:form:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['flowable:form:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="formList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="表单主键" align="center" prop="formId" />
<el-table-column label="表单名称" align="center" prop="formName" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['flowable:form:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['flowable:form:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改流程表单对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="表单名称" prop="formName">
<el-input v-model="form.formName" placeholder="请输入表单名称" />
</el-form-item>
<el-form-item label="表单内容">
<editor v-model="form.formContent" :min-height="192"/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!--表单配置详情-->
<el-dialog :title="formTitle" :visible.sync="formConfOpen" width="60%" append-to-body>
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="formConf" />
</div>
</el-dialog>
</div>
</template>
<script>
import { listForm, getForm, delForm, addForm, updateForm, exportForm } from "@/api/flowable/form";
import Editor from '@/components/Editor';
import Parser from '@/components/parser/Parser'
export default {
name: "Form",
components: {
Editor,
Parser
},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
formList: [],
//
title: "",
formConf: {}, //
formConfOpen: false,
formTitle: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
formName: null,
formContent: null,
},
//
form: {},
//
rules: {
}
};
},
created() {
this.getList();
},
methods: {
/** 查询流程表单列表 */
getList() {
this.loading = true;
listForm(this.queryParams).then(response => {
this.formList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
formId: null,
formName: null,
formContent: null,
createTime: null,
updateTime: null,
createBy: null,
updateBy: null,
remark: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.formId)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 表单配置信息 */
handleDetail(row){
this.formConfOpen = true;
this.formTitle = "流程表单配置详细";
this.formConf = JSON.parse(row.formContent)
},
/** 新增按钮操作 */
handleAdd() {
// this.reset();
// this.open = true;
// this.title = "";
this.$router.push({ path: '/tool/build/index', query: {formId: null }})
},
/** 修改按钮操作 */
handleUpdate(row) {
// this.reset();
// const formId = row.formId || this.ids
// getForm(formId).then(response => {
// this.form = response.data;
// this.open = true;
// this.title = "";
// });
this.$router.push({ path: '/tool/build/index', query: {formId: row.formId }})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.formId != null) {
updateForm(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addForm(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const formIds = row.formId || this.ids;
this.$confirm('是否确认删除流程表单编号为"' + formIds + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delForm(formIds);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有流程表单数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportForm(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>
<style lang="scss" scoped>
.test-form {
margin: 15px auto;
width: 800px;
padding: 15px;
}
</style>

403
ruoyi-ui/src/views/flowable/task/process/index.vue

@ -0,0 +1,403 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="开始时间" prop="deployTime">
<el-date-picker clearable size="small"
v-model="queryParams.deployTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:deployment:add']"
>新增流程</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:deployment:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:deployment:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="myProcessList" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="流程编号" align="center" prop="procInsId" :show-overflow-tooltip="true"/>
<el-table-column label="流程名称" align="center" prop="procDefName" :show-overflow-tooltip="true"/>
<el-table-column label="流程类别" align="center" prop="category" width="100px" />
<el-table-column label="流程版本" align="center" width="80px">
<template slot-scope="scope">
<el-tag size="medium" >v{{ scope.row.procDefVersion }}</el-tag>
</template>
</el-table-column>
<el-table-column label="提交时间" align="center" prop="createTime" width="180"/>
<el-table-column label="流程状态" align="center" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.finishTime == null" size="mini">进行中</el-tag>
<el-tag type="success" v-if="scope.row.finishTime != null" size="mini">已完成</el-tag>
</template>
</el-table-column>
<el-table-column label="耗时" align="center" prop="duration" width="180"/>
<el-table-column label="当前节点" align="center" prop="taskName"/>
<el-table-column label="办理" align="center">
<template slot-scope="scope">
<label v-if="scope.row.assigneeName">{{scope.row.assigneeName}} <el-tag type="info" size="mini">{{scope.row.deptName}}</el-tag></label>
<label v-if="scope.row.candidate">{{scope.row.candidate}}</label>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-dropdown>
<span class="el-dropdown-link">
更多操作<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-tickets" @click.native="handleFlowRecord(scope.row)">
详情
</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-close" @click.native="handleStop(scope.row)">
取消申请
</el-dropdown-item>
<el-dropdown-item icon="el-icon-delete" @click.native="handleDelete(scope.row)" v-hasPermi="['system:deployment:remove']">
删除
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 发起流程 -->
<el-dialog :title="title" :visible.sync="open" width="60%" append-to-body>
<el-form :model="queryProcessParams" ref="queryProcessForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryProcessParams.name"
placeholder="请输入名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleProcessQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetProcessQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="processLoading" fit :data="definitionList" border >
<el-table-column label="流程名称" align="center" prop="name" />
<el-table-column label="流程版本" align="center">
<template slot-scope="scope">
<el-tag size="medium" >v{{ scope.row.version }}</el-tag>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="category" />
<el-table-column label="操作" align="center" width="300" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit-outline"
@click="handleStartProcess(scope.row)"
>发起流程</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="processTotal>0"
:total="processTotal"
:page.sync="queryProcessParams.pageNum"
:limit.sync="queryProcessParams.pageSize"
@pagination="listDefinition"
/>
</el-dialog>
</div>
</template>
<script>
import {
getDeployment,
delDeployment,
addDeployment,
updateDeployment,
exportDeployment,
flowRecord
} from "@/api/flowable/finished";
import { myProcessList,stopProcess } from "@/api/flowable/process";
import {listDefinition} from "@/api/flowable/definition";
export default {
name: "Deploy",
components: {
},
data() {
return {
//
loading: true,
processLoading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
processTotal:0,
//
myProcessList: [],
//
title: "",
//
open: false,
src: "",
definitionList:[],
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
},
//
queryProcessParams: {
pageNum: 1,
pageSize: 10,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
},
//
form: {},
//
rules: {
},
};
},
created() {
this.getList();
},
methods: {
/** 查询流程定义列表 */
getList() {
this.loading = true;
myProcessList(this.queryParams).then(response => {
this.myProcessList = response.data.records;
this.total = response.data.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 搜索按钮操作 */
handleProcessQuery() {
this.queryProcessParams.pageNum = 1;
this.listDefinition();
},
/** 重置按钮操作 */
resetProcessQuery() {
this.resetForm("queryProcessForm");
this.handleProcessQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.open = true;
this.title = "发起流程";
this.listDefinition();
},
listDefinition(){
listDefinition(this.queryProcessParams).then(response => {
this.definitionList = response.data.records;
this.processTotal = response.data.total;
this.processLoading = false;
});
},
/** 发起流程申请 */
handleStartProcess(row){
this.$router.push({ path: '/flowable/task/record/index',
query: {
deployId: row.deploymentId,
procDefId: row.id,
finished: true
}
})
},
/** 取消流程申请 */
handleStop(row){
const params = {
instanceId: row.procInsId
}
stopProcess(params).then( res => {
this.msgSuccess(res.msg);
this.getList();
});
},
/** 流程流转记录 */
handleFlowRecord(row){
this.$router.push({ path: '/flowable/task/record/index',
query: {
procInsId: row.procInsId,
deployId: row.deployId,
taskId: row.taskId,
finished: false
}})
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getDeployment(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改流程定义";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateDeployment(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addDeployment(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
// const ids = row.procInsId || this.ids;//
const ids = row.procInsId;
this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delDeployment(ids);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportDeployment(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>

36
ruoyi-ui/src/views/flowable/task/record/flow.vue

@ -0,0 +1,36 @@
<template>
<div>
<!--<bpmn-modeler
ref="refNode"
:xml="xmlData"
:is-view="true"
:taskList="taskData"
/>-->
<flow-view :xmlData="xmlData" :taskList="taskData"/>
</div>
</template>
<script>
import bpmnModeler from '@/components/Process/index'
import FlowView from './flowview'
export default {
name: "Flow",
components: {
bpmnModeler,
FlowView
},
props: {
xmlData: {
type: String,
default: ''
},
taskData: {
type: Array,
default: () => []
}
},
data() {
return {};
}
};
</script>

243
ruoyi-ui/src/views/flowable/task/record/flowview.vue

@ -0,0 +1,243 @@
<template>
<div class="containers main-box">
<el-button type="success"
size="small"
icon="el-icon-zoom-in"
@click="zoomViewport(true)">放大</el-button>
<el-button type="warning"
size="small"
icon="el-icon-zoom-out"
@click="zoomViewport(false)">缩小</el-button>
<el-button type="info"
size="small"
icon="el-icon-rank"
@click="fitViewport">适中</el-button>
<div class="canvas" ref="flowCanvas"></div>
</div>
</template>
<script>
import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
export default {
name: "FlowView",
props: {
xmlData: {
type: String,
default: ''
},
taskList: {
type: Array,
default: () => []
}
},
data() {
return {
bpmnViewer: null
};
},
watch: {
xmlData: function(val) {
if (val) {
this.getImg(val)
}
}
},
mounted() {
//
this.bpmnViewer && this.bpmnViewer.destroy();
this.bpmnViewer = new BpmnViewer({
container: this.$refs.flowCanvas,
height: 'calc(100vh - 200px)',
});
this.getImg(this.xmlData)
},
methods: {
//
async getImg(xmlUrl) {
const self = this
try {
await self.bpmnViewer.importXML(xmlUrl);
self.fitViewport()
if (self.taskList !==undefined && self.taskList.length > 0 ) {
self.fillColor()
}
} catch (err) {
console.error(err.message, err.warnings)
}
},
// class
setNodeColor(nodeCodes, colorClass, canvas) {
for (let i = 0; i < nodeCodes.length; i++) {
canvas.addMarker(nodeCodes[i], colorClass);
}
},
//
fitViewport() {
this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
},
//
zoomViewport(zoomIn = true) {
this.zoom = this.bpmnViewer.get('canvas').zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
},
//
fillColor() {
const canvas = this.bpmnViewer.get('canvas')
this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
const completeTask = this.taskList.find(m => m.key === n.id)
const todoTask = this.taskList.find(m => !m.completed)
const endTask = this.taskList[this.taskList.length - 1]
if (n.$type === 'bpmn:UserTask') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
}else {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
}
})
}
}
//
else if (n.$type === 'bpmn:ExclusiveGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
//
else if (n.$type === 'bpmn:ParallelGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
else if (n.$type === 'bpmn:StartEvent') {
n.outgoing.forEach(nn => {
const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (completeTask) {
canvas.addMarker(nn.id, 'highlight')
canvas.addMarker(n.id, 'highlight')
return
}
})
}
else if (n.$type === 'bpmn:EndEvent') {
if (endTask.key === n.id && endTask.completed) {
canvas.addMarker(n.id, 'highlight')
return
}
}
})
},
}
};
</script>
<style lang="scss">
@import "../../../../../node_modules/bpmn-js/dist/assets/diagram-js.css";
@import "../../../../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
@import "../../../../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
@import "../../../../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";
.bjs-powered-by {
display: none;
}
.view-mode {
.el-header, .el-aside, .djs-palette, .bjs-powered-by {
display: none;
}
.el-loading-mask {
background-color: initial;
}
.el-loading-spinner {
display: none;
}
}
.containers {
// background-color: #ffffff;
width: 100%;
height: 100%;
.canvas {
width: 100%;
height: 100%;
}
.panel {
position: absolute;
right: 0;
top: 50px;
width: 300px;
}
.load {
margin-right: 10px;
}
.el-form-item__label{
font-size: 13px;
}
.djs-palette{
left: 0px!important;
top: 0px;
border-top: none;
}
.djs-container svg {
min-height: 650px;
}
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: green !important;
}
.highlight.djs-shape .djs-visual > path {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: green !important;
}
.highlight-todo.djs-connection > .djs-visual > path {
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
fill: orange !important;
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
}
</style>

607
ruoyi-ui/src/views/flowable/task/record/index.vue

@ -0,0 +1,607 @@
<template>
<div class="app-container">
<el-card class="box-card" >
<div slot="header" class="clearfix">
<span class="el-icon-document">基础信息</span>
<el-button style="float: right;" type="primary" @click="goBack">返回</el-button>
</div>
<!--流程处理表单模块-->
<el-col :span="16" :offset="6" v-if="variableOpen">
<div>
<parser :key="new Date().getTime()" :form-conf="variablesData" />
</div>
<div style="margin-left:10%;margin-bottom: 20px;font-size: 14px;" v-if="finished === 'true'">
<el-button icon="el-icon-edit-outline" type="success" size="mini" @click="handleComplete">审批</el-button>
<!-- <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">委派</el-button>-->
<!-- <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleAssign">转办</el-button>-->
<!-- <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">签收</el-button>-->
<el-button icon="el-icon-refresh-left" type="warning" size="mini" @click="handleReturn">退回</el-button>
<el-button icon="el-icon-circle-close" type="danger" size="mini" @click="handleReject">驳回</el-button>
</div>
</el-col>
<!--初始化流程加载表单信息-->
<el-col :span="16" :offset="4" v-if="formConfOpen">
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="formConf" @submit="submitForm" ref="parser" @getData="getData" />
</div>
</el-col>
</el-card>
<!--流程流转记录-->
<el-card class="box-card" v-if="flowRecordList">
<div slot="header" class="clearfix">
<span class="el-icon-notebook-1">审批记录</span>
</div>
<el-col :span="16" :offset="4" >
<div class="block">
<el-timeline>
<el-timeline-item
v-for="(item,index ) in flowRecordList"
:key="index"
:icon="setIcon(item.finishTime)"
:color="setColor(item.finishTime)"
>
<p style="font-weight: 700">{{item.taskName}}</p>
<el-card :body-style="{ padding: '10px' }">
<el-descriptions class="margin-top" :column="1" size="small" border>
<el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
<template slot="label"><i class="el-icon-user"></i>实际办理</template>
{{item.assigneeName}}
<el-tag type="info" size="mini">{{item.deptName}}</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="item.candidate" label-class-name="my-label">
<template slot="label"><i class="el-icon-user"></i>候选办理</template>
{{item.candidate}}
</el-descriptions-item>
<el-descriptions-item label-class-name="my-label">
<template slot="label"><i class="el-icon-date"></i>接收时间</template>
{{item.createTime}}
</el-descriptions-item>
<el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
<template slot="label"><i class="el-icon-date"></i>处理时间</template>
{{item.finishTime}}
</el-descriptions-item>
<el-descriptions-item v-if="item.duration" label-class-name="my-label">
<template slot="label"><i class="el-icon-time"></i>耗时</template>
{{item.duration}}
</el-descriptions-item>
<el-descriptions-item v-if="item.comment" label-class-name="my-label">
<template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
{{item.comment.comment}}
</el-descriptions-item>
</el-descriptions>
<!-- <p v-if="item.comment">-->
<!-- <el-tag type="success" v-if="item.comment.type === '1'"> {{item.comment.comment}}</el-tag>-->
<!-- <el-tag type="warning" v-if="item.comment.type === '2'"> {{item.comment.comment}}</el-tag>-->
<!-- <el-tag type="danger" v-if="item.comment.type === '3'"> {{item.comment.comment}}</el-tag>-->
<!-- </p>-->
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-col>
</el-card>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span class="el-icon-picture-outline">流程图</span>
</div>
<flow :xmlData="xmlData" :taskData="taskList"></flow>
</el-card>
<!--审批正常流程-->
<el-dialog :title="completeTitle" :visible.sync="completeOpen" :width="checkSendUser? '60%':'40%'" append-to-body>
<el-form ref="taskForm" :model="taskForm" label-width="80px" >
<el-form-item v-if="checkSendUser" prop="targetKey">
<el-row :gutter="20">
<!--部门数据-->
<el-col :span="6" :xs="24">
<h6>部门列表</h6>
<div class="head-container">
<el-input
v-model="deptName"
placeholder="请输入部门名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
</div>
<div class="head-container">
<el-tree
:data="deptOptions"
:props="defaultProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
default-expand-all
@node-click="handleNodeClick"
/>
</div>
</el-col>
<el-col :span="10" :xs="24">
<h6>待选人员</h6>
<el-table
ref="singleTable"
:data="userList"
border
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户名" align="center" prop="nickName" />
<el-table-column label="部门" align="center" prop="dept.deptName" />
</el-table>
</el-col>
<el-col :span="8" :xs="24">
<h6>已选人员</h6>
<el-tag
v-for="(user,index) in userData"
:key="index"
closable
@close="handleClose(user)">
{{user.nickName}} {{user.dept.deptName}}
</el-tag>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="处理意见" prop="comment" :rules="[{ required: true, message: '请输入处理意见', trigger: 'blur' }]">
<el-input type="textarea" v-model="taskForm.comment" placeholder="请输入处理意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="completeOpen = false"> </el-button>
<el-button type="primary" @click="taskComplete"> </el-button>
</span>
</el-dialog>
<!--退回流程-->
<el-dialog :title="returnTitle" :visible.sync="returnOpen" width="40%" append-to-body>
<el-form ref="taskForm" :model="taskForm" label-width="80px" >
<el-form-item label="退回节点" prop="targetKey">
<el-radio-group v-model="taskForm.targetKey">
<el-radio-button
v-for="item in returnTaskList"
:key="item.id"
:label="item.id"
>{{item.name}}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="退回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
<el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="returnOpen = false"> </el-button>
<el-button type="primary" @click="taskReturn"> </el-button>
</span>
</el-dialog>
<!--驳回流程-->
<el-dialog :title="rejectTitle" :visible.sync="rejectOpen" width="40%" append-to-body>
<el-form ref="taskForm" :model="taskForm" label-width="80px" >
<el-form-item label="驳回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
<el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="rejectOpen = false"> </el-button>
<el-button type="primary" @click="taskReject"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {flowRecord} from "@/api/flowable/finished";
import Parser from '@/components/parser/Parser'
import {definitionStart, getProcessVariables, readXml, getFlowViewer} from "@/api/flowable/definition";
import {complete, rejectTask, returnList, returnTask, getNextFlowNode, delegate} from "@/api/flowable/todo";
import flow from '@/views/flowable/task/record/flow'
import {treeselect} from "@/api/system/dept";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import Treeselect from "@riophae/vue-treeselect";
import {listUser} from "@/api/system/user";
export default {
name: "Record",
components: {
Parser,
flow,
Treeselect
},
props: {},
data() {
return {
// xml
xmlData: "",
taskList: [],
//
deptName: undefined,
//
deptOptions: undefined,
//
userList: null,
defaultProps: {
children: "children",
label: "label"
},
//
queryParams: {
deptId: undefined
},
//
loading: true,
flowRecordList: [], //
formConfCopy: {},
src: null,
rules: {}, //
variablesForm: {}, //
taskForm:{
returnTaskShow: false, // 退
delegateTaskShow: false, // 退
defaultTaskShow: true, //
sendUserShow: false, //
multiple: false,
comment:"", //
procInsId: "", //
instanceId: "", //
deployId: "", //
taskId: "" ,//
procDefId: "", //
vars: "",
targetKey:""
},
userDataList:[], //
assignee: null,
formConf: {}, //
formConfOpen: false, //
variables: [], //
variablesData: {}, //
variableOpen: false, //
returnTaskList: [], // 退
finished: 'false',
completeTitle: null,
completeOpen: false,
returnTitle: null,
returnOpen: false,
rejectOpen: false,
rejectTitle: null,
userData:[],
checkSendUser: false //
};
},
created() {
this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
this.taskForm.taskId = this.$route.query && this.$route.query.taskId;
this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
this.taskForm.executionId = this.$route.query && this.$route.query.executionId;
this.taskForm.instanceId = this.$route.query && this.$route.query.procInsId;
//
this.taskForm.procDefId = this.$route.query && this.$route.query.procDefId;
//
this.getFlowViewer(this.taskForm.procInsId,this.taskForm.executionId);
this.getModelDetail(this.taskForm.deployId);
//
if (this.taskForm.taskId){
this.processVariables( this.taskForm.taskId)
this.getNextFlowNode(this.taskForm.taskId)
this.taskForm.deployId = null
}
this.getFlowRecordList( this.taskForm.procInsId, this.taskForm.deployId);
this.finished = this.$route.query && this.$route.query.finished
},
methods: {
/** 查询部门下拉树结构 */
getTreeselect() {
treeselect().then(response => {
this.deptOptions = response.data;
});
},
/** 查询用户列表 */
getList() {
listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.userList = response.rows;
this.total = response.total;
}
);
},
//
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
//
handleNodeClick(data) {
this.queryParams.deptId = data.id;
this.getList();
},
/** xml 文件 */
getModelDetail(deployId) {
// xml
readXml(deployId).then(res => {
this.xmlData = res.data
})
},
getFlowViewer(procInsId,executionId) {
getFlowViewer(procInsId,executionId).then(res => {
this.taskList = res.data
})
},
setIcon(val) {
if (val) {
return "el-icon-check";
} else {
return "el-icon-time";
}
},
setColor(val) {
if (val) {
return "#2bc418";
} else {
return "#b3bdbb";
}
},
//
handleSelectionChange(selection) {
if (selection) {
this.userData = selection
const selectVal = selection.map(item => item.userId);
if (selectVal instanceof Array) {
this.taskForm.values = {
"approval": selectVal.join(',')
}
} else {
this.taskForm.values = {
"approval": selectVal
}
}
}
},
//
handleClose(tag) {
this.userData.splice(this.userData.indexOf(tag), 1);
this.$refs.singleTable.toggleRowSelection(tag, false)
},
/** 流程变量赋值 */
handleCheckChange(val) {
if (val instanceof Array) {
this.taskForm.values = {
"approval": val.join(',')
}
} else {
this.taskForm.values = {
"approval": val
}
}
},
/** 流程流转记录 */
getFlowRecordList(procInsId, deployId) {
const params = {procInsId: procInsId, deployId: deployId}
flowRecord(params).then(res => {
this.flowRecordList = res.data.flowList;
//
if (res.data.formData) {
this.formConf = res.data.formData;
this.formConfOpen = true
}
}).catch(res => {
this.goBack();
})
},
fillFormData(form, data) {
form.fields.forEach(item => {
const val = data[item.__vModel__]
if (val) {
item.__config__.defaultValue = val
}
})
},
/** 获取流程变量内容 */
processVariables(taskId) {
if (taskId) {
//
getProcessVariables(taskId).then(res => {
// this.variables = res.data.variables;
this.variablesData = res.data.variables;
this.variableOpen = true
});
}
},
/** 根据当前任务或者流程设计配置的下一步节点 */
getNextFlowNode(taskId) {
// todo
const params = {taskId: taskId}
getNextFlowNode(params).then(res => {
const data = res.data;
if (data) {
this.checkSendUser = true
if (data.type === 'assignee') { //
this.userDataList = res.data.userList;
} else if (data.type === 'candidateUsers') { // ()
this.userDataList = res.data.userList;
this.taskForm.multiple = true;
} else if (data.type === 'candidateGroups') { // ()
res.data.roleList.forEach(role => {
role.userId = role.roleId;
role.nickName = role.roleName;
})
this.userDataList = res.data.roleList;
this.taskForm.multiple = false;
} else if (data.type === 'multiInstance') { // ?
this.userDataList = res.data.userList;
this.taskForm.multiple = true;
}else if (data.type === 'fixed') { //
this.checkSendUser = false;
}
}
})
},
/** 审批任务选择 */
handleComplete() {
this.completeOpen = true;
this.completeTitle = "审批流程";
this.getTreeselect();
},
/** 审批任务 */
taskComplete() {
if (!this.taskForm.values && this.checkSendUser){
this.msgError("请选择流程接收人员");
return;
}
if (!this.taskForm.comment){
this.msgError("请输入审批意见");
return;
}
complete(this.taskForm).then(response => {
this.msgSuccess(response.msg);
this.goBack();
});
},
/** 委派任务 */
handleDelegate() {
this.taskForm.delegateTaskShow = true;
this.taskForm.defaultTaskShow = false;
},
handleAssign(){
},
/** 返回页面 */
goBack() {
//
this.$store.dispatch("tagsView/delView", this.$route);
this.$router.go(-1)
},
/** 接收子组件传的值 */
getData(data) {
if (data) {
const variables = [];
data.fields.forEach(item => {
let variableData = {};
variableData.label = item.__config__.label
//
if (item.__config__.defaultValue instanceof Array) {
const array = [];
item.__config__.defaultValue.forEach(val => {
array.push(val)
})
variableData.val = array;
} else {
variableData.val = item.__config__.defaultValue
}
variables.push(variableData)
})
this.variables = variables;
}
},
/** 申请流程表单数据提交 */
submitForm(data) {
if (data) {
const variables = data.valData;
const formData = data.formData;
formData.disabled = true;
formData.formBtns = false;
if (this.taskForm.procDefId) {
variables.variables = formData;
//
definitionStart(this.taskForm.procDefId, JSON.stringify(variables)).then(res => {
this.msgSuccess(res.msg);
this.goBack();
})
}
}
},
/** 驳回任务 */
handleReject() {
this.rejectOpen = true;
this.rejectTitle = "驳回流程";
},
/** 驳回任务 */
taskReject() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
rejectTask(this.taskForm).then(res => {
this.msgSuccess(res.msg);
this.goBack();
});
}
});
},
/** 可退回任务列表 */
handleReturn() {
this.returnOpen = true;
this.returnTitle = "退回流程";
returnList(this.taskForm).then(res => {
this.returnTaskList = res.data;
this.taskForm.values = null;
})
},
/** 提交退回任务 */
taskReturn() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
returnTask(this.taskForm).then(res => {
this.msgSuccess(res.msg);
this.goBack()
});
}
});
},
/** 取消回退任务按钮 */
cancelTask() {
this.taskForm.returnTaskShow = false;
this.taskForm.defaultTaskShow = true;
this.taskForm.sendUserShow = true;
this.returnTaskList = [];
},
/** 委派任务 */
submitDeleteTask() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
delegate(this.taskForm).then(response => {
this.msgSuccess(response.msg);
this.goBack();
});
}
});
},
/** 取消回退任务按钮 */
cancelDelegateTask() {
this.taskForm.delegateTaskShow = false;
this.taskForm.defaultTaskShow = true;
this.taskForm.sendUserShow = true;
this.returnTaskList = [];
},
}
};
</script>
<style lang="scss" scoped>
.test-form {
margin: 15px auto;
width: 800px;
padding: 15px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.box-card {
width: 100%;
margin-bottom: 20px;
}
.el-tag + .el-tag {
margin-left: 10px;
}
.my-label {
background: #E1F3D8;
}
</style>

237
ruoyi-ui/src/views/flowable/task/todo/index.vue

@ -0,0 +1,237 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="开始时间" prop="deployTime">
<el-date-picker clearable size="small"
v-model="queryParams.deployTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:deployment:remove']"
>删除
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="todoList" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="任务编号" align="center" prop="taskId" :show-overflow-tooltip="true"/>
<el-table-column label="流程名称" align="center" prop="procDefName"/>
<el-table-column label="任务节点" align="center" prop="taskName"/>
<el-table-column label="流程版本" align="center">
<template slot-scope="scope">
<el-tag size="medium" >v{{scope.row.procDefVersion}}</el-tag>
</template>
</el-table-column>
<el-table-column label="流程发起人" align="center">
<template slot-scope="scope">
<label>{{scope.row.startUserName}} <el-tag type="info" size="mini">{{scope.row.startDeptName}}</el-tag></label>
</template>
</el-table-column>
<el-table-column label="接收时间" align="center" prop="createTime" width="180"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit-outline"
@click="handleProcess(scope.row)"
>处理
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import {
todoList,
complete,
returnList,
returnTask,
rejectTask,
getDeployment,
delDeployment,
exportDeployment
} from "@/api/flowable/todo";
export default {
name: "Deploy",
components: {},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
todoList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
category: null
},
//
form: {},
//
rules: {}
};
},
created() {
this.getList();
},
methods: {
/** 查询流程定义列表 */
getList() {
this.loading = true;
todoList(this.queryParams).then(response => {
this.todoList = response.data.records;
this.total = response.data.total;
this.loading = false;
});
},
//
handleProcess(row){
this.$router.push({ path: '/flowable/task/record/index',
query: {
procInsId: row.procInsId,
executionId: row.executionId,
deployId: row.deployId,
taskId: row.taskId,
finished: true
}})
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加流程定义";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getDeployment(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改流程定义";
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return delDeployment(ids);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return exportDeployment(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>

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

Loading…
Cancel
Save