Commit 62423262 authored by kaitly205422@163.com's avatar kaitly205422@163.com

init流程代码

parent 80804b89
......@@ -37,6 +37,8 @@
},
"dependencies": {
"@riophae/vue-treeselect": "0.4.0",
"@vant/area-data": "^1.5.1",
"@zxing/library": "^0.20.0",
"axios": "0.24.0",
"clipboard": "2.0.8",
"core-js": "3.25.3",
......@@ -48,10 +50,14 @@
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"moment": "^2.30.1",
"monaco-editor": "^0.46.0",
"nprogress": "0.2.0",
"quill": "1.3.7",
"screenfull": "5.0.2",
"signature_pad": "^4.1.7",
"sortablejs": "1.10.2",
"vant": "^2.12.48",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
......
import request from '@/utils/request'
// 查询表单及分组
export function getGroupModels(param) {
return request({
url: 'wflow/model/group/list',
method: 'get',
params: param
})
}
// 查询表单及分组
export function getGroupModelsByUser(param) {
return request({
url: 'wflow/model/list/byUser',
method: 'get',
params: param
})
}
// 查询所有模型分组
export function getModelGroups(param) {
return request({
url: 'wflow/model/group',
method: 'get',
params: param
})
}
// 表单分组排序
export function modelGroupsSort(param) {
return request({
url: 'wflow/model/group/sort',
method: 'put',
data: param
})
}
// 表单排序
export function modelsSort(groupId, param) {
return request({
url: `wflow/model/sort/${groupId}`,
method: 'put',
data: param
})
}
// 修改分组
export function updateModelGroupName(groupId, param) {
return request({
url: `wflow/model/group/${groupId}`,
method: 'put',
params: param
})
}
// 新增模型分组
export function createModelGroup(param) {
return request({
url: `wflow/model/group`,
method: 'post',
params: param
})
}
// 删除分组
export function deleteModelGroup(groupId) {
return request({
url: `wflow/model/group/${groupId}`,
method: 'delete'
})
}
// 删除模型
export function deleteModel(modelId) {
return request({
url: `wflow/model/${modelId}`,
method: 'delete'
})
}
// 获取模型
export function getModelById(modelId) {
return request({
url: `wflow/model/detail/${modelId}`,
method: 'get'
})
}
// 获取模型
export function getModelByDefId(defId) {
return request({
url: `wflow/model/detail/def/${defId}`,
method: 'get'
})
}
// 修改分组
export function modelMoveToGroup(modelId, groupId) {
return request({
url: `wflow/model/${modelId}/move/${groupId}`,
method: 'put'
})
}
// 启用或停用流程
export function enOrDisModel(modelId, type) {
return request({
url: `wflow/model/${modelId}/active/${type}`,
method: 'put'
})
}
// 获取抄送我的流程
export function getCcMeList(params) {
return request({
url: `wflow/process/ccMe`,
method: 'get',
params: params
})
}
// 获取控制台统计数据
export function getProcessCountData() {
return request({
url: `wflow/process/instance/count`,
method: 'get'
})
}
export default {
createModelGroup, updateModelGroupName, modelGroupsSort,
modelsSort, getGroupModels, getModelGroups, modelMoveToGroup,
deleteModelGroup, deleteModel, getModelById, getModelByDefId,
enOrDisModel, getCcMeList, getProcessCountData, getGroupModelsByUser
}
import request from '@/utils/request'
// 查询组织架构树
export function getOrgTree(param) {
return request({
url: 'oa/org/tree',
method: 'get',
params: param
})
}
// 查询系统角色
export function getRole() {
return request({
url: 'oa/org/role',
method: 'get'
})
}
// 搜索人员
export function getUserByName(param) {
return request({
url: 'oa/org/tree/user/search',
method: 'get',
params: param
})
}
// 搜索人员
export function getUserDepts(userId) {
return request({
url: `oa/org/user/${userId}/dept`,
method: 'get'
})
}
// 获取审批代理人
export function getUserAgent() {
return request({
url: `oa/org/user/agent`,
method: 'get'
})
}
// 获取审批代理人
export function setUserAgent(params) {
return request({
url: `oa/org/user/agent`,
method: 'put',
data: params
})
}
// 获取审批代理人
export function cancelUserAgent() {
return request({
url: `oa/org/user/agent`,
method: 'delete'
})
}
export default {
getOrgTree, getUserByName, getRole,
getUserDepts, getUserAgent, setUserAgent, cancelUserAgent
}
import request from '@/utils/request'
//获取用户待办
export function getUserTodoList(params) {
return request({
url: `wflow/process/task/todoList`,
method: 'get',
params: params
})
}
//获取用户发起的实例
export function getUserSubmittedList(params) {
return request({
url: `wflow/process/mySubmitted`,
method: 'get',
params: params
})
}
//获取所有发起的实例
export function getSubmittedList(params) {
return request({
url: `wflow/process/submittedList`,
method: 'get',
params: params
})
}
//获取我已处理的所有审批实例
export function getIdoList(params) {
return request({
url: `wflow/process/task/idoList`,
method: 'get',
params: params
})
}
//查询流程进度及表单
export function getFormAndProcessProgress(instanceId, nodeId) {
return request({
url: `wflow/process/progress/${instanceId}/${nodeId}`,
method: 'get'
})
}
//处理任务
export function approvalTask(params) {
return request({
url: `wflow/process/task/handler`,
method: 'post',
data: params
})
}
//获取流程实例表单数据
export function getInstanceFormData(instanceId) {
return request({
url: `wflow/process/form/data/by/${instanceId}`,
method: 'get'
})
}
//获取可回退的节点
export function getEnableRecallNodes(instanceId, taskId) {
return request({
url: `wflow/process/task/recall/nodes`,
method: 'get',
params: { instanceId: instanceId, taskId: taskId }
})
}
export default {
getUserTodoList, getUserSubmittedList, getSubmittedList,
getFormAndProcessProgress, approvalTask, getInstanceFormData,
getEnableRecallNodes, getIdoList
}
<template>
<div ref="editor" :style="{ width: '100%', 'height': height }"></div>
</template>
<script>
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
export default {
name: 'CodeEditor',
components: {},
inheritAttrs: false,
props: {
options: {
type: Object,
default: () => {
return {
theme: "vs",
selectOnLineNumbers: true,
roundedSelection: false,
readOnly: false,
automaticLayout: true,
glyphMargin: true,
showFoldingControls: "always",
formatOnPaste: true,
formatOnType: true,
folding: true
}
}
},
lang: {
type: String,
default: 'javascript'
},
height: {
type: String,
default: '300px'
},
value: {
type: String,
default: ''
}
},
data() {
return {
editor: null,
randomKey: '',
code: ''
}
},
mounted() {
this.init();
},
beforeDestroy() {
// 销毁之前把monaco的实例也销毁了,不然会多次注册
if (this.editor) {
this.editor.dispose();
}
},
methods: {
init() {
this.editor = monaco.editor.create(this.$refs.editor, {
theme: "vs", // 主题
value: this.value, // 默认显示的值
language: this.lang,
folding: true, // 是否折叠
foldingHighlight: true, // 折叠等高线
foldingStrategy: "indentation", // 折叠方式 auto | indentation
showFoldingControls: "always", // 是否一直显示折叠 always | mouseover
disableLayerHinting: true, // 等宽优化
emptySelectionClipboard: false, // 空选择剪切板
selectionClipboard: false, // 选择剪切板
automaticLayout: true, // 自动布局
codeLens: false, // 代码镜头
scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
colorDecorators: true, // 颜色装饰器
accessibilitySupport: "off", // 辅助功能支持 "auto" | "off" | "on"
lineNumbers: "on", // 行号 取值: "on" | "off" | "relative" | "interval" | function
lineNumbersMinChars: 5, // 行号最小字符 number
enableSplitViewResizing: false,
readOnly: false, //是否只读 取值 true | false
});
this.editor.onDidChangeModelContent((event) => {
this.$emit('input', this.editor.getValue())
})
},
reloadCode(value) {
this.editor.setValue(value || this.value)
}
},
}
</script>
<style lang="scss" scoped>
.code-editor-wrapper {
height: 100%;
text-align: left;
overflow: auto;
::v-deep .CodeMirror {
height: 100%;
}
}
</style>
<template>
<vue-editor v-if="!reloading" v-model="_value" :init="init" :disabled="readonly"></vue-editor>
</template>
<script>
import tinymce from 'tinymce/tinymce'
import vueEditor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
import 'tinymce/icons/default'
import 'tinymce/skins/ui/oxide/skin.css'
import 'tinymce/plugins/print'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/template'
import 'tinymce/plugins/code'
import 'tinymce/plugins/table'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/imagetools'
import 'tinymce/plugins/textpattern'
import 'tinymce/plugins/help'
import 'tinymce/plugins/autoresize'
export default {
components: {
vueEditor
},
props: {
value: {
type: String,
required:false
},
variable:{
type: Object,
default: () => {
return {}
}
},
readonly: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default: 'print preview autolink directionality visualblocks visualchars fullscreen image link template code table charmap pagebreak nonbreaking insertdatetime advlist lists imagetools textpattern help autoresize'
},
toolbar: {
type: [String, Array],
default: 'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link | \a\
lignleft aligncenter alignright alignjustify outdent indent | bullist numlist | blockquote subscript superscript removeformat |\
table image charmap emoticons hr pagebreak insertdatetime | \
styleselect formatselect fontselect fontsizeselect | \
| bdmap indent2em lineheight formatpainter axupimgs | print preview | fullscreen',
branding:false
}
},
data() {
return {
//初始化配置
editor: null,
init: {
language_url: '/editor/js/zh_CN.js',
language: 'zh_CN',
skin_url: '/editor/skins/lightgray',
height: 800,
inline: false,
plugins: this.plugins,
toolbar: this.toolbar,
branding: false,
menubar: true,
toolbar_drawer: false,
toolbar_sticky: true,
convert_urls: false,
images_upload_handler: (blobInfo, success) => {
let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename());
formData.append('biz', "jeditor");
formData.append("jeditor","1");
/* uploadAction(window._CONFIG['domianURL']+"/sys/common/upload", formData).then((res) => {
if (res.success) {
if(res.message == 'local'){
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
success(img)
}else{
let img = ''//getFileAccessHttpUrl(res.message)
success(img)
}
}
})*/
},
setup: (editor) => {
editor.on('init', (e) => {
console.log('编辑器初始化完成...', editor)
this.editor = editor
editor.dom.doc.ondrop = this.dropElOnEditor
});
}
},
myValue: this.value,
reloading: false,
}
},
computed:{
_value:{
get(){
return this.value
},
set(val){
this.$emit('input', val)
}
}
},
mounted() {
},
methods: {
dropElOnEditor(ev){
tinymce.activeEditor.focus();
const text = ev.dataTransfer.getData('text')
ev.dataTransfer.clearData()
console.log(text, ev)
const cp = JSON.parse(text)
//this.setCursor(ev.target, 1)
if (cp.name && cp.name === 'TableList'){
this.insertDom(this.createTable(cp))
}else {
this.insertText('${' + cp.id + '}')
}
ev.preventDefault()
},
insertText(text){
this.editor.execCommand('mceInsertContent', false, text);
},
autoFocus(x, y){
let evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 0, 0, 0, x, y, false, false, false, false, 0, null);
document.body.dispatchEvent(evt);
},
//移动游标到指定位置,这里需要计算下移动多少
setCursor(target, offset){
tinymce.activeEditor.selection.setCursorLocation(target, offset)
//this.editor.setCursorLocation(target, 2)
},
//将变量绑定文档
bindVar(){
let content = this.editor.dom.doc.getContent()
//替换基础变量
content = content.replace(/\$\{.*\}/gi, mc => {
return this.variable[mc.substring(2, mc.length-1).trim()]
})
//根据字段搜索所有表格
const tableDoms = []
for (let key in this.variable) {
const dom = this.editor.dom.doc.getElementsByClassName(key)
if (dom && dom.tagName === 'table'){
tableDoms.push(dom)
}
}
document.getElementById().children
//对所有表格进行数据填充
tableDoms.forEach(tb => {
//找表格存在变量那一行开始,进行行数渲染
//取得tr表格行
const rows = tb.children[0].children || []
for (let row of rows) {
if (/\$\{.*\}/gi.test(row.innerText)){
}
}
})
this.editor.dom.doc.getElementById()
},
insertDom(dom){
this.insertText(dom.innerHTML)
},
createTable(field){
//const height = 40;
const table = document.createElement('table')
const tbody = document.createElement('tbody')
const trh = document.createElement('tr')
const trd = document.createElement('tr')
table.setAttribute('class', field.id)
table.setAttribute('style', 'border-collapse: collapse; width: 99.8363%; height: 74px;')
table.setAttribute('border', '1')
table.appendChild(tbody)
trh.setAttribute('style', 'height: 40px;')
trd.setAttribute('style', 'height: 40px;')
tbody.appendChild(trh)
tbody.appendChild(trd)
//构造表格行
field.props.columns.forEach(col => {
const tdh = document.createElement('td')
const style = `width: ${(100 / field.props.columns.length).toFixed(3)}%; height: 40px;`
tdh.setAttribute('style', style)
tdh.innerText = col.title
trh.appendChild(tdh)
const tdd = document.createElement('td')
tdd.setAttribute('style', style)
tdd.innerText = '${' + col.id + '}'
trd.appendChild(tdd)
})
const tableDom = document.createElement('span')
tableDom.appendChild(table)
return tableDom
},
reload() {
this.reloading = true
this.$nextTick(() => this.reloading = false)
},
},
watch: {
value(newValue) {
this.myValue = (newValue == null ? '' : newValue)
},
myValue(newValue) {
if(this.triggerChange){
this.$emit('change', newValue)
}else{
this.$emit('input', newValue)
}
}
}
}
</script>
<style scoped>
</style>
<template>
<div :class="{ 'line': row === 1, 'lines': row > 1 }" :title="hoverTip ? content : null" :style="{ '--row': row }">
<slot name="pre"></slot>
{{ content }}
<slot name="aft"></slot>
</div>
</template>
<script>
//超出指定行数自动隐藏文字
export default {
name: "Ellipsis",
install(Vue) {
Vue.component('ellipsis', this)
},
components: {},
props: {
row: {
type: Number,
default: 1
},
hoverTip: {
type: Boolean,
default: false
},
content: {
type: String,
default: ''
}
},
data() {
return {}
},
methods: {}
}
</script>
<style lang="scss" scoped>
.line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lines {
display: -webkit-box;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: var(--row);
-webkit-box-orient: vertical;
}
</style>
<template>
<div class="m-form-item">
<div class="m-form-item_title">
<span class="title-required" v-if="required || rule.length > 0">* </span>
<span>{{ label }}</span>
</div>
<div class="m-form-item_content">
<slot ref="item"></slot>
</div>
<div v-if="showError && rule.length > 0" class="valid-error">
{{ rule[0].message || '请完善' }}
</div>
</div>
</template>
<script>
export default {
name: "FormItem",
components: {},
props: {
label: {
type: String,
default: ''
},
rule: {
type: Array,
default: () => {
return []
}
},
prop: {
type: String,
default: ''
},
model: {
type: Object,
default: () => {
return {}
}
},
required: {
type: Boolean,
default: false
}
},
data() {
return {
showError: false
}
},
methods: {
isError() {
return this.showError
},
validate(call) {
if (this.rule.length > 0) {
if (this.rule[0].type === 'array') {
this.showError = !(Array.isArray(this.model[this.prop]) && this.model[this.prop].length > 0)
} else {
this.showError = !this.$isNotEmpty(this.model[this.prop])
}
}
if (!this.showError) {
}
if (call) {
call(!this.showError)
}
}
}
}
</script>
<style lang="scss" scoped>
.m-form-item {
position: relative;
padding: 10px 10px 20px 10px;
background: white;
//margin-top: 5px;
margin-bottom: 10px;
&>div:first-child {
margin-bottom: 8px;
font-size: 1.2rem;
color: #545456;
}
&>div:last-child {
//padding: 0 5px;
}
.title-required {
color: #f56c6c;
}
.valid-error {
color: #f56c6c;
position: absolute;
font-size: 0.85rem;
}
}
</style>
This diff is collapsed.
<template>
<div v-if="!isTask">
<el-tag type="primary" :size="size" v-if="instance.status === 'RUNNING'">进行中</el-tag>
<el-tag type="danger" :size="size" v-else-if="instance.result === 'refuse-end'">审批驳回</el-tag>
<el-tag type="info" :size="size" v-else-if="instance.result === 'cancel-end'">已撤销</el-tag>
<el-tag type="success" :size="size" v-else>审批通过</el-tag>
</div>
<el-tag v-else :type="(taskStatus[instance.taskResult] || {}).type" size="medium">{{(taskStatus[instance.taskResult] || {}).text}}</el-tag>
</template>
<script>
export default {
name: "ProcessStatus",
components: {},
props:{
instance:{
type: Object,
default: () => {
return {}
}
},
size:{
type: String,
default: 'medium'
},
isTask: {
type: Boolean,
default: false
}
},
data() {
return {
taskStatus:{
agree: {type: 'success', text: '已同意'},
refuse: {type: 'danger', text: '已拒绝'},
recall: {type: 'warning', text: '已退回'},
transfer: {type: 'primary', text: '已转交'}
},
}
},
methods: {}
}
</script>
<style scoped>
</style>
<template>
<w-dialog v-model="_value" closeFree title="扫码录入" width="500px" :showFooter="false" @closed="closeScan"
@opened="openScan" v-if="pcMode">
<div class="scan-video" v-if="_value">
<video ref="video" style="width: 100%;" id="video" autoplay></video>
</div>
<p style="color:#656363; text-align: center">{{ tipMsg }}</p>
</w-dialog>
<popup v-else v-model="_value" :style="popupStyle" @closed="closeScan" @opened="openScan" position="left" lazy-render
closeable safe-area-inset-bottom>
<div class="scan-cancel" @click="_value = false">取消</div>
<div class="m-scan-video" v-if="_value">
<video ref="video" id="video" autoplay></video>
</div>
<p style="color:white; text-align: center">{{ tipMsg }}</p>
</popup>
</template>
<script>
import { Popup } from 'vant'
import { BrowserMultiFormatReader } from '@zxing/library';
export default {
name: "ScanCode",
components: { Popup },
props: {
value: {
type: Boolean,
default: false
},
pcMode: {
type: Boolean,
default: false
}
},
data() {
return {
tipMsg: '',
codeReader: null,
popupStyle: {
height: '100%',
width: '100%',
background: 'black',
opacity: '0.8',
}
}
},
computed: {
_value: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
}
}
},
destroyed() {
this.closeScan()
},
methods: {
closeScan() {
if (this.codeReader) {
this.codeReader.reset();
this.codeReader = null
}
},
async openScan() {
if (!this.codeReader) {
this.codeReader = new BrowserMultiFormatReader();
}
this.codeReader.getVideoInputDevices().then((videoInputDevices) => {
this.tipMsg = '正在打开摄像头...';
console.log('videoInputDevices', videoInputDevices);
// 默认获取第一个摄像头设备id
let firstDeviceId = videoInputDevices[0].deviceId;
// 获取第一个摄像头设备的名称
const videoInputDeviceslablestr = JSON.stringify(videoInputDevices[0].label);
if (videoInputDevices.length > 1) {
// 判断是否后置摄像头
if (videoInputDeviceslablestr.indexOf('back') > -1) {
firstDeviceId = videoInputDevices[0].deviceId;
} else {
firstDeviceId = videoInputDevices[1].deviceId;
}
}
this.decodeFromInputVideoFunc(firstDeviceId);
}).catch(err => {
this.tipMsg = '发生错误: ' + JSON.stringify(err);
});
},
decodeFromInputVideoFunc(firstDeviceId) {
this.codeReader.reset(); // 重置
this.codeReader.decodeFromInputVideoDeviceContinuously(firstDeviceId, 'video', (result, err) => {
this.tipMsg = '请将摄像头对准条形码/二维码...';
if (result) {
if (result.text) {
this.$emit('ok', result.text)
this._value = false
}
}
if (err && !(err)) {
this.tipMsg = '识别失败: ' + JSON.stringify(err);;
}
});
}
}
}
</script>
<style lang="scss" scoped>
.scan-cancel {
position: fixed;
top: 20px;
left: 20px;
color: white;
cursor: pointer;
font-size: 1.1rem;
opacity: 1
}
.m-scan-video {
width: 100%;
text-align: center;
margin-top: 40%;
video {
width: 80%;
}
}</style>
<template>
<el-tooltip :effect="isDark ? 'dark':'light'" :content="content" placement="top-start">
<span>
<slot></slot>
<i class="el-icon-question" style="cursor: pointer"></i>
</span>
</el-tooltip>
</template>
<script>
export default {
install(Vue) {
Vue.component('Tip', this)
},
name: "Tip",
components: {},
props:{
isDark:{
type: Boolean,
default: true
},
content:{
type: String,
default: ''
}
},
data() {
return {}
},
methods: {}
}
</script>
<style scoped>
</style>
<template>
<el-dialog custom-class="custom-dialog" :class="{ 'border': true, 'fullscreen': fullscreen }" :width="width"
:title="title" append-to-body :close-on-click-modal="clickClose" @opened="$emit('opened')" @closed="$emit('closed')"
:destroy-on-close="closeFree" :visible.sync="_value">
<slot name="title" slot="title"></slot>
<slot></slot>
<div slot="footer" v-if="showFooter">
<el-button size="mini" @click="_value = false; $emit('cancel')">{{ cancelText }}</el-button>
<el-button size="mini" type="primary" @click="$emit('ok')">{{ okText }}</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: "WDialog",
install(Vue) {
Vue.component('WDialog', this)
},
components: {},
props: {
title: {
type: String,
default: ''
},
width: {
type: String,
default: '50%'
},
fullscreen: {
type: Boolean,
default: false
},
noPadding: {
type: Boolean,
default: false
},
value: {
type: Boolean,
default: false
},
clickClose: {
type: Boolean,
default: false
},
closeFree: {
type: Boolean,
default: false
},
showFooter: {
type: Boolean,
default: true
},
cancelText: {
type: String,
default: '取 消'
},
okText: {
type: String,
default: '确 定'
},
border: {
type: Boolean,
default: true
}
},
computed: {
_value: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
}
}
},
data() {
return {}
},
methods: {}
}
</script>
<style lang="scss" scoped>
::v-deep .custom-dialog {
.el-dialog__header {
padding: 10px 20px;
.el-dialog__title {
font-size: 17px;
}
.el-dialog__headerbtn {
top: 15px;
.i {
font-size: large;
}
}
}
.el-dialog__footer {
padding: 10px 20px;
}
}
.border {
::v-deep .el-dialog__header {
border-bottom: 1px solid #e8e8e8;
}
::v-deep .el-dialog__footer {
border-top: 1px solid #e8e8e8;
}
}
.fullscreen {
overflow: hidden;
::v-deep .el-dialog {
margin-top: 0 !important;
width: 100% !important;
.el-dialog__body {
padding: 0;
height: calc(100vh - 94px);
}
}
}
</style>
......@@ -4,6 +4,7 @@ import Cookies from 'js-cookie'
import Element from 'element-ui'
import './assets/styles/element-variables.scss'
import 'vant/lib/index.css';
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
......@@ -37,7 +38,7 @@ import DictTag from '@/components/DictTag'
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
import Ellipsis from '@/components/common/Ellipsis'
// 全局方法挂载
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
......@@ -48,6 +49,9 @@ Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree
Vue.prototype.$deepCopy = function (obj) {
return JSON.parse(JSON.stringify(obj))
}
// 全局组件挂载
Vue.component('DictTag', DictTag)
......@@ -58,6 +62,7 @@ Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.use(Ellipsis);
Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
......
......@@ -30,11 +30,11 @@ router.beforeEach((to, from, next) => {
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
......
......@@ -6,6 +6,7 @@ import user from './modules/user'
import tagsView from './modules/tagsView'
import permission from './modules/permission'
import settings from './modules/settings'
import task from './modules/task'
import getters from './getters'
Vue.use(Vuex)
......@@ -17,6 +18,7 @@ const store = new Vuex.Store({
user,
tagsView,
permission,
task,
settings
},
getters
......
const state = {
nodeMap: new Map(),
isEdit: null,
loginUser: JSON.parse(localStorage.getItem('loginUser') || '{}'),
selectedNode: {},
selectFormItem: null,
design: {},
}
const mutations = {
selectedNode(state, val) {
state.selectedNode = val
},
loadForm(state, val) {
state.design = val
},
setIsEdit(state, val) {
state.isEdit = val
}
}
const actions = {
}
export default {
namespaced: true,
state,
mutations,
actions
}
//判断是否为主要业务节点
export function isPrimaryNode(node){
return node &&
(node.type === 'ROOT' || node.type === 'APPROVAL'
|| node.type === 'CC' || node.type === 'DELAY'
|| node.type === 'TRIGGER');
}
export function isBranchNode(node){
return node && (node.type === 'CONDITIONS' || node.type === 'CONCURRENTS');
}
export function isEmptyNode(node){
return node && (node.type === 'EMPTY')
}
//是分支节点
export function isConditionNode(node){
return node.type === 'CONDITIONS';
}
//是分支节点
export function isBranchSubNode(node){
return node && (node.type === 'CONDITION' || node.type === 'CONCURRENT');
}
export function isConcurrentNode(node){
return node.type === 'CONCURRENTS'
}
export function forEachNode(node, callback){
if (isBranchNode(node)){
if (callback(node)){return}
node.branchs.map(branchNode => {
if (callback(branchNode)){return}
forEachNode(branchNode.children, callback)
})
forEachNode(node.children, callback)
}else if (isPrimaryNode(node) || isEmptyNode(node) || isBranchSubNode(node)){
if (callback(node)){return}
forEachNode(node.children, callback)
}
}
export default {
forEachNode, isPrimaryNode, isBranchNode, isEmptyNode,
isConditionNode, isBranchSubNode, isConcurrentNode
}
/**
* 图片转base64并压缩指定大小
* @param self
* @param size
*/
export function imgFileZip(self, size, call) {
//创建一个读取文件的对象
let reader = new FileReader();
//读取文件,转码
reader.readAsDataURL(self.files[0]);
reader.onload = function (e) {
let base64 = e.target.result; //转码过后的base64编码
console.log("压缩前", base64.length / 1024 + 'KB');
//创建一个图片
let newImage = new Image();
let quality = 0.6; //压缩系数0-1之间,压缩到0.9以上会有bug,注意!(可以自行设置)
newImage.src = base64;
newImage.setAttribute("crossOrigin", 'Anonymous'); //url为外域时需要
let imgWidth, imgHeight;
newImage.onload = function () {
imgWidth = this.width;
imgHeight = this.height;
//给生成图片设置一个默认的宽度(可以自行设置)
let myWidth = 50;
//准备在画布上绘制图片
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
//判断上传的图片的宽高是否超过设置的默认宽度以及设置同比例的高
if (Math.max(imgWidth, imgHeight) > myWidth) {
if (imgWidth > imgHeight) {
canvas.width = myWidth;
canvas.height = myWidth * imgHeight / imgWidth;
} else {
canvas.height = myWidth;
canvas.width = myWidth * imgWidth / imgHeight;
}
} else {
canvas.width = imgWidth;
canvas.height = imgHeight;
}
//清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
//开始绘制图片到画布上
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
let newBase64 = canvas.toDataURL("image/jpeg", quality);//压缩图片大小(重点代码)
// 获取到当前的图片的大小,然后调整成自己需要的大小,例如说需要200KB-500KB之间(可以自行设置)
while (newBase64.length > 1024 * size && quality > 0) {
quality -= 0.02;
newBase64 = canvas.toDataURL("image/jpeg", quality);
}
call(newBase64);
console.log("压缩后", newBase64.length / 1024 + 'KB')
}
};
}
export function imgZip(path, obj, callback) {
var img = new Image();
img.src = path;
img.onload = function () {
var that = this;
// 默认按比例压缩
var w = that.width, h = that.height, scale = w / h;
w = (obj.width || w) / 3;
h = (obj.height || (w / scale)) / 3;
var quality = 0.6; // 默认图片质量为0.7
// 生成canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// 创建属性节点
var anw = document.createAttribute("width");
anw.nodeValue = w;
var anh = document.createAttribute("height");
anh.nodeValue = h;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
ctx.drawImage(that, 0, 0, w, h);
// 图像质量
if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
quality = obj.quality;
}
// quality值越小,所绘制出的图像越模糊
var base64 = canvas.toDataURL('image/jpeg', quality);
// 回调函数返回base64的值
callback(base64);
}
}
export default {
imgFileZip, imgZip
}
// 打印类属性、方法定义
/* eslint-disable */
const Print = function (dom, options) {
if (!(this instanceof Print)) return new Print(dom, options);
this.options = this.extend({
'noPrint': '.no-print'
}, options);
if ((typeof dom) === "string") {
this.dom = document.querySelector(dom);
} else {
this.isDOM(dom)
this.dom = this.isDOM(dom) ? dom : dom.$el;
}
this.init();
};
Print.prototype = {
init: function () {
var content = this.getStyle() + this.getHtml();
this.writeIframe(content);
},
extend: function (obj, obj2) {
for (var k in obj2) {
obj[k] = obj2[k];
}
return obj;
},
getStyle: function () {
var str = "",
styles = document.querySelectorAll('style,link');
for (var i = 0; i < styles.length; i++) {
str += styles[i].outerHTML;
}
str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}</style>";
return str;
},
getHtml: function () {
var inputs = document.querySelectorAll('input');
var textareas = document.querySelectorAll('textarea');
var selects = document.querySelectorAll('select');
for (var k = 0; k < inputs.length; k++) {
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
if (inputs[k].checked == true) {
inputs[k].setAttribute('checked', "checked")
} else {
inputs[k].removeAttribute('checked')
}
} else if (inputs[k].type == "text") {
inputs[k].setAttribute('value', inputs[k].value)
} else {
inputs[k].setAttribute('value', inputs[k].value)
}
}
for (var k2 = 0; k2 < textareas.length; k2++) {
if (textareas[k2].type == 'textarea') {
textareas[k2].innerHTML = textareas[k2].value
}
}
for (var k3 = 0; k3 < selects.length; k3++) {
if (selects[k3].type == 'select-one') {
var child = selects[k3].children;
for (var i in child) {
if (child[i].tagName == 'OPTION') {
if (child[i].selected == true) {
child[i].setAttribute('selected', "selected")
} else {
child[i].removeAttribute('selected')
}
}
}
}
}
// 包裹要打印的元素
let outerHTML = this.dom.parentElement.innerHTML
//this.wrapperRefDom(this.dom).outerHTML
return outerHTML;
},
// 向父级元素循环,包裹当前需要打印的元素
// 防止根级别开头的 css 选择器不生效
wrapperRefDom: function (refDom) {
let prevDom = null
let currDom = refDom
// 判断当前元素是否在 body 中,不在文档中则直接返回该节点
if (!this.isInBody(currDom)) return currDom
while (currDom) {
if (prevDom) {
let element = currDom.cloneNode(false)
element.appendChild(prevDom)
prevDom = element
} else {
prevDom = currDom.cloneNode(true)
}
currDom = currDom.parentElement
}
return prevDom
},
writeIframe: function (content) {
var w, doc, iframe = document.createElement('iframe'),
f = document.body.appendChild(iframe);
iframe.id = "myIframe";
iframe.style.width = '100vw';
iframe.style.height = '100vh';
w = f.contentWindow || f.contentDocument;
doc = f.contentDocument || f.contentWindow.document;
doc.open();
doc.write(content);
doc.close();
var _this = this
iframe.onload = function(){
_this.toPrint(w);
setTimeout(function () {
document.body.removeChild(iframe)
}, 100)
}
},
toPrint: function (frameWindow) {
try {
setTimeout(function () {
frameWindow.focus();
try {
if (!frameWindow.document.execCommand('print', false, null)) {
frameWindow.print();
}
} catch (e) {
frameWindow.print();
}
frameWindow.close();
}, 10);
} catch (err) {
console.log('err', err);
}
},
// 检查一个元素是否是 body 元素的后代元素且非 body 元素本身
isInBody: function (node) {
return (node === document.body) ? false : document.body.contains(node);
},
isDOM: (typeof HTMLElement === 'object') ?
function (obj) {
return obj instanceof HTMLElement;
} :
function (obj) {
return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
}
};
export default Print
......@@ -21,7 +21,14 @@ const service = axios.create({
})
// request拦截器
service.interceptors.request.use(config => {
if (config.url.indexOf('wflow') > -1) {
config.baseURL = 'http://106.13.16.28:10000'
} else {
config.baseURL = process.env.VUE_APP_BASE_API
}
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
......@@ -61,46 +68,47 @@ service.interceptors.request.use(config => {
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = '/index';
})
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
console.log(isRelogin);
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = '/index';
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
Message({ message: msg, type: 'warning' })
return Promise.reject('error')
} else if (code !== 200) {
Notification.error({ title: msg })
return Promise.reject('error')
} else {
return res.data
}
},
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
Message({ message: msg, type: 'warning' })
return Promise.reject('error')
} else if (code !== 200) {
Notification.error({ title: msg })
return Promise.reject('error')
} else {
return res.data
}
},
error => {
console.log('err' + error)
let { message } = error;
......
<template>
<el-popover placement="bottom-start" title="添加流程节点" width="350" trigger="click">
<div class="node-select">
<div @click="addApprovalNode">
<i class="el-icon-s-check" style="color:rgb(255, 148, 62);"></i>
<span>审批人</span>
</div>
<div @click="addCcNode">
<i class="el-icon-s-promotion" style="color:rgb(50, 150, 250);"></i>
<span>抄送人</span>
</div>
<div @click="addConditionsNode">
<i class="el-icon-share" style="color:rgb(21, 188, 131);"></i>
<span>条件分支</span>
</div>
<div @click="addConcurrentsNode">
<i class="el-icon-s-operation" style="color:#718dff;"></i>
<span>并行分支</span>
</div>
<div @click="addDelayNode">
<i class="el-icon-time" style="color:#f25643;"></i>
<span>延迟等待</span>
</div>
<div @click="addTriggerNode">
<i class="el-icon-set-up" style="color:#15BC83;"></i>
<span>触发器</span>
</div>
</div>
<el-button icon="el-icon-plus" slot="reference" type="primary" size="small" circle></el-button>
</el-popover>
</template>
<script>
export default {
name: "InsertButton",
components: {},
data() {
return {}
},
computed: {
selectedNode() {
this.$store.state.task.selectedNode
}
},
methods: {
addApprovalNode() {
this.$emit('insertNode', "APPROVAL")
},
addCcNode() {
this.$emit('insertNode', "CC")
},
addDelayNode() {
this.$emit('insertNode', "DELAY")
},
addConditionsNode() {
this.$emit('insertNode', "CONDITIONS")
},
addConcurrentsNode() {
this.$emit('insertNode', "CONCURRENTS")
},
addTriggerNode() {
this.$emit('insertNode', "TRIGGER")
}
}
}
</script>
<style lang="scss" scoped>
.node-select {
div {
display: inline-block;
margin: 5px 5px;
cursor: pointer;
padding: 10px 15px;
border: 1px solid #F8F9F9;
background-color: #F8F9F9;
border-radius: 10px;
width: 130px;
position: relative;
span {
position: absolute;
left: 65px;
top: 18px;
}
&:hover {
background-color: #fff;
box-shadow: 0 0 8px 2px #d6d6d6;
}
i {
font-size: 25px;
padding: 5px;
border: 1px solid #dedfdf;
border-radius: 14px;
}
}
}
</style>
<template>
<div style="margin-top: 10px">
<el-tag class="org-item" :type="org.type === 'dept'?'':'info'"
v-for="(org, index) in _value" :key="index + '_org'"
closable size="mini" @close="removeOrgItem(index)">
{{ org.name }}
</el-tag>
</div>
</template>
<script>
export default {
name: "OrgItems",
components: {},
props: {
value: {
type: Array,
default: () => {
return []
}
}
},
computed: {
_value: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
}
}
},
data() {
return {}
},
methods: {
removeOrgItem(index) {
this._value.splice(index, 1)
}
}
}
</script>
<style scoped>
.org-item{
margin: 5px;
}
</style>
This diff is collapsed.
<template>
<div>
<el-button size="mini" icon="el-icon-plus" type="primary" @click="selectOrg" round>选择抄送人</el-button>
<div class="option">
<el-checkbox label="允许发起人添加抄送人" v-model="config.shouldAdd"></el-checkbox>
</div>
<org-items v-model="select" />
<org-picker multiple ref="orgPicker" type="org" :selected="select" @ok="selected" />
</div>
</template>
<script>
import OrgPicker from "@/components/common/OrgPicker";
import OrgItems from "../OrgItems";
export default {
name: "CcNodeConfig.vue",
components: { OrgPicker, OrgItems },
props: {
config: {
type: Object,
default: () => {
return {}
}
}
},
computed: {
select: {
get() {
return this.config.assignedUser || []
},
set(val) {
this.config.assignedUser = val
}
}
},
data() {
return {}
},
methods: {
selectOrg() {
this.$refs.orgPicker.show()
},
selected(select) {
console.log(select)
this.select = Object.assign([], select)
},
removeOrgItem(index) {
this.select.splice(index, 1)
}
}
}
</script>
<style lang="scss" scoped>
.option {
color: #606266;
margin-top: 20px;
font-size: small;
}
.desc {
font-size: small;
color: #8c8c8c;
}
.org-item {
margin: 5px;
}
</style>
<template>
<div>
并行网关无设置项
</div>
</template>
<script>
export default {
name: "ConcurrentNodeConfig",
components: {},
data() {
return {}
},
methods: {}
}
</script>
<style scoped>
</style>
<template>
<div>
<el-form inline label-width="100px">
<el-form-item label="调整优先级" prop="level">
<el-popover placement="right" title="拖拽条件调整优先级顺序" width="250" trigger="click">
<draggable style="width: 100%; min-height:25px" :list="prioritySortList" group="from" :options="sortOption">
<div :class="{ 'drag-no-choose': true, 'drag-hover': cd.id === selectedNode.id }"
v-for="(cd, index) in prioritySortList">
<ellipsis style="width: 160px;" hover-tip :content="cd.name" />
<div>优先级 {{ index + 1 }}</div>
</div>
</draggable>
<el-button icon="el-icon-sort" size="small" slot="reference">{{ nowNodeLeave + 1 }}</el-button>
</el-popover>
</el-form-item>
<el-form-item label="条件组关系" label-width="150px">
<el-switch v-model="config.groupsType" active-color="#409EFF" inactive-color="#c1c1c1" active-value="AND"
inactive-value="OR" active-text="且" inactive-text="或">
</el-switch>
</el-form-item>
<el-form-item label="条件组表达式">
<el-input size="mini" v-model="config.expression" placeholder="输入条件组关系表达式 &为与,|为或" />
<span class="item-desc">使用表达式构建复杂逻辑,例如: (A & B) | C</span>
</el-form-item>
</el-form>
<div>
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin: 0 15px 15px 0" round
@click="addConditionGroup">
添加条件组
</el-button>
<span class="item-desc">只有必填选项才能作为审批条件</span>
</div>
<group-item />
</div>
</template>
<script>
import draggable from "vuedraggable";
import GroupItem from "./ConditionGroupItemConfig.vue"
export default {
name: "ConditionNodeConfig",
components: { draggable, GroupItem },
props: {
config: {
type: Object,
default: () => {
return {}
}
}
},
computed: {
selectedNode() {
return this.$store.state.task.selectedNode
},
select() {
return this.config.assignedUser || []
},
nowNodeLeave() {
return this.prioritySortList.indexOf(this.selectedNode)
},
//条件节点
prioritySortList() {
let node = this.$store.state.task.nodeMap.get(this.selectedNode.parentId)
console.log(this.selectedNode.id, node)
if (node) {
return node.branchs || []
}
return []
}
},
data() {
return {
sortOption: {
animation: 300,
chosenClass: 'choose',
scroll: true,
sort: true
}
}
},
methods: {
addConditionGroup() {
this.config.groups.push({
cids: [],
groupType: "OR",
conditions: []
})
},
selectUser() {
this.showOrgSelect = true
},
selected(select) {
console.log(select)
this.showOrgSelect = false
select.forEach(val => this.select.push(val))
},
removeOrgItem(index) {
this.select.splice(index, 1)
}
}
}
</script>
<style lang="scss" scoped>
.choose {
border-radius: 5px;
margin-top: 2px;
background: #f4f4f4;
border: 1px dashed #1890FF !important;
}
.drag-hover {
color: #1890FF
}
.drag-no-choose {
cursor: move;
background: #f8f8f8;
border-radius: 5px;
margin: 5px 0;
height: 25px;
line-height: 25px;
padding: 5px 10px;
border: 1px solid #ffffff;
div {
display: inline-block;
font-size: small !important;
}
div:nth-child(2) {
float: right !important;
}
}
</style>
<template>
<div>
<div style="margin-bottom: 20px">
<p class="item-desc">延时方式</p>
<el-radio-group v-model="config.type" size="small">
<el-radio-button label="FIXED">固定时长</el-radio-button>
<el-radio-button label="AUTO">自动计算</el-radio-button>
</el-radio-group>
</div>
<div v-if="config.type === 'FIXED'">
<el-input style="width: 180px;" placeholder="时间单位" size="small" type="number" v-model="config.time">
<el-select style="width: 75px;" v-model="config.unit" slot="append" placeholder="请选择">
<el-option label="天" value="D"></el-option>
<el-option label="小时" value="H"></el-option>
<el-option label="分钟" value="M"></el-option>
</el-select>
</el-input>
<span class="item-desc"> 后进入下一步</span>
</div>
<div class="item-desc" v-else>
<el-time-picker value-format="HH:mm:ss" style="width: 150px;" size="small" v-model="config.dateTime" placeholder="任意时间点"></el-time-picker>
<span class="item-desc"> 后进入下一步</span>
</div>
</div>
</template>
<script>
export default {
name: "DelayNodeConfig",
components: {},
props:{
config:{
type: Object,
default: ()=>{
return {}
}
}
},
data() {
return {}
},
methods: {}
}
</script>
<style scoped>
</style>
<template>
<div>
<el-table :header-cell-style="{ background: '#f5f6f6' }" :data="formPerms" border style="width: 100%">
<el-table-column prop="title" show-overflow-tooltip label="表单字段">
<template slot-scope="scope">
<span v-if="scope.row.required" style="color: #c75450"> * </span>
<span>{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="readOnly" label="只读" width="80" v-if="nowNode.type !== 'ROOT'">
<template slot="header" slot-scope="scope">
<el-radio label="R" v-model="permSelect" @change="allSelect('R')">只读</el-radio>
</template>
<template slot-scope="scope">
<el-radio v-model="scope.row.perm" label="R" :name="scope.row.id"></el-radio>
</template>
</el-table-column>
<el-table-column prop="editable" label="可编辑" width="90" v-if="nowNode.type !== 'CC'">
<template slot="header" slot-scope="scope">
<el-radio label="E" v-model="permSelect" @change="allSelect('E')">可编辑</el-radio>
</template>
<template slot-scope="scope">
<el-radio v-model="scope.row.perm" label="E" :name="scope.row.id"></el-radio>
</template>
</el-table-column>
<el-table-column prop="hide" label="隐藏" width="80">
<template slot="header" slot-scope="scope">
<el-radio label="H" v-model="permSelect" @change="allSelect('H')">隐藏</el-radio>
</template>
<template slot-scope="scope">
<el-radio v-model="scope.row.perm" label="H" :name="scope.row.id"></el-radio>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: "FormAuthorityConfig",
components: {},
data() {
return {
tableData: [],
isIndeterminate: false,
permSelect: '',
checkStatus: {
readOnly: true,
editable: false,
hide: false
}
}
},
created() {
//备份
let oldPermMap = this.formPerms.toMap('id')
//重新清空,按顺序加载权限
this.formPerms.length = 0;
this.formPermsLoad(oldPermMap, this.formData)
},
computed: {
nowNode() {
return this.$store.state.task.selectedNode
},
formData() {
return this.$store.state.task.design.formItems
},
formPerms() {
return this.$store.state.task.selectedNode.props.formPerms
}
},
methods: {
allSelect(type) {
this.permSelect = type
this.formPerms.forEach(f => f.perm = type)
},
formPermsLoad(oldPermMap, forms) {
forms.forEach(form => {
if (form.name === 'SpanLayout') {
this.formPermsLoad(oldPermMap, form.props.items)
} else {
//刷新名称
let old = oldPermMap.get(form.id)
if (old) {
old.title = form.title
old.required = form.props.required
this.formPerms.push(old)
} else {
this.formPerms.push({
id: form.id,
title: form.title,
required: form.props.required,
perm: this.$store.state.task.selectedNode.type === 'ROOT' ? 'E' : 'R'
})
}
}
})
},
handleCheckAllChange() {
}
},
watch: {
formPerms: {
deep: true,
handler() {
const set = new Set(this.formPerms.map(f => f.perm))
this.permSelect = set.size === 1 ? set.values()[0] : ''
}
},
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-table__row {
&>td:first-child {
.cell {
text-align: left;
}
}
.cell {
text-align: center;
}
.el-radio__label {
display: none;
}
}
</style>
<template>
<div>
<el-tabs v-model="active" v-if="name">
<el-tab-pane :label="name" name="properties">
<component :is="(selectNode.type||'').toLowerCase()" :config="selectNode.props" />
</el-tab-pane>
<!-- <el-tab-pane label="表单权限设置" name="permissions">
<form-authority-config />
</el-tab-pane> -->
</el-tabs>
<component :is="(selectNode.type||'').toLowerCase()" v-else :config="selectNode.props" />
</div>
</template>
<script>
import Approval from './ApprovalNodeConfig.vue'
import Condition from './ConditionNodeConfig.vue'
import Delay from './DelayNodeConfig.vue'
import Cc from './CcNodeConfig.vue'
import Trigger from './TriggerNodeConfig.vue'
// import FormAuthorityConfig from './FormAuthorityConfig.vue'
import Root from './RootNodeConfig.vue'
export default {
name: "NodeConfig",
components: {
Approval,
Condition,
Trigger,
Delay,
Root,
Cc,
// FormAuthorityConfig
},
data() {
return {
active: 'properties',
}
},
computed: {
selectNode() {
return this.$store.state.task.selectedNode
},
formConfig() {
return this.$store.state.task.design.formItems
},
name() {
switch (this.selectNode.type) {
case 'ROOT':
return '设置发起人';
case 'APPROVAL':
return '设置审批人';
case 'CC':
return '设置抄送人';
default:
return null;
}
}
},
methods: {}
}
</script>
<style lang="scss" scoped></style>
<template>
<div>
<p class="desc">选择能发起该审批的人员/部门,不选则默认开放给所有人</p>
<el-button size="mini" @click="selectOrg" icon="el-icon-plus" type="primary" round>请选择</el-button>
<org-items v-model="select" />
<org-picker title="请选择可发起本审批的人员/部门" multiple ref="orgPicker" :selected="select" @ok="selected" />
</div>
</template>
<script>
import OrgPicker from "@/components/common/OrgPicker";
import OrgItems from "../OrgItems";
export default {
name: "RootConfig",
components: { OrgPicker, OrgItems },
props: {
config: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
showOrgSelect: false
}
},
computed: {
select() {
return this.config.assignedUser
}
},
methods: {
selectOrg() {
this.$refs.orgPicker.show()
},
selected(select) {
this.select.length = 0
select.forEach(val => this.select.push(val))
},
removeOrgItem(index) {
this.select.splice(index, 1)
}
}
}
</script>
<style lang="scss" scoped>
.desc {
font-size: small;
color: #8c8c8c;
}
.org-item {
margin: 5px;
}
</style>
<template>
<div>
<el-alert title="触发器支持模板变量替换语法 ${变量名}, 变量名支持所有的【表单字段ID】及【扩展变量】
扩展变量如下:[formName 审批表单名] [instanceId 审批实例ID]
[owner.id 发起人ID] [owner.name 发起人名] [owner.deptId 发起人部门ID] [owner.deptName 发起人部门名]" type="success"
:closable="false">
</el-alert>
<el-form label-position="top" label-width="90px">
<el-form-item label="选择触发的动作" prop="text" class="user-type">
<el-radio-group v-model="config.type">
<el-radio label="WEBHOOK">发送网络请求</el-radio>
<el-radio label="EMAIL">发送邮件</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="config.type === 'WEBHOOK'">
<el-form-item label="请求地址" prop="text">
<el-input placeholder="请输入URL地址" size="medium" v-model="config.http.url">
<el-select v-model="config.http.method" style="width: 85px;" slot="prepend" placeholder="URL">
<el-option label="GET" value="GET"></el-option>
<el-option label="POST" value="POST"></el-option>
<el-option label="PUT" value="PUT"></el-option>
<el-option label="DELETE" value="DELETE"></el-option>
</el-select>
</el-input>
</el-form-item>
<el-form-item label="Header请求头" prop="text">
<div slot="label">
<span style="margin-right: 10px">Header请求头</span>
<el-button type="text" @click="addItem(config.http.headers)"> + 添加</el-button>
</div>
<div v-for="(header, index) in config.http.headers" :key="index">
- <el-input placeholder="参数名" size="small" style="width: 100px;" v-model="header.name" />
<el-radio-group size="small" style="margin: 0 5px;" v-model="header.isField">
<el-radio-button :label="true">表单</el-radio-button>
<el-radio-button :label="false">固定</el-radio-button>
</el-radio-group>
<el-select v-if="header.isField" style="width: 180px;" v-model="header.value" size="small"
placeholder="请选择表单字段">
<el-option v-for="form in forms" :key="form.id" :label="form.title" :value="form.id"></el-option>
</el-select>
<el-input v-else placeholder="请设置字段值" size="small" v-model="header.value" style="width: 180px;" />
<el-icon class="el-icon-delete" @click="delItem(config.http.headers, index)"
style="margin-left: 5px; color: #c75450; cursor: pointer" />
</div>
</el-form-item>
<el-form-item label="Header请求参数" prop="text">
<div slot="label">
<span style="margin-right: 10px">请求参数 </span>
<el-button style="margin-right: 20px" type="text" @click="addItem(config.http.params)"> + 添加</el-button>
<span>参数类型 - </span>
<el-radio-group size="mini" style="margin: 0 5px;" v-model="config.http.contentType">
<el-radio-button label="JSON">json</el-radio-button>
<el-radio-button label="FORM">form</el-radio-button>
</el-radio-group>
</div>
<div v-for="(param, index) in config.http.params" :key="index">
- <el-input placeholder="参数名" size="small" style="width: 100px;" v-model="param.name" />
<el-radio-group size="small" style="margin: 0 5px;" v-model="param.isField">
<el-radio-button :label="true">表单</el-radio-button>
<el-radio-button :label="false">固定</el-radio-button>
</el-radio-group>
<el-select v-if="param.isField" style="width: 180px;" v-model="param.value" size="small"
placeholder="请选择表单字段">
<el-option v-for="form in forms" :key="form.id" :label="form.title" :value="form.id"></el-option>
</el-select>
<el-input v-else placeholder="请设置字段值" size="small" v-model="param.value" style="width: 180px;" />
<el-icon class="el-icon-delete" @click="delItem(config.http.params, index)"
style="margin-left: 5px; color: #c75450; cursor: pointer" />
</div>
<div>
</div>
</el-form-item>
<el-form-item label="请求结果处理" prop="text">
<div slot="label">
<span>请求结果处理</span>
<span style="margin-left: 20px">自定义脚本: </span>
<el-switch v-model="config.http.handlerByScript"></el-switch>
</div>
<span class="item-desc" v-if="config.http.handlerByScript">
👉 返回值为 ture 则流程通过,为 false 则流程将被驳回
<div>支持函数
<span style="color: dodgerblue">setFormByName(
<span style="color: #939494">'表单字段名', '表单字段值'</span>
)</span>
可改表单数据
</div>
</span>
<span class="item-desc" v-else>👉 无论请求结果如何,均通过</span>
<div v-if="config.http.handlerByScript">
<div>
<span>请求成功😀:</span>
<el-input type="textarea" v-model="config.http.success" :rows="3"></el-input>
</div>
<div>
<span>请求失败😥:</span>
<el-input type="textarea" v-model="config.http.fail" :rows="3"></el-input>
</div>
</div>
</el-form-item>
</div>
<div v-else-if="config.type === 'EMAIL'">
<el-form-item label="邮件主题" prop="text">
<el-input placeholder="请输入邮件主题" size="medium" v-model="config.email.subject" />
</el-form-item>
<el-form-item label="收件方" prop="text">
<el-select size="small" style="width: 100%;" v-model="config.email.to" filterable multiple allow-create
default-first-option placeholder="请输入收件人">
<el-option v-for="item in config.email.to" :key="item" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="邮件正文" prop="text">
<el-input type="textarea" v-model="config.email.content" :rows="4"
placeholder="邮件内容,支持变量提取表单数据 ${表单字段名} "></el-input>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script>
//import { codemirror } from 'vue-codemirror'
// 引入主题 可以从 codemirror/theme/ 下引入多个
//import 'codemirror/theme/idea.css'
// 引入语言模式 可以从 codemirror/mode/ 下引入多个
//import "codemirror/mode/javascript/javascript.js"
export default {
name: "TriggerNodeConfig",
components: {/*codemirror*/ },
props: {
config: {
type: Object,
default: () => {
return {}
}
}
},
computed: {
forms() {
return this.$store.state.task.design.formItems || []
}
},
data() {
return {
cmOptions: {
tabSize: 4, // tab
indentUnit: 4,
styleActiveLine: true, // 高亮选中行
lineNumbers: true, // 显示行号
styleSelectedText: true,
line: true,
foldGutter: true, // 块槽
gutters: ['CodeMirror-linenumbers', "lock", "warn"],
highlightSelectionMatches: { showToken: /w/, annotateScrollbar: true }, // 可以启用该选项来突出显示当前选中的内容的所有实例
mode: 'javascript',
// hint.js options
hintOptions: {
// 当匹配只有一项的时候是否自动补全
completeSingle: false
},
// 快捷键 可提供三种模式 sublime、emacs、vim
keyMap: 'sublime',
matchBrackets: true,
showCursorWhenSelecting: false,
// scrollbarStyle:null,
// readOnly:true, //是否只读
theme: 'material', // 主题 material
extraKeys: { 'Ctrl': 'autocomplete' }, // 可以用于为编辑器指定额外的键绑定,以及keyMap定义的键绑定
lastLineBefore: 0
}
}
},
methods: {
addItem(items) {
if (items.length > 0 && (items[items.length - 1].name.trim() === ''
|| items[items.length - 1].value.trim() === '')) {
this.$message.warning("请完善之前项后在添加")
return;
}
items.push({ name: '', value: '', isField: true })
},
delItem(items, index) {
items.splice(index, 1)
},
onCmCodeChange() {
},
onCmReady() {
}
}
}
</script>
<style lang="scss" scoped>
.item-desc {
color: #939494;
}
</style>
<template>
<node :title="config.name" :show-error="showError" :content="content" :error-info="errorInfo"
@selected="$emit('selected')" @delNode="$emit('delNode')" @insertNode="type => $emit('insertNode', type)"
placeholder="请设置审批人" header-bgc="#f78f5f" header-icon="el-icon-s-check" />
</template>
<script>
import Node from './Node'
export default {
name: "ApprovalNode",
props: {
config: {
type: Object,
default: () => {
return {}
}
}
},
components: { Node },
data() {
return {
showError: false,
errorInfo: '',
}
},
computed: {
formItems() {
return this.$store.state.task.design.formItems
},
content() {
const config = this.config.props
switch (config.assignedType) {
case "ASSIGN_USER":
if (config.assignedUser.length > 0) {
let texts = []
config.assignedUser.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '')
} else {
return '请指定审批人'
}
case "SELF":
return '发起人自己'
case "SELF_SELECT":
return config.selfSelect.multiple ? '发起人自选多人' : '发起人自选一人'
case "LEADER_TOP":
return '多级主管依次审批'
case "LEADER":
return config.leader.level > 1 ? '发起人的第 ' + config.leader.level + ' 级主管' : '发起人的直接主管'
case "FORM_USER":
if (!config.formUser || config.formUser === '') {
return '表单内联系人(未选择)'
} else {
let text = this.getFormItemById(this.formItems, config.formUser)
if (text && text.title) {
return `表单(${text.title})内的人员`
} else {
return '该表单项已被移除😥'
}
}
case "ROLE":
if (config.role.length > 0) {
return `角色 [${String(config.role.map(r => r.name)).replaceAll(',', '')}] `
} else {
return '指定角色(未设置)'
}
case 'REFUSE':
return '系统自动拒绝审批'
case 'FORM_DEPT':
if (!config.formDept || config.formDept === '') {
return '表单内部门的主管(未选择)'
} else {
let text = this.getFormItemById(this.formItems, config.formDept)
if (text && text.title) {
return `表单(${text.title})内的部门主管`
} else {
return '该表单项已被移除😥'
}
}
case 'ASSIGN_LEADER':
if ((config.assignedDept || []).length > 0) {
let texts = []
config.assignedDept.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '')
} else {
return '请指定部门'
}
default: return '未知设置项😥'
}
}
},
methods: {
getFormItemById(items, id) {
for (let i = 0; i < items.length; i++) {
if (items[i].name === 'SpanLayout') {
let result = this.getFormItemById(items[i].props.items, id)
if (result) {
return result
}
} else if (items[i].id === id) {
return items[i]
}
}
return null
},
//校验数据配置的合法性
validate(err) {
try {
return this.showError = !this[`validate_${this.config.props.assignedType}`](err)
} catch (e) {
return true;
}
},
validate_ASSIGN_USER(err) {
if (this.config.props.assignedUser.length > 0) {
return true;
} else {
this.errorInfo = '请指定审批人员'
err.push(`${this.config.name} 未指定审批人员`)
return false
}
},
validate_ASSIGN_LEADER(err) {
if ((this.config.props.assignedDept || []).length > 0) {
return true;
} else {
this.errorInfo = '请指定要审批的部门'
err.push(`${this.config.name} 未指定审批部门`)
return false
}
},
validate_SELF_SELECT(err) {
return true;
},
validate_LEADER_TOP(err) {
return true;
},
validate_LEADER(err) {
return true;
},
validate_ROLE(err) {
if (this.config.props.role.length <= 0) {
this.errorInfo = '请指定负责审批的系统角色'
err.push(`${this.config.name} 未指定审批角色`)
return false
}
return true;
},
validate_SELF(err) {
return true;
},
validate_FORM_USER(err) {
if ((this.config.props.formUser || '') === '') {
this.errorInfo = '请指定表单中的人员组件'
err.push(`${this.config.name} 审批人为表单中人员,但未指定`)
return false
}
return true;
},
validate_FORM_DEPT(err) {
if ((this.config.props.formDept || '') === '') {
this.errorInfo = '请指定表单中的部门选择组件'
err.push(`${this.config.name} 审批人为表单中的部门主管,但未指定`)
return false
}
return true;
},
validate_REFUSE(err) {
return true;
},
}
}
</script>
<style scoped></style>
<template>
<node :title="config.name" :show-error="showError" :content="content" :error-info="errorInfo"
@selected="$emit('selected')" @delNode="$emit('delNode')" @insertNode="type => $emit('insertNode', type)"
placeholder="请设置抄送人" header-bgc="#409eff" header-icon="el-icon-s-promotion"/>
</template>
<script>
import Node from './Node'
export default {
name: "CcNode",
props:{
config:{
type: Object,
default: () => {
return {}
}
}
},
components: {Node},
data() {
return {
showError: false,
errorInfo: '',
}
},
computed:{
content() {
if (this.config.props.shouldAdd){
return '由发起人指定'
}else if (this.config.props.assignedUser.length > 0) {
let texts = []
this.config.props.assignedUser.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '')
} else {
return null
}
}
},
methods: {
//校验数据配置的合法性
validate(err){
this.showError = false
if(this.config.props.shouldAdd){
this.showError = false
}else if(this.config.props.assignedUser.length === 0){
this.showError = true
this.errorInfo = '请选择需要抄送的人员'
}
if (this.showError){
err.push(`抄送节点 ${this.config.name} 未设置抄送人`)
}
return !this.showError
}
}
}
</script>
<style scoped>
</style>
<template>
<div :class="{ 'node': true, 'node-error-state': showError }">
<div :class="{ 'node-body': true, 'error': showError }" @click="$emit('selected')">
<div class="node-body-left" @click.stop="$emit('leftMove')" v-if="level > 1">
<i class="el-icon-arrow-left"></i>
</div>
<div class="node-body-main">
<div class="node-body-main-header">
<span class="title">
<i class="el-icon-s-operation"></i>
<ellipsis class="name" hover-tip :content="config.name ? config.name : ('并行任务' + level)" />
</span>
<span class="option">
<el-tooltip effect="dark" content="复制分支" placement="top">
<i class="el-icon-copy-document" @click="$emit('copy')"></i>
</el-tooltip>
<i class="el-icon-close" @click.stop="$emit('delNode')"></i>
</span>
</div>
<div class="node-body-main-content">
<span>并行任务(同时进行)</span>
</div>
</div>
<div class="node-body-right" @click.stop="$emit('rightMove')" v-if="level < size">
<i class="el-icon-arrow-right"></i>
</div>
<div class="node-error" v-if="showError">
<el-tooltip effect="dark" :content="errorInfo" placement="top-start">
<i class="el-icon-warning-outline"></i>
</el-tooltip>
</div>
</div>
<div class="node-footer">
<div class="btn">
<insert-button @insertNode="type => $emit('insertNode', type)"></insert-button>
</div>
</div>
</div>
</template>
<script>
import InsertButton from '@/views/task/common/InsertButton.vue'
export default {
name: "ConcurrentNode",
components: { InsertButton },
props: {
config: {
type: Object,
default: () => {
return {}
}
},
level: {
type: Number,
default: 1
},
//条件数
size: {
type: Number,
default: 0
}
},
data() {
return {
errorInfo: '',
showError: false
}
},
methods: {
//校验数据配置的合法性
validate(err) {
try {
this.showError = !this.config.children || !this.$isNotEmpty(this.config.children.id)
if (this.showError) {
err.push(`并行分支${this.config.name}下面无节点`)
this.errorInfo = '请添加业务节点'
}
return this.showError
} catch (e) {
return true;
}
},
}
}
</script>
<style lang="scss" scoped>
.node-error-state {
.node-body {
box-shadow: 0px 0px 5px 0px #F56C6C !important;
}
}
.node {
padding: 30px 55px 0;
width: 220px;
.node-body {
cursor: pointer;
min-height: 80px;
max-height: 120px;
position: relative;
border-radius: 5px;
background-color: white;
box-shadow: 0px 0px 5px 0px #d8d8d8;
&:hover {
.node-body-left,
.node-body-right {
i {
display: block !important;
}
}
.node-body-main {
.option {
display: inline-block !important;
}
}
box-shadow: 0px 0px 3px 0px #1989fa;
}
.node-body-left,
.node-body-right {
display: flex;
align-items: center;
position: absolute;
height: 100%;
i {
display: none;
}
&:hover {
background-color: #ececec;
}
}
.node-body-left {
left: 0;
}
.node-body-right {
right: 0;
}
.node-body-main {
position: absolute;
width: 188px;
left: 17px;
display: inline-block;
.node-body-main-header {
padding: 10px 0px 5px;
font-size: xx-small;
position: relative;
.title {
color: #718dff;
.name {
display: inline-block;
height: 14px;
width: 130px;
margin-left: 2px;
}
}
.option {
position: absolute;
right: 0;
display: none;
font-size: medium;
i {
color: #888888;
padding: 0 3px;
}
}
}
.node-body-main-content {
padding: 6px;
color: #656363;
font-size: 14px;
i {
position: absolute;
top: 55%;
right: 10px;
font-size: medium;
}
}
}
.node-error {
position: absolute;
right: -40px;
top: 20px;
font-size: 25px;
color: #F56C6C;
}
}
.node-footer {
position: relative;
.btn {
width: 100%;
display: flex;
height: 70px;
padding: 20px 0 32px;
justify-content: center;
}
::v-deep .el-button {
height: 32px;
}
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
margin: auto;
width: 2px;
height: 100%;
background-color: #CACACA;
}
}
}
</style>
<template>
<div :class="{ 'node': true, 'node-error-state': showError }">
<div :class="{ 'node-body': true, 'error': showError }">
<div class="node-body-left" @click="$emit('leftMove')" v-if="level > 1">
<i class="el-icon-arrow-left"></i>
</div>
<div class="node-body-main" @click="$emit('selected')">
<div class="node-body-main-header">
<ellipsis class="title" hover-tip :content="config.name ? config.name : ('条件' + level)" />
<span class="level">优先级{{ level }}</span>
<span class="option">
<el-tooltip effect="dark" content="复制条件" placement="top">
<i class="el-icon-copy-document" @click.stop="$emit('copy')"></i>
</el-tooltip>
<i class="el-icon-close" @click.stop="$emit('delNode')"></i>
</span>
</div>
<div class="node-body-main-content">
<span class="placeholder" v-if="(content || '').trim() === ''">{{ placeholder }}</span>
<ellipsis hoverTip :row="4" :content="content" v-else />
</div>
</div>
<div class="node-body-right" @click="$emit('rightMove')" v-if="level < size">
<i class="el-icon-arrow-right"></i>
</div>
<div class="node-error" v-if="showError">
<el-tooltip effect="dark" :content="errorInfo" placement="top-start">
<i class="el-icon-warning-outline"></i>
</el-tooltip>
</div>
</div>
<div class="node-footer">
<div class="btn">
<insert-button @insertNode="type => $emit('insertNode', type)"></insert-button>
</div>
</div>
</div>
</template>
<script>
import InsertButton from '@/views/task/common/InsertButton.vue'
import { ValueType } from '@/views/task/common/form/ComponentsConfigExport'
const groupNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
export default {
name: "ConditionNode",
components: { InsertButton },
props: {
config: {
type: Object,
default: () => {
return {}
}
},
//索引位置
level: {
type: Number,
default: 1
},
//条件数
size: {
type: Number,
default: 0
}
},
data() {
return {
ValueType,
groupNames,
placeholder: '请设置条件',
errorInfo: '',
showError: false
}
},
computed: {
content() {
const groups = this.config.props.groups
if (groups.length === 0) {
return '无条件,默认满足'
}
let confitions = []
groups.forEach(group => {
let subConditions = []
group.conditions.forEach(subCondition => {
let subConditionStr = ''
switch (subCondition.valueType) {
case ValueType.dept:
case ValueType.user:
subConditionStr = `${subCondition.title}属于[${String(subCondition.value.map(u => u.name)).replaceAll(',', '. ')}]之一`
break;
case ValueType.number:
case ValueType.string:
subConditionStr = this.getOrdinaryConditionContent(subCondition)
break;
}
subConditions.push(subConditionStr)
})
//根据子条件关系构建描述
let subConditionsStr = String(subConditions)
.replaceAll(',', subConditions.length > 1 ?
(group.groupType === 'AND' ? ') 且 (' : ') 或 (') :
(group.groupType === 'AND' ? '' : ''))
confitions.push(subConditions.length > 1 ? `(${subConditionsStr})` : subConditionsStr)
})
//构建最终描述
return String(confitions).replaceAll(',', (this.config.props.groupsType === 'AND' ? '' : ''))
}
},
methods: {
getDefault(val, df) {
return val && val !== '' ? val : df;
},
getOrdinaryConditionContent(subCondition) {
switch (subCondition.compare) {
case 'IN':
return `${subCondition.title}为[${String(subCondition.value).replaceAll(',', '')}]中之一`
case 'B':
return `${subCondition.value[0]} < ${subCondition.title} < ${subCondition.value[1]}`
case 'AB':
return `${subCondition.value[0]}${subCondition.title} < ${subCondition.value[1]}`
case 'BA':
return `${subCondition.value[0]} < ${subCondition.title}${subCondition.value[1]}`
case 'ABA':
return `${subCondition.value[0]}${subCondition.title}${subCondition.value[1]}`
case '<=':
return `${subCondition.title}${this.getDefault(subCondition.value[0], ' ?')}`
case '>=':
return `${subCondition.title}${this.getDefault(subCondition.value[0], ' ?')}`
default:
return `${subCondition.title}${subCondition.compare}${this.getDefault(subCondition.value[0], ' ?')}`
}
},
//校验数据配置的合法性
validate(err) {
const props = this.config.props
if (props.groups.length <= 0) {
this.showError = false
this.errorInfo = '无条件,默认满足'
} else {
for (let i = 0; i < props.groups.length; i++) {
if (props.groups[i].cids.length === 0) {
this.showError = true
this.errorInfo = `请设置条件组${this.groupNames[i]}内的条件`
err.push(`条件 ${this.config.name} 条件组${this.groupNames[i]}内未设置条件`)
break
} else {
let conditions = props.groups[i].conditions
for (let ci = 0; ci < conditions.length; ci++) {
let subc = conditions[ci]
if (subc.value.length === 0) {
this.showError = true
} else {
this.showError = false
}
if (this.showError) {
this.errorInfo = `请完善条件组${this.groupNames[i]}内的${subc.title}条件`
err.push(`条件 ${this.config.name} 条件组${this.groupNames[i]}${subc.title}条件未完善`)
return false
}
}
}
}
if (!this.showError && !this.$isNotEmpty((this.config.children || {}).id)) {
//校验下方节点
this.showError = true
this.errorInfo = '非默认条件下方节点不允许为空'
err.push(`条件 ${this.config.name} 下方分支无流程节点`)
}
}
return !this.showError
}
}
}
</script>
<style lang="scss" scoped>
.node-error-state {
.node-body {
box-shadow: 0px 0px 5px 0px #F56C6C !important;
}
}
.node {
padding: 30px 55px 0;
width: 220px;
.node-body {
cursor: pointer;
min-height: 80px;
max-height: 120px;
position: relative;
border-radius: 5px;
background-color: white;
box-shadow: 0px 0px 5px 0px #d8d8d8;
&:hover {
.node-body-left,
.node-body-right {
i {
display: block !important;
}
}
.node-body-main {
.level {
display: none !important;
}
.option {
display: inline-block !important;
}
}
box-shadow: 0px 0px 3px 0px #1989fa;
}
.node-body-left,
.node-body-right {
display: flex;
align-items: center;
position: absolute;
height: 100%;
i {
display: none;
}
&:hover {
background-color: #ececec;
}
}
.node-body-left {
left: 0;
}
.node-body-right {
right: 0;
top: 0;
}
.node-body-main {
//position: absolute;
width: 188px;
margin-left: 17px;
display: inline-block;
.node-body-main-header {
padding: 10px 0px 5px;
font-size: xx-small;
position: relative;
.title {
color: #15bca3;
display: inline-block;
height: 14px;
width: 125px;
}
.level {
position: absolute;
right: 15px;
color: #888888;
}
.option {
position: absolute;
right: 0;
display: none;
font-size: medium;
i {
color: #888888;
padding: 0 3px;
}
}
}
.node-body-main-content {
padding: 6px;
color: #656363;
font-size: 14px;
i {
position: absolute;
top: 55%;
right: 10px;
font-size: medium;
}
.placeholder {
color: #8c8c8c;
}
}
}
.node-error {
position: absolute;
right: -40px;
top: 20px;
font-size: 25px;
color: #F56C6C;
}
}
.node-footer {
position: relative;
.btn {
width: 100%;
display: flex;
height: 70px;
padding: 20px 0 32px;
justify-content: center;
}
::v-deep .el-button {
height: 32px;
}
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
margin: auto;
width: 2px;
height: 100%;
background-color: #CACACA;
}
}
}
</style>
<template>
<node :title="config.name" :show-error="showError" :content="content" :error-info="errorInfo"
@selected="$emit('selected')" @delNode="$emit('delNode')" @insertNode="type => $emit('insertNode', type)"
placeholder="请设置延时时间" header-bgc="#f95166" header-icon="el-icon-time"/>
</template>
<script>
import Node from './Node'
export default {
name: "DelayNode",
props:{
config:{
type: Object,
default: () => {
return {}
}
}
},
components: {Node},
data() {
return {
showError: false,
errorInfo: '',
}
},
computed:{
content(){
if (this.config.props.type === 'FIXED'){
return `等待 ${this.config.props.time} ${this.getName(this.config.props.unit)}`
}else if(this.config.props.type === 'AUTO'){
return `至当天 ${this.config.props.dateTime}`
}else {
return null
}
}
},
methods: {
//校验数据配置的合法性
validate(err){
this.showError = false
try {
if (this.config.props.type === "AUTO") {
if ((this.config.props.dateTime || "") === ""){
this.showError = true
this.errorInfo = "请选择时间点"
}
} else {
if (this.config.props.time <= 0) {
this.showError = true
this.errorInfo = "请设置延时时长"
}
}
} catch (e) {
this.showError = true
this.errorInfo = "配置出现问题"
}
if (this.showError){
err.push(`${this.config.name} 未设置延时规则`)
}
return !this.showError
},
getName(unit){
switch (unit){
case 'D': return '';
case 'H': return '小时';
case 'M': return '分钟';
default: return '未知';
}
}
}
}
</script>
<style scoped>
</style>
<template>
<node :show="false" :merge="config.parentType === 'CONCURRENTS'" @insertNode="type => $emit('insertNode', type)"/>
</template>
<script>
import Node from './Node'
export default {
name: "EmptyNode",
components: {Node},
props:{
config:{
type: Object,
default: () => {
return {}
}
}
},
data() {
return {}
},
methods: {}
}
</script>
<style scoped>
</style>
<template>
<node :title="config.name" :show-error="showError" :content="content" :error-info="errorInfo"
@selected="$emit('selected')" @delNode="$emit('delNode')" @insertNode="type => $emit('insertNode', type)"
placeholder="请设置跳转节点" header-bgc="#696bdb" header-icon="el-icon-user-solid" />
</template>
<script>
import Node from './Node'
export default {
name: "JumpNode",
props: {
config: {
type: Object,
default: () => {
return {}
}
}
},
components: { Node },
data() {
return {
showError: false,
errorInfo: '',
}
},
computed: {
content() {
const config = this.config.props
}
},
methods: {
getFormItemById(id) {
return this.$store.state.task.design.formItems.find(item => item.id === id)
},
//校验数据配置的合法性
validate(err) {
try {
return this.showError = !this[`validate_${this.config.props.assignedType}`](err)
} catch (e) {
return true;
}
}
}
}
</script>
<style scoped></style>
<template>
<div :class="{ 'node': true, 'root': isRoot || !show, 'node-error-state': showError }">
<div v-if="show" @click="$emit('selected')" :class="{ 'node-body': true, 'error': showError }">
<div>
<div class="node-body-header" :style="{ 'background-color': headerBgc }">
<i :class="headerIcon" style="margin-right: 5px" v-if="(headerIcon || '') !== ''"></i>
<ellipsis class="name" hover-tip :content="title" />
<!-- <el-input v-else autofocus v-model="titleContent" @blur="enableEdit = false"></el-input>-->
<i class="el-icon-close" v-if="!isRoot" style="float:right;" @click="$emit('delNode')"></i>
</div>
<div class="node-body-content">
<i :class="leftIcon" v-if="leftIcon"></i>
<span class="placeholder" v-if="(content || '').trim() === ''">{{ placeholder }}</span>
<ellipsis :row="3" :content="content" v-else />
<i class="el-icon-arrow-right"></i>
</div>
<div class="node-error" v-if="showError">
<el-tooltip effect="dark" :content="errorInfo" placement="top-start">
<i class="el-icon-warning-outline"></i>
</el-tooltip>
</div>
</div>
</div>
<div :class="{ 'node-footer': true, 'space': merge }">
<div v-if="merge" class="branch-merge">
<!-- <img src="../@/assets/task/branch.png" alt=""> -->
</div>
<div class="btn">
<insert-button @insertNode="type => $emit('insertNode', type)"></insert-button>
</div>
</div>
</div>
</template>
<script>
import InsertButton from '@/views/task/common/InsertButton.vue'
export default {
name: "Node",
components: { InsertButton },
props: {
//是否为根节点
isRoot: {
type: Boolean,
default: false
},
//是否显示节点体
show: {
type: Boolean,
default: true
},
//并行网关聚合
merge: {
type: Boolean,
default: false
},
//节点内容区域文字
content: {
type: String,
default: ""
},
title: {
type: String,
default: "标题"
},
placeholder: {
type: String,
default: "请设置"
},
//节点体左侧图标
leftIcon: {
type: String,
default: undefined
},
//头部图标
headerIcon: {
type: String,
default: ''
},
//头部背景色
headerBgc: {
type: String,
default: '#576a95'
},
//是否显示错误状态
showError: {
type: Boolean,
default: false
},
errorInfo: {
type: String,
default: '无信息'
},
},
data() {
return {
enableEdit: false,
}
},
computed: {
titleContent: {
get() {
return this.title
},
set(val) {
this.$emit('title:update', val)
}
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
::v-deep .el-input {
width: 80%;
.el-input__inner {
padding: 0 !important;
font-size: 12px;
height: 20px;
}
}
.root {
&:before {
display: none !important;
}
}
.node-error-state {
.node-body {
box-shadow: 0px 0px 5px 0px #F56C6C !important;
}
}
.node {
padding: 0 50px;
width: 220px;
position: relative;
&:before {
content: '';
position: absolute;
top: -12px;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
width: 0;
border-style: solid;
border-width: 8px 6px 4px;
border-color: #CACACA transparent transparent;
background: #F5F5F7;
}
.node-body {
cursor: pointer;
max-height: 120px;
position: relative;
border-radius: 5px;
background-color: white;
box-shadow: 0px 0px 5px 0px #d8d8d8;
&:hover {
box-shadow: 0px 0px 3px 0px #1989fa;
.node-body-header {
.el-icon-close {
display: inline;
font-size: medium;
}
}
}
.node-body-header {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 5px 15px;
color: white;
font-size: xx-small;
.el-icon-close {
display: none;
}
.name {
height: 14px;
width: 150px;
display: inline-block
}
}
.node-body-content {
padding: 18px;
color: #656363;
font-size: 14px;
i {
position: absolute;
top: 55%;
right: 5px;
font-size: medium;
}
.placeholder {
color: #8c8c8c;
}
}
.node-error {
position: absolute;
right: -40px;
top: 20px;
font-size: 25px;
color: #F56C6C;
}
}
.node-footer {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.branch-merge {
font-size: 12px;
display: flex;
width: 38px;
border-radius: 50%;
position: absolute;
top: -40px;
background: white;
justify-content: center;
flex-direction: column;
box-shadow: 0px 0px 5px 0px #d8d8d8;
}
.btn {
width: 100%;
display: flex;
padding: 20px 0 32px;
justify-content: center;
}
::v-deep .el-button {
height: 32px;
}
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
margin: auto;
width: 2px;
height: 100%;
background-color: #CACACA;
}
}
}
.space {
margin-top: 20px;
}
</style>
<template>
<node title="发起人" :is-root="true" :content="content"
@selected="$emit('selected')" @insertNode="type => $emit('insertNode', type)"
placeholder="所有人" header-bgc="#7a939d" header-icon="el-icon-user-solid"/>
</template>
<script>
import Node from './Node'
export default {
name: "RootNode",
components: {Node},
props:{
config:{
type: Object,
default: () => {
return {}
}
}
},
computed:{
content(){
if (this.config.props.assignedUser.length > 0){
let texts = []
this.config.props.assignedUser.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '')
} else {
return '所有人'
}
}
},
data() {
return {
}
},
methods: {}
}
</script>
<style scoped>
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment