@ -1,90 +1,93 @@ |
|||
{ |
|||
"name": "ruoyi", |
|||
"version": "3.8.4", |
|||
"description": "若依管理系统", |
|||
"author": "若依", |
|||
"license": "MIT", |
|||
"scripts": { |
|||
"dev": "vue-cli-service serve", |
|||
"build:prod": "vue-cli-service build", |
|||
"build:stage": "vue-cli-service build --mode staging", |
|||
"preview": "node build/index.js --preview", |
|||
"lint": "eslint --ext .js,.vue src" |
|||
}, |
|||
"husky": { |
|||
"hooks": { |
|||
"pre-commit": "lint-staged" |
|||
} |
|||
}, |
|||
"lint-staged": { |
|||
"src/**/*.{js,vue}": [ |
|||
"eslint --fix", |
|||
"git add" |
|||
] |
|||
}, |
|||
"keywords": [ |
|||
"vue", |
|||
"admin", |
|||
"dashboard", |
|||
"element-ui", |
|||
"boilerplate", |
|||
"admin-template", |
|||
"management-system" |
|||
], |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://gitee.com/y_project/RuoYi-Vue.git" |
|||
}, |
|||
"dependencies": { |
|||
"@riophae/vue-treeselect": "0.4.0", |
|||
"axios": "0.24.0", |
|||
"clipboard": "2.0.8", |
|||
"core-js": "3.25.3", |
|||
"echarts": "5.4.0", |
|||
"element-ui": "2.15.10", |
|||
"file-saver": "2.0.5", |
|||
"fuse.js": "6.4.3", |
|||
"highlight.js": "9.18.5", |
|||
"js-beautify": "1.13.0", |
|||
"js-cookie": "3.0.1", |
|||
"jsencrypt": "3.0.0-rc.1", |
|||
"nprogress": "0.2.0", |
|||
"quill": "1.3.7", |
|||
"screenfull": "5.0.2", |
|||
"sortablejs": "1.10.2", |
|||
"vue": "2.6.12", |
|||
"vue-count-to": "1.0.13", |
|||
"vue-cropper": "0.5.5", |
|||
"vue-meta": "2.4.0", |
|||
"vue-router": "3.4.9", |
|||
"vuedraggable": "2.24.3", |
|||
"vuex": "3.6.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@vue/cli-plugin-babel": "4.4.6", |
|||
"@vue/cli-plugin-eslint": "4.4.6", |
|||
"@vue/cli-service": "4.4.6", |
|||
"babel-eslint": "10.1.0", |
|||
"babel-plugin-dynamic-import-node": "2.3.3", |
|||
"chalk": "4.1.0", |
|||
"compression-webpack-plugin": "5.0.2", |
|||
"connect": "3.6.6", |
|||
"eslint": "7.15.0", |
|||
"eslint-plugin-vue": "7.2.0", |
|||
"lint-staged": "10.5.3", |
|||
"runjs": "4.4.2", |
|||
"sass": "1.32.13", |
|||
"sass-loader": "10.1.1", |
|||
"script-ext-html-webpack-plugin": "2.1.5", |
|||
"svg-sprite-loader": "5.1.1", |
|||
"vue-template-compiler": "2.6.12" |
|||
}, |
|||
"engines": { |
|||
"node": ">=8.9", |
|||
"npm": ">= 3.0.0" |
|||
}, |
|||
"browserslist": [ |
|||
"> 1%", |
|||
"last 2 versions" |
|||
] |
|||
} |
|||
{ |
|||
"name": "ruoyi", |
|||
"version": "3.8.4", |
|||
"description": "若依管理系统", |
|||
"author": "若依", |
|||
"license": "MIT", |
|||
"scripts": { |
|||
"dev": "vue-cli-service serve", |
|||
"build:prod": "vue-cli-service build", |
|||
"build:stage": "vue-cli-service build --mode staging", |
|||
"preview": "node build/index.js --preview", |
|||
"lint": "eslint --ext .js,.vue src" |
|||
}, |
|||
"husky": { |
|||
"hooks": { |
|||
"pre-commit": "lint-staged" |
|||
} |
|||
}, |
|||
"lint-staged": { |
|||
"src/**/*.{js,vue}": [ |
|||
"eslint --fix", |
|||
"git add" |
|||
] |
|||
}, |
|||
"keywords": [ |
|||
"vue", |
|||
"admin", |
|||
"dashboard", |
|||
"element-ui", |
|||
"boilerplate", |
|||
"admin-template", |
|||
"management-system" |
|||
], |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://gitee.com/y_project/RuoYi-Vue.git" |
|||
}, |
|||
"dependencies": { |
|||
"@riophae/vue-treeselect": "0.4.0", |
|||
"axios": "0.24.0", |
|||
"clipboard": "2.0.8", |
|||
"core-js": "3.25.3", |
|||
"echarts": "5.4.0", |
|||
"element-ui": "2.15.10", |
|||
"file-saver": "2.0.5", |
|||
"fuse.js": "6.4.3", |
|||
"highlight.js": "9.18.5", |
|||
"js-beautify": "1.13.0", |
|||
"js-cookie": "3.0.1", |
|||
"jsencrypt": "3.0.0-rc.1", |
|||
"nprogress": "0.2.0", |
|||
"quill": "1.3.7", |
|||
"screenfull": "5.0.2", |
|||
"sortablejs": "1.10.2", |
|||
"vue": "2.6.12", |
|||
"vue-count-to": "1.0.13", |
|||
"vue-cropper": "0.5.5", |
|||
"vue-meta": "2.4.0", |
|||
"vue-router": "3.4.9", |
|||
"vuedraggable": "2.24.3", |
|||
"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", |
|||
"@vue/cli-plugin-eslint": "4.4.6", |
|||
"@vue/cli-service": "4.4.6", |
|||
"babel-eslint": "10.1.0", |
|||
"babel-plugin-dynamic-import-node": "2.3.3", |
|||
"chalk": "4.1.0", |
|||
"compression-webpack-plugin": "5.0.2", |
|||
"connect": "3.6.6", |
|||
"eslint": "7.15.0", |
|||
"eslint-plugin-vue": "7.2.0", |
|||
"lint-staged": "10.5.3", |
|||
"runjs": "4.4.2", |
|||
"sass": "1.32.13", |
|||
"sass-loader": "10.1.1", |
|||
"script-ext-html-webpack-plugin": "2.1.5", |
|||
"svg-sprite-loader": "5.1.1", |
|||
"vue-template-compiler": "2.6.12" |
|||
}, |
|||
"engines": { |
|||
"node": ">=8.9", |
|||
"npm": ">= 3.0.0" |
|||
}, |
|||
"browserslist": [ |
|||
"> 1%", |
|||
"last 2 versions" |
|||
] |
|||
} |
|||
|
|||
@ -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 |
|||
}) |
|||
} |
|||
@ -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 |
|||
}) |
|||
} |
|||
@ -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 |
|||
}) |
|||
} |
|||
@ -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 |
|||
}) |
|||
} |
|||
@ -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 |
|||
}) |
|||
} |
|||
@ -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] || {} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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 + '}' |
|||
}) |
|||
} |
|||
@ -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 = '' |
|||
} |
|||
} |
|||
} |
|||
@ -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] || {} |
|||
} |
|||
} |
|||
} |
|||
@ -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 } |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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' |
|||
]; |
|||
@ -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> |
|||
@ -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> |
|||
@ -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 中定义的string和expression类为小写,不然会和原生的String类冲突,此处为hack |
|||
// 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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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 中定义的string和expression类为小写,不然会和原生的String类冲突,此处为hack |
|||
// 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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
` |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
import workflowBpmnModeler from './index.vue' |
|||
|
|||
workflowBpmnModeler.install = Vue => Vue.component(workflowBpmnModeler.name, workflowBpmnModeler) // 给组件配置install方法
|
|||
|
|||
export default workflowBpmnModeler |
|||
@ -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, '<![CDATA[$1]]>') |
|||
data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) { |
|||
return str.replace(/</g, '<') |
|||
}) |
|||
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> |
|||
@ -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': '事件网关' |
|||
} |
|||
@ -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 |
|||
}; |
|||
@ -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__中的方法绑定this和event |
|||
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> |
|||
@ -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) |
|||
@ -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' |
|||
|
|||
// 若parser是通过安装npm方式集成到项目中的,使用此行引入 |
|||
// 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> |
|||
@ -0,0 +1,3 @@ |
|||
import Parser from './Parser' |
|||
|
|||
export default Parser |
|||
@ -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" |
|||
} |
|||
@ -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" |
|||
} |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
export default { |
|||
default(h, conf, key) { |
|||
return conf.__slot__[key] |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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> |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
## 简介 |
|||
富文本编辑器tinymce的一个vue版本封装。使用cdn动态脚本引入的方式加载。 |
|||
|
|||
@ -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' |
|||
] |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
import Index from './index.vue' |
|||
|
|||
export default Index |
|||
@ -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> |
|||
@ -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" |
|||
} |
|||
@ -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" |
|||
}); |
|||
@ -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) |
|||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 744 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 873 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 787 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 805 B |
|
After Width: | Height: | Size: 1.4 KiB |
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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)) |
|||
} |
|||
@ -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 |
|||
} |
|||
] |
|||
@ -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: '手机号格式错误' |
|||
}] |
|||
} |
|||
] |
|||
@ -1,390 +1,435 @@ |
|||
import { parseTime } from './ruoyi' |
|||
|
|||
/** |
|||
* 表格时间格式化 |
|||
*/ |
|||
export function formatDate(cellValue) { |
|||
if (cellValue == null || cellValue == "") return ""; |
|||
var date = new Date(cellValue) |
|||
var year = date.getFullYear() |
|||
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 |
|||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() |
|||
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() |
|||
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() |
|||
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() |
|||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds |
|||
} |
|||
|
|||
/** |
|||
* @param {number} time |
|||
* @param {string} option |
|||
* @returns {string} |
|||
*/ |
|||
export function formatTime(time, option) { |
|||
if (('' + time).length === 10) { |
|||
time = parseInt(time) * 1000 |
|||
} else { |
|||
time = +time |
|||
} |
|||
const d = new Date(time) |
|||
const now = Date.now() |
|||
|
|||
const diff = (now - d) / 1000 |
|||
|
|||
if (diff < 30) { |
|||
return '刚刚' |
|||
} else if (diff < 3600) { |
|||
// less 1 hour
|
|||
return Math.ceil(diff / 60) + '分钟前' |
|||
} else if (diff < 3600 * 24) { |
|||
return Math.ceil(diff / 3600) + '小时前' |
|||
} else if (diff < 3600 * 24 * 2) { |
|||
return '1天前' |
|||
} |
|||
if (option) { |
|||
return parseTime(time, option) |
|||
} else { |
|||
return ( |
|||
d.getMonth() + |
|||
1 + |
|||
'月' + |
|||
d.getDate() + |
|||
'日' + |
|||
d.getHours() + |
|||
'时' + |
|||
d.getMinutes() + |
|||
'分' |
|||
) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {string} url |
|||
* @returns {Object} |
|||
*/ |
|||
export function getQueryObject(url) { |
|||
url = url == null ? window.location.href : url |
|||
const search = url.substring(url.lastIndexOf('?') + 1) |
|||
const obj = {} |
|||
const reg = /([^?&=]+)=([^?&=]*)/g |
|||
search.replace(reg, (rs, $1, $2) => { |
|||
const name = decodeURIComponent($1) |
|||
let val = decodeURIComponent($2) |
|||
val = String(val) |
|||
obj[name] = val |
|||
return rs |
|||
}) |
|||
return obj |
|||
} |
|||
|
|||
/** |
|||
* @param {string} input value |
|||
* @returns {number} output value |
|||
*/ |
|||
export function byteLength(str) { |
|||
// returns the byte length of an utf8 string
|
|||
let s = str.length |
|||
for (var i = str.length - 1; i >= 0; i--) { |
|||
const code = str.charCodeAt(i) |
|||
if (code > 0x7f && code <= 0x7ff) s++ |
|||
else if (code > 0x7ff && code <= 0xffff) s += 2 |
|||
if (code >= 0xDC00 && code <= 0xDFFF) i-- |
|||
} |
|||
return s |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} actual |
|||
* @returns {Array} |
|||
*/ |
|||
export function cleanArray(actual) { |
|||
const newArray = [] |
|||
for (let i = 0; i < actual.length; i++) { |
|||
if (actual[i]) { |
|||
newArray.push(actual[i]) |
|||
} |
|||
} |
|||
return newArray |
|||
} |
|||
|
|||
/** |
|||
* @param {Object} json |
|||
* @returns {Array} |
|||
*/ |
|||
export function param(json) { |
|||
if (!json) return '' |
|||
return cleanArray( |
|||
Object.keys(json).map(key => { |
|||
if (json[key] === undefined) return '' |
|||
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) |
|||
}) |
|||
).join('&') |
|||
} |
|||
|
|||
/** |
|||
* @param {string} url |
|||
* @returns {Object} |
|||
*/ |
|||
export function param2Obj(url) { |
|||
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') |
|||
if (!search) { |
|||
return {} |
|||
} |
|||
const obj = {} |
|||
const searchArr = search.split('&') |
|||
searchArr.forEach(v => { |
|||
const index = v.indexOf('=') |
|||
if (index !== -1) { |
|||
const name = v.substring(0, index) |
|||
const val = v.substring(index + 1, v.length) |
|||
obj[name] = val |
|||
} |
|||
}) |
|||
return obj |
|||
} |
|||
|
|||
/** |
|||
* @param {string} val |
|||
* @returns {string} |
|||
*/ |
|||
export function html2Text(val) { |
|||
const div = document.createElement('div') |
|||
div.innerHTML = val |
|||
return div.textContent || div.innerText |
|||
} |
|||
|
|||
/** |
|||
* Merges two objects, giving the last one precedence |
|||
* @param {Object} target |
|||
* @param {(Object|Array)} source |
|||
* @returns {Object} |
|||
*/ |
|||
export function objectMerge(target, source) { |
|||
if (typeof target !== 'object') { |
|||
target = {} |
|||
} |
|||
if (Array.isArray(source)) { |
|||
return source.slice() |
|||
} |
|||
Object.keys(source).forEach(property => { |
|||
const sourceProperty = source[property] |
|||
if (typeof sourceProperty === 'object') { |
|||
target[property] = objectMerge(target[property], sourceProperty) |
|||
} else { |
|||
target[property] = sourceProperty |
|||
} |
|||
}) |
|||
return target |
|||
} |
|||
|
|||
/** |
|||
* @param {HTMLElement} element |
|||
* @param {string} className |
|||
*/ |
|||
export function toggleClass(element, className) { |
|||
if (!element || !className) { |
|||
return |
|||
} |
|||
let classString = element.className |
|||
const nameIndex = classString.indexOf(className) |
|||
if (nameIndex === -1) { |
|||
classString += '' + className |
|||
} else { |
|||
classString = |
|||
classString.substr(0, nameIndex) + |
|||
classString.substr(nameIndex + className.length) |
|||
} |
|||
element.className = classString |
|||
} |
|||
|
|||
/** |
|||
* @param {string} type |
|||
* @returns {Date} |
|||
*/ |
|||
export function getTime(type) { |
|||
if (type === 'start') { |
|||
return new Date().getTime() - 3600 * 1000 * 24 * 90 |
|||
} else { |
|||
return new Date(new Date().toDateString()) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {Function} func |
|||
* @param {number} wait |
|||
* @param {boolean} immediate |
|||
* @return {*} |
|||
*/ |
|||
export function debounce(func, wait, immediate) { |
|||
let timeout, args, context, timestamp, result |
|||
|
|||
const later = function() { |
|||
// 据上一次触发时间间隔
|
|||
const last = +new Date() - timestamp |
|||
|
|||
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
|
|||
if (last < wait && last > 0) { |
|||
timeout = setTimeout(later, wait - last) |
|||
} else { |
|||
timeout = null |
|||
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
|
|||
if (!immediate) { |
|||
result = func.apply(context, args) |
|||
if (!timeout) context = args = null |
|||
} |
|||
} |
|||
} |
|||
|
|||
return function(...args) { |
|||
context = this |
|||
timestamp = +new Date() |
|||
const callNow = immediate && !timeout |
|||
// 如果延时不存在,重新设定延时
|
|||
if (!timeout) timeout = setTimeout(later, wait) |
|||
if (callNow) { |
|||
result = func.apply(context, args) |
|||
context = args = null |
|||
} |
|||
|
|||
return result |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This is just a simple version of deep copy |
|||
* Has a lot of edge cases bug |
|||
* If you want to use a perfect deep copy, use lodash's _.cloneDeep |
|||
* @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 |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} arr |
|||
* @returns {Array} |
|||
*/ |
|||
export function uniqueArr(arr) { |
|||
return Array.from(new Set(arr)) |
|||
} |
|||
|
|||
/** |
|||
* @returns {string} |
|||
*/ |
|||
export function createUniqueString() { |
|||
const timestamp = +new Date() + '' |
|||
const randomNum = parseInt((1 + Math.random()) * 65536) + '' |
|||
return (+(randomNum + timestamp)).toString(32) |
|||
} |
|||
|
|||
/** |
|||
* Check if an element has a class |
|||
* @param {HTMLElement} elm |
|||
* @param {string} cls |
|||
* @returns {boolean} |
|||
*/ |
|||
export function hasClass(ele, cls) { |
|||
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) |
|||
} |
|||
|
|||
/** |
|||
* Add class to element |
|||
* @param {HTMLElement} elm |
|||
* @param {string} cls |
|||
*/ |
|||
export function addClass(ele, cls) { |
|||
if (!hasClass(ele, cls)) ele.className += ' ' + cls |
|||
} |
|||
|
|||
/** |
|||
* Remove class from element |
|||
* @param {HTMLElement} elm |
|||
* @param {string} cls |
|||
*/ |
|||
export function removeClass(ele, cls) { |
|||
if (hasClass(ele, cls)) { |
|||
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') |
|||
ele.className = ele.className.replace(reg, ' ') |
|||
} |
|||
} |
|||
|
|||
export function makeMap(str, expectsLowerCase) { |
|||
const map = Object.create(null) |
|||
const list = str.split(',') |
|||
for (let i = 0; i < list.length; i++) { |
|||
map[list[i]] = true |
|||
} |
|||
return expectsLowerCase |
|||
? val => map[val.toLowerCase()] |
|||
: val => map[val] |
|||
} |
|||
|
|||
export const exportDefault = 'export default ' |
|||
|
|||
export const beautifierConf = { |
|||
html: { |
|||
indent_size: '2', |
|||
indent_char: ' ', |
|||
max_preserve_newlines: '-1', |
|||
preserve_newlines: false, |
|||
keep_array_indentation: false, |
|||
break_chained_methods: false, |
|||
indent_scripts: 'separate', |
|||
brace_style: 'end-expand', |
|||
space_before_conditional: true, |
|||
unescape_strings: false, |
|||
jslint_happy: false, |
|||
end_with_newline: true, |
|||
wrap_line_length: '110', |
|||
indent_inner_html: true, |
|||
comma_first: false, |
|||
e4x: true, |
|||
indent_empty_lines: true |
|||
}, |
|||
js: { |
|||
indent_size: '2', |
|||
indent_char: ' ', |
|||
max_preserve_newlines: '-1', |
|||
preserve_newlines: false, |
|||
keep_array_indentation: false, |
|||
break_chained_methods: false, |
|||
indent_scripts: 'normal', |
|||
brace_style: 'end-expand', |
|||
space_before_conditional: true, |
|||
unescape_strings: false, |
|||
jslint_happy: true, |
|||
end_with_newline: true, |
|||
wrap_line_length: '110', |
|||
indent_inner_html: true, |
|||
comma_first: false, |
|||
e4x: true, |
|||
indent_empty_lines: true |
|||
} |
|||
} |
|||
|
|||
// 首字母大小
|
|||
export function titleCase(str) { |
|||
return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) |
|||
} |
|||
|
|||
// 下划转驼峰
|
|||
export function camelCase(str) { |
|||
return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) |
|||
} |
|||
|
|||
export function isNumberStr(str) { |
|||
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) |
|||
} |
|||
|
|||
import { parseTime } from './ruoyi' |
|||
|
|||
/** |
|||
* 表格时间格式化 |
|||
*/ |
|||
export function formatDate(cellValue) { |
|||
if (cellValue == null || cellValue == "") return ""; |
|||
var date = new Date(cellValue) |
|||
var year = date.getFullYear() |
|||
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 |
|||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() |
|||
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() |
|||
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() |
|||
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() |
|||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds |
|||
} |
|||
|
|||
/** |
|||
* @param {number} time |
|||
* @param {string} option |
|||
* @returns {string} |
|||
*/ |
|||
export function formatTime(time, option) { |
|||
if (('' + time).length === 10) { |
|||
time = parseInt(time) * 1000 |
|||
} else { |
|||
time = +time |
|||
} |
|||
const d = new Date(time) |
|||
const now = Date.now() |
|||
|
|||
const diff = (now - d) / 1000 |
|||
|
|||
if (diff < 30) { |
|||
return '刚刚' |
|||
} else if (diff < 3600) { |
|||
// less 1 hour
|
|||
return Math.ceil(diff / 60) + '分钟前' |
|||
} else if (diff < 3600 * 24) { |
|||
return Math.ceil(diff / 3600) + '小时前' |
|||
} else if (diff < 3600 * 24 * 2) { |
|||
return '1天前' |
|||
} |
|||
if (option) { |
|||
return parseTime(time, option) |
|||
} else { |
|||
return ( |
|||
d.getMonth() + |
|||
1 + |
|||
'月' + |
|||
d.getDate() + |
|||
'日' + |
|||
d.getHours() + |
|||
'时' + |
|||
d.getMinutes() + |
|||
'分' |
|||
) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {string} url |
|||
* @returns {Object} |
|||
*/ |
|||
export function getQueryObject(url) { |
|||
url = url == null ? window.location.href : url |
|||
const search = url.substring(url.lastIndexOf('?') + 1) |
|||
const obj = {} |
|||
const reg = /([^?&=]+)=([^?&=]*)/g |
|||
search.replace(reg, (rs, $1, $2) => { |
|||
const name = decodeURIComponent($1) |
|||
let val = decodeURIComponent($2) |
|||
val = String(val) |
|||
obj[name] = val |
|||
return rs |
|||
}) |
|||
return obj |
|||
} |
|||
|
|||
/** |
|||
* @param {string} input value |
|||
* @returns {number} output value |
|||
*/ |
|||
export function byteLength(str) { |
|||
// returns the byte length of an utf8 string
|
|||
let s = str.length |
|||
for (var i = str.length - 1; i >= 0; i--) { |
|||
const code = str.charCodeAt(i) |
|||
if (code > 0x7f && code <= 0x7ff) s++ |
|||
else if (code > 0x7ff && code <= 0xffff) s += 2 |
|||
if (code >= 0xDC00 && code <= 0xDFFF) i-- |
|||
} |
|||
return s |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} actual |
|||
* @returns {Array} |
|||
*/ |
|||
export function cleanArray(actual) { |
|||
const newArray = [] |
|||
for (let i = 0; i < actual.length; i++) { |
|||
if (actual[i]) { |
|||
newArray.push(actual[i]) |
|||
} |
|||
} |
|||
return newArray |
|||
} |
|||
|
|||
/** |
|||
* @param {Object} json |
|||
* @returns {Array} |
|||
*/ |
|||
export function param(json) { |
|||
if (!json) return '' |
|||
return cleanArray( |
|||
Object.keys(json).map(key => { |
|||
if (json[key] === undefined) return '' |
|||
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) |
|||
}) |
|||
).join('&') |
|||
} |
|||
|
|||
/** |
|||
* @param {string} url |
|||
* @returns {Object} |
|||
*/ |
|||
export function param2Obj(url) { |
|||
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') |
|||
if (!search) { |
|||
return {} |
|||
} |
|||
const obj = {} |
|||
const searchArr = search.split('&') |
|||
searchArr.forEach(v => { |
|||
const index = v.indexOf('=') |
|||
if (index !== -1) { |
|||
const name = v.substring(0, index) |
|||
const val = v.substring(index + 1, v.length) |
|||
obj[name] = val |
|||
} |
|||
}) |
|||
return obj |
|||
} |
|||
|
|||
/** |
|||
* @param {string} val |
|||
* @returns {string} |
|||
*/ |
|||
export function html2Text(val) { |
|||
const div = document.createElement('div') |
|||
div.innerHTML = val |
|||
return div.textContent || div.innerText |
|||
} |
|||
|
|||
/** |
|||
* Merges two objects, giving the last one precedence |
|||
* @param {Object} target |
|||
* @param {(Object|Array)} source |
|||
* @returns {Object} |
|||
*/ |
|||
export function objectMerge(target, source) { |
|||
if (typeof target !== 'object') { |
|||
target = {} |
|||
} |
|||
if (Array.isArray(source)) { |
|||
return source.slice() |
|||
} |
|||
Object.keys(source).forEach(property => { |
|||
const sourceProperty = source[property] |
|||
if (typeof sourceProperty === 'object') { |
|||
target[property] = objectMerge(target[property], sourceProperty) |
|||
} else { |
|||
target[property] = sourceProperty |
|||
} |
|||
}) |
|||
return target |
|||
} |
|||
|
|||
/** |
|||
* @param {HTMLElement} element |
|||
* @param {string} className |
|||
*/ |
|||
export function toggleClass(element, className) { |
|||
if (!element || !className) { |
|||
return |
|||
} |
|||
let classString = element.className |
|||
const nameIndex = classString.indexOf(className) |
|||
if (nameIndex === -1) { |
|||
classString += '' + className |
|||
} else { |
|||
classString = |
|||
classString.substr(0, nameIndex) + |
|||
classString.substr(nameIndex + className.length) |
|||
} |
|||
element.className = classString |
|||
} |
|||
|
|||
/** |
|||
* @param {string} type |
|||
* @returns {Date} |
|||
*/ |
|||
export function getTime(type) { |
|||
if (type === 'start') { |
|||
return new Date().getTime() - 3600 * 1000 * 24 * 90 |
|||
} else { |
|||
return new Date(new Date().toDateString()) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {Function} func |
|||
* @param {number} wait |
|||
* @param {boolean} immediate |
|||
* @return {*} |
|||
*/ |
|||
export function debounce(func, wait, immediate) { |
|||
let timeout, args, context, timestamp, result |
|||
|
|||
const later = function() { |
|||
// 据上一次触发时间间隔
|
|||
const last = +new Date() - timestamp |
|||
|
|||
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
|
|||
if (last < wait && last > 0) { |
|||
timeout = setTimeout(later, wait - last) |
|||
} else { |
|||
timeout = null |
|||
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
|
|||
if (!immediate) { |
|||
result = func.apply(context, args) |
|||
if (!timeout) context = args = null |
|||
} |
|||
} |
|||
} |
|||
|
|||
return function(...args) { |
|||
context = this |
|||
timestamp = +new Date() |
|||
const callNow = immediate && !timeout |
|||
// 如果延时不存在,重新设定延时
|
|||
if (!timeout) timeout = setTimeout(later, wait) |
|||
if (callNow) { |
|||
result = func.apply(context, args) |
|||
context = args = null |
|||
} |
|||
|
|||
return result |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This is just a simple version of deep copy |
|||
* Has a lot of edge cases bug |
|||
* If you want to use a perfect deep copy, use lodash's _.cloneDeep |
|||
* @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
|
|||
// }
|
|||
|
|||
/** |
|||
* @param {Array} arr |
|||
* @returns {Array} |
|||
*/ |
|||
export function uniqueArr(arr) { |
|||
return Array.from(new Set(arr)) |
|||
} |
|||
|
|||
/** |
|||
* @returns {string} |
|||
*/ |
|||
export function createUniqueString() { |
|||
const timestamp = +new Date() + '' |
|||
const randomNum = parseInt((1 + Math.random()) * 65536) + '' |
|||
return (+(randomNum + timestamp)).toString(32) |
|||
} |
|||
|
|||
/** |
|||
* Check if an element has a class |
|||
* @param {HTMLElement} elm |
|||
* @param {string} cls |
|||
* @returns {boolean} |
|||
*/ |
|||
export function hasClass(ele, cls) { |
|||
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) |
|||
} |
|||
|
|||
/** |
|||
* Add class to element |
|||
* @param {HTMLElement} elm |
|||
* @param {string} cls |
|||
*/ |
|||
export function addClass(ele, cls) { |
|||
if (!hasClass(ele, cls)) ele.className += ' ' + cls |
|||
} |
|||
|
|||
/** |
|||
* Remove class from element |
|||
* @param {HTMLElement} elm |
|||
* @param {string} cls |
|||
*/ |
|||
export function removeClass(ele, cls) { |
|||
if (hasClass(ele, cls)) { |
|||
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') |
|||
ele.className = ele.className.replace(reg, ' ') |
|||
} |
|||
} |
|||
|
|||
export function makeMap(str, expectsLowerCase) { |
|||
const map = Object.create(null) |
|||
const list = str.split(',') |
|||
for (let i = 0; i < list.length; i++) { |
|||
map[list[i]] = true |
|||
} |
|||
return expectsLowerCase |
|||
? val => map[val.toLowerCase()] |
|||
: val => map[val] |
|||
} |
|||
|
|||
export const exportDefault = 'export default ' |
|||
|
|||
export const beautifierConf = { |
|||
html: { |
|||
indent_size: '2', |
|||
indent_char: ' ', |
|||
max_preserve_newlines: '-1', |
|||
preserve_newlines: false, |
|||
keep_array_indentation: false, |
|||
break_chained_methods: false, |
|||
indent_scripts: 'separate', |
|||
brace_style: 'end-expand', |
|||
space_before_conditional: true, |
|||
unescape_strings: false, |
|||
jslint_happy: false, |
|||
end_with_newline: true, |
|||
wrap_line_length: '110', |
|||
indent_inner_html: true, |
|||
comma_first: false, |
|||
e4x: true, |
|||
indent_empty_lines: true |
|||
}, |
|||
js: { |
|||
indent_size: '2', |
|||
indent_char: ' ', |
|||
max_preserve_newlines: '-1', |
|||
preserve_newlines: false, |
|||
keep_array_indentation: false, |
|||
break_chained_methods: false, |
|||
indent_scripts: 'normal', |
|||
brace_style: 'end-expand', |
|||
space_before_conditional: true, |
|||
unescape_strings: false, |
|||
jslint_happy: true, |
|||
end_with_newline: true, |
|||
wrap_line_length: '110', |
|||
indent_inner_html: true, |
|||
comma_first: false, |
|||
e4x: true, |
|||
indent_empty_lines: true |
|||
} |
|||
} |
|||
|
|||
// 首字母大小
|
|||
export function titleCase(str) { |
|||
return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) |
|||
} |
|||
|
|||
// 下划转驼峰
|
|||
export function camelCase(str) { |
|||
return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) |
|||
} |
|||
|
|||
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]' |
|||
} |
|||
|
|||
|
|||
@ -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) |
|||
}) |
|||
} |
|||
@ -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) |
|||
}) |
|||
}) |
|||
} |
|||
@ -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 |
|||
@ -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) |
|||
}) |
|||
} |
|||
@ -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') |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
|
|||
@ -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> |
|||
@ -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> |
|||
|
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
|
|||