多模块重构 12:修改项目名字,按照新的规则
This commit is contained in:
74
yudao-ui-admin/src/components/Breadcrumb/index.vue
Normal file
74
yudao-ui-admin/src/components/Breadcrumb/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route(route) {
|
||||
// if you go to the redirect page, do not update the breadcrumbs
|
||||
if (route.path.startsWith('/redirect/')) {
|
||||
return
|
||||
}
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb() {
|
||||
// only show routes with meta.title
|
||||
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
|
||||
if (!this.isDashboard(first)) {
|
||||
matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched)
|
||||
}
|
||||
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
isDashboard(route) {
|
||||
const name = route && route.name
|
||||
if (!name) {
|
||||
return false
|
||||
}
|
||||
return name.trim() === '首页'
|
||||
},
|
||||
handleLink(item) {
|
||||
const { redirect, path } = item
|
||||
if (redirect) {
|
||||
this.$router.push(redirect)
|
||||
return
|
||||
}
|
||||
this.$router.push(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
195
yudao-ui-admin/src/components/Editor/index.vue
Normal file
195
yudao-ui-admin/src/components/Editor/index.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="editor" ref="editor" :style="styles"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Quill from "quill";
|
||||
import "quill/dist/quill.core.css";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import "quill/dist/quill.bubble.css";
|
||||
|
||||
export default {
|
||||
name: "Editor",
|
||||
props: {
|
||||
/* 编辑器的内容 */
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
/* 高度 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
/* 最小高度 */
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Quill: null,
|
||||
currentValue: "",
|
||||
options: {
|
||||
theme: "snow",
|
||||
bounds: document.body,
|
||||
debug: "warn",
|
||||
modules: {
|
||||
// 工具栏配置
|
||||
toolbar: [
|
||||
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
|
||||
["blockquote", "code-block"], // 引用 代码块
|
||||
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
|
||||
[{ indent: "-1" }, { indent: "+1" }], // 缩进
|
||||
[{ size: ["small", false, "large", "huge"] }], // 字体大小
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
|
||||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
|
||||
[{ align: [] }], // 对齐方式
|
||||
["clean"], // 清除文本格式
|
||||
["link", "image", "video"] // 链接、图片、视频
|
||||
],
|
||||
},
|
||||
placeholder: "请输入内容",
|
||||
readOnly: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
let style = {};
|
||||
if (this.minHeight) {
|
||||
style.minHeight = `${this.minHeight}px`;
|
||||
}
|
||||
if (this.height) {
|
||||
style.height = `${this.height}px`;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
if (val !== this.currentValue) {
|
||||
this.currentValue = val === null ? "" : val;
|
||||
if (this.Quill) {
|
||||
this.Quill.pasteHTML(this.currentValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.Quill = null;
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const editor = this.$refs.editor;
|
||||
this.Quill = new Quill(editor, this.options);
|
||||
this.Quill.pasteHTML(this.currentValue);
|
||||
this.Quill.on("text-change", (delta, oldDelta, source) => {
|
||||
const html = this.$refs.editor.children[0].innerHTML;
|
||||
const text = this.Quill.getText();
|
||||
const quill = this.Quill;
|
||||
this.currentValue = html;
|
||||
this.$emit("input", html);
|
||||
this.$emit("on-change", { html, text, quill });
|
||||
});
|
||||
this.Quill.on("text-change", (delta, oldDelta, source) => {
|
||||
this.$emit("on-text-change", delta, oldDelta, source);
|
||||
});
|
||||
this.Quill.on("selection-change", (range, oldRange, source) => {
|
||||
this.$emit("on-selection-change", range, oldRange, source);
|
||||
});
|
||||
this.Quill.on("editor-change", (eventName, ...args) => {
|
||||
this.$emit("on-editor-change", eventName, ...args);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.editor, .ql-toolbar {
|
||||
white-space: pre-wrap!important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
.quill-img {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode="link"]::before {
|
||||
content: "请输入链接地址:";
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0px;
|
||||
content: "保存";
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode="video"]::before {
|
||||
content: "请输入视频地址:";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: "14px";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
|
||||
content: "10px";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
|
||||
content: "18px";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
|
||||
content: "32px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: "文本";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
|
||||
content: "标题1";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
|
||||
content: "标题2";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
|
||||
content: "标题3";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
|
||||
content: "标题4";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
|
||||
content: "标题5";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
|
||||
content: "标题6";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: "标准字体";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
|
||||
content: "衬线字体";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
|
||||
content: "等宽字体";
|
||||
}
|
||||
</style>
|
179
yudao-ui-admin/src/components/FileUpload/index.vue
Normal file
179
yudao-ui-admin/src/components/FileUpload/index.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div class="upload-file">
|
||||
<el-upload
|
||||
:action="uploadFileUrl"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:file-list="fileList"
|
||||
:limit="1"
|
||||
:on-error="handleUploadError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="handleUploadSuccess"
|
||||
:show-file-list="false"
|
||||
:headers="headers"
|
||||
class="upload-file-uploader"
|
||||
ref="upload"
|
||||
>
|
||||
<!-- 上传按钮 -->
|
||||
<el-button size="mini" type="primary">选取文件</el-button>
|
||||
<!-- 上传提示 -->
|
||||
<div class="el-upload__tip" slot="tip" v-if="showTip">
|
||||
请上传
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
|
||||
的文件
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
|
||||
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in list">
|
||||
<el-link :href="file.url" :underline="false" target="_blank">
|
||||
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
|
||||
</el-link>
|
||||
<div class="ele-upload-list__item-content-action">
|
||||
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
|
||||
</div>
|
||||
</li>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 值
|
||||
value: [String, Object, Array],
|
||||
// 大小限制(MB)
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["doc", "xls", "ppt", "txt", "pdf"],
|
||||
},
|
||||
// 是否显示提示
|
||||
isShowTip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
fileList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 是否显示提示
|
||||
showTip() {
|
||||
return this.isShowTip && (this.fileType || this.fileSize);
|
||||
},
|
||||
// 列表
|
||||
list() {
|
||||
let temp = 1;
|
||||
if (this.value) {
|
||||
// 首先将值转为数组
|
||||
const list = Array.isArray(this.value) ? this.value : [this.value];
|
||||
// 然后将数组转为对象数组
|
||||
return list.map((item) => {
|
||||
if (typeof item === "string") {
|
||||
item = { name: item, url: item };
|
||||
}
|
||||
item.uid = item.uid || new Date().getTime() + temp++;
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
this.fileList = [];
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 上传前校检格式和大小
|
||||
handleBeforeUpload(file) {
|
||||
// 校检文件类型
|
||||
if (this.fileType) {
|
||||
let fileExtension = "";
|
||||
if (file.name.lastIndexOf(".") > -1) {
|
||||
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
|
||||
}
|
||||
const isTypeOk = this.fileType.some((type) => {
|
||||
if (file.type.indexOf(type) > -1) return true;
|
||||
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
|
||||
return false;
|
||||
});
|
||||
if (!isTypeOk) {
|
||||
this.$message.error(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 校检文件大小
|
||||
if (this.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < this.fileSize;
|
||||
if (!isLt) {
|
||||
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 文件个数超出
|
||||
handleExceed() {
|
||||
this.$message.error(`只允许上传单个文件`);
|
||||
},
|
||||
// 上传失败
|
||||
handleUploadError(err) {
|
||||
this.$message.error("上传失败, 请重试");
|
||||
},
|
||||
// 上传成功回调
|
||||
handleUploadSuccess(res, file) {
|
||||
this.$message.success("上传成功");
|
||||
this.$emit("input", res.url);
|
||||
},
|
||||
// 删除文件
|
||||
handleDelete(index) {
|
||||
this.fileList.splice(index, 1);
|
||||
this.$emit("input", '');
|
||||
},
|
||||
// 获取文件名称
|
||||
getFileName(name) {
|
||||
if (name.lastIndexOf("/") > -1) {
|
||||
return name.slice(name.lastIndexOf("/") + 1).toLowerCase();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fileList = this.list;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upload-file-uploader {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.upload-file-list .el-upload-list__item {
|
||||
border: 1px solid #e4e7ed;
|
||||
line-height: 2;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.upload-file-list .ele-upload-list__item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
}
|
||||
.ele-upload-list__item-content-action .el-link {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
44
yudao-ui-admin/src/components/Hamburger/index.vue
Normal file
44
yudao-ui-admin/src/components/Hamburger/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div style="padding: 0 15px;" @click="toggleClick">
|
||||
<svg
|
||||
:class="{'is-active':isActive}"
|
||||
class="hamburger"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
188
yudao-ui-admin/src/components/HeaderSearch/index.vue
Normal file
188
yudao-ui-admin/src/components/HeaderSearch/index.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div :class="{'show':show}" class="header-search">
|
||||
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
|
||||
<el-select
|
||||
ref="headerSearchSelect"
|
||||
v-model="search"
|
||||
:remote-method="querySearch"
|
||||
filterable
|
||||
default-first-option
|
||||
remote
|
||||
placeholder="Search"
|
||||
class="header-search-select"
|
||||
@change="change"
|
||||
>
|
||||
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// fuse is a lightweight fuzzy-search module
|
||||
// make search results more in line with expectations
|
||||
import Fuse from 'fuse.js'
|
||||
import path from 'path'
|
||||
|
||||
export default {
|
||||
name: 'HeaderSearch',
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
options: [],
|
||||
searchPool: [],
|
||||
show: false,
|
||||
fuse: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
return this.$store.getters.permission_routes
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
routes() {
|
||||
this.searchPool = this.generateRoutes(this.routes)
|
||||
},
|
||||
searchPool(list) {
|
||||
this.initFuse(list)
|
||||
},
|
||||
show(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.close)
|
||||
} else {
|
||||
document.body.removeEventListener('click', this.close)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.searchPool = this.generateRoutes(this.routes)
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.show = !this.show
|
||||
if (this.show) {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
|
||||
this.options = []
|
||||
this.show = false
|
||||
},
|
||||
change(val) {
|
||||
if(this.ishttp(val.path)) {
|
||||
// http(s):// 路径新窗口打开
|
||||
window.open(val.path, "_blank");
|
||||
} else {
|
||||
this.$router.push(val.path)
|
||||
}
|
||||
this.search = ''
|
||||
this.options = []
|
||||
this.$nextTick(() => {
|
||||
this.show = false
|
||||
})
|
||||
},
|
||||
initFuse(list) {
|
||||
this.fuse = new Fuse(list, {
|
||||
shouldSort: true,
|
||||
threshold: 0.4,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: [{
|
||||
name: 'title',
|
||||
weight: 0.7
|
||||
}, {
|
||||
name: 'path',
|
||||
weight: 0.3
|
||||
}]
|
||||
})
|
||||
},
|
||||
// Filter out the routes that can be displayed in the sidebar
|
||||
// And generate the internationalized title
|
||||
generateRoutes(routes, basePath = '/', prefixTitle = []) {
|
||||
let res = []
|
||||
|
||||
for (const router of routes) {
|
||||
// skip hidden router
|
||||
if (router.hidden) { continue }
|
||||
|
||||
const data = {
|
||||
path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path,
|
||||
title: [...prefixTitle]
|
||||
}
|
||||
|
||||
if (router.meta && router.meta.title) {
|
||||
data.title = [...data.title, router.meta.title]
|
||||
|
||||
if (router.redirect !== 'noRedirect') {
|
||||
// only push the routes with title
|
||||
// special case: need to exclude parent router without redirect
|
||||
res.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
// recursive child routes
|
||||
if (router.children) {
|
||||
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
|
||||
if (tempRoutes.length >= 1) {
|
||||
res = [...res, ...tempRoutes]
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
querySearch(query) {
|
||||
if (query !== '') {
|
||||
this.options = this.fuse.search(query)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
ishttp(url) {
|
||||
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-search {
|
||||
font-size: 0 !important;
|
||||
|
||||
.search-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-search-select {
|
||||
font-size: 18px;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
.header-search-select {
|
||||
width: 210px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
68
yudao-ui-admin/src/components/IconSelect/index.vue
Normal file
68
yudao-ui-admin/src/components/IconSelect/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<!-- @author zhengjie -->
|
||||
<template>
|
||||
<div class="icon-body">
|
||||
<el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons">
|
||||
<i slot="suffix" class="el-icon-search el-input__icon" />
|
||||
</el-input>
|
||||
<div class="icon-list">
|
||||
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
|
||||
<svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import icons from './requireIcons'
|
||||
export default {
|
||||
name: 'IconSelect',
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
iconList: icons
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterIcons() {
|
||||
this.iconList = icons
|
||||
if (this.name) {
|
||||
this.iconList = this.iconList.filter(item => item.includes(this.name))
|
||||
}
|
||||
},
|
||||
selectedIcon(name) {
|
||||
this.$emit('selected', name)
|
||||
document.body.click()
|
||||
},
|
||||
reset() {
|
||||
this.name = ''
|
||||
this.iconList = icons
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.icon-body {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
.icon-list {
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
div {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin-bottom: -5px;
|
||||
cursor: pointer;
|
||||
width: 33%;
|
||||
float: left;
|
||||
}
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
11
yudao-ui-admin/src/components/IconSelect/requireIcons.js
Normal file
11
yudao-ui-admin/src/components/IconSelect/requireIcons.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const req = require.context('../../assets/icons/svg', false, /\.svg$/)
|
||||
const requireAll = requireContext => requireContext.keys()
|
||||
|
||||
const re = /\.\/(.*)\.svg/
|
||||
|
||||
const icons = requireAll(req).map(i => {
|
||||
return i.match(re)[1]
|
||||
})
|
||||
|
||||
export default icons
|
100
yudao-ui-admin/src/components/ImageUpload/index.vue
Normal file
100
yudao-ui-admin/src/components/ImageUpload/index.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
:action="uploadImgUrl"
|
||||
list-type="picture-card"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-error="handleUploadError"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="headers"
|
||||
style="display: inline-block; vertical-align: top"
|
||||
>
|
||||
<el-image v-if="!value" :src="value">
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-plus" />
|
||||
</div>
|
||||
</el-image>
|
||||
<div v-else class="image">
|
||||
<el-image :src="value" :style="`width:150px;height:150px;`" fit="fill"/>
|
||||
<div class="mask">
|
||||
<div class="actions">
|
||||
<span title="预览" @click.stop="dialogVisible = true">
|
||||
<i class="el-icon-zoom-in" />
|
||||
</span>
|
||||
<span title="移除" @click.stop="removeImage">
|
||||
<i class="el-icon-delete" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogVisible" title="预览" width="800" append-to-body>
|
||||
<img :src="value" style="display: block; max-width: 100%; margin: 0 auto;">
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeImage() {
|
||||
this.$emit("input", "");
|
||||
},
|
||||
handleUploadSuccess(res) {
|
||||
this.$emit("input", res.url);
|
||||
this.loading.close();
|
||||
},
|
||||
handleBeforeUpload() {
|
||||
this.loading = this.$loading({
|
||||
lock: true,
|
||||
text: "上传中",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
});
|
||||
},
|
||||
handleUploadError() {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "上传失败",
|
||||
});
|
||||
this.loading.close();
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.image {
|
||||
position: relative;
|
||||
.mask {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
&:hover .mask {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
101
yudao-ui-admin/src/components/Pagination/index.vue
Normal file
101
yudao-ui-admin/src/components/Pagination/index.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div :class="{'hidden':hidden}" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
:current-page.sync="currentPage"
|
||||
:page-size.sync="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
v-bind="$attrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { scrollTo } from '@/utils/scroll-to'
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:page', val)
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:limit', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { page: this.currentPage, limit: val })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { page: val, limit: this.pageSize })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
142
yudao-ui-admin/src/components/PanThumb/index.vue
Normal file
142
yudao-ui-admin/src/components/PanThumb/index.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
|
||||
<div class="pan-info">
|
||||
<div class="pan-info-roles-container">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<!-- eslint-disable-next-line -->
|
||||
<div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PanThumb',
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pan-item {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.pan-info-roles-container {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pan-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transform-origin: 95% 40%;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* .pan-thumb:after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: 40%;
|
||||
left: 95%;
|
||||
margin: -4px 0 0 -4px;
|
||||
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
|
||||
} */
|
||||
|
||||
.pan-info {
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.pan-info h3 {
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
letter-spacing: 2px;
|
||||
font-size: 18px;
|
||||
margin: 0 60px;
|
||||
padding: 22px 0 0 0;
|
||||
height: 85px;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.pan-info p {
|
||||
color: #fff;
|
||||
padding: 10px 5px;
|
||||
font-style: italic;
|
||||
margin: 0 30px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-info p a {
|
||||
display: block;
|
||||
color: #333;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
letter-spacing: 1px;
|
||||
padding-top: 24px;
|
||||
margin: 7px auto 0;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
opacity: 0;
|
||||
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
|
||||
transform: translateX(60px) rotate(90deg);
|
||||
}
|
||||
|
||||
.pan-info p a:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-thumb {
|
||||
transform: rotate(-110deg);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-info p a {
|
||||
opacity: 1;
|
||||
transform: translateX(0px) rotate(0deg);
|
||||
}
|
||||
</style>
|
3
yudao-ui-admin/src/components/ParentView/index.vue
Normal file
3
yudao-ui-admin/src/components/ParentView/index.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template >
|
||||
<router-view />
|
||||
</template>
|
21
yudao-ui-admin/src/components/RADME.md
Normal file
21
yudao-ui-admin/src/components/RADME.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## form-generator
|
||||
|
||||
github 地址:https://github.com/JakHuang/form-generator
|
||||
|
||||
* generator
|
||||
* parser
|
||||
* render
|
||||
* tinymce
|
||||
|
||||
## bpmn-process-designer
|
||||
|
||||
github 地址:https://github.com/miyuesc/bpmn-process-designer
|
||||
|
||||
* bpmnProcessDesigner
|
||||
|
||||
TODO 目前存在的问题,如果选择 activiti 类型时,因为不支持内置表单的设计,所以会报 Error: unknown type activiti:FormData 错误。具体可见 https://github.com/miyuesc/bpmn-process-designer/issues/16 。
|
||||
|
||||
另外几个流程设计器的选型:
|
||||
|
||||
* https://gitee.com/jimlow/vue-bpmn 相比差一些,已经停止维护,不算推荐。
|
||||
* https://github.com/GoldSubmarine/workflow-bpmn-modeler 仅支持 flowable 流程引擎。如果只考虑 flowable 的话,也是非常不错的选择。
|
149
yudao-ui-admin/src/components/RightPanel/index.vue
Normal file
149
yudao-ui-admin/src/components/RightPanel/index.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
|
||||
<div class="rightPanel-background" />
|
||||
<div class="rightPanel">
|
||||
<div class="rightPanel-items">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addClass, removeClass } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'RightPanel',
|
||||
props: {
|
||||
clickNotClose: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
buttonTop: {
|
||||
default: 250,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
return this.$store.state.settings.showSettings
|
||||
},
|
||||
set(val) {
|
||||
this.$store.dispatch('settings/changeSetting', {
|
||||
key: 'showSettings',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
},
|
||||
theme() {
|
||||
return this.$store.state.settings.theme
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
show(value) {
|
||||
if (value && !this.clickNotClose) {
|
||||
this.addEventClick()
|
||||
}
|
||||
if (value) {
|
||||
addClass(document.body, 'showRightPanel')
|
||||
} else {
|
||||
removeClass(document.body, 'showRightPanel')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.insertToBody()
|
||||
this.addEventClick()
|
||||
},
|
||||
beforeDestroy() {
|
||||
const elx = this.$refs.rightPanel
|
||||
elx.remove()
|
||||
},
|
||||
methods: {
|
||||
addEventClick() {
|
||||
window.addEventListener('click', this.closeSidebar)
|
||||
},
|
||||
closeSidebar(evt) {
|
||||
const parent = evt.target.closest('.rightPanel')
|
||||
if (!parent) {
|
||||
this.show = false
|
||||
window.removeEventListener('click', this.closeSidebar)
|
||||
}
|
||||
},
|
||||
insertToBody() {
|
||||
const elx = this.$refs.rightPanel
|
||||
const body = document.querySelector('body')
|
||||
body.insertBefore(elx, body.firstChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.showRightPanel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rightPanel-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
|
||||
background: rgba(0, 0, 0, .2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
|
||||
transition: all .25s cubic-bezier(.7, .3, .1, 1);
|
||||
transform: translate(100%);
|
||||
background: #fff;
|
||||
z-index: 40000;
|
||||
}
|
||||
|
||||
.show {
|
||||
transition: all .3s cubic-bezier(.7, .3, .1, 1);
|
||||
|
||||
.rightPanel-background {
|
||||
z-index: 20000;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
|
||||
.handle-button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
z-index: 0;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
i {
|
||||
font-size: 24px;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
38
yudao-ui-admin/src/components/RightToolbar/index.vue
Normal file
38
yudao-ui-admin/src/components/RightToolbar/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<!-- @author Shiyn/ huangmx 20200807优化-->
|
||||
<template>
|
||||
<div class="top-right-btn">
|
||||
<el-row>
|
||||
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
|
||||
<el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" />
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
|
||||
<el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" />
|
||||
</el-tooltip>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "RightToolbar",
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
//搜索
|
||||
toggleSearch() {
|
||||
this.$emit("update:showSearch", !this.showSearch);
|
||||
},
|
||||
//刷新
|
||||
refresh() {
|
||||
this.$emit("queryTable");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
21
yudao-ui-admin/src/components/RuoYi/Doc/index.vue
Normal file
21
yudao-ui-admin/src/components/RuoYi/Doc/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon icon-class="question" @click="goto"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'YudaoDoc',
|
||||
data() {
|
||||
return {
|
||||
url: 'http://www.iocoder.cn/Yudao/build-debugger-environment/?yudao'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goto() {
|
||||
window.open(this.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
21
yudao-ui-admin/src/components/RuoYi/Git/index.vue
Normal file
21
yudao-ui-admin/src/components/RuoYi/Git/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon icon-class="github" @click="goto"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'YudaoGit',
|
||||
data() {
|
||||
return {
|
||||
url: 'https://github.com/YunaiV/ruoyi-vue-pro'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goto() {
|
||||
window.open(this.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
57
yudao-ui-admin/src/components/Screenfull/index.vue
Normal file
57
yudao-ui-admin/src/components/Screenfull/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy()
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.isEnabled) {
|
||||
this.$message({ message: '你的浏览器不支持全屏', type: 'warning' })
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
},
|
||||
change() {
|
||||
this.isFullscreen = screenfull.isFullscreen
|
||||
},
|
||||
init() {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.on('change', this.change)
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.off('change', this.change)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.screenfull-svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
fill: #5a5e66;;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: 10px;
|
||||
}
|
||||
</style>
|
57
yudao-ui-admin/src/components/SizeSelect/index.vue
Normal file
57
yudao-ui-admin/src/components/SizeSelect/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click" @command="handleSetSize">
|
||||
<div>
|
||||
<svg-icon class-name="size-icon" icon-class="size" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
|
||||
{{
|
||||
item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sizeOptions: [
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Medium', value: 'medium' },
|
||||
{ label: 'Small', value: 'small' },
|
||||
{ label: 'Mini', value: 'mini' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
size() {
|
||||
return this.$store.getters.size
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSetSize(size) {
|
||||
this.$ELEMENT.size = size
|
||||
this.$store.dispatch('app/setSize', size)
|
||||
this.refreshView()
|
||||
this.$message({
|
||||
message: 'Switch Size Success',
|
||||
type: 'success'
|
||||
})
|
||||
},
|
||||
refreshView() {
|
||||
// In order to make the cached page re-rendered
|
||||
this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
|
||||
|
||||
const { fullPath } = this.$route
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
61
yudao-ui-admin/src/components/SvgIcon/index.vue
Normal file
61
yudao-ui-admin/src/components/SvgIcon/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
|
||||
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isExternal } from '@/utils/validate'
|
||||
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExternal() {
|
||||
return isExternal(this.iconClass)
|
||||
},
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass() {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
},
|
||||
styleExternalIcon() {
|
||||
return {
|
||||
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.svg-external-icon {
|
||||
background-color: currentColor;
|
||||
mask-size: cover!important;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
175
yudao-ui-admin/src/components/ThemePicker/index.vue
Normal file
175
yudao-ui-admin/src/components/ThemePicker/index.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const version = require('element-ui/package.json').version // element-ui version from node_modules
|
||||
const ORIGINAL_THEME = '#409EFF' // default color
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chalk: '', // content of theme-chalk css
|
||||
theme: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultTheme() {
|
||||
return this.$store.state.settings.theme
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
defaultTheme: {
|
||||
handler: function(val, oldVal) {
|
||||
this.theme = val
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
async theme(val) {
|
||||
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
|
||||
if (typeof val !== 'string') return
|
||||
const themeCluster = this.getThemeCluster(val.replace('#', ''))
|
||||
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
|
||||
console.log(themeCluster, originalCluster)
|
||||
|
||||
const $message = this.$message({
|
||||
message: ' Compiling the theme',
|
||||
customClass: 'theme-message',
|
||||
type: 'success',
|
||||
duration: 0,
|
||||
iconClass: 'el-icon-loading'
|
||||
})
|
||||
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
|
||||
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
|
||||
|
||||
let styleTag = document.getElementById(id)
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.setAttribute('id', id)
|
||||
document.head.appendChild(styleTag)
|
||||
}
|
||||
styleTag.innerText = newStyle
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.chalk) {
|
||||
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
|
||||
await this.getCSSString(url, 'chalk')
|
||||
}
|
||||
|
||||
const chalkHandler = getHandler('chalk', 'chalk-style')
|
||||
|
||||
chalkHandler()
|
||||
|
||||
const styles = [].slice.call(document.querySelectorAll('style'))
|
||||
.filter(style => {
|
||||
const text = style.innerText
|
||||
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
|
||||
})
|
||||
styles.forEach(style => {
|
||||
const { innerText } = style
|
||||
if (typeof innerText !== 'string') return
|
||||
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
|
||||
})
|
||||
|
||||
this.$emit('change', val)
|
||||
|
||||
$message.close()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateStyle(style, oldCluster, newCluster) {
|
||||
let newStyle = style
|
||||
oldCluster.forEach((color, index) => {
|
||||
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
|
||||
})
|
||||
return newStyle
|
||||
},
|
||||
|
||||
getCSSString(url, variable) {
|
||||
return new Promise(resolve => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
})
|
||||
},
|
||||
|
||||
getThemeCluster(theme) {
|
||||
const tintColor = (color, tint) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
if (tint === 0) { // when primary color is in its rgb space
|
||||
return [red, green, blue].join(',')
|
||||
} else {
|
||||
red += Math.round(tint * (255 - red))
|
||||
green += Math.round(tint * (255 - green))
|
||||
blue += Math.round(tint * (255 - blue))
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
}
|
||||
|
||||
const shadeColor = (color, shade) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
red = Math.round((1 - shade) * red)
|
||||
green = Math.round((1 - shade) * green)
|
||||
blue = Math.round((1 - shade) * blue)
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
|
||||
const clusters = [theme]
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
|
||||
}
|
||||
clusters.push(shadeColor(theme, 0.1))
|
||||
return clusters
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.theme-message,
|
||||
.theme-picker-dropdown {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.theme-picker .el-color-picker__trigger {
|
||||
height: 26px !important;
|
||||
width: 26px !important;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
68
yudao-ui-admin/src/components/UploadImage/index.vue
Normal file
68
yudao-ui-admin/src/components/UploadImage/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
:action="uploadImgUrl"
|
||||
list-type="picture-card"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-error="handleUploadError"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="headers"
|
||||
style="display: inline-block; vertical-align: top"
|
||||
>
|
||||
<img v-if="value" :src="value" class="avatar" />
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleUploadSuccess(res) {
|
||||
this.$emit("input", res.url);
|
||||
this.loading.close();
|
||||
},
|
||||
handleBeforeUpload() {
|
||||
this.loading = this.$loading({
|
||||
lock: true,
|
||||
text: "上传中",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
});
|
||||
},
|
||||
handleUploadError() {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "上传失败",
|
||||
});
|
||||
this.loading.close();
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,480 @@
|
||||
<template>
|
||||
<div class="my-process-designer">
|
||||
<div class="my-process-designer__header">
|
||||
<slot name="control-header"></slot>
|
||||
<template v-if="!$slots['control-header']">
|
||||
<el-button-group key="file-control">
|
||||
<el-button :size="headerButtonSize" icon="el-icon-folder-opened" @click="$refs.refFile.click()">打开文件</el-button>
|
||||
<el-tooltip effect="light">
|
||||
<div slot="content">
|
||||
<el-button :size="headerButtonSize" type="text" @click="downloadProcessAsXml()">下载为XML文件</el-button>
|
||||
<br />
|
||||
<el-button :size="headerButtonSize" type="text" @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
|
||||
<br />
|
||||
<el-button :size="headerButtonSize" type="text" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
|
||||
</div>
|
||||
<el-button :size="headerButtonSize" icon="el-icon-download">下载文件</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light">
|
||||
<div slot="content">
|
||||
<el-button :size="headerButtonSize" type="text" @click="previewProcessXML">预览XML</el-button>
|
||||
<br />
|
||||
<el-button :size="headerButtonSize" type="text" @click="previewProcessJson">预览JSON</el-button>
|
||||
</div>
|
||||
<el-button :size="headerButtonSize" icon="el-icon-view">预览</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="simulation" effect="light" :content="this.simulationStatus ? '退出模拟' : '开启模拟'">
|
||||
<el-button :size="headerButtonSize" icon="el-icon-cpu" @click="processSimulation">
|
||||
模拟
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
<el-button-group key="align-control">
|
||||
<el-tooltip effect="light" content="向左对齐">
|
||||
<el-button :size="headerButtonSize" class="align align-left" icon="el-icon-s-data" @click="elementsAlign('left')" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="向右对齐">
|
||||
<el-button :size="headerButtonSize" class="align align-right" icon="el-icon-s-data" @click="elementsAlign('right')" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="向上对齐">
|
||||
<el-button :size="headerButtonSize" class="align align-top" icon="el-icon-s-data" @click="elementsAlign('top')" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="向下对齐">
|
||||
<el-button :size="headerButtonSize" class="align align-bottom" icon="el-icon-s-data" @click="elementsAlign('bottom')" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="水平居中">
|
||||
<el-button :size="headerButtonSize" class="align align-center" icon="el-icon-s-data" @click="elementsAlign('center')" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="垂直居中">
|
||||
<el-button :size="headerButtonSize" class="align align-middle" icon="el-icon-s-data" @click="elementsAlign('middle')" />
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
<el-button-group key="scale-control">
|
||||
<el-tooltip effect="light" content="缩小视图">
|
||||
<el-button :size="headerButtonSize" :disabled="defaultZoom < 0.2" icon="el-icon-zoom-out" @click="processZoomOut()" />
|
||||
</el-tooltip>
|
||||
<el-button :size="headerButtonSize">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
|
||||
<el-tooltip effect="light" content="放大视图">
|
||||
<el-button :size="headerButtonSize" :disabled="defaultZoom > 4" icon="el-icon-zoom-in" @click="processZoomIn()" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="重置视图并居中">
|
||||
<el-button :size="headerButtonSize" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
<el-button-group key="stack-control">
|
||||
<el-tooltip effect="light" content="撤销">
|
||||
<el-button :size="headerButtonSize" :disabled="!revocable" icon="el-icon-refresh-left" @click="processUndo()" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="恢复">
|
||||
<el-button :size="headerButtonSize" :disabled="!recoverable" icon="el-icon-refresh-right" @click="processRedo()" />
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="重新绘制">
|
||||
<el-button :size="headerButtonSize" icon="el-icon-refresh" @click="processRestart" />
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
<el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-plus" @click="processSave">保存模型</el-button>
|
||||
</template>
|
||||
<!-- 用于打开本地文件-->
|
||||
<input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" />
|
||||
</div>
|
||||
<div class="my-process-designer__container">
|
||||
<div class="my-process-designer__canvas" ref="bpmn-canvas"></div>
|
||||
</div>
|
||||
<el-dialog title="预览" width="80%" :visible.sync="previewModelVisible" append-to-body destroy-on-close>
|
||||
<pre><code class="hljs" v-html="highlightedCode(previewType, previewResult)"></code></pre>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BpmnModeler from "bpmn-js/lib/Modeler";
|
||||
import DefaultEmptyXML from "./plugins/defaultEmpty";
|
||||
// 翻译方法
|
||||
import customTranslate from "./plugins/translate/customTranslate";
|
||||
import translationsCN from "./plugins/translate/zh";
|
||||
// 模拟流转流程
|
||||
import tokenSimulation from "bpmn-js-token-simulation";
|
||||
// 标签解析构建器
|
||||
// import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
|
||||
// 标签解析 Moddle
|
||||
import camundaModdleDescriptor from "./plugins/descriptor/camundaDescriptor.json";
|
||||
import activitiModdleDescriptor from "./plugins/descriptor/activitiDescriptor.json";
|
||||
import flowableModdleDescriptor from "./plugins/descriptor/flowableDescriptor.json";
|
||||
// 标签解析 Extension
|
||||
import camundaModdleExtension from "./plugins/extension-moddle/camunda";
|
||||
import activitiModdleExtension from "./plugins/extension-moddle/activiti";
|
||||
import flowableModdleExtension from "./plugins/extension-moddle/flowable";
|
||||
// 引入json转换与高亮
|
||||
import convert from "xml-js";
|
||||
|
||||
// 代码高亮插件
|
||||
import hljs from "highlight.js/lib/highlight";
|
||||
import "highlight.js/styles/github-gist.css";
|
||||
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"));
|
||||
hljs.registerLanguage("json", require("highlight.js/lib/languages/json"));
|
||||
|
||||
export default {
|
||||
name: "MyProcessDesigner",
|
||||
componentName: "MyProcessDesigner",
|
||||
props: {
|
||||
value: String, // xml 字符串
|
||||
processId: String, // 流程 key 标识
|
||||
processName: String, // 流程 name 名字
|
||||
formId: Number, // 流程 form 表单编号
|
||||
translations: Object, // 自定义的翻译文件
|
||||
additionalModel: [Object, Array], // 自定义model
|
||||
moddleExtension: Object, // 自定义moddle
|
||||
onlyCustomizeAddi: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onlyCustomizeModdle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
simulation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
keyboard: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: "camunda"
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: () => ["element.click"]
|
||||
},
|
||||
headerButtonSize: {
|
||||
type: String,
|
||||
default: "small",
|
||||
validator: value => ["default", "medium", "small", "mini"].indexOf(value) !== -1
|
||||
},
|
||||
headerButtonType: {
|
||||
type: String,
|
||||
default: "primary",
|
||||
validator: value => ["default", "primary", "success", "warning", "danger", "info"].indexOf(value) !== -1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultZoom: 1,
|
||||
previewModelVisible: false,
|
||||
simulationStatus: false,
|
||||
previewResult: "",
|
||||
previewType: "xml",
|
||||
recoverable: false,
|
||||
revocable: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
additionalModules() {
|
||||
const Modules = [];
|
||||
// 仅保留用户自定义扩展模块
|
||||
if (this.onlyCustomizeAddi) {
|
||||
if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
|
||||
return this.additionalModel || [];
|
||||
}
|
||||
return [this.additionalModel];
|
||||
}
|
||||
|
||||
// 插入用户自定义扩展模块
|
||||
if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
|
||||
Modules.push(...this.additionalModel);
|
||||
} else {
|
||||
this.additionalModel && Modules.push(this.additionalModel);
|
||||
}
|
||||
|
||||
// 翻译模块
|
||||
const TranslateModule = {
|
||||
translate: ["value", customTranslate(this.translations || translationsCN)]
|
||||
};
|
||||
Modules.push(TranslateModule);
|
||||
|
||||
// 模拟流转模块
|
||||
if (this.simulation) {
|
||||
Modules.push(tokenSimulation);
|
||||
}
|
||||
|
||||
// 根据需要的流程类型设置扩展元素构建模块
|
||||
// if (this.prefix === "bpmn") {
|
||||
// Modules.push(bpmnModdleExtension);
|
||||
// }
|
||||
if (this.prefix === "camunda") {
|
||||
Modules.push(camundaModdleExtension);
|
||||
}
|
||||
if (this.prefix === "flowable") {
|
||||
Modules.push(flowableModdleExtension);
|
||||
}
|
||||
if (this.prefix === "activiti") {
|
||||
Modules.push(activitiModdleExtension);
|
||||
}
|
||||
|
||||
return Modules;
|
||||
},
|
||||
moddleExtensions() {
|
||||
const Extensions = {};
|
||||
// 仅使用用户自定义模块
|
||||
if (this.onlyCustomizeModdle) {
|
||||
return this.moddleExtension || null;
|
||||
}
|
||||
|
||||
// 插入用户自定义模块
|
||||
if (this.moddleExtension) {
|
||||
for (let key in this.moddleExtension) {
|
||||
Extensions[key] = this.moddleExtension[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据需要的 "流程类型" 设置 对应的解析文件
|
||||
if (this.prefix === "activiti") {
|
||||
Extensions.activiti = activitiModdleDescriptor;
|
||||
}
|
||||
if (this.prefix === "flowable") {
|
||||
Extensions.flowable = flowableModdleDescriptor;
|
||||
}
|
||||
if (this.prefix === "camunda") {
|
||||
Extensions.camunda = camundaModdleDescriptor;
|
||||
}
|
||||
|
||||
return Extensions;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initBpmnModeler();
|
||||
this.createNewDiagram(this.value);
|
||||
this.$once("hook:beforeDestroy", () => {
|
||||
if (this.bpmnModeler) this.bpmnModeler.destroy();
|
||||
this.$emit("destroy", this.bpmnModeler);
|
||||
this.bpmnModeler = null;
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
value: function (newValue) { // 在 xmlString 发生变化时,重新创建,从而绘制流程图
|
||||
this.createNewDiagram(newValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initBpmnModeler() {
|
||||
if (this.bpmnModeler) return;
|
||||
this.bpmnModeler = new BpmnModeler({
|
||||
container: this.$refs["bpmn-canvas"],
|
||||
keyboard: this.keyboard ? { bindTo: document } : null,
|
||||
additionalModules: this.additionalModules,
|
||||
moddleExtensions: this.moddleExtensions
|
||||
});
|
||||
this.$emit("init-finished", this.bpmnModeler);
|
||||
this.initModelListeners();
|
||||
},
|
||||
initModelListeners() {
|
||||
const EventBus = this.bpmnModeler.get("eventBus");
|
||||
const that = this;
|
||||
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
|
||||
this.events.forEach(event => {
|
||||
EventBus.on(event, function(eventObj) {
|
||||
let eventName = event.replace(/\./g, "-");
|
||||
let element = eventObj ? eventObj.element : null;
|
||||
that.$emit(eventName, element, eventObj);
|
||||
});
|
||||
});
|
||||
// 监听图形改变返回xml
|
||||
EventBus.on("commandStack.changed", async event => {
|
||||
try {
|
||||
this.recoverable = this.bpmnModeler.get("commandStack").canRedo();
|
||||
this.revocable = this.bpmnModeler.get("commandStack").canUndo();
|
||||
let { xml } = await this.bpmnModeler.saveXML({ format: true });
|
||||
this.$emit("commandStack-changed", event);
|
||||
this.$emit("input", xml);
|
||||
this.$emit("change", xml);
|
||||
} catch (e) {
|
||||
console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
}
|
||||
});
|
||||
// 监听视图缩放变化
|
||||
this.bpmnModeler.on("canvas.viewbox.changed", ({ viewbox }) => {
|
||||
this.$emit("canvas-viewbox-changed", { viewbox });
|
||||
const { scale } = viewbox;
|
||||
this.defaultZoom = Math.floor(scale * 100) / 100;
|
||||
});
|
||||
},
|
||||
/* 创建新的流程图 */
|
||||
async createNewDiagram(xml) {
|
||||
// 将字符串转换成图显示出来
|
||||
let newId = this.processId || `Process_${new Date().getTime()}`;
|
||||
let newName = this.processName || `业务流程_${new Date().getTime()}`;
|
||||
let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
|
||||
try {
|
||||
console.log(this.bpmnModeler.importXML);
|
||||
let { warnings } = await this.bpmnModeler.importXML(xmlString);
|
||||
if (warnings && warnings.length) {
|
||||
warnings.forEach(warn => console.warn(warn));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[Process Designer Warn]: ${e?.message || e}`);
|
||||
}
|
||||
},
|
||||
|
||||
// 下载流程图到本地
|
||||
async downloadProcess(type, name) {
|
||||
try {
|
||||
const _this = this;
|
||||
// 按需要类型创建文件并下载
|
||||
if (type === "xml" || type === "bpmn") {
|
||||
const { err, xml } = await this.bpmnModeler.saveXML();
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
console.error(`[Process Designer Warn ]: ${err.message || err}`);
|
||||
}
|
||||
let { href, filename } = _this.setEncoded(type.toUpperCase(), name, xml);
|
||||
downloadFunc(href, filename);
|
||||
} else {
|
||||
const { err, svg } = await this.bpmnModeler.saveSVG();
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
let { href, filename } = _this.setEncoded("SVG", name, svg);
|
||||
downloadFunc(href, filename);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[Process Designer Warn ]: ${e.message || e}`);
|
||||
}
|
||||
// 文件下载方法
|
||||
function downloadFunc(href, filename) {
|
||||
if (href && filename) {
|
||||
let a = document.createElement("a");
|
||||
a.download = filename; //指定下载的文件名
|
||||
a.href = href; // URL对象
|
||||
a.click(); // 模拟点击
|
||||
URL.revokeObjectURL(a.href); // 释放URL 对象
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 根据所需类型进行转码并返回下载地址
|
||||
setEncoded(type, filename = "diagram", data) {
|
||||
const encodedData = encodeURIComponent(data);
|
||||
return {
|
||||
filename: `${filename}.${type}`,
|
||||
href: `data:application/${type === "svg" ? "text/xml" : "bpmn20-xml"};charset=UTF-8,${encodedData}`,
|
||||
data: data
|
||||
};
|
||||
},
|
||||
|
||||
// 加载本地文件
|
||||
importLocalFile() {
|
||||
const that = this;
|
||||
const file = this.$refs.refFile.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = function() {
|
||||
let xmlStr = this.result;
|
||||
that.createNewDiagram(xmlStr);
|
||||
};
|
||||
},
|
||||
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
|
||||
downloadProcessAsXml() {
|
||||
this.downloadProcess("xml");
|
||||
},
|
||||
downloadProcessAsBpmn() {
|
||||
this.downloadProcess("bpmn");
|
||||
},
|
||||
downloadProcessAsSvg() {
|
||||
this.downloadProcess("svg");
|
||||
},
|
||||
processSimulation() {
|
||||
this.simulationStatus = !this.simulationStatus;
|
||||
this.simulation && this.bpmnModeler.get("toggleMode").toggleMode();
|
||||
},
|
||||
processRedo() {
|
||||
this.bpmnModeler.get("commandStack").redo();
|
||||
},
|
||||
processUndo() {
|
||||
this.bpmnModeler.get("commandStack").undo();
|
||||
},
|
||||
processZoomIn(zoomStep = 0.1) {
|
||||
let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
|
||||
}
|
||||
this.defaultZoom = newZoom;
|
||||
this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
|
||||
},
|
||||
processZoomOut(zoomStep = 0.1) {
|
||||
let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
|
||||
}
|
||||
this.defaultZoom = newZoom;
|
||||
this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
|
||||
},
|
||||
processZoomTo(newZoom = 1) {
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
|
||||
}
|
||||
if (newZoom > 4) {
|
||||
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
|
||||
}
|
||||
this.defaultZoom = newZoom;
|
||||
this.bpmnModeler.get("canvas").zoom(newZoom);
|
||||
},
|
||||
processReZoom() {
|
||||
this.defaultZoom = 1;
|
||||
this.bpmnModeler.get("canvas").zoom("fit-viewport", "auto");
|
||||
},
|
||||
processRestart() {
|
||||
this.recoverable = false;
|
||||
this.revocable = false;
|
||||
this.createNewDiagram(null);
|
||||
},
|
||||
elementsAlign(align) {
|
||||
const Align = this.bpmnModeler.get("alignElements");
|
||||
const Selection = this.bpmnModeler.get("selection");
|
||||
const SelectedElements = Selection.get();
|
||||
if (!SelectedElements || SelectedElements.length <= 1) {
|
||||
this.$message.warning("请按住 Ctrl 键选择多个元素对齐");
|
||||
return;
|
||||
}
|
||||
this.$confirm("自动对齐可能造成图形变形,是否继续?", "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => Align.trigger(SelectedElements, align));
|
||||
},
|
||||
/*----------------------------- 方法结束 ---------------------------------*/
|
||||
previewProcessXML() {
|
||||
this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
this.previewResult = xml;
|
||||
this.previewType = "xml";
|
||||
this.previewModelVisible = true;
|
||||
});
|
||||
},
|
||||
previewProcessJson() {
|
||||
this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
this.previewResult = convert.xml2json(xml, { spaces: 2 });
|
||||
this.previewType = "json";
|
||||
this.previewModelVisible = true;
|
||||
});
|
||||
},
|
||||
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
|
||||
async processSave() {
|
||||
const { err, xml } = await this.bpmnModeler.saveXML();
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
this.msgError('保存模型失败,请重试!')
|
||||
return
|
||||
}
|
||||
// 触发 save 事件
|
||||
this.$emit('save', xml)
|
||||
},
|
||||
/** 高亮显示 */
|
||||
highlightedCode(previewType, previewResult) {
|
||||
const result = hljs.highlight(previewType, previewResult || "", true);
|
||||
return result.value || ' ';
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,470 @@
|
||||
<template>
|
||||
<div class="my-process-designer">
|
||||
<div class="my-process-designer__container">
|
||||
<div class="my-process-designer__canvas" ref="bpmn-canvas"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BpmnViewer from "bpmn-js/lib/Viewer";
|
||||
import DefaultEmptyXML from "./plugins/defaultEmpty";
|
||||
|
||||
export default {
|
||||
name: "MyProcessViewer",
|
||||
componentName: "MyProcessViewer",
|
||||
props: {
|
||||
value: { // BPMN XML 字符串
|
||||
type: String,
|
||||
},
|
||||
prefix: { // 使用哪个引擎
|
||||
type: String,
|
||||
default: "camunda",
|
||||
},
|
||||
activityData: { // 活动的数据。传递时,可高亮流程
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
processInstanceData: { // 流程实例的数据。传递时,可展示流程发起人等信息
|
||||
type: Object,
|
||||
},
|
||||
taskData: { // 任务实例的数据。传递时,可展示 UserTask 审核相关的信息
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
xml: '',
|
||||
activityList: [],
|
||||
processInstance: undefined,
|
||||
taskList: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.xml = this.value;
|
||||
this.activityList = this.activityData;
|
||||
// 初始化
|
||||
this.initBpmnModeler();
|
||||
this.createNewDiagram(this.xml);
|
||||
this.$once("hook:beforeDestroy", () => {
|
||||
if (this.bpmnModeler) this.bpmnModeler.destroy();
|
||||
this.$emit("destroy", this.bpmnModeler);
|
||||
this.bpmnModeler = null;
|
||||
});
|
||||
// 初始模型的监听器
|
||||
this.initModelListeners();
|
||||
},
|
||||
watch: {
|
||||
value: function (newValue) { // 在 xmlString 发生变化时,重新创建,从而绘制流程图
|
||||
this.xml = newValue;
|
||||
this.createNewDiagram(this.xml);
|
||||
},
|
||||
activityData: function (newActivityData) {
|
||||
this.activityList = newActivityData;
|
||||
this.createNewDiagram(this.xml);
|
||||
},
|
||||
processInstanceData: function (newProcessInstanceData) {
|
||||
this.processInstance = newProcessInstanceData;
|
||||
this.createNewDiagram(this.xml);
|
||||
},
|
||||
taskData: function (newTaskListData) {
|
||||
this.taskList = newTaskListData;
|
||||
this.createNewDiagram(this.xml);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initBpmnModeler() {
|
||||
if (this.bpmnModeler) return;
|
||||
this.bpmnModeler = new BpmnViewer({
|
||||
container: this.$refs["bpmn-canvas"],
|
||||
bpmnRenderer: {
|
||||
}
|
||||
})
|
||||
},
|
||||
/* 创建新的流程图 */
|
||||
async createNewDiagram(xml) {
|
||||
// 将字符串转换成图显示出来
|
||||
let newId = `Process_${new Date().getTime()}`;
|
||||
let newName = `业务流程_${new Date().getTime()}`;
|
||||
let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
|
||||
try {
|
||||
// console.log(this.bpmnModeler.importXML);
|
||||
let { warnings } = await this.bpmnModeler.importXML(xmlString);
|
||||
if (warnings && warnings.length) {
|
||||
warnings.forEach(warn => console.warn(warn));
|
||||
}
|
||||
// 高亮流程图
|
||||
await this.highlightDiagram();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// console.error(`[Process Designer Warn]: ${e?.message || e}`);
|
||||
}
|
||||
},
|
||||
/* 高亮流程图 */
|
||||
// TODO 芋艿:如果多个 endActivity 的话,目前的逻辑可能有一定的问题。https://www.jdon.com/workflow/multi-events.html
|
||||
async highlightDiagram() {
|
||||
const activityList = this.activityList;
|
||||
if (activityList.length === 0) {
|
||||
return;
|
||||
}
|
||||
// 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现
|
||||
// 再次基础上,增加不同审批结果的颜色等等
|
||||
let canvas = this.bpmnModeler.get('canvas');
|
||||
let todoActivity = activityList.find(m => !m.endTime) // 找到待办的任务
|
||||
let endActivity = activityList[activityList.length - 1] // 获得最后一个任务
|
||||
// debugger
|
||||
console.log(this.bpmnModeler.getDefinitions().rootElements[0].flowElements);
|
||||
this.bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach(n => {
|
||||
let activity = activityList.find(m => m.key === n.id) // 找到对应的活动
|
||||
if (n.$type === 'bpmn:UserTask') { // 用户任务
|
||||
if (!activity) {
|
||||
return;
|
||||
}
|
||||
// 处理用户任务的高亮
|
||||
const task = this.taskList.find(m => m.id === activity.taskId); // 找到活动对应的 taskId
|
||||
if (task) {
|
||||
canvas.addMarker(n.id, this.getResultCss(task.result));
|
||||
// 如果非通过,就不走后面的线条了
|
||||
if (task.result !== 2) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 outgoing 出线
|
||||
const outgoing = this.getActivityOutgoing(activity);
|
||||
outgoing?.forEach(nn => {
|
||||
// debugger
|
||||
let targetActivity = activityList.find(m => m.key === nn.targetRef.id)
|
||||
// 如果目标活动存在,则根据该活动是否结束,进行【bpmn:SequenceFlow】连线的高亮设置
|
||||
if (targetActivity) {
|
||||
canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo');
|
||||
} else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') { // TODO 芋艿:这个流程,暂时没走到过
|
||||
canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo');
|
||||
canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo');
|
||||
} else if (nn.targetRef.$type === 'bpmn:EndEvent') { // TODO 芋艿:这个流程,暂时没走到过
|
||||
if (!todoActivity && endActivity.key === n.id) {
|
||||
canvas.addMarker(nn.id, 'highlight');
|
||||
canvas.addMarker(nn.targetRef.id, 'highlight');
|
||||
}
|
||||
if (!activity.endTime) {
|
||||
canvas.addMarker(nn.id, 'highlight-todo');
|
||||
canvas.addMarker(nn.targetRef.id, 'highlight-todo');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (n.$type === 'bpmn:ExclusiveGateway') { // 排它网关
|
||||
if (!activity) {
|
||||
return
|
||||
}
|
||||
// 设置【bpmn:ExclusiveGateway】排它网关的高亮
|
||||
canvas.addMarker(n.id, this.getActivityHighlightCss(activity));
|
||||
// 查找需要高亮的连线
|
||||
let matchNN = undefined;
|
||||
let matchActivity = undefined;
|
||||
n.outgoing?.forEach(nn => {
|
||||
let targetActivity = activityList.find(m => m.key === nn.targetRef.id);
|
||||
if (!targetActivity) {
|
||||
return;
|
||||
}
|
||||
// 特殊判断 endEvent 类型的原因,ExclusiveGateway 可能后续连有 2 个路径:
|
||||
// 1. 一个是 UserTask => EndEvent
|
||||
// 2. 一个是 EndEvent
|
||||
// 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。
|
||||
// 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~
|
||||
if (!matchActivity || matchActivity.type === 'endEvent') {
|
||||
matchNN = nn;
|
||||
matchActivity = targetActivity;
|
||||
}
|
||||
})
|
||||
if (matchNN && matchActivity) {
|
||||
canvas.addMarker(matchNN.id, this.getActivityHighlightCss(matchActivity));
|
||||
}
|
||||
} else if (n.$type === 'bpmn:ParallelGateway') { // 并行网关
|
||||
if (!activity) {
|
||||
return
|
||||
}
|
||||
// 设置【bpmn:ParallelGateway】并行网关的高亮
|
||||
canvas.addMarker(n.id, this.getActivityHighlightCss(activity));
|
||||
n.outgoing?.forEach(nn => {
|
||||
// 获得连线是否有指向目标。如果有,则进行高亮
|
||||
const targetActivity = activityList.find(m => m.key === nn.targetRef.id)
|
||||
if (targetActivity) {
|
||||
canvas.addMarker(nn.id, this.getActivityHighlightCss(targetActivity)); // 高亮【bpmn:SequenceFlow】连线
|
||||
// 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然,如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。
|
||||
canvas.addMarker(nn.targetRef.id, this.getActivityHighlightCss(targetActivity));
|
||||
}
|
||||
})
|
||||
} else if (n.$type === 'bpmn:StartEvent') { // 开始节点
|
||||
n.outgoing?.forEach(nn => { // outgoing 例如说【bpmn:SequenceFlow】连线
|
||||
// 获得连线是否有指向目标。如果有,则进行高亮
|
||||
let targetActivity = activityList.find(m => m.key === nn.targetRef.id);
|
||||
if (targetActivity) {
|
||||
canvas.addMarker(nn.id, 'highlight'); // 高亮【bpmn:SequenceFlow】连线
|
||||
canvas.addMarker(n.id, 'highlight'); // 高亮【bpmn:StartEvent】开始节点(自己)
|
||||
}
|
||||
});
|
||||
} else if (n.$type === 'bpmn:EndEvent') { // 结束节点
|
||||
if (!this.processInstance || this.processInstance.result === 1) {
|
||||
return;
|
||||
}
|
||||
canvas.addMarker(n.id, this.getResultCss(this.processInstance.result));
|
||||
}
|
||||
})
|
||||
},
|
||||
getActivityHighlightCss(activity) {
|
||||
return activity.endTime ? 'highlight' : 'highlight-todo';
|
||||
},
|
||||
getResultCss(result) {
|
||||
if (result === 1) {
|
||||
return 'highlight-todo';
|
||||
} else if (result === 2) {
|
||||
return 'highlight';
|
||||
} else if (result === 3) {
|
||||
return 'highlight-reject';
|
||||
} else if (result === 4) {
|
||||
return 'highlight-cancel';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
getActivityOutgoing(activity) {
|
||||
// 如果有 outgoing,则直接使用它
|
||||
if (activity.outgoing && activity.outgoing.length > 0) {
|
||||
return activity.outgoing;
|
||||
}
|
||||
// 如果没有,则遍历获得起点为它的【bpmn:SequenceFlow】节点们。原因是:bpmn-js 的 UserTask 拿不到 outgoing
|
||||
const flowElements = this.bpmnModeler.getDefinitions().rootElements[0].flowElements;
|
||||
const outgoing = [];
|
||||
flowElements.forEach(item => {
|
||||
if (item.$type !== 'bpmn:SequenceFlow') {
|
||||
return;
|
||||
}
|
||||
if (item.sourceRef.id === activity.key) {
|
||||
outgoing.push(item);
|
||||
}
|
||||
});
|
||||
return outgoing;
|
||||
},
|
||||
initModelListeners() {
|
||||
const EventBus = this.bpmnModeler.get("eventBus");
|
||||
const that = this;
|
||||
// 注册需要的监听事件
|
||||
EventBus.on('element.hover', function(eventObj) {
|
||||
let element = eventObj ? eventObj.element : null;
|
||||
that.elementHover(element);
|
||||
});
|
||||
EventBus.on('element.out', function(eventObj) {
|
||||
let element = eventObj ? eventObj.element : null;
|
||||
that.elementOut(element);
|
||||
});
|
||||
},
|
||||
// 流程图的元素被 hover
|
||||
elementHover(element) {
|
||||
this.element = element;
|
||||
!this.elementOverlayIds && (this.elementOverlayIds = {});
|
||||
!this.overlays && (this.overlays = this.bpmnModeler.get("overlays"));
|
||||
// 展示信息
|
||||
if (!this.elementOverlayIds[element.id] && element.type !== "bpmn:Process") {
|
||||
let html = `<div class="element-overlays">
|
||||
<p>Elemet id: ${element.id}</p>
|
||||
<p>Elemet type: ${element.type}</p>
|
||||
</div>`; // 默认值
|
||||
if (element.type === 'bpmn:StartEvent' && this.processInstance) {
|
||||
html = `<p>发起人:${this.processInstance.startUser.nickname}</p>
|
||||
<p>部门:${this.processInstance.startUser.deptName}</p>
|
||||
<p>创建时间:${this.parseTime(this.processInstance.createTime)}`;
|
||||
} else if (element.type === 'bpmn:UserTask') {
|
||||
// debugger
|
||||
const activity = this.activityList.find(m => m.key === element.id);
|
||||
if (!activity) {
|
||||
return;
|
||||
}
|
||||
let task = this.taskList.find(m => m.id === activity.taskId); // 找到活动对应的 taskId
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
html = `<p>审批人:${task.assigneeUser.nickname}</p>
|
||||
<p>部门:${task.assigneeUser.deptName}</p>
|
||||
<p>结果:${this.getDictDataLabel(this.DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT, task.result)}</p>
|
||||
<p>创建时间:${this.parseTime(task.createTime)}</p>`;
|
||||
if (task.endTime) {
|
||||
html += `<p>结束时间:${this.parseTime(task.endTime)}</p>`
|
||||
}
|
||||
if (task.comment) {
|
||||
html += `<p>审批建议:${task.comment}</p>`
|
||||
}
|
||||
} else if (element.type === 'bpmn:EndEvent' && this.processInstance) {
|
||||
html = `<p>结果:${this.getDictDataLabel(this.DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT, this.processInstance.result)}</p>`;
|
||||
if (this.processInstance.endTime) {
|
||||
html += `<p>结束时间:${this.parseTime(this.processInstance.endTime)}</p>`
|
||||
}
|
||||
}
|
||||
this.elementOverlayIds[element.id] = this.overlays.add(element, {
|
||||
position: { left: 0, bottom: 0 },
|
||||
html: `<div class="element-overlays">${html}</div>`
|
||||
});
|
||||
}
|
||||
},
|
||||
// 流程图的元素被 out
|
||||
elementOut(element) {
|
||||
this.overlays.remove({ element });
|
||||
this.elementOverlayIds[element.id] = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
/** 处理中 */
|
||||
.highlight-todo.djs-connection > .djs-visual > path {
|
||||
stroke: #1890ff !important;
|
||||
stroke-dasharray: 4px !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: #1890ff !important;
|
||||
stroke: #1890ff !important;
|
||||
stroke-dasharray: 4px !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
/deep/.highlight-todo.djs-connection > .djs-visual > path {
|
||||
stroke: #1890ff !important;
|
||||
stroke-dasharray: 4px !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
marker-end: url(#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr);
|
||||
}
|
||||
/deep/.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: #1890ff !important;
|
||||
stroke: #1890ff !important;
|
||||
stroke-dasharray: 4px !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
/** 通过 */
|
||||
.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:not(.djs-connection) .djs-visual > :nth-child(1) {
|
||||
fill: green !important; /* color elements as green */
|
||||
}
|
||||
|
||||
/deep/.highlight.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: green !important;
|
||||
stroke: green !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
/deep/.highlight.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: green !important;
|
||||
}
|
||||
/deep/.highlight.djs-shape .djs-visual > path {
|
||||
fill: green !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: green !important;
|
||||
}
|
||||
/deep/.highlight.djs-connection > .djs-visual > path {
|
||||
stroke: green !important;
|
||||
}
|
||||
|
||||
/** 不通过 */
|
||||
.highlight-reject.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: red !important;
|
||||
stroke: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
.highlight-reject.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: red !important;
|
||||
}
|
||||
.highlight-reject.djs-shape .djs-visual > path {
|
||||
fill: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: red !important;
|
||||
}
|
||||
.highlight-reject.djs-connection > .djs-visual > path {
|
||||
stroke: red !important;
|
||||
}
|
||||
|
||||
.highlight-reject:not(.djs-connection) .djs-visual > :nth-child(1) {
|
||||
fill: red !important; /* color elements as green */
|
||||
}
|
||||
|
||||
/deep/.highlight-reject.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: red !important;
|
||||
stroke: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
/deep/.highlight-reject.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: red !important;
|
||||
}
|
||||
/deep/.highlight-reject.djs-shape .djs-visual > path {
|
||||
fill: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: red !important;
|
||||
}
|
||||
/deep/.highlight-reject.djs-connection > .djs-visual > path {
|
||||
stroke: red !important;
|
||||
}
|
||||
|
||||
/** 已取消 */
|
||||
.highlight-cancel.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: grey !important;
|
||||
stroke: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
.highlight-cancel.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: grey !important;
|
||||
}
|
||||
.highlight-cancel.djs-shape .djs-visual > path {
|
||||
fill: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: grey !important;
|
||||
}
|
||||
.highlight-cancel.djs-connection > .djs-visual > path {
|
||||
stroke: grey !important;
|
||||
}
|
||||
|
||||
.highlight-cancel:not(.djs-connection) .djs-visual > :nth-child(1) {
|
||||
fill: grey !important; /* color elements as green */
|
||||
}
|
||||
|
||||
/deep/.highlight-cancel.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: grey !important;
|
||||
stroke: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
/deep/.highlight-cancel.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: grey !important;
|
||||
}
|
||||
/deep/.highlight-cancel.djs-shape .djs-visual > path {
|
||||
fill: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: grey !important;
|
||||
}
|
||||
/deep/.highlight-cancel.djs-connection > .djs-visual > path {
|
||||
stroke: grey !important;
|
||||
}
|
||||
|
||||
.element-overlays {
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 4px;
|
||||
color: #fafafa;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,8 @@
|
||||
import MyProcessDesigner from "./ProcessDesigner.vue";
|
||||
|
||||
MyProcessDesigner.install = function(Vue) {
|
||||
Vue.component(MyProcessDesigner.name, MyProcessDesigner);
|
||||
};
|
||||
|
||||
// 流程图的设计器,可编辑
|
||||
export default MyProcessDesigner;
|
@@ -0,0 +1,8 @@
|
||||
import MyProcessViewer from "./ProcessViewer.vue";
|
||||
|
||||
MyProcessViewer.install = function(Vue) {
|
||||
Vue.component(MyProcessViewer.name, MyProcessViewer);
|
||||
};
|
||||
|
||||
// 流程图的查看器,不可编辑
|
||||
export default MyProcessViewer;
|
@@ -0,0 +1,390 @@
|
||||
import { assign, forEach, isArray } from "min-dash";
|
||||
|
||||
import { is } from "bpmn-js/lib/util/ModelUtil";
|
||||
|
||||
import { isExpanded, isEventSubProcess } from "bpmn-js/lib/util/DiUtil";
|
||||
|
||||
import { isAny } from "bpmn-js/lib/features/modeling/util/ModelingUtil";
|
||||
|
||||
import { getChildLanes } from "bpmn-js/lib/features/modeling/util/LaneUtil";
|
||||
|
||||
import { hasPrimaryModifier } from "diagram-js/lib/util/Mouse";
|
||||
|
||||
/**
|
||||
* A provider for BPMN 2.0 elements context pad
|
||||
*/
|
||||
export default function ContextPadProvider(
|
||||
config,
|
||||
injector,
|
||||
eventBus,
|
||||
contextPad,
|
||||
modeling,
|
||||
elementFactory,
|
||||
connect,
|
||||
create,
|
||||
popupMenu,
|
||||
canvas,
|
||||
rules,
|
||||
translate,
|
||||
elementRegistry
|
||||
) {
|
||||
config = config || {};
|
||||
|
||||
contextPad.registerProvider(this);
|
||||
|
||||
this._contextPad = contextPad;
|
||||
|
||||
this._modeling = modeling;
|
||||
|
||||
this._elementFactory = elementFactory;
|
||||
this._connect = connect;
|
||||
this._create = create;
|
||||
this._popupMenu = popupMenu;
|
||||
this._canvas = canvas;
|
||||
this._rules = rules;
|
||||
this._translate = translate;
|
||||
|
||||
if (config.autoPlace !== false) {
|
||||
this._autoPlace = injector.get("autoPlace", false);
|
||||
}
|
||||
|
||||
eventBus.on("create.end", 250, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = contextPad.getEntries(shape);
|
||||
|
||||
if (entries.replace) {
|
||||
entries.replace.action.click(event, shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContextPadProvider.$inject = [
|
||||
"config.contextPad",
|
||||
"injector",
|
||||
"eventBus",
|
||||
"contextPad",
|
||||
"modeling",
|
||||
"elementFactory",
|
||||
"connect",
|
||||
"create",
|
||||
"popupMenu",
|
||||
"canvas",
|
||||
"rules",
|
||||
"translate",
|
||||
"elementRegistry"
|
||||
];
|
||||
|
||||
ContextPadProvider.prototype.getContextPadEntries = function(element) {
|
||||
var contextPad = this._contextPad,
|
||||
modeling = this._modeling,
|
||||
elementFactory = this._elementFactory,
|
||||
connect = this._connect,
|
||||
create = this._create,
|
||||
popupMenu = this._popupMenu,
|
||||
canvas = this._canvas,
|
||||
rules = this._rules,
|
||||
autoPlace = this._autoPlace,
|
||||
translate = this._translate;
|
||||
|
||||
var actions = {};
|
||||
|
||||
if (element.type === "label") {
|
||||
return actions;
|
||||
}
|
||||
|
||||
var businessObject = element.businessObject;
|
||||
|
||||
function startConnect(event, element) {
|
||||
connect.start(event, element);
|
||||
}
|
||||
|
||||
function removeElement() {
|
||||
modeling.removeElements([element]);
|
||||
}
|
||||
|
||||
function getReplaceMenuPosition(element) {
|
||||
var Y_OFFSET = 5;
|
||||
|
||||
var diagramContainer = canvas.getContainer(),
|
||||
pad = contextPad.getPad(element).html;
|
||||
|
||||
var diagramRect = diagramContainer.getBoundingClientRect(),
|
||||
padRect = pad.getBoundingClientRect();
|
||||
|
||||
var top = padRect.top - diagramRect.top;
|
||||
var left = padRect.left - diagramRect.left;
|
||||
|
||||
var pos = {
|
||||
x: left,
|
||||
y: top + padRect.height + Y_OFFSET
|
||||
};
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an append action
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} className
|
||||
* @param {string} [title]
|
||||
* @param {Object} [options]
|
||||
*
|
||||
* @return {Object} descriptor
|
||||
*/
|
||||
function appendAction(type, className, title, options) {
|
||||
if (typeof title !== "string") {
|
||||
options = title;
|
||||
title = translate("Append {type}", { type: type.replace(/^bpmn:/, "") });
|
||||
}
|
||||
|
||||
function appendStart(event, element) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
create.start(event, shape, {
|
||||
source: element
|
||||
});
|
||||
}
|
||||
|
||||
var append = autoPlace
|
||||
? function(event, element) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
autoPlace.append(element, shape);
|
||||
}
|
||||
: appendStart;
|
||||
|
||||
return {
|
||||
group: "model",
|
||||
className: className,
|
||||
title: title,
|
||||
action: {
|
||||
dragstart: appendStart,
|
||||
click: append
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function splitLaneHandler(count) {
|
||||
return function(event, element) {
|
||||
// actual split
|
||||
modeling.splitLane(element, count);
|
||||
|
||||
// refresh context pad after split to
|
||||
// get rid of split icons
|
||||
contextPad.open(element, true);
|
||||
};
|
||||
}
|
||||
|
||||
if (isAny(businessObject, ["bpmn:Lane", "bpmn:Participant"]) && isExpanded(businessObject)) {
|
||||
var childLanes = getChildLanes(element);
|
||||
|
||||
assign(actions, {
|
||||
"lane-insert-above": {
|
||||
group: "lane-insert-above",
|
||||
className: "bpmn-icon-lane-insert-above",
|
||||
title: translate("Add Lane above"),
|
||||
action: {
|
||||
click: function(event, element) {
|
||||
modeling.addLane(element, "top");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (childLanes.length < 2) {
|
||||
if (element.height >= 120) {
|
||||
assign(actions, {
|
||||
"lane-divide-two": {
|
||||
group: "lane-divide",
|
||||
className: "bpmn-icon-lane-divide-two",
|
||||
title: translate("Divide into two Lanes"),
|
||||
action: {
|
||||
click: splitLaneHandler(2)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (element.height >= 180) {
|
||||
assign(actions, {
|
||||
"lane-divide-three": {
|
||||
group: "lane-divide",
|
||||
className: "bpmn-icon-lane-divide-three",
|
||||
title: translate("Divide into three Lanes"),
|
||||
action: {
|
||||
click: splitLaneHandler(3)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
"lane-insert-below": {
|
||||
group: "lane-insert-below",
|
||||
className: "bpmn-icon-lane-insert-below",
|
||||
title: translate("Add Lane below"),
|
||||
action: {
|
||||
click: function(event, element) {
|
||||
modeling.addLane(element, "bottom");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, "bpmn:FlowNode")) {
|
||||
if (is(businessObject, "bpmn:EventBasedGateway")) {
|
||||
assign(actions, {
|
||||
"append.receive-task": appendAction("bpmn:ReceiveTask", "bpmn-icon-receive-task", translate("Append ReceiveTask")),
|
||||
"append.message-intermediate-event": appendAction(
|
||||
"bpmn:IntermediateCatchEvent",
|
||||
"bpmn-icon-intermediate-event-catch-message",
|
||||
translate("Append MessageIntermediateCatchEvent"),
|
||||
{ eventDefinitionType: "bpmn:MessageEventDefinition" }
|
||||
),
|
||||
"append.timer-intermediate-event": appendAction(
|
||||
"bpmn:IntermediateCatchEvent",
|
||||
"bpmn-icon-intermediate-event-catch-timer",
|
||||
translate("Append TimerIntermediateCatchEvent"),
|
||||
{ eventDefinitionType: "bpmn:TimerEventDefinition" }
|
||||
),
|
||||
"append.condition-intermediate-event": appendAction(
|
||||
"bpmn:IntermediateCatchEvent",
|
||||
"bpmn-icon-intermediate-event-catch-condition",
|
||||
translate("Append ConditionIntermediateCatchEvent"),
|
||||
{ eventDefinitionType: "bpmn:ConditionalEventDefinition" }
|
||||
),
|
||||
"append.signal-intermediate-event": appendAction(
|
||||
"bpmn:IntermediateCatchEvent",
|
||||
"bpmn-icon-intermediate-event-catch-signal",
|
||||
translate("Append SignalIntermediateCatchEvent"),
|
||||
{ eventDefinitionType: "bpmn:SignalEventDefinition" }
|
||||
)
|
||||
});
|
||||
} else if (isEventType(businessObject, "bpmn:BoundaryEvent", "bpmn:CompensateEventDefinition")) {
|
||||
assign(actions, {
|
||||
"append.compensation-activity": appendAction("bpmn:Task", "bpmn-icon-task", translate("Append compensation activity"), {
|
||||
isForCompensation: true
|
||||
})
|
||||
});
|
||||
} else if (
|
||||
!is(businessObject, "bpmn:EndEvent") &&
|
||||
!businessObject.isForCompensation &&
|
||||
!isEventType(businessObject, "bpmn:IntermediateThrowEvent", "bpmn:LinkEventDefinition") &&
|
||||
!isEventSubProcess(businessObject)
|
||||
) {
|
||||
assign(actions, {
|
||||
"append.end-event": appendAction("bpmn:EndEvent", "bpmn-icon-end-event-none", translate("Append EndEvent")),
|
||||
"append.gateway": appendAction("bpmn:ExclusiveGateway", "bpmn-icon-gateway-none", translate("Append Gateway")),
|
||||
"append.append-task": appendAction("bpmn:UserTask", "bpmn-icon-user-task", translate("Append Task")),
|
||||
"append.intermediate-event": appendAction(
|
||||
"bpmn:IntermediateThrowEvent",
|
||||
"bpmn-icon-intermediate-event-none",
|
||||
translate("Append Intermediate/Boundary Event")
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!popupMenu.isEmpty(element, "bpmn-replace")) {
|
||||
// Replace menu entry
|
||||
assign(actions, {
|
||||
replace: {
|
||||
group: "edit",
|
||||
className: "bpmn-icon-screw-wrench",
|
||||
title: translate("Change type"),
|
||||
action: {
|
||||
click: function(event, element) {
|
||||
var position = assign(getReplaceMenuPosition(element), {
|
||||
cursor: { x: event.x, y: event.y }
|
||||
});
|
||||
|
||||
popupMenu.open(element, "bpmn-replace", position);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isAny(businessObject, ["bpmn:FlowNode", "bpmn:InteractionNode", "bpmn:DataObjectReference", "bpmn:DataStoreReference"])) {
|
||||
assign(actions, {
|
||||
"append.text-annotation": appendAction("bpmn:TextAnnotation", "bpmn-icon-text-annotation"),
|
||||
|
||||
connect: {
|
||||
group: "connect",
|
||||
className: "bpmn-icon-connection-multi",
|
||||
title: translate("Connect using " + (businessObject.isForCompensation ? "" : "Sequence/MessageFlow or ") + "Association"),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isAny(businessObject, ["bpmn:DataObjectReference", "bpmn:DataStoreReference"])) {
|
||||
assign(actions, {
|
||||
connect: {
|
||||
group: "connect",
|
||||
className: "bpmn-icon-connection-multi",
|
||||
title: translate("Connect using DataInputAssociation"),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, "bpmn:Group")) {
|
||||
assign(actions, {
|
||||
"append.text-annotation": appendAction("bpmn:TextAnnotation", "bpmn-icon-text-annotation")
|
||||
});
|
||||
}
|
||||
|
||||
// delete element entry, only show if allowed by rules
|
||||
var deleteAllowed = rules.allowed("elements.delete", { elements: [element] });
|
||||
|
||||
if (isArray(deleteAllowed)) {
|
||||
// was the element returned as a deletion candidate?
|
||||
deleteAllowed = deleteAllowed[0] === element;
|
||||
}
|
||||
|
||||
if (deleteAllowed) {
|
||||
assign(actions, {
|
||||
delete: {
|
||||
group: "edit",
|
||||
className: "bpmn-icon-trash",
|
||||
title: translate("Remove"),
|
||||
action: {
|
||||
click: removeElement
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
// helpers /////////
|
||||
|
||||
function isEventType(eventBo, type, definition) {
|
||||
var isType = eventBo.$instanceOf(type);
|
||||
var isDefinition = false;
|
||||
|
||||
var definitions = eventBo.eventDefinitions || [];
|
||||
forEach(definitions, function(def) {
|
||||
if (def.$type === definition) {
|
||||
isDefinition = true;
|
||||
}
|
||||
});
|
||||
|
||||
return isType && isDefinition;
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
import CustomContextPadProvider from "./contentPadProvider";
|
||||
|
||||
export default {
|
||||
__init__: ["contextPadProvider"],
|
||||
contextPadProvider: ["type", CustomContextPadProvider]
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
export default (key, name, type) => {
|
||||
if (!type) type = "camunda";
|
||||
const TYPE_TARGET = {
|
||||
activiti: "http://activiti.org/bpmn",
|
||||
camunda: "http://bpmn.io/schema/bpmn",
|
||||
flowable: "http://flowable.org/bpmn"
|
||||
};
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
|
||||
id="diagram_${key}"
|
||||
targetNamespace="${TYPE_TARGET[type]}">
|
||||
<bpmn2:process id="${key}" name="${name}" isExecutable="true">
|
||||
</bpmn2:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}">
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>`;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
|
||||
var some = require("min-dash").some;
|
||||
|
||||
var ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: ["bpmn:StartEvent", "bpmn:BoundaryEvent", "bpmn:IntermediateCatchEvent", "bpmn:Activity"],
|
||||
Connector: ["bpmn:EndEvent", "bpmn:IntermediateThrowEvent"],
|
||||
Field: ["bpmn:EndEvent", "bpmn:IntermediateThrowEvent"]
|
||||
};
|
||||
|
||||
function is(element, type) {
|
||||
return element && typeof element.$instanceOf === "function" && element.$instanceOf(type);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return (
|
||||
exists(collection) &&
|
||||
some(collection, function(element) {
|
||||
return is(element, type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return some(types, function(type) {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
var name = propDescriptor.name,
|
||||
types = ALLOWED_TYPES[name.replace(/activiti:/, "")];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
function ActivitiModdleExtension(eventBus) {
|
||||
eventBus.on(
|
||||
"property.clone",
|
||||
function(context) {
|
||||
var newElement = context.newElement,
|
||||
propDescriptor = context.propertyDescriptor;
|
||||
|
||||
this.canCloneProperty(newElement, propDescriptor);
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
ActivitiModdleExtension.$inject = ["eventBus"];
|
||||
|
||||
ActivitiModdleExtension.prototype.canCloneProperty = function(newElement, propDescriptor) {
|
||||
if (isAllowed("activiti:FailedJobRetryTimeCycle", propDescriptor, newElement)) {
|
||||
return (
|
||||
includesType(newElement.eventDefinitions, "bpmn:TimerEventDefinition") ||
|
||||
includesType(newElement.eventDefinitions, "bpmn:SignalEventDefinition") ||
|
||||
is(newElement.loopCharacteristics, "bpmn:MultiInstanceLoopCharacteristics")
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed("activiti:Connector", propDescriptor, newElement)) {
|
||||
return includesType(newElement.eventDefinitions, "bpmn:MessageEventDefinition");
|
||||
}
|
||||
|
||||
if (isAllowed("activiti:Field", propDescriptor, newElement)) {
|
||||
return includesType(newElement.eventDefinitions, "bpmn:MessageEventDefinition");
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ActivitiModdleExtension;
|
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* @author igdianov
|
||||
* address https://github.com/igdianov/activiti-bpmn-moddle
|
||||
* */
|
||||
|
||||
module.exports = {
|
||||
__init__: ["ActivitiModdleExtension"],
|
||||
ActivitiModdleExtension: ["type", require("./activitiExtension")]
|
||||
};
|
@@ -0,0 +1,148 @@
|
||||
"use strict";
|
||||
|
||||
var isFunction = require("min-dash").isFunction,
|
||||
isObject = require("min-dash").isObject,
|
||||
some = require("min-dash").some;
|
||||
|
||||
var WILDCARD = "*";
|
||||
|
||||
function CamundaModdleExtension(eventBus) {
|
||||
var self = this;
|
||||
|
||||
eventBus.on("moddleCopy.canCopyProperty", function(context) {
|
||||
var property = context.property,
|
||||
parent = context.parent;
|
||||
|
||||
return self.canCopyProperty(property, parent);
|
||||
});
|
||||
}
|
||||
|
||||
CamundaModdleExtension.$inject = ["eventBus"];
|
||||
|
||||
/**
|
||||
* Check wether to disallow copying property.
|
||||
*/
|
||||
CamundaModdleExtension.prototype.canCopyProperty = function(property, parent) {
|
||||
// (1) check wether property is allowed in parent
|
||||
if (isObject(property) && !isAllowedInParent(property, parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (2) check more complex scenarios
|
||||
|
||||
if (is(property, "camunda:InputOutput") && !this.canHostInputOutput(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAny(property, ["camunda:Connector", "camunda:Field"]) && !this.canHostConnector(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is(property, "camunda:In") && !this.canHostIn(parent)) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostInputOutput = function(parent) {
|
||||
// allowed in camunda:Connector
|
||||
var connector = getParent(parent, "camunda:Connector");
|
||||
|
||||
if (connector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// special rules inside bpmn:FlowNode
|
||||
var flowNode = getParent(parent, "bpmn:FlowNode");
|
||||
|
||||
if (!flowNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAny(flowNode, ["bpmn:StartEvent", "bpmn:Gateway", "bpmn:BoundaryEvent"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is(flowNode, "bpmn:SubProcess") && flowNode.get("triggeredByEvent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostConnector = function(parent) {
|
||||
var serviceTaskLike = getParent(parent, "camunda:ServiceTaskLike");
|
||||
|
||||
if (is(serviceTaskLike, "bpmn:MessageEventDefinition")) {
|
||||
// only allow on throw and end events
|
||||
return getParent(parent, "bpmn:IntermediateThrowEvent") || getParent(parent, "bpmn:EndEvent");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostIn = function(parent) {
|
||||
var callActivity = getParent(parent, "bpmn:CallActivity");
|
||||
|
||||
if (callActivity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var signalEventDefinition = getParent(parent, "bpmn:SignalEventDefinition");
|
||||
|
||||
if (signalEventDefinition) {
|
||||
// only allow on throw and end events
|
||||
return getParent(parent, "bpmn:IntermediateThrowEvent") || getParent(parent, "bpmn:EndEvent");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = CamundaModdleExtension;
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function is(element, type) {
|
||||
return element && isFunction(element.$instanceOf) && element.$instanceOf(type);
|
||||
}
|
||||
|
||||
function isAny(element, types) {
|
||||
return some(types, function(t) {
|
||||
return is(element, t);
|
||||
});
|
||||
}
|
||||
|
||||
function getParent(element, type) {
|
||||
if (!type) {
|
||||
return element.$parent;
|
||||
}
|
||||
|
||||
if (is(element, type)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (!element.$parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getParent(element.$parent, type);
|
||||
}
|
||||
|
||||
function isAllowedInParent(property, parent) {
|
||||
// (1) find property descriptor
|
||||
var descriptor = property.$type && property.$model.getTypeDescriptor(property.$type);
|
||||
|
||||
var allowedIn = descriptor && descriptor.meta && descriptor.meta.allowedIn;
|
||||
|
||||
if (!allowedIn || isWildcard(allowedIn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// (2) check wether property has parent of allowed type
|
||||
return some(allowedIn, function(type) {
|
||||
return getParent(parent, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isWildcard(allowedIn) {
|
||||
return allowedIn.indexOf(WILDCARD) !== -1;
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
__init__: ["camundaModdleExtension"],
|
||||
camundaModdleExtension: ["type", require("./extension")]
|
||||
};
|
@@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
|
||||
var some = require("min-dash").some;
|
||||
|
||||
var ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: ["bpmn:StartEvent", "bpmn:BoundaryEvent", "bpmn:IntermediateCatchEvent", "bpmn:Activity"],
|
||||
Connector: ["bpmn:EndEvent", "bpmn:IntermediateThrowEvent"],
|
||||
Field: ["bpmn:EndEvent", "bpmn:IntermediateThrowEvent"]
|
||||
};
|
||||
|
||||
function is(element, type) {
|
||||
return element && typeof element.$instanceOf === "function" && element.$instanceOf(type);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return (
|
||||
exists(collection) &&
|
||||
some(collection, function(element) {
|
||||
return is(element, type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return some(types, function(type) {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
var name = propDescriptor.name,
|
||||
types = ALLOWED_TYPES[name.replace(/flowable:/, "")];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
function FlowableModdleExtension(eventBus) {
|
||||
eventBus.on(
|
||||
"property.clone",
|
||||
function(context) {
|
||||
var newElement = context.newElement,
|
||||
propDescriptor = context.propertyDescriptor;
|
||||
|
||||
this.canCloneProperty(newElement, propDescriptor);
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
FlowableModdleExtension.$inject = ["eventBus"];
|
||||
|
||||
FlowableModdleExtension.prototype.canCloneProperty = function(newElement, propDescriptor) {
|
||||
if (isAllowed("flowable:FailedJobRetryTimeCycle", propDescriptor, newElement)) {
|
||||
return (
|
||||
includesType(newElement.eventDefinitions, "bpmn:TimerEventDefinition") ||
|
||||
includesType(newElement.eventDefinitions, "bpmn:SignalEventDefinition") ||
|
||||
is(newElement.loopCharacteristics, "bpmn:MultiInstanceLoopCharacteristics")
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed("flowable:Connector", propDescriptor, newElement)) {
|
||||
return includesType(newElement.eventDefinitions, "bpmn:MessageEventDefinition");
|
||||
}
|
||||
|
||||
if (isAllowed("flowable:Field", propDescriptor, newElement)) {
|
||||
return includesType(newElement.eventDefinitions, "bpmn:MessageEventDefinition");
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FlowableModdleExtension;
|
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* @author igdianov
|
||||
* address https://github.com/igdianov/activiti-bpmn-moddle
|
||||
* */
|
||||
|
||||
module.exports = {
|
||||
__init__: ["FlowableModdleExtension"],
|
||||
FlowableModdleExtension: ["type", require("./flowableExtension")]
|
||||
};
|
@@ -0,0 +1,156 @@
|
||||
import PaletteProvider from "bpmn-js/lib/features/palette/PaletteProvider";
|
||||
import { assign } from "min-dash";
|
||||
|
||||
export default function CustomPalette(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) {
|
||||
PaletteProvider.call(this, palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate, 2000);
|
||||
}
|
||||
|
||||
const F = function() {}; // 核心,利用空对象作为中介;
|
||||
F.prototype = PaletteProvider.prototype; // 核心,将父类的原型赋值给空对象F;
|
||||
|
||||
// 利用中介函数重写原型链方法
|
||||
F.prototype.getPaletteEntries = function() {
|
||||
var actions = {},
|
||||
create = this._create,
|
||||
elementFactory = this._elementFactory,
|
||||
spaceTool = this._spaceTool,
|
||||
lassoTool = this._lassoTool,
|
||||
handTool = this._handTool,
|
||||
globalConnect = this._globalConnect,
|
||||
translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
function createListener(event) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
var shortType = type.replace(/^bpmn:/, "");
|
||||
|
||||
return {
|
||||
group: group,
|
||||
className: className,
|
||||
title: title || translate("Create {type}", { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createSubprocess(event) {
|
||||
var subProcess = elementFactory.createShape({
|
||||
type: "bpmn:SubProcess",
|
||||
x: 0,
|
||||
y: 0,
|
||||
isExpanded: true
|
||||
});
|
||||
|
||||
var startEvent = elementFactory.createShape({
|
||||
type: "bpmn:StartEvent",
|
||||
x: 40,
|
||||
y: 82,
|
||||
parent: subProcess
|
||||
});
|
||||
|
||||
create.start(event, [subProcess, startEvent], {
|
||||
hints: {
|
||||
autoSelect: [startEvent]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createParticipant(event) {
|
||||
create.start(event, elementFactory.createParticipantShape());
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
"hand-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-hand-tool",
|
||||
title: translate("Activate the hand tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
handTool.activateHand(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"lasso-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-lasso-tool",
|
||||
title: translate("Activate the lasso tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"space-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-space-tool",
|
||||
title: translate("Activate the create/remove space tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"global-connect-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-connection-multi",
|
||||
title: translate("Activate the global connect tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
globalConnect.toggle(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"tool-separator": {
|
||||
group: "tools",
|
||||
separator: true
|
||||
},
|
||||
"create.start-event": createAction("bpmn:StartEvent", "event", "bpmn-icon-start-event-none", translate("Create StartEvent")),
|
||||
"create.intermediate-event": createAction(
|
||||
"bpmn:IntermediateThrowEvent",
|
||||
"event",
|
||||
"bpmn-icon-intermediate-event-none",
|
||||
translate("Create Intermediate/Boundary Event")
|
||||
),
|
||||
"create.end-event": createAction("bpmn:EndEvent", "event", "bpmn-icon-end-event-none", translate("Create EndEvent")),
|
||||
"create.exclusive-gateway": createAction("bpmn:ExclusiveGateway", "gateway", "bpmn-icon-gateway-none", translate("Create Gateway")),
|
||||
"create.user-task": createAction("bpmn:UserTask", "activity", "bpmn-icon-user-task", translate("Create User Task")),
|
||||
"create.data-object": createAction("bpmn:DataObjectReference", "data-object", "bpmn-icon-data-object", translate("Create DataObjectReference")),
|
||||
"create.data-store": createAction("bpmn:DataStoreReference", "data-store", "bpmn-icon-data-store", translate("Create DataStoreReference")),
|
||||
"create.subprocess-expanded": {
|
||||
group: "activity",
|
||||
className: "bpmn-icon-subprocess-expanded",
|
||||
title: translate("Create expanded SubProcess"),
|
||||
action: {
|
||||
dragstart: createSubprocess,
|
||||
click: createSubprocess
|
||||
}
|
||||
},
|
||||
"create.participant-expanded": {
|
||||
group: "collaboration",
|
||||
className: "bpmn-icon-participant",
|
||||
title: translate("Create Pool/Participant"),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant
|
||||
}
|
||||
},
|
||||
"create.group": createAction("bpmn:Group", "artifact", "bpmn-icon-group", translate("Create Group"))
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
CustomPalette.$inject = ["palette", "create", "elementFactory", "spaceTool", "lassoTool", "handTool", "globalConnect", "translate"];
|
||||
|
||||
CustomPalette.prototype = new F(); // 核心,将 F的实例赋值给子类;
|
||||
CustomPalette.prototype.constructor = CustomPalette; // 修复子类CustomPalette的构造器指向,防止原型链的混乱;
|
@@ -0,0 +1,22 @@
|
||||
// import PaletteModule from "diagram-js/lib/features/palette";
|
||||
// import CreateModule from "diagram-js/lib/features/create";
|
||||
// import SpaceToolModule from "diagram-js/lib/features/space-tool";
|
||||
// import LassoToolModule from "diagram-js/lib/features/lasso-tool";
|
||||
// import HandToolModule from "diagram-js/lib/features/hand-tool";
|
||||
// import GlobalConnectModule from "diagram-js/lib/features/global-connect";
|
||||
// import translate from "diagram-js/lib/i18n/translate";
|
||||
//
|
||||
// import PaletteProvider from "./paletteProvider";
|
||||
//
|
||||
// export default {
|
||||
// __depends__: [PaletteModule, CreateModule, SpaceToolModule, LassoToolModule, HandToolModule, GlobalConnectModule, translate],
|
||||
// __init__: ["paletteProvider"],
|
||||
// paletteProvider: ["type", PaletteProvider]
|
||||
// };
|
||||
|
||||
import CustomPalette from "./CustomPalette";
|
||||
|
||||
export default {
|
||||
__init__: ["paletteProvider"],
|
||||
paletteProvider: ["type", CustomPalette]
|
||||
};
|
@@ -0,0 +1,160 @@
|
||||
import { assign } from "min-dash";
|
||||
|
||||
/**
|
||||
* A palette provider for BPMN 2.0 elements.
|
||||
*/
|
||||
export default function PaletteProvider(palette, create, elementFactory, spaceTool, lassoTool, handTool, globalConnect, translate) {
|
||||
this._palette = palette;
|
||||
this._create = create;
|
||||
this._elementFactory = elementFactory;
|
||||
this._spaceTool = spaceTool;
|
||||
this._lassoTool = lassoTool;
|
||||
this._handTool = handTool;
|
||||
this._globalConnect = globalConnect;
|
||||
this._translate = translate;
|
||||
|
||||
palette.registerProvider(this);
|
||||
}
|
||||
|
||||
PaletteProvider.$inject = ["palette", "create", "elementFactory", "spaceTool", "lassoTool", "handTool", "globalConnect", "translate"];
|
||||
|
||||
PaletteProvider.prototype.getPaletteEntries = function() {
|
||||
var actions = {},
|
||||
create = this._create,
|
||||
elementFactory = this._elementFactory,
|
||||
spaceTool = this._spaceTool,
|
||||
lassoTool = this._lassoTool,
|
||||
handTool = this._handTool,
|
||||
globalConnect = this._globalConnect,
|
||||
translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
function createListener(event) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
var shortType = type.replace(/^bpmn:/, "");
|
||||
|
||||
return {
|
||||
group: group,
|
||||
className: className,
|
||||
title: title || translate("Create {type}", { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createSubprocess(event) {
|
||||
var subProcess = elementFactory.createShape({
|
||||
type: "bpmn:SubProcess",
|
||||
x: 0,
|
||||
y: 0,
|
||||
isExpanded: true
|
||||
});
|
||||
|
||||
var startEvent = elementFactory.createShape({
|
||||
type: "bpmn:StartEvent",
|
||||
x: 40,
|
||||
y: 82,
|
||||
parent: subProcess
|
||||
});
|
||||
|
||||
create.start(event, [subProcess, startEvent], {
|
||||
hints: {
|
||||
autoSelect: [startEvent]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createParticipant(event) {
|
||||
create.start(event, elementFactory.createParticipantShape());
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
"hand-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-hand-tool",
|
||||
title: translate("Activate the hand tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
handTool.activateHand(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"lasso-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-lasso-tool",
|
||||
title: translate("Activate the lasso tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"space-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-space-tool",
|
||||
title: translate("Activate the create/remove space tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"global-connect-tool": {
|
||||
group: "tools",
|
||||
className: "bpmn-icon-connection-multi",
|
||||
title: translate("Activate the global connect tool"),
|
||||
action: {
|
||||
click: function(event) {
|
||||
globalConnect.toggle(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
"tool-separator": {
|
||||
group: "tools",
|
||||
separator: true
|
||||
},
|
||||
"create.start-event": createAction("bpmn:StartEvent", "event", "bpmn-icon-start-event-none", translate("Create StartEvent")),
|
||||
"create.intermediate-event": createAction(
|
||||
"bpmn:IntermediateThrowEvent",
|
||||
"event",
|
||||
"bpmn-icon-intermediate-event-none",
|
||||
translate("Create Intermediate/Boundary Event")
|
||||
),
|
||||
"create.end-event": createAction("bpmn:EndEvent", "event", "bpmn-icon-end-event-none", translate("Create EndEvent")),
|
||||
"create.exclusive-gateway": createAction("bpmn:ExclusiveGateway", "gateway", "bpmn-icon-gateway-none", translate("Create Gateway")),
|
||||
"create.user-task": createAction("bpmn:UserTask", "activity", "bpmn-icon-user-task", translate("Create User Task")),
|
||||
"create.data-object": createAction("bpmn:DataObjectReference", "data-object", "bpmn-icon-data-object", translate("Create DataObjectReference")),
|
||||
"create.data-store": createAction("bpmn:DataStoreReference", "data-store", "bpmn-icon-data-store", translate("Create DataStoreReference")),
|
||||
"create.subprocess-expanded": {
|
||||
group: "activity",
|
||||
className: "bpmn-icon-subprocess-expanded",
|
||||
title: translate("Create expanded SubProcess"),
|
||||
action: {
|
||||
dragstart: createSubprocess,
|
||||
click: createSubprocess
|
||||
}
|
||||
},
|
||||
"create.participant-expanded": {
|
||||
group: "collaboration",
|
||||
className: "bpmn-icon-participant",
|
||||
title: translate("Create Pool/Participant"),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant
|
||||
}
|
||||
},
|
||||
"create.group": createAction("bpmn:Group", "artifact", "bpmn-icon-group", translate("Create Group"))
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
@@ -0,0 +1,41 @@
|
||||
// import translations from "./zh";
|
||||
//
|
||||
// export default function customTranslate(template, replacements) {
|
||||
// replacements = replacements || {};
|
||||
//
|
||||
// // Translate
|
||||
// template = translations[template] || template;
|
||||
//
|
||||
// // Replace
|
||||
// return template.replace(/{([^}]+)}/g, function(_, key) {
|
||||
// let str = replacements[key];
|
||||
// if (
|
||||
// translations[replacements[key]] !== null &&
|
||||
// translations[replacements[key]] !== "undefined"
|
||||
// ) {
|
||||
// // eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
// str = translations[replacements[key]];
|
||||
// // eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
// }
|
||||
// return str || "{" + key + "}";
|
||||
// });
|
||||
// }
|
||||
|
||||
export default function customTranslate(translations) {
|
||||
return function(template, replacements) {
|
||||
replacements = replacements || {};
|
||||
// Translate
|
||||
template = translations[template] || template;
|
||||
|
||||
// Replace
|
||||
return template.replace(/{([^}]+)}/g, function(_, key) {
|
||||
let str = replacements[key];
|
||||
if (translations[replacements[key]] !== null && translations[replacements[key]] !== undefined) {
|
||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
str = translations[replacements[key]];
|
||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
}
|
||||
return str || "{" + key + "}";
|
||||
});
|
||||
};
|
||||
}
|
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* This is a sample file that should be replaced with the actual translation.
|
||||
*
|
||||
* Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available
|
||||
* translations and labels to translate.
|
||||
*/
|
||||
export default {
|
||||
// 添加部分
|
||||
"Append EndEvent": "追加结束事件",
|
||||
"Append Gateway": "追加网关",
|
||||
"Append Task": "追加任务",
|
||||
"Append Intermediate/Boundary Event": "追加中间抛出事件/边界事件",
|
||||
|
||||
"Activate the global connect tool": "激活全局连接工具",
|
||||
"Append {type}": "添加 {type}",
|
||||
"Add Lane above": "在上面添加道",
|
||||
"Divide into two Lanes": "分割成两个道",
|
||||
"Divide into three Lanes": "分割成三个道",
|
||||
"Add Lane below": "在下面添加道",
|
||||
"Append compensation activity": "追加补偿活动",
|
||||
"Change type": "修改类型",
|
||||
"Connect using Association": "使用关联连接",
|
||||
"Connect using Sequence/MessageFlow or Association": "使用顺序/消息流或者关联连接",
|
||||
"Connect using DataInputAssociation": "使用数据输入关联连接",
|
||||
Remove: "移除",
|
||||
"Activate the hand tool": "激活抓手工具",
|
||||
"Activate the lasso tool": "激活套索工具",
|
||||
"Activate the create/remove space tool": "激活创建/删除空间工具",
|
||||
"Create expanded SubProcess": "创建扩展子过程",
|
||||
"Create IntermediateThrowEvent/BoundaryEvent": "创建中间抛出事件/边界事件",
|
||||
"Create Pool/Participant": "创建池/参与者",
|
||||
"Parallel Multi Instance": "并行多重事件",
|
||||
"Sequential Multi Instance": "时序多重事件",
|
||||
DataObjectReference: "数据对象参考",
|
||||
DataStoreReference: "数据存储参考",
|
||||
Loop: "循环",
|
||||
"Ad-hoc": "即席",
|
||||
"Create {type}": "创建 {type}",
|
||||
Task: "任务",
|
||||
"Send Task": "发送任务",
|
||||
"Receive Task": "接收任务",
|
||||
"User Task": "用户任务",
|
||||
"Manual Task": "手工任务",
|
||||
"Business Rule Task": "业务规则任务",
|
||||
"Service Task": "服务任务",
|
||||
"Script Task": "脚本任务",
|
||||
"Call Activity": "调用活动",
|
||||
"Sub Process (collapsed)": "子流程(折叠的)",
|
||||
"Sub Process (expanded)": "子流程(展开的)",
|
||||
"Start Event": "开始事件",
|
||||
StartEvent: "开始事件",
|
||||
"Intermediate Throw Event": "中间事件",
|
||||
"End Event": "结束事件",
|
||||
EndEvent: "结束事件",
|
||||
"Create StartEvent": "创建开始事件",
|
||||
"Create EndEvent": "创建结束事件",
|
||||
"Create Task": "创建任务",
|
||||
"Create User Task": "创建用户任务",
|
||||
"Create Gateway": "创建网关",
|
||||
"Create DataObjectReference": "创建数据对象",
|
||||
"Create DataStoreReference": "创建数据存储",
|
||||
"Create Group": "创建分组",
|
||||
"Create Intermediate/Boundary Event": "创建中间/边界事件",
|
||||
"Message Start Event": "消息开始事件",
|
||||
"Timer Start Event": "定时开始事件",
|
||||
"Conditional Start Event": "条件开始事件",
|
||||
"Signal Start Event": "信号开始事件",
|
||||
"Error Start Event": "错误开始事件",
|
||||
"Escalation Start Event": "升级开始事件",
|
||||
"Compensation Start Event": "补偿开始事件",
|
||||
"Message Start Event (non-interrupting)": "消息开始事件(非中断)",
|
||||
"Timer Start Event (non-interrupting)": "定时开始事件(非中断)",
|
||||
"Conditional Start Event (non-interrupting)": "条件开始事件(非中断)",
|
||||
"Signal Start Event (non-interrupting)": "信号开始事件(非中断)",
|
||||
"Escalation Start Event (non-interrupting)": "升级开始事件(非中断)",
|
||||
"Message Intermediate Catch Event": "消息中间捕获事件",
|
||||
"Message Intermediate Throw Event": "消息中间抛出事件",
|
||||
"Timer Intermediate Catch Event": "定时中间捕获事件",
|
||||
"Escalation Intermediate Throw Event": "升级中间抛出事件",
|
||||
"Conditional Intermediate Catch Event": "条件中间捕获事件",
|
||||
"Link Intermediate Catch Event": "链接中间捕获事件",
|
||||
"Link Intermediate Throw Event": "链接中间抛出事件",
|
||||
"Compensation Intermediate Throw Event": "补偿中间抛出事件",
|
||||
"Signal Intermediate Catch Event": "信号中间捕获事件",
|
||||
"Signal Intermediate Throw Event": "信号中间抛出事件",
|
||||
"Message End Event": "消息结束事件",
|
||||
"Escalation End Event": "定时结束事件",
|
||||
"Error End Event": "错误结束事件",
|
||||
"Cancel End Event": "取消结束事件",
|
||||
"Compensation End Event": "补偿结束事件",
|
||||
"Signal End Event": "信号结束事件",
|
||||
"Terminate End Event": "终止结束事件",
|
||||
"Message Boundary Event": "消息边界事件",
|
||||
"Message Boundary Event (non-interrupting)": "消息边界事件(非中断)",
|
||||
"Timer Boundary Event": "定时边界事件",
|
||||
"Timer Boundary Event (non-interrupting)": "定时边界事件(非中断)",
|
||||
"Escalation Boundary Event": "升级边界事件",
|
||||
"Escalation Boundary Event (non-interrupting)": "升级边界事件(非中断)",
|
||||
"Conditional Boundary Event": "条件边界事件",
|
||||
"Conditional Boundary Event (non-interrupting)": "条件边界事件(非中断)",
|
||||
"Error Boundary Event": "错误边界事件",
|
||||
"Cancel Boundary Event": "取消边界事件",
|
||||
"Signal Boundary Event": "信号边界事件",
|
||||
"Signal Boundary Event (non-interrupting)": "信号边界事件(非中断)",
|
||||
"Compensation Boundary Event": "补偿边界事件",
|
||||
"Exclusive Gateway": "互斥网关",
|
||||
"Parallel Gateway": "并行网关",
|
||||
"Inclusive Gateway": "相容网关",
|
||||
"Complex Gateway": "复杂网关",
|
||||
"Event based Gateway": "事件网关",
|
||||
Transaction: "转运",
|
||||
"Sub Process": "子流程",
|
||||
"Event Sub Process": "事件子流程",
|
||||
"Collapsed Pool": "折叠池",
|
||||
"Expanded Pool": "展开池",
|
||||
|
||||
// Errors
|
||||
"no parent for {element} in {parent}": "在{parent}里,{element}没有父类",
|
||||
"no shape type specified": "没有指定的形状类型",
|
||||
"flow elements must be children of pools/participants": "流元素必须是池/参与者的子类",
|
||||
"out of bounds release": "out of bounds release",
|
||||
"more than {count} child lanes": "子道大于{count} ",
|
||||
"element required": "元素不能为空",
|
||||
"diagram not part of bpmn:Definitions": "流程图不符合bpmn规范",
|
||||
"no diagram to display": "没有可展示的流程图",
|
||||
"no process or collaboration to display": "没有可展示的流程/协作",
|
||||
"element {element} referenced by {referenced}#{property} not yet drawn": "由{referenced}#{property}引用的{element}元素仍未绘制",
|
||||
"already rendered {element}": "{element} 已被渲染",
|
||||
"failed to import {element}": "导入{element}失败",
|
||||
//属性面板的参数
|
||||
Id: "编号",
|
||||
Name: "名称",
|
||||
General: "常规",
|
||||
Details: "详情",
|
||||
"Message Name": "消息名称",
|
||||
Message: "消息",
|
||||
Initiator: "创建者",
|
||||
"Asynchronous Continuations": "持续异步",
|
||||
"Asynchronous Before": "异步前",
|
||||
"Asynchronous After": "异步后",
|
||||
"Job Configuration": "工作配置",
|
||||
Exclusive: "排除",
|
||||
"Job Priority": "工作优先级",
|
||||
"Retry Time Cycle": "重试时间周期",
|
||||
Documentation: "文档",
|
||||
"Element Documentation": "元素文档",
|
||||
"History Configuration": "历史配置",
|
||||
"History Time To Live": "历史的生存时间",
|
||||
Forms: "表单",
|
||||
"Form Key": "表单key",
|
||||
"Form Fields": "表单字段",
|
||||
"Business Key": "业务key",
|
||||
"Form Field": "表单字段",
|
||||
ID: "编号",
|
||||
Type: "类型",
|
||||
Label: "名称",
|
||||
"Default Value": "默认值",
|
||||
"Default Flow": "默认流转路径",
|
||||
"Conditional Flow": "条件流转路径",
|
||||
"Sequence Flow": "普通流转路径",
|
||||
Validation: "校验",
|
||||
"Add Constraint": "添加约束",
|
||||
Config: "配置",
|
||||
Properties: "属性",
|
||||
"Add Property": "添加属性",
|
||||
Value: "值",
|
||||
Listeners: "监听器",
|
||||
"Execution Listener": "执行监听",
|
||||
"Event Type": "事件类型",
|
||||
"Listener Type": "监听器类型",
|
||||
"Java Class": "Java类",
|
||||
Expression: "表达式",
|
||||
"Must provide a value": "必须提供一个值",
|
||||
"Delegate Expression": "代理表达式",
|
||||
Script: "脚本",
|
||||
"Script Format": "脚本格式",
|
||||
"Script Type": "脚本类型",
|
||||
"Inline Script": "内联脚本",
|
||||
"External Script": "外部脚本",
|
||||
Resource: "资源",
|
||||
"Field Injection": "字段注入",
|
||||
Extensions: "扩展",
|
||||
"Input/Output": "输入/输出",
|
||||
"Input Parameters": "输入参数",
|
||||
"Output Parameters": "输出参数",
|
||||
Parameters: "参数",
|
||||
"Output Parameter": "输出参数",
|
||||
"Timer Definition Type": "定时器定义类型",
|
||||
"Timer Definition": "定时器定义",
|
||||
Date: "日期",
|
||||
Duration: "持续",
|
||||
Cycle: "循环",
|
||||
Signal: "信号",
|
||||
"Signal Name": "信号名称",
|
||||
Escalation: "升级",
|
||||
Error: "错误",
|
||||
"Link Name": "链接名称",
|
||||
Condition: "条件名称",
|
||||
"Variable Name": "变量名称",
|
||||
"Variable Event": "变量事件",
|
||||
"Specify more than one variable change event as a comma separated list.": "多个变量事件以逗号隔开",
|
||||
"Wait for Completion": "等待完成",
|
||||
"Activity Ref": "活动参考",
|
||||
"Version Tag": "版本标签",
|
||||
Executable: "可执行文件",
|
||||
"External Task Configuration": "扩展任务配置",
|
||||
"Task Priority": "任务优先级",
|
||||
External: "外部",
|
||||
Connector: "连接器",
|
||||
"Must configure Connector": "必须配置连接器",
|
||||
"Connector Id": "连接器编号",
|
||||
Implementation: "实现方式",
|
||||
"Field Injections": "字段注入",
|
||||
Fields: "字段",
|
||||
"Result Variable": "结果变量",
|
||||
Topic: "主题",
|
||||
"Configure Connector": "配置连接器",
|
||||
"Input Parameter": "输入参数",
|
||||
Assignee: "代理人",
|
||||
"Candidate Users": "候选用户",
|
||||
"Candidate Groups": "候选组",
|
||||
"Due Date": "到期时间",
|
||||
"Follow Up Date": "跟踪日期",
|
||||
Priority: "优先级",
|
||||
"The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)":
|
||||
"跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00",
|
||||
"The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)":
|
||||
"跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00",
|
||||
Variables: "变量",
|
||||
"Candidate Starter Configuration": "候选人起动器配置",
|
||||
"Candidate Starter Groups": "候选人起动器组",
|
||||
"This maps to the process definition key.": "这映射到流程定义键。",
|
||||
"Candidate Starter Users": "候选人起动器的用户",
|
||||
"Specify more than one user as a comma separated list.": "指定多个用户作为逗号分隔的列表。",
|
||||
"Tasklist Configuration": "Tasklist配置",
|
||||
Startable: "启动",
|
||||
"Specify more than one group as a comma separated list.": "指定多个组作为逗号分隔的列表。"
|
||||
};
|
@@ -0,0 +1,21 @@
|
||||
import MyProcessDesigner from "./designer";
|
||||
import MyProcessPenal from "./penal";
|
||||
import MyProcessViewer from './designer/index2';
|
||||
|
||||
const components = [MyProcessDesigner, MyProcessPenal, MyProcessViewer];
|
||||
|
||||
const install = function(Vue) {
|
||||
components.forEach(component => {
|
||||
Vue.component(component.name, component);
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined" && window.Vue) {
|
||||
install(window.Vue);
|
||||
}
|
||||
|
||||
export default {
|
||||
version: "0.0.1",
|
||||
install,
|
||||
...components
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="my-process-palette">
|
||||
<div class="test-button" @click="addTask" @mousedown="addTask">测试任务</div>
|
||||
<div class="test-container" id="palette-container">1</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { assign } from "min-dash";
|
||||
|
||||
export default {
|
||||
name: "MyProcessPalette",
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
addTask(event, options = {}) {
|
||||
const ElementFactory = window.bpmnInstances.elementFactory;
|
||||
const create = window.bpmnInstances.modeler.get("create");
|
||||
|
||||
console.log(ElementFactory, create);
|
||||
|
||||
const shape = ElementFactory.createShape(assign({ type: "bpmn:UserTask" }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my-process-palette {
|
||||
box-sizing: border-box;
|
||||
padding: 80px 20px 20px 20px;
|
||||
.test-button {
|
||||
box-sizing: border-box;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(24, 144, 255, 0.8);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="process-panel__container" :style="{ width: `${this.width}px` }">
|
||||
<el-collapse v-model="activeTab">
|
||||
<el-collapse-item name="base">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-info"></i>常规</div>
|
||||
<element-base-info :id-edit-disabled="idEditDisabled" :business-object="elementBusinessObject" :type="elementType"
|
||||
:model="model" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="condition" v-if="elementType === 'Process'" key="message">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-s-comment"></i>消息与信号</div>
|
||||
<signal-and-massage />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="condition" v-if="conditionFormVisible" key="condition">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-s-promotion"></i>流转条件</div>
|
||||
<flow-condition :business-object="elementBusinessObject" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="condition" v-if="formVisible" key="form">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-s-order"></i>表单</div>
|
||||
<!-- <element-form :id="elementId" :type="elementType" />-->
|
||||
友情提示:使用 <router-link target="_blank" :to="{path:'/bpm/manager/form'}"><el-link type="danger">流程表单</el-link> </router-link>
|
||||
替代,提供更好的表单设计功能
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="task" v-if="elementType.indexOf('Task') !== -1" key="task">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-s-claim"></i>任务</div>
|
||||
<element-task :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="multiInstance" v-if="elementType.indexOf('Task') !== -1" key="multiInstance">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-s-help"></i>多实例</div>
|
||||
<element-multi-instance :business-object="elementBusinessObject" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="listeners" key="listeners">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-message-solid"></i>执行监听器</div>
|
||||
<element-listeners :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="taskListeners" v-if="elementType === 'UserTask'" key="taskListeners">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-message-solid"></i>任务监听器</div>
|
||||
<user-task-listeners :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="extensions" key="extensions">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-circle-plus"></i>扩展属性</div>
|
||||
<element-properties :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="other" key="other">
|
||||
<div slot="title" class="panel-tab__title"><i class="el-icon-s-promotion"></i>其他</div>
|
||||
<element-other-config :id="elementId" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ElementBaseInfo from "./base/ElementBaseInfo";
|
||||
import ElementOtherConfig from "./other/ElementOtherConfig";
|
||||
import ElementTask from "./task/ElementTask";
|
||||
import ElementMultiInstance from "./multi-instance/ElementMultiInstance";
|
||||
import FlowCondition from "./flow-condition/FlowCondition";
|
||||
import SignalAndMassage from "./signal-message/SignalAndMessage";
|
||||
import ElementListeners from "./listeners/ElementListeners";
|
||||
import ElementProperties from "./properties/ElementProperties";
|
||||
import ElementForm from "./form/ElementForm";
|
||||
import UserTaskListeners from "./listeners/UserTaskListeners";
|
||||
/**
|
||||
* 侧边栏
|
||||
* @Author MiyueFE
|
||||
* @Home https://github.com/miyuesc
|
||||
* @Date 2021年3月31日18:57:51
|
||||
*/
|
||||
export default {
|
||||
name: "MyPropertiesPanel",
|
||||
components: {
|
||||
UserTaskListeners,
|
||||
ElementForm,
|
||||
ElementProperties,
|
||||
ElementListeners,
|
||||
SignalAndMassage,
|
||||
FlowCondition,
|
||||
ElementMultiInstance,
|
||||
ElementTask,
|
||||
ElementOtherConfig,
|
||||
ElementBaseInfo
|
||||
},
|
||||
componentName: "MyPropertiesPanel",
|
||||
props: {
|
||||
bpmnModeler: Object,
|
||||
prefix: {
|
||||
type: String,
|
||||
default: "camunda"
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 480
|
||||
},
|
||||
idEditDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
model: Object, // 流程模型的数据
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
prefix: this.prefix,
|
||||
width: this.width
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: "base",
|
||||
elementId: "",
|
||||
elementType: "",
|
||||
elementBusinessObject: {}, // 元素 businessObject 镜像,提供给需要做判断的组件使用
|
||||
conditionFormVisible: false, // 流转条件设置
|
||||
formVisible: false // 表单配置
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
elementId: {
|
||||
handler() {
|
||||
this.activeTab = "base";
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initModels();
|
||||
},
|
||||
methods: {
|
||||
initModels() {
|
||||
// 初始化 modeler 以及其他 moddle
|
||||
if (!this.bpmnModeler) {
|
||||
// 避免加载时 流程图 并未加载完成
|
||||
this.timer = setTimeout(() => this.initModels(), 10);
|
||||
return;
|
||||
}
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
window.bpmnInstances = {
|
||||
modeler: this.bpmnModeler,
|
||||
modeling: this.bpmnModeler.get("modeling"),
|
||||
moddle: this.bpmnModeler.get("moddle"),
|
||||
eventBus: this.bpmnModeler.get("eventBus"),
|
||||
bpmnFactory: this.bpmnModeler.get("bpmnFactory"),
|
||||
elementFactory: this.bpmnModeler.get("elementFactory"),
|
||||
elementRegistry: this.bpmnModeler.get("elementRegistry"),
|
||||
replace: this.bpmnModeler.get("replace"),
|
||||
selection: this.bpmnModeler.get("selection")
|
||||
};
|
||||
this.getActiveElement();
|
||||
},
|
||||
getActiveElement() {
|
||||
// 初始第一个选中元素 bpmn:Process
|
||||
this.initFormOnChanged(null);
|
||||
this.bpmnModeler.on("import.done", e => {
|
||||
this.initFormOnChanged(null);
|
||||
});
|
||||
// 监听选择事件,修改当前激活的元素以及表单
|
||||
this.bpmnModeler.on("selection.changed", ({ newSelection }) => {
|
||||
this.initFormOnChanged(newSelection[0] || null);
|
||||
});
|
||||
this.bpmnModeler.on("element.changed", ({ element }) => {
|
||||
// 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
|
||||
if (element && element.id === this.elementId) {
|
||||
this.initFormOnChanged(element);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 初始化数据
|
||||
initFormOnChanged(element) {
|
||||
let activatedElement = element;
|
||||
// debugger
|
||||
if (!activatedElement) {
|
||||
activatedElement =
|
||||
window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process") ??
|
||||
window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Collaboration");
|
||||
}
|
||||
if (!activatedElement) return;
|
||||
console.log(`
|
||||
----------
|
||||
select element changed:
|
||||
id: ${activatedElement.id}
|
||||
type: ${activatedElement.businessObject.$type}
|
||||
----------
|
||||
`);
|
||||
console.log("businessObject: ", activatedElement.businessObject);
|
||||
window.bpmnInstances.bpmnElement = activatedElement;
|
||||
this.bpmnElement = activatedElement;
|
||||
this.elementId = activatedElement.id;
|
||||
this.elementType = activatedElement.type.split(":")[1] || "";
|
||||
this.elementBusinessObject = JSON.parse(JSON.stringify(activatedElement.businessObject));
|
||||
this.conditionFormVisible = !!(
|
||||
this.elementType === "SequenceFlow" &&
|
||||
activatedElement.source &&
|
||||
activatedElement.source.type.indexOf("StartEvent") === -1
|
||||
);
|
||||
this.formVisible = this.elementType === "UserTask" || this.elementType === "StartEvent";
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.bpmnInstances = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-form size="mini" label-width="90px" :model="model" :rules="rules" @submit.native.prevent>
|
||||
<div v-if="elementBaseInfo.$type === 'bpmn:Process'"> <!-- 如果是 Process 信息的时候,使用自定义表单 -->
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input v-model="model.key" placeholder="请输入流标标识"
|
||||
:disabled="model.id !== undefined && model.id.length > 0" @change="handleKeyUpdate" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input v-model="model.name" placeholder="请输入流程名称" clearable @change="handleNameUpdate" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-form-item label="ID">
|
||||
<el-input v-model="elementBaseInfo.id" clearable @change="updateBaseInfo('id')"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="elementBaseInfo.name" clearable @change="updateBaseInfo('name')" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "ElementBaseInfo",
|
||||
props: {
|
||||
businessObject: Object,
|
||||
model: Object, // 流程模型的数据
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elementBaseInfo: {},
|
||||
// 流程表单的下拉框的数据
|
||||
forms: [],
|
||||
// 流程模型的校验
|
||||
rules: {
|
||||
key: [{ required: true, message: "流程标识不能为空", trigger: "blur" }],
|
||||
name: [{ required: true, message: "流程名称不能为空", trigger: "blur" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
businessObject: {
|
||||
immediate: false,
|
||||
handler: function(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => this.resetBaseInfo());
|
||||
}
|
||||
}
|
||||
},
|
||||
// 'model.key': {
|
||||
// immediate: false,
|
||||
// handler: function (val) {
|
||||
// this.handleKeyUpdate(val)
|
||||
// }
|
||||
// }
|
||||
},
|
||||
created() {
|
||||
// 针对上传的 bpmn 流程图时,需要延迟 1 秒的时间,保证 key 和 name 的更新
|
||||
setTimeout(() => {
|
||||
this.handleKeyUpdate(this.model.key)
|
||||
this.handleNameUpdate(this.model.name)
|
||||
}, 1000)
|
||||
},
|
||||
methods: {
|
||||
resetBaseInfo() {
|
||||
this.bpmnElement = window?.bpmnInstances?.bpmnElement;
|
||||
this.elementBaseInfo = JSON.parse(JSON.stringify(this.bpmnElement.businessObject));
|
||||
},
|
||||
handleKeyUpdate(value) {
|
||||
// 校验 value 的值,只有 XML NCName 通过的情况下,才进行赋值。否则,会导致流程图报错,无法绘制的问题
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (!value.match(/[a-zA-Z_][\-_.0-9_a-zA-Z$]*/)) {
|
||||
console.log('key 不满足 XML NCName 规则,所以不进行赋值');
|
||||
return;
|
||||
}
|
||||
console.log('key 满足 XML NCName 规则,所以进行赋值');
|
||||
|
||||
// 在 BPMN 的 XML 中,流程标识 key,其实对应的是 id 节点
|
||||
this.elementBaseInfo['id'] = value;
|
||||
this.updateBaseInfo('id');
|
||||
},
|
||||
handleNameUpdate(value) {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
this.elementBaseInfo['name'] = value;
|
||||
this.updateBaseInfo('name');
|
||||
},
|
||||
handleDescriptionUpdate(value) {
|
||||
// TODO 芋艿:documentation 暂时无法修改,后续在看看
|
||||
// this.elementBaseInfo['documentation'] = value;
|
||||
// this.updateBaseInfo('documentation');
|
||||
},
|
||||
updateBaseInfo(key) {
|
||||
// 触发 elementBaseInfo 对应的字段
|
||||
const attrObj = Object.create(null);
|
||||
attrObj[key] = this.elementBaseInfo[key];
|
||||
if (key === "id") {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
id: this.elementBaseInfo[key],
|
||||
di: { id: `${this.elementBaseInfo[key]}_di` }
|
||||
});
|
||||
} else {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, attrObj);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bpmnElement = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-form :model="flowConditionForm" label-width="90px" size="mini" @submit.native.prevent>
|
||||
<el-form-item label="流转类型">
|
||||
<el-select v-model="flowConditionForm.type" @change="updateFlowType">
|
||||
<el-option label="普通流转路径" value="normal" />
|
||||
<el-option label="默认流转路径" value="default" />
|
||||
<el-option label="条件流转路径" value="condition" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="条件格式" v-if="flowConditionForm.type === 'condition'" key="condition">
|
||||
<el-select v-model="flowConditionForm.conditionType">
|
||||
<el-option label="表达式" value="expression" />
|
||||
<el-option label="脚本" value="script" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="表达式" v-if="flowConditionForm.conditionType && flowConditionForm.conditionType === 'expression'" key="express">
|
||||
<el-input v-model="flowConditionForm.body" clearable @change="updateFlowCondition" />
|
||||
</el-form-item>
|
||||
<template v-if="flowConditionForm.conditionType && flowConditionForm.conditionType === 'script'">
|
||||
<el-form-item label="脚本语言" key="language">
|
||||
<el-input v-model="flowConditionForm.language" clearable @change="updateFlowCondition" />
|
||||
</el-form-item>
|
||||
<el-form-item label="脚本类型" key="scriptType">
|
||||
<el-select v-model="flowConditionForm.scriptType">
|
||||
<el-option label="内联脚本" value="inlineScript" />
|
||||
<el-option label="外部脚本" value="externalScript" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="脚本" v-if="flowConditionForm.scriptType === 'inlineScript'" key="body">
|
||||
<el-input v-model="flowConditionForm.body" type="textarea" clearable @change="updateFlowCondition" />
|
||||
</el-form-item>
|
||||
<el-form-item label="资源地址" v-if="flowConditionForm.scriptType === 'externalScript'" key="resource">
|
||||
<el-input v-model="flowConditionForm.resource" clearable @change="updateFlowCondition" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FlowCondition",
|
||||
props: {
|
||||
businessObject: Object,
|
||||
type: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
flowConditionForm: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
businessObject: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.$nextTick(() => this.resetFlowCondition());
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetFlowCondition() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.bpmnElementSource = this.bpmnElement.source;
|
||||
this.bpmnElementSourceRef = this.bpmnElement.businessObject.sourceRef;
|
||||
if (this.bpmnElementSourceRef && this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.bpmnElement.id) {
|
||||
// 默认
|
||||
this.flowConditionForm = { type: "default" };
|
||||
} else if (!this.bpmnElement.businessObject.conditionExpression) {
|
||||
// 普通
|
||||
this.flowConditionForm = { type: "normal" };
|
||||
} else {
|
||||
// 带条件
|
||||
const conditionExpression = this.bpmnElement.businessObject.conditionExpression;
|
||||
this.flowConditionForm = { ...conditionExpression, type: "condition" };
|
||||
// resource 可直接标识 是否是外部资源脚本
|
||||
if (this.flowConditionForm.resource) {
|
||||
this.$set(this.flowConditionForm, "conditionType", "script");
|
||||
this.$set(this.flowConditionForm, "scriptType", "externalScript");
|
||||
return;
|
||||
}
|
||||
if (conditionExpression.language) {
|
||||
this.$set(this.flowConditionForm, "conditionType", "script");
|
||||
this.$set(this.flowConditionForm, "scriptType", "inlineScript");
|
||||
return;
|
||||
}
|
||||
this.$set(this.flowConditionForm, "conditionType", "expression");
|
||||
}
|
||||
},
|
||||
updateFlowType(flowType) {
|
||||
// 正常条件类
|
||||
if (flowType === "condition") {
|
||||
this.flowConditionRef = window.bpmnInstances.moddle.create("bpmn:FormalExpression");
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
conditionExpression: this.flowConditionRef
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 默认路径
|
||||
if (flowType === "default") {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
conditionExpression: null
|
||||
});
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElementSource, {
|
||||
default: this.bpmnElement
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 正常路径,如果来源节点的默认路径是当前连线时,清除父元素的默认路径配置
|
||||
if (this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.bpmnElement.id) {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElementSource, {
|
||||
default: null
|
||||
});
|
||||
}
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
conditionExpression: null
|
||||
});
|
||||
},
|
||||
updateFlowCondition() {
|
||||
let { conditionType, scriptType, body, resource, language } = this.flowConditionForm;
|
||||
let condition;
|
||||
if (conditionType === "expression") {
|
||||
condition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body });
|
||||
} else {
|
||||
if (scriptType === "inlineScript") {
|
||||
condition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body, language });
|
||||
this.$set(this.flowConditionForm, "resource", "");
|
||||
} else {
|
||||
this.$set(this.flowConditionForm, "body", "");
|
||||
condition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { resource, language });
|
||||
}
|
||||
}
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { conditionExpression: condition });
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bpmnElement = null;
|
||||
this.bpmnElementSource = null;
|
||||
this.bpmnElementSourceRef = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,361 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-form size="mini" label-width="80px" @submit.native.prevent>
|
||||
<el-form-item label="表单标识">
|
||||
<el-input v-model="formKey" clearable @change="updateElementFormKey" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业务标识">
|
||||
<el-select v-model="businessKey" @change="updateElementBusinessKey">
|
||||
<el-option v-for="i in fieldList" :key="i.id" :value="i.id" :label="i.label" />
|
||||
<el-option label="无" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!--字段列表-->
|
||||
<div class="element-property list-property">
|
||||
<el-divider><i class="el-icon-coin"></i> 表单字段</el-divider>
|
||||
<el-table :data="fieldList" size="mini" max-height="240" border fit>
|
||||
<el-table-column label="序号" type="index" width="50px" />
|
||||
<el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />
|
||||
<el-table-column label="字段类型" prop="type" min-width="80px" :formatter="row => fieldType[row.type] || row.type" show-overflow-tooltip />
|
||||
<el-table-column label="默认值" prop="defaultValue" min-width="80px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openFieldForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeField(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openFieldForm(null, -1)">添加字段</el-button>
|
||||
</div>
|
||||
|
||||
<!--字段配置侧边栏-->
|
||||
<el-drawer :visible.sync="fieldModelVisible" title="字段配置" :size="`${width}px`" append-to-body destroy-on-close>
|
||||
<el-form :model="formFieldForm" label-width="90px" size="mini" @submit.native.prevent>
|
||||
<el-form-item label="字段ID">
|
||||
<el-input v-model="formFieldForm.id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="formFieldForm.typeType" placeholder="请选择字段类型" clearable @change="changeFieldTypeType">
|
||||
<el-option v-for="(value, key) of fieldType" :label="value" :value="key" :key="key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型名称" v-if="formFieldForm.typeType === 'custom'">
|
||||
<el-input v-model="formFieldForm.type" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="formFieldForm.label" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间格式" v-if="formFieldForm.typeType === 'date'">
|
||||
<el-input v-model="formFieldForm.datePattern" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="默认值">
|
||||
<el-input v-model="formFieldForm.defaultValue" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 枚举值设置 -->
|
||||
<template v-if="formFieldForm.type === 'enum'">
|
||||
<el-divider key="enum-divider" />
|
||||
<p class="listener-filed__title" key="enum-title">
|
||||
<span><i class="el-icon-menu"></i>枚举值列表:</span>
|
||||
<el-button size="mini" type="primary" @click="openFieldOptionForm(null, -1, 'enum')">添加枚举值</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldEnumList" size="mini" key="enum-table" max-height="240" border fit>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openFieldOptionForm(row, $index, 'enum')">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'enum')">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<!-- 校验规则 -->
|
||||
<el-divider key="validation-divider" />
|
||||
<p class="listener-filed__title" key="validation-title">
|
||||
<span><i class="el-icon-menu"></i>约束条件列表:</span>
|
||||
<el-button size="mini" type="primary" @click="openFieldOptionForm(null, -1, 'constraint')">添加约束</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldConstraintsList" size="mini" key="validation-table" max-height="240" border fit>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openFieldOptionForm(row, $index, 'constraint')">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'constraint')">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 表单属性 -->
|
||||
<el-divider key="property-divider" />
|
||||
<p class="listener-filed__title" key="property-title">
|
||||
<span><i class="el-icon-menu"></i>字段属性列表:</span>
|
||||
<el-button size="mini" type="primary" @click="openFieldOptionForm(null, -1, 'property')">添加属性</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldPropertiesList" size="mini" key="property-table" max-height="240" border fit>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openFieldOptionForm(row, $index, 'property')">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'property')">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveField">保 存</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog :visible.sync="fieldOptionModelVisible" :title="optionModelTitle" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="fieldOptionForm" size="mini" label-width="96px" @submit.native.prevent>
|
||||
<el-form-item label="编号/ID" v-if="fieldOptionType !== 'constraint'" key="option-id">
|
||||
<el-input v-model="fieldOptionForm.id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" v-if="fieldOptionType !== 'property'" key="option-name">
|
||||
<el-input v-model="fieldOptionForm.name" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="配置" v-if="fieldOptionType === 'constraint'" key="option-config">
|
||||
<el-input v-model="fieldOptionForm.config" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="值" v-if="fieldOptionType === 'property'" key="option-value">
|
||||
<el-input v-model="fieldOptionForm.value" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" @click="fieldOptionModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveFieldOption">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ElementForm",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
inject: {
|
||||
prefix: "prefix",
|
||||
width: "width"
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formKey: "",
|
||||
businessKey: "",
|
||||
optionModelTitle: "",
|
||||
fieldList: [],
|
||||
formFieldForm: {},
|
||||
fieldType: {
|
||||
long: "长整型",
|
||||
string: "字符串",
|
||||
boolean: "布尔类",
|
||||
date: "日期类",
|
||||
enum: "枚举类",
|
||||
custom: "自定义类型"
|
||||
},
|
||||
formFieldIndex: -1, // 编辑中的字段, -1 为新增
|
||||
formFieldOptionIndex: -1, // 编辑中的字段配置项, -1 为新增
|
||||
fieldModelVisible: false,
|
||||
fieldOptionModelVisible: false,
|
||||
fieldOptionForm: {}, // 当前激活的字段配置项数据
|
||||
fieldOptionType: "", // 当前激活的字段配置项弹窗 类型
|
||||
fieldEnumList: [], // 枚举值列表
|
||||
fieldConstraintsList: [], // 约束条件列表
|
||||
fieldPropertiesList: [] // 绑定属性列表
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
val && val.length && this.$nextTick(() => this.resetFormList());
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetFormList() {
|
||||
this.bpmnELement = window.bpmnInstances.bpmnElement;
|
||||
this.formKey = this.bpmnELement.businessObject.formKey;
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
this.elExtensionElements =
|
||||
this.bpmnELement.businessObject.get("extensionElements") || window.bpmnInstances.moddle.create("bpmn:ExtensionElements", { values: [] });
|
||||
// 获取元素表单配置 或者 创建新的表单配置
|
||||
this.formData =
|
||||
this.elExtensionElements.values.filter(ex => ex.$type === `${this.prefix}:FormData`)?.[0] ||
|
||||
window.bpmnInstances.moddle.create(`${this.prefix}:FormData`, { fields: [] });
|
||||
|
||||
// 业务标识 businessKey, 绑定在 formData 中
|
||||
this.businessKey = this.formData.businessKey;
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
this.otherExtensions = this.elExtensionElements.values.filter(ex => ex.$type !== `${this.prefix}:FormData`);
|
||||
|
||||
// 复制原始值,填充表格
|
||||
this.fieldList = JSON.parse(JSON.stringify(this.formData.fields || []));
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
this.updateElementExtensions();
|
||||
},
|
||||
updateElementFormKey() {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnELement, { formKey: this.formKey });
|
||||
},
|
||||
updateElementBusinessKey() {
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnELement, this.formData, { businessKey: this.businessKey });
|
||||
},
|
||||
// 根据类型调整字段type
|
||||
changeFieldTypeType(type) {
|
||||
this.$set(this.formFieldForm, "type", type === "custom" ? "" : type);
|
||||
},
|
||||
|
||||
// 打开字段详情侧边栏
|
||||
openFieldForm(field, index) {
|
||||
this.formFieldIndex = index;
|
||||
if (index !== -1) {
|
||||
const FieldObject = this.formData.fields[index];
|
||||
this.formFieldForm = JSON.parse(JSON.stringify(field));
|
||||
// 设置自定义类型
|
||||
this.$set(this.formFieldForm, "typeType", !this.fieldType[field.type] ? "custom" : field.type);
|
||||
// 初始化枚举值列表
|
||||
field.type === "enum" && (this.fieldEnumList = JSON.parse(JSON.stringify(FieldObject?.values || [])));
|
||||
// 初始化约束条件列表
|
||||
this.fieldConstraintsList = JSON.parse(JSON.stringify(FieldObject?.validation?.constraints || []));
|
||||
// 初始化自定义属性列表
|
||||
this.fieldPropertiesList = JSON.parse(JSON.stringify(FieldObject?.properties?.values || []));
|
||||
} else {
|
||||
this.formFieldForm = {};
|
||||
// 初始化枚举值列表
|
||||
this.fieldEnumList = [];
|
||||
// 初始化约束条件列表
|
||||
this.fieldConstraintsList = [];
|
||||
// 初始化自定义属性列表
|
||||
this.fieldPropertiesList = [];
|
||||
}
|
||||
this.fieldModelVisible = true;
|
||||
},
|
||||
// 打开字段 某个 配置项 弹窗
|
||||
openFieldOptionForm(option, index, type) {
|
||||
this.fieldOptionModelVisible = true;
|
||||
this.fieldOptionType = type;
|
||||
this.formFieldOptionIndex = index;
|
||||
if (type === "property") {
|
||||
this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
|
||||
return (this.optionModelTitle = "属性配置");
|
||||
}
|
||||
if (type === "enum") {
|
||||
this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
|
||||
return (this.optionModelTitle = "枚举值配置");
|
||||
}
|
||||
this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
|
||||
return (this.optionModelTitle = "约束条件配置");
|
||||
},
|
||||
|
||||
// 保存字段 某个 配置项
|
||||
saveFieldOption() {
|
||||
if (this.formFieldOptionIndex === -1) {
|
||||
if (this.fieldOptionType === "property") {
|
||||
this.fieldPropertiesList.push(this.fieldOptionForm);
|
||||
}
|
||||
if (this.fieldOptionType === "constraint") {
|
||||
this.fieldConstraintsList.push(this.fieldOptionForm);
|
||||
}
|
||||
if (this.fieldOptionType === "enum") {
|
||||
this.fieldEnumList.push(this.fieldOptionForm);
|
||||
}
|
||||
} else {
|
||||
this.fieldOptionType === "property" && this.fieldPropertiesList.splice(this.formFieldOptionIndex, 1, this.fieldOptionForm);
|
||||
this.fieldOptionType === "constraint" && this.fieldConstraintsList.splice(this.formFieldOptionIndex, 1, this.fieldOptionForm);
|
||||
this.fieldOptionType === "enum" && this.fieldEnumList.splice(this.formFieldOptionIndex, 1, this.fieldOptionForm);
|
||||
}
|
||||
this.fieldOptionModelVisible = false;
|
||||
this.fieldOptionForm = {};
|
||||
},
|
||||
// 保存字段配置
|
||||
saveField() {
|
||||
const { id, type, label, defaultValue, datePattern } = this.formFieldForm;
|
||||
const Field = window.bpmnInstances.moddle.create(`${this.prefix}:FormField`, { id, type, label });
|
||||
defaultValue && (Field.defaultValue = defaultValue);
|
||||
datePattern && (Field.datePattern = datePattern);
|
||||
// 构建属性
|
||||
if (this.fieldPropertiesList && this.fieldPropertiesList.length) {
|
||||
const fieldPropertyList = this.fieldPropertiesList.map(fp => {
|
||||
return window.bpmnInstances.moddle.create(`${this.prefix}:Property`, { id: fp.id, value: fp.value });
|
||||
});
|
||||
Field.properties = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, { values: fieldPropertyList });
|
||||
}
|
||||
// 构建校验规则
|
||||
if (this.fieldConstraintsList && this.fieldConstraintsList.length) {
|
||||
const fieldConstraintList = this.fieldConstraintsList.map(fc => {
|
||||
return window.bpmnInstances.moddle.create(`${this.prefix}:Constraint`, { name: fc.name, config: fc.config });
|
||||
});
|
||||
Field.validation = window.bpmnInstances.moddle.create(`${this.prefix}:Validation`, { constraints: fieldConstraintList });
|
||||
}
|
||||
// 构建枚举值
|
||||
if (this.fieldEnumList && this.fieldEnumList.length) {
|
||||
Field.values = this.fieldEnumList.map(fe => {
|
||||
return window.bpmnInstances.moddle.create(`${this.prefix}:Value`, { name: fe.name, id: fe.id });
|
||||
});
|
||||
}
|
||||
// 更新数组 与 表单配置实例
|
||||
if (this.formFieldIndex === -1) {
|
||||
this.fieldList.push(this.formFieldForm);
|
||||
this.formData.fields.push(Field);
|
||||
} else {
|
||||
this.fieldList.splice(this.formFieldIndex, 1, this.formFieldForm);
|
||||
this.formData.fields.splice(this.formFieldIndex, 1, Field);
|
||||
}
|
||||
this.updateElementExtensions();
|
||||
this.fieldModelVisible = false;
|
||||
},
|
||||
|
||||
// 移除某个 字段的 配置项
|
||||
removeFieldOptionItem(option, index, type) {
|
||||
if (type === "property") {
|
||||
this.fieldPropertiesList.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
if (type === "enum") {
|
||||
this.fieldEnumList.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
this.fieldConstraintsList.splice(index, 1);
|
||||
},
|
||||
// 移除 字段
|
||||
removeField(field, index) {
|
||||
this.fieldList.splice(index, 1);
|
||||
this.formData.fields.splice(index, 1);
|
||||
this.updateElementExtensions();
|
||||
},
|
||||
|
||||
updateElementExtensions() {
|
||||
// 更新回扩展元素
|
||||
const newElExtensionElements = window.bpmnInstances.moddle.create(`bpmn:ExtensionElements`, {
|
||||
values: this.otherExtensions.concat(this.formData)
|
||||
});
|
||||
// 更新到元素上
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnELement, {
|
||||
extensionElements: newElExtensionElements
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,7 @@
|
||||
import MyPropertiesPanel from "./PropertiesPanel.vue";
|
||||
|
||||
MyPropertiesPanel.install = function(Vue) {
|
||||
Vue.component(MyPropertiesPanel.name, MyPropertiesPanel);
|
||||
};
|
||||
|
||||
export default MyPropertiesPanel;
|
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-table :data="elementListenersList" size="mini" border>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="事件类型" min-width="100px" prop="event" />
|
||||
<el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openListenerForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openListenerForm(null)">添加监听器</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<el-drawer :visible.sync="listenerFormModelVisible" title="执行监听器" :size="`${width}px`" append-to-body destroy-on-close>
|
||||
<el-form size="mini" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
|
||||
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerForm.event">
|
||||
<el-option label="start" value="start" />
|
||||
<el-option label="end" value="end" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerForm.listenerType">
|
||||
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
prop="class"
|
||||
key="listener-class"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.class" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
prop="expression"
|
||||
key="listener-expression"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.expression" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
prop="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.delegateExpression" clearable />
|
||||
</el-form-item>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<el-form-item
|
||||
label="脚本格式"
|
||||
prop="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.scriptFormat" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="脚本类型"
|
||||
prop="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
|
||||
>
|
||||
<el-select v-model="listenerForm.scriptType">
|
||||
<el-option label="内联脚本" value="inlineScript" />
|
||||
<el-option label="外部脚本" value="externalScript" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
prop="value"
|
||||
key="listener-script"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.value" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
prop="resource"
|
||||
key="listener-resource"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.resource" clearable />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<el-divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><i class="el-icon-menu"></i>注入字段:</span>
|
||||
<el-button size="mini" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="字段名称" min-width="100px" prop="name" />
|
||||
<el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
|
||||
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
|
||||
<el-table-column label="操作" width="100px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openListenerFieldForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" @click="listenerFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveListenerConfig">保 存</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 注入西段 编辑/创建 部分 -->
|
||||
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="listenerFieldForm" size="mini" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
|
||||
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-input v-model="listenerFieldForm.name" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerFieldForm.fieldType">
|
||||
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
prop="string"
|
||||
key="field-string"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerFieldForm.string" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
prop="expression"
|
||||
key="field-expression"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerFieldForm.expression" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" @click="listenerFieldFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveListenerFiled">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createListenerObject, updateElementExtensions } from "../../utils";
|
||||
import { initListenerType, initListenerForm, listenerType, fieldType } from "./utilSelf";
|
||||
|
||||
export default {
|
||||
name: "ElementListeners",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
inject: {
|
||||
prefix: "prefix",
|
||||
width: "width"
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elementListenersList: [], // 监听器列表
|
||||
listenerForm: {}, // 监听器详情表单
|
||||
listenerFormModelVisible: false, // 监听器 编辑 侧边栏显示状态
|
||||
fieldsListOfListener: [],
|
||||
listenerFieldForm: {}, // 监听器 注入字段 详情表单
|
||||
listenerFieldFormModelVisible: false, // 监听器 注入字段表单弹窗 显示状态
|
||||
editingListenerIndex: -1, // 监听器所在下标,-1 为新增
|
||||
editingListenerFieldIndex: -1, // 字段所在下标,-1 为新增
|
||||
listenerTypeObject: listenerType,
|
||||
fieldTypeObject: fieldType
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
val && val.length && this.$nextTick(() => this.resetListenersList());
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetListenersList() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.otherExtensionList = [];
|
||||
this.bpmnElementListeners =
|
||||
this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:ExecutionListener`) ?? [];
|
||||
this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
|
||||
},
|
||||
// 打开 监听器详情 侧边栏
|
||||
openListenerForm(listener, index) {
|
||||
if (listener) {
|
||||
this.listenerForm = initListenerForm(listener);
|
||||
this.editingListenerIndex = index;
|
||||
} else {
|
||||
this.listenerForm = {};
|
||||
this.editingListenerIndex = -1; // 标记为新增
|
||||
}
|
||||
if (listener && listener.fields) {
|
||||
this.fieldsListOfListener = listener.fields.map(field => ({ ...field, fieldType: field.string ? "string" : "expression" }));
|
||||
} else {
|
||||
this.fieldsListOfListener = [];
|
||||
this.$set(this.listenerForm, "fields", []);
|
||||
}
|
||||
// 打开侧边栏并清楚验证状态
|
||||
this.listenerFormModelVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
|
||||
});
|
||||
},
|
||||
// 打开监听器字段编辑弹窗
|
||||
openListenerFieldForm(field, index) {
|
||||
this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
|
||||
this.editingListenerFieldIndex = field ? index : -1;
|
||||
this.listenerFieldFormModelVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
|
||||
});
|
||||
},
|
||||
// 保存监听器注入字段
|
||||
async saveListenerFiled() {
|
||||
let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
if (this.editingListenerFieldIndex === -1) {
|
||||
this.fieldsListOfListener.push(this.listenerFieldForm);
|
||||
this.listenerForm.fields.push(this.listenerFieldForm);
|
||||
} else {
|
||||
this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
|
||||
this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
|
||||
}
|
||||
this.listenerFieldFormModelVisible = false;
|
||||
this.$nextTick(() => (this.listenerFieldForm = {}));
|
||||
},
|
||||
// 移除监听器字段
|
||||
removeListenerField(field, index) {
|
||||
this.$confirm("确认移除该字段吗?", "提示", {
|
||||
confirmButtonText: "确 认",
|
||||
cancelButtonText: "取 消"
|
||||
})
|
||||
.then(() => {
|
||||
this.fieldsListOfListener.splice(index, 1);
|
||||
this.listenerForm.fields.splice(index, 1);
|
||||
})
|
||||
.catch(() => console.info("操作取消"));
|
||||
},
|
||||
// 移除监听器
|
||||
removeListener(listener, index) {
|
||||
this.$confirm("确认移除该监听器吗?", "提示", {
|
||||
confirmButtonText: "确 认",
|
||||
cancelButtonText: "取 消"
|
||||
})
|
||||
.then(() => {
|
||||
this.bpmnElementListeners.splice(index, 1);
|
||||
this.elementListenersList.splice(index, 1);
|
||||
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
|
||||
})
|
||||
.catch(() => console.info("操作取消"));
|
||||
},
|
||||
// 保存监听器配置
|
||||
async saveListenerConfig() {
|
||||
let validateStatus = await this.$refs["listenerFormRef"].validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
const listenerObject = createListenerObject(this.listenerForm, false, this.prefix);
|
||||
if (this.editingListenerIndex === -1) {
|
||||
this.bpmnElementListeners.push(listenerObject);
|
||||
this.elementListenersList.push(this.listenerForm);
|
||||
} else {
|
||||
this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
|
||||
this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
|
||||
}
|
||||
// 保存其他配置
|
||||
this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:ExecutionListener`) ?? [];
|
||||
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
|
||||
// 4. 隐藏侧边栏
|
||||
this.listenerFormModelVisible = false;
|
||||
this.listenerForm = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-table :data="elementListenersList" size="mini" border>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="事件类型" min-width="80px" show-overflow-tooltip :formatter="row => listenerEventTypeObject[row.event]" />
|
||||
<el-table-column label="事件id" min-width="80px" prop="id" show-overflow-tooltip />
|
||||
<el-table-column label="监听器类型" min-width="80px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openListenerForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openListenerForm(null)">添加监听器</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<el-drawer :visible.sync="listenerFormModelVisible" title="任务监听器" :size="`${width}px`" append-to-body destroy-on-close>
|
||||
<el-form size="mini" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
|
||||
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerForm.event">
|
||||
<el-option v-for="i in Object.keys(listenerEventTypeObject)" :key="i" :label="listenerEventTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监听器ID" prop="id" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-input v-model="listenerForm.id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerForm.listenerType">
|
||||
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
prop="class"
|
||||
key="listener-class"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.class" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
prop="expression"
|
||||
key="listener-expression"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.expression" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
prop="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.delegateExpression" clearable />
|
||||
</el-form-item>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<el-form-item
|
||||
label="脚本格式"
|
||||
prop="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.scriptFormat" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="脚本类型"
|
||||
prop="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
|
||||
>
|
||||
<el-select v-model="listenerForm.scriptType">
|
||||
<el-option label="内联脚本" value="inlineScript" />
|
||||
<el-option label="外部脚本" value="externalScript" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
prop="value"
|
||||
key="listener-script"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.value" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
prop="resource"
|
||||
key="listener-resource"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.resource" clearable />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="listenerForm.event === 'timeout'">
|
||||
<el-form-item label="定时器类型" prop="eventDefinitionType" key="eventDefinitionType">
|
||||
<el-select v-model="listenerForm.eventDefinitionType">
|
||||
<el-option label="日期" value="date" />
|
||||
<el-option label="持续时长" value="duration" />
|
||||
<el-option label="循环" value="cycle" />
|
||||
<el-option label="无" value="null" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!!listenerForm.eventDefinitionType && listenerForm.eventDefinitionType !== 'null'"
|
||||
label="定时器"
|
||||
prop="eventTimeDefinitions"
|
||||
key="eventTimeDefinitions"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写定时器配置' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.eventTimeDefinitions" clearable />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
|
||||
<el-divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><i class="el-icon-menu"></i>注入字段:</span>
|
||||
<el-button size="mini" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="字段名称" min-width="100px" prop="name" />
|
||||
<el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
|
||||
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
|
||||
<el-table-column label="操作" width="100px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openListenerFieldForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" @click="listenerFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveListenerConfig">保 存</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 注入西段 编辑/创建 部分 -->
|
||||
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="listenerFieldForm" size="mini" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
|
||||
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-input v-model="listenerFieldForm.name" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerFieldForm.fieldType">
|
||||
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
prop="string"
|
||||
key="field-string"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerFieldForm.string" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
prop="expression"
|
||||
key="field-expression"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerFieldForm.expression" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" @click="listenerFieldFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveListenerFiled">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createListenerObject, updateElementExtensions } from "../../utils";
|
||||
import { initListenerForm, initListenerType, eventType, listenerType, fieldType } from "./utilSelf";
|
||||
|
||||
export default {
|
||||
name: "UserTaskListeners",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
inject: {
|
||||
prefix: "prefix",
|
||||
width: "width"
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elementListenersList: [],
|
||||
listenerEventTypeObject: eventType,
|
||||
listenerTypeObject: listenerType,
|
||||
listenerFormModelVisible: false,
|
||||
listenerForm: {},
|
||||
fieldTypeObject: fieldType,
|
||||
fieldsListOfListener: [],
|
||||
listenerFieldFormModelVisible: false, // 监听器 注入字段表单弹窗 显示状态
|
||||
editingListenerIndex: -1, // 监听器所在下标,-1 为新增
|
||||
editingListenerFieldIndex: -1, // 字段所在下标,-1 为新增
|
||||
listenerFieldForm: {} // 监听器 注入字段 详情表单
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
val && val.length && this.$nextTick(() => this.resetListenersList());
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetListenersList() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.otherExtensionList = [];
|
||||
this.bpmnElementListeners = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:TaskListener`) ?? [];
|
||||
this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
|
||||
},
|
||||
openListenerForm(listener, index) {
|
||||
if (listener) {
|
||||
this.listenerForm = initListenerForm(listener);
|
||||
this.editingListenerIndex = index;
|
||||
} else {
|
||||
this.listenerForm = {};
|
||||
this.editingListenerIndex = -1; // 标记为新增
|
||||
}
|
||||
if (listener && listener.fields) {
|
||||
this.fieldsListOfListener = listener.fields.map(field => ({ ...field, fieldType: field.string ? "string" : "expression" }));
|
||||
} else {
|
||||
this.fieldsListOfListener = [];
|
||||
this.$set(this.listenerForm, "fields", []);
|
||||
}
|
||||
// 打开侧边栏并清楚验证状态
|
||||
this.listenerFormModelVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
|
||||
});
|
||||
},
|
||||
// 移除监听器
|
||||
removeListener(listener, index) {
|
||||
this.$confirm("确认移除该监听器吗?", "提示", {
|
||||
confirmButtonText: "确 认",
|
||||
cancelButtonText: "取 消"
|
||||
})
|
||||
.then(() => {
|
||||
this.bpmnElementListeners.splice(index, 1);
|
||||
this.elementListenersList.splice(index, 1);
|
||||
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
|
||||
})
|
||||
.catch(() => console.info("操作取消"));
|
||||
},
|
||||
// 保存监听器
|
||||
async saveListenerConfig() {
|
||||
let validateStatus = await this.$refs["listenerFormRef"].validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
const listenerObject = createListenerObject(this.listenerForm, true, this.prefix);
|
||||
if (this.editingListenerIndex === -1) {
|
||||
this.bpmnElementListeners.push(listenerObject);
|
||||
this.elementListenersList.push(this.listenerForm);
|
||||
} else {
|
||||
this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
|
||||
this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
|
||||
}
|
||||
// 保存其他配置
|
||||
this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:TaskListener`) ?? [];
|
||||
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
|
||||
// 4. 隐藏侧边栏
|
||||
this.listenerFormModelVisible = false;
|
||||
this.listenerForm = {};
|
||||
},
|
||||
// 打开监听器字段编辑弹窗
|
||||
openListenerFieldForm(field, index) {
|
||||
this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
|
||||
this.editingListenerFieldIndex = field ? index : -1;
|
||||
this.listenerFieldFormModelVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
|
||||
});
|
||||
},
|
||||
// 保存监听器注入字段
|
||||
async saveListenerFiled() {
|
||||
let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
if (this.editingListenerFieldIndex === -1) {
|
||||
this.fieldsListOfListener.push(this.listenerFieldForm);
|
||||
this.listenerForm.fields.push(this.listenerFieldForm);
|
||||
} else {
|
||||
this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
|
||||
this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
|
||||
}
|
||||
this.listenerFieldFormModelVisible = false;
|
||||
this.$nextTick(() => (this.listenerFieldForm = {}));
|
||||
},
|
||||
// 移除监听器字段
|
||||
removeListenerField(field, index) {
|
||||
this.$confirm("确认移除该字段吗?", "提示", {
|
||||
confirmButtonText: "确 认",
|
||||
cancelButtonText: "取 消"
|
||||
})
|
||||
.then(() => {
|
||||
this.fieldsListOfListener.splice(index, 1);
|
||||
this.listenerForm.fields.splice(index, 1);
|
||||
})
|
||||
.catch(() => console.info("操作取消"));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,178 @@
|
||||
export const template = isTaskListener => {
|
||||
return `
|
||||
<div class="panel-tab__content">
|
||||
<el-table :data="elementListenersList" size="mini" border>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="事件类型" min-width="100px" prop="event" />
|
||||
<el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openListenerForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openListenerForm(null)">添加监听器</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<el-drawer :visible.sync="listenerFormModelVisible" title="执行监听器" :size="width + 'px'" append-to-body destroy-on-close>
|
||||
<el-form size="mini" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
|
||||
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerForm.event">
|
||||
<el-option label="start" value="start" />
|
||||
<el-option label="end" value="end" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerForm.listenerType">
|
||||
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
prop="class"
|
||||
key="listener-class"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.class" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
prop="expression"
|
||||
key="listener-expression"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.expression" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
prop="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerForm.delegateExpression" clearable />
|
||||
</el-form-item>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<el-form-item
|
||||
label="脚本格式"
|
||||
prop="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.scriptFormat" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="脚本类型"
|
||||
prop="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
|
||||
>
|
||||
<el-select v-model="listenerForm.scriptType">
|
||||
<el-option label="内联脚本" value="inlineScript" />
|
||||
<el-option label="外部脚本" value="externalScript" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
prop="value"
|
||||
key="listener-script"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.value" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
prop="resource"
|
||||
key="listener-resource"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
|
||||
>
|
||||
<el-input v-model="listenerForm.resource" clearable />
|
||||
</el-form-item>
|
||||
</template>
|
||||
${
|
||||
isTaskListener
|
||||
? "<el-form-item label='定时器类型' prop='eventDefinitionType' key='eventDefinitionType'>" +
|
||||
"<el-select v-model='listenerForm.eventDefinitionType'>" +
|
||||
"<el-option label='日期' value='date' />" +
|
||||
"<el-option label='持续时长' value='duration' />" +
|
||||
"<el-option label='循环' value='cycle' />" +
|
||||
"<el-option label='无' value='' />" +
|
||||
"</el-select>" +
|
||||
"</el-form-item>" +
|
||||
"<el-form-item v-if='!!listenerForm.eventDefinitionType' label='定时器' prop='eventDefinitions' key='eventDefinitions'>" +
|
||||
"<el-input v-model='listenerForm.eventDefinitions' clearable />" +
|
||||
"</el-form-item>"
|
||||
: ""
|
||||
}
|
||||
</el-form>
|
||||
<el-divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><i class="el-icon-menu"></i>注入字段:</span>
|
||||
<el-button size="mini" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="字段名称" min-width="100px" prop="name" />
|
||||
<el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
|
||||
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
|
||||
<el-table-column label="操作" width="100px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openListenerFieldForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" @click="listenerFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveListenerConfig">保 存</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 注入西段 编辑/创建 部分 -->
|
||||
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="listenerFieldForm" size="mini" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
|
||||
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-input v-model="listenerFieldForm.name" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
|
||||
<el-select v-model="listenerFieldForm.fieldType">
|
||||
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
prop="string"
|
||||
key="field-string"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerFieldForm.string" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
prop="expression"
|
||||
key="field-expression"
|
||||
:rules="{ required: true, trigger: ['blur', 'change'] }"
|
||||
>
|
||||
<el-input v-model="listenerFieldForm.expression" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" @click="listenerFieldFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveListenerFiled">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
`;
|
||||
};
|
@@ -0,0 +1,62 @@
|
||||
// 初始化表单数据
|
||||
export function initListenerForm(listener) {
|
||||
let self = {
|
||||
...listener
|
||||
};
|
||||
if (listener.script) {
|
||||
self = {
|
||||
...listener,
|
||||
...listener.script,
|
||||
scriptType: listener.script.resource ? "externalScript" : "inlineScript"
|
||||
};
|
||||
}
|
||||
if (listener.event === "timeout" && listener.eventDefinitions) {
|
||||
if (listener.eventDefinitions.length) {
|
||||
let k = "";
|
||||
for (let key in listener.eventDefinitions[0]) {
|
||||
console.log(listener.eventDefinitions, key);
|
||||
if (key.indexOf("time") !== -1) {
|
||||
k = key;
|
||||
self.eventDefinitionType = key.replace("time", "").toLowerCase();
|
||||
}
|
||||
}
|
||||
console.log(k);
|
||||
self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
export function initListenerType(listener) {
|
||||
let listenerType;
|
||||
if (listener.class) listenerType = "classListener";
|
||||
if (listener.expression) listenerType = "expressionListener";
|
||||
if (listener.delegateExpression) listenerType = "delegateExpressionListener";
|
||||
if (listener.script) listenerType = "scriptListener";
|
||||
return {
|
||||
...JSON.parse(JSON.stringify(listener)),
|
||||
...(listener.script ?? {}),
|
||||
listenerType: listenerType
|
||||
};
|
||||
}
|
||||
|
||||
export const listenerType = {
|
||||
classListener: "Java 类",
|
||||
expressionListener: "表达式",
|
||||
delegateExpressionListener: "代理表达式",
|
||||
scriptListener: "脚本"
|
||||
};
|
||||
|
||||
export const eventType = {
|
||||
create: "创建",
|
||||
assignment: "指派",
|
||||
complete: "完成",
|
||||
delete: "删除",
|
||||
update: "更新",
|
||||
timeout: "超时"
|
||||
};
|
||||
|
||||
export const fieldType = {
|
||||
string: "字符串",
|
||||
expression: "表达式"
|
||||
};
|
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-form size="mini" label-width="90px" @submit.native.prevent>
|
||||
<el-form-item label="回路特性">
|
||||
<el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType">
|
||||
<!--bpmn:MultiInstanceLoopCharacteristics-->
|
||||
<el-option label="并行多重事件" value="ParallelMultiInstance" />
|
||||
<el-option label="时序多重事件" value="SequentialMultiInstance" />
|
||||
<!--bpmn:StandardLoopCharacteristics-->
|
||||
<el-option label="循环事件" value="StandardLoop" />
|
||||
<el-option label="无" value="Null" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="loopCharacteristics === 'ParallelMultiInstance' || loopCharacteristics === 'SequentialMultiInstance'">
|
||||
<el-form-item label="循环基数" key="loopCardinality">
|
||||
<el-input v-model="loopInstanceForm.loopCardinality" clearable @change="updateLoopCardinality" />
|
||||
</el-form-item>
|
||||
<el-form-item label="集合" key="collection">
|
||||
<el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
|
||||
</el-form-item>
|
||||
<el-form-item label="元素变量" key="elementVariable">
|
||||
<el-input v-model="loopInstanceForm.elementVariable" clearable @change="updateLoopBase" />
|
||||
</el-form-item>
|
||||
<el-form-item label="完成条件" key="completionCondition">
|
||||
<el-input v-model="loopInstanceForm.completionCondition" clearable @change="updateLoopCondition" />
|
||||
</el-form-item>
|
||||
<el-form-item label="异步状态" key="async">
|
||||
<el-checkbox v-model="loopInstanceForm.asyncBefore" label="异步前" @change="updateLoopAsync('asyncBefore')" />
|
||||
<el-checkbox v-model="loopInstanceForm.asyncAfter" label="异步后" @change="updateLoopAsync('asyncAfter')" />
|
||||
<el-checkbox
|
||||
v-model="loopInstanceForm.exclusive"
|
||||
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
||||
label="排除"
|
||||
@change="updateLoopAsync('exclusive')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="重试周期" prop="timeCycle" v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore" key="timeCycle">
|
||||
<el-input v-model="loopInstanceForm.timeCycle" clearable @change="updateLoopTimeCycle" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ElementMultiInstance",
|
||||
props: {
|
||||
businessObject: Object,
|
||||
type: String
|
||||
},
|
||||
inject: {
|
||||
prefix: "prefix"
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loopCharacteristics: "",
|
||||
//默认配置,用来覆盖原始不存在的选项,避免报错
|
||||
defaultLoopInstanceForm: {
|
||||
completionCondition: "",
|
||||
loopCardinality: "",
|
||||
extensionElements: [],
|
||||
asyncAfter: false,
|
||||
asyncBefore: false,
|
||||
exclusive: false
|
||||
},
|
||||
loopInstanceForm: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
businessObject: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.getElementLoop(val);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getElementLoop(businessObject) {
|
||||
if (!businessObject.loopCharacteristics) {
|
||||
this.loopCharacteristics = "Null";
|
||||
this.loopInstanceForm = {};
|
||||
return;
|
||||
}
|
||||
if (businessObject.loopCharacteristics.$type === "bpmn:StandardLoopCharacteristics") {
|
||||
this.loopCharacteristics = "StandardLoop";
|
||||
this.loopInstanceForm = {};
|
||||
return;
|
||||
}
|
||||
if (businessObject.loopCharacteristics.isSequential) {
|
||||
this.loopCharacteristics = "SequentialMultiInstance";
|
||||
} else {
|
||||
this.loopCharacteristics = "ParallelMultiInstance";
|
||||
}
|
||||
// 合并配置
|
||||
this.loopInstanceForm = {
|
||||
...this.defaultLoopInstanceForm,
|
||||
...businessObject.loopCharacteristics,
|
||||
completionCondition: businessObject.loopCharacteristics?.completionCondition?.body ?? "",
|
||||
loopCardinality: businessObject.loopCharacteristics?.loopCardinality?.body ?? ""
|
||||
};
|
||||
// 保留当前元素 businessObject 上的 loopCharacteristics 实例
|
||||
this.multiLoopInstance = window.bpmnInstances.bpmnElement.businessObject.loopCharacteristics;
|
||||
// 更新表单
|
||||
if (
|
||||
businessObject.loopCharacteristics.extensionElements &&
|
||||
businessObject.loopCharacteristics.extensionElements.values &&
|
||||
businessObject.loopCharacteristics.extensionElements.values.length
|
||||
) {
|
||||
this.$set(this.loopInstanceForm, "timeCycle", businessObject.loopCharacteristics.extensionElements.values[0].body);
|
||||
}
|
||||
},
|
||||
changeLoopCharacteristicsType(type) {
|
||||
// this.loopInstanceForm = { ...this.defaultLoopInstanceForm }; // 切换类型取消原表单配置
|
||||
// 取消多实例配置
|
||||
if (type === "Null") {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null });
|
||||
return;
|
||||
}
|
||||
// 配置循环
|
||||
if (type === "StandardLoop") {
|
||||
const loopCharacteristicsObject = window.bpmnInstances.moddle.create("bpmn:StandardLoopCharacteristics");
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
loopCharacteristics: loopCharacteristicsObject
|
||||
});
|
||||
this.multiLoopInstance = null;
|
||||
return;
|
||||
}
|
||||
// 时序
|
||||
if (type === "SequentialMultiInstance") {
|
||||
this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: true });
|
||||
} else {
|
||||
this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics");
|
||||
}
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
loopCharacteristics: this.multiLoopInstance
|
||||
});
|
||||
},
|
||||
// 循环基数
|
||||
updateLoopCardinality(cardinality) {
|
||||
let loopCardinality = null;
|
||||
if (cardinality && cardinality.length) {
|
||||
loopCardinality = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: cardinality });
|
||||
}
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, { loopCardinality });
|
||||
},
|
||||
// 完成条件
|
||||
updateLoopCondition(condition) {
|
||||
let completionCondition = null;
|
||||
if (condition && condition.length) {
|
||||
completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: condition });
|
||||
}
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, { completionCondition });
|
||||
},
|
||||
// 重试周期
|
||||
updateLoopTimeCycle(timeCycle) {
|
||||
const extensionElements = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
|
||||
values: [
|
||||
window.bpmnInstances.moddle.create(`${this.prefix}:FailedJobRetryTimeCycle`, {
|
||||
body: timeCycle
|
||||
})
|
||||
]
|
||||
});
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, { extensionElements });
|
||||
},
|
||||
// 直接更新的基础信息
|
||||
updateLoopBase() {
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
|
||||
collection: this.loopInstanceForm.collection || null,
|
||||
elementVariable: this.loopInstanceForm.elementVariable || null
|
||||
});
|
||||
},
|
||||
// 各异步状态
|
||||
updateLoopAsync(key) {
|
||||
const { asyncBefore, asyncAfter } = this.loopInstanceForm;
|
||||
let asyncAttr = Object.create(null);
|
||||
if (!asyncBefore && !asyncAfter) {
|
||||
this.$set(this.loopInstanceForm, "exclusive", false);
|
||||
asyncAttr = { asyncBefore: false, asyncAfter: false, exclusive: false, extensionElements: null };
|
||||
} else {
|
||||
asyncAttr[key] = this.loopInstanceForm[key];
|
||||
}
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, asyncAttr);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.multiLoopInstance = null;
|
||||
this.bpmnElement = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="element-property input-property">
|
||||
<div class="element-property__label">元素文档:</div>
|
||||
<div class="element-property__value">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="documentation"
|
||||
size="mini"
|
||||
resize="vertical"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
@input="updateDocumentation"
|
||||
@blur="updateDocumentation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ElementOtherConfig",
|
||||
props: {
|
||||
id: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
documentation: ""
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler: function(id) {
|
||||
if (id && id.length) {
|
||||
this.$nextTick(() => {
|
||||
const documentations = window.bpmnInstances.bpmnElement.businessObject?.documentation;
|
||||
this.documentation = documentations && documentations.length ? documentations[0].text : "";
|
||||
});
|
||||
} else {
|
||||
this.documentation = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateDocumentation() {
|
||||
(this.bpmnElement && this.bpmnElement.id === this.id) || (this.bpmnElement = window.bpmnInstances.elementRegistry.get(this.id));
|
||||
const documentation = window.bpmnInstances.bpmnFactory.create("bpmn:Documentation", { text: this.documentation });
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
documentation: [documentation]
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bpmnElement = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-table :data="elementPropertyList" size="mini" max-height="240" border fit>
|
||||
<el-table-column label="序号" width="50px" type="index" />
|
||||
<el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<el-button size="mini" type="text" @click="openAttributesForm(row, $index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openAttributesForm(null, -1)">添加属性</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog :visible.sync="propertyFormModelVisible" title="属性配置" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="propertyForm" label-width="80px" size="mini" ref="attributeFormRef" @submit.native.prevent>
|
||||
<el-form-item label="属性名:" prop="name">
|
||||
<el-input v-model="propertyForm.name" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="属性值:" prop="value">
|
||||
<el-input v-model="propertyForm.value" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" @click="propertyFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="saveAttribute">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ElementProperties",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
inject: {
|
||||
prefix: "prefix",
|
||||
width: "width"
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elementPropertyList: [],
|
||||
propertyForm: {},
|
||||
editingPropertyIndex: -1,
|
||||
propertyFormModelVisible: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
val && val.length && this.resetAttributesList();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetAttributesList() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.otherExtensionList = []; // 其他扩展配置
|
||||
this.bpmnElementProperties =
|
||||
this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
|
||||
if (ex.$type !== `${this.prefix}:Properties`) {
|
||||
this.otherExtensionList.push(ex);
|
||||
}
|
||||
return ex.$type === `${this.prefix}:Properties`;
|
||||
}) ?? [];
|
||||
|
||||
// 保存所有的 扩展属性字段
|
||||
this.bpmnElementPropertyList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
|
||||
// 复制 显示
|
||||
this.elementPropertyList = JSON.parse(JSON.stringify(this.bpmnElementPropertyList ?? []));
|
||||
},
|
||||
openAttributesForm(attr, index) {
|
||||
this.editingPropertyIndex = index;
|
||||
this.propertyForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
|
||||
this.propertyFormModelVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
|
||||
});
|
||||
},
|
||||
removeAttributes(attr, index) {
|
||||
this.$confirm("确认移除该属性吗?", "提示", {
|
||||
confirmButtonText: "确 认",
|
||||
cancelButtonText: "取 消"
|
||||
})
|
||||
.then(() => {
|
||||
this.elementPropertyList.splice(index, 1);
|
||||
this.bpmnElementPropertyList.splice(index, 1);
|
||||
// 新建一个属性字段的保存列表
|
||||
const propertiesObject = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, {
|
||||
values: this.bpmnElementPropertyList
|
||||
});
|
||||
this.updateElementExtensions(propertiesObject);
|
||||
this.resetAttributesList();
|
||||
})
|
||||
.catch(() => console.info("操作取消"));
|
||||
},
|
||||
saveAttribute() {
|
||||
const { name, value } = this.propertyForm;
|
||||
console.log(this.bpmnElementPropertyList);
|
||||
if (this.editingPropertyIndex !== -1) {
|
||||
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementPropertyList[this.editingPropertyIndex], {
|
||||
name,
|
||||
value
|
||||
});
|
||||
} else {
|
||||
// 新建属性字段
|
||||
const newPropertyObject = window.bpmnInstances.moddle.create(`${this.prefix}:Property`, { name, value });
|
||||
// 新建一个属性字段的保存列表
|
||||
const propertiesObject = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, {
|
||||
values: this.bpmnElementPropertyList.concat([newPropertyObject])
|
||||
});
|
||||
this.updateElementExtensions(propertiesObject);
|
||||
}
|
||||
this.propertyFormModelVisible = false;
|
||||
this.resetAttributesList();
|
||||
},
|
||||
updateElementExtensions(properties) {
|
||||
const extensions = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
|
||||
values: this.otherExtensionList.concat([properties])
|
||||
});
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
extensionElements: extensions
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="panel-tab__content--title">
|
||||
<span><i class="el-icon-menu" style="margin-right: 8px; color: #555555"></i>消息列表</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openModel('message')">创建新消息</el-button>
|
||||
</div>
|
||||
<el-table :data="messageList" size="mini" border>
|
||||
<el-table-column type="index" label="序号" width="60px" />
|
||||
<el-table-column label="消息ID" prop="id" max-width="300px" show-overflow-tooltip />
|
||||
<el-table-column label="消息名称" prop="name" max-width="300px" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<div class="panel-tab__content--title" style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eeeeee">
|
||||
<span><i class="el-icon-menu" style="margin-right: 8px; color: #555555"></i>信号列表</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openModel('signal')">创建新信号</el-button>
|
||||
</div>
|
||||
<el-table :data="signalList" size="mini" border>
|
||||
<el-table-column type="index" label="序号" width="60px" />
|
||||
<el-table-column label="信号ID" prop="id" max-width="300px" show-overflow-tooltip />
|
||||
<el-table-column label="信号名称" prop="name" max-width="300px" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<el-dialog :visible.sync="modelVisible" :title="modelConfig.title" :close-on-click-modal="false" width="400px" append-to-body destroy-on-close>
|
||||
<el-form :model="modelObjectForm" size="mini" label-width="90px" @submit.native.prevent>
|
||||
<el-form-item :label="modelConfig.idLabel">
|
||||
<el-input v-model="modelObjectForm.id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="modelConfig.nameLabel">
|
||||
<el-input v-model="modelObjectForm.name" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" @click="modelVisible = false">取 消</el-button>
|
||||
<el-button size="mini" type="primary" @click="addNewObject">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "SignalAndMassage",
|
||||
data() {
|
||||
return {
|
||||
signalList: [],
|
||||
messageList: [],
|
||||
modelVisible: false,
|
||||
modelType: "",
|
||||
modelObjectForm: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
modelConfig() {
|
||||
if (this.modelType === "message") {
|
||||
return { title: "创建消息", idLabel: "消息ID", nameLabel: "消息名称" };
|
||||
} else {
|
||||
return { title: "创建信号", idLabel: "信号ID", nameLabel: "信号名称" };
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initDataList();
|
||||
},
|
||||
methods: {
|
||||
initDataList() {
|
||||
this.rootElements = window.bpmnInstances.modeler.getDefinitions().rootElements;
|
||||
this.messageIdMap = {};
|
||||
this.signalIdMap = {};
|
||||
this.messageList = [];
|
||||
this.signalList = [];
|
||||
this.rootElements.forEach(el => {
|
||||
if (el.$type === "bpmn:Message") {
|
||||
this.messageIdMap[el.id] = true;
|
||||
this.messageList.push({ ...el });
|
||||
}
|
||||
if (el.$type === "bpmn:Signal") {
|
||||
this.signalIdMap[el.id] = true;
|
||||
this.signalList.push({ ...el });
|
||||
}
|
||||
});
|
||||
},
|
||||
openModel(type) {
|
||||
this.modelType = type;
|
||||
this.modelObjectForm = {};
|
||||
this.modelVisible = true;
|
||||
},
|
||||
addNewObject() {
|
||||
if (this.modelType === "message") {
|
||||
if (this.messageIdMap[this.modelObjectForm.id]) {
|
||||
return this.$message.error("该消息已存在,请修改id后重新保存");
|
||||
}
|
||||
const messageRef = window.bpmnInstances.moddle.create("bpmn:Message", this.modelObjectForm);
|
||||
this.rootElements.push(messageRef);
|
||||
} else {
|
||||
if (this.signalIdMap[this.modelObjectForm.id]) {
|
||||
return this.$message.error("该信号已存在,请修改id后重新保存");
|
||||
}
|
||||
const signalRef = window.bpmnInstances.moddle.create("bpmn:Signal", this.modelObjectForm);
|
||||
this.rootElements.push(signalRef);
|
||||
}
|
||||
this.modelVisible = false;
|
||||
this.initDataList();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-form size="mini" label-width="90px" @submit.native.prevent>
|
||||
<el-form-item label="异步延续">
|
||||
<el-checkbox v-model="taskConfigForm.asyncBefore" label="异步前" @change="changeTaskAsync" />
|
||||
<el-checkbox v-model="taskConfigForm.asyncAfter" label="异步后" @change="changeTaskAsync" />
|
||||
<el-checkbox v-model="taskConfigForm.exclusive" v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore" label="排除" @change="changeTaskAsync" />
|
||||
</el-form-item>
|
||||
<component :is="witchTaskComponent" v-bind="$props" />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserTask from "./task-components/UserTask";
|
||||
import ScriptTask from "./task-components/ScriptTask";
|
||||
import ReceiveTask from "./task-components/ReceiveTask";
|
||||
|
||||
export default {
|
||||
name: "ElementTaskConfig",
|
||||
components: { UserTask, ScriptTask, ReceiveTask },
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
taskConfigForm: {
|
||||
asyncAfter: false,
|
||||
asyncBefore: false,
|
||||
exclusive: false
|
||||
},
|
||||
witchTaskComponent: "",
|
||||
installedComponent: {
|
||||
// 手工任务与普通任务一致,不需要其他配置
|
||||
// 接收消息任务,需要在全局下插入新的消息实例,并在该节点下的 messageRef 属性绑定该实例
|
||||
// 发送任务、服务任务、业务规则任务共用一个相同配置
|
||||
UserTask: "UserTask", // 用户任务配置
|
||||
ScriptTask: "ScriptTask", // 脚本任务配置
|
||||
ReceiveTask: "ReceiveTask" // 消息接收任务
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.taskConfigForm.asyncBefore = this.bpmnElement?.businessObject?.asyncBefore;
|
||||
this.taskConfigForm.asyncAfter = this.bpmnElement?.businessObject?.asyncAfter;
|
||||
this.taskConfigForm.exclusive = this.bpmnElement?.businessObject?.exclusive;
|
||||
}
|
||||
},
|
||||
type: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.witchTaskComponent = this.installedComponent[this.type];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTaskAsync() {
|
||||
if (!this.taskConfigForm.asyncBefore && !this.taskConfigForm.asyncAfter) {
|
||||
this.taskConfigForm.exclusive = false;
|
||||
}
|
||||
window.bpmnInstances.modeling.updateProperties(window.bpmnInstances.bpmnElement, {
|
||||
...this.taskConfigForm
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div style="margin-top: 16px">
|
||||
<el-form-item label="消息实例">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap">
|
||||
<el-select v-model="bindMessageId" @change="updateTaskMessage">
|
||||
<el-option v-for="id in Object.keys(messageMap)" :value="id" :label="messageMap[id]" :key="id" />
|
||||
</el-select>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" style="margin-left: 8px" @click="openMessageModel" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-dialog :visible.sync="messageModelVisible" :close-on-click-modal="false" title="创建新消息" width="400px" append-to-body destroy-on-close>
|
||||
<el-form :model="newMessageForm" size="mini" label-width="90px" @submit.native.prevent>
|
||||
<el-form-item label="消息ID">
|
||||
<el-input v-model="newMessageForm.id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="消息名称">
|
||||
<el-input v-model="newMessageForm.name" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="mini" type="primary" @click="createNewMessage">确 认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ReceiveTask",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bindMessageId: "",
|
||||
newMessageForm: {},
|
||||
messageMap: {},
|
||||
messageModelVisible: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.$nextTick(() => this.getBindMessage());
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.bpmnMessageRefsMap = Object.create(null);
|
||||
this.bpmnRootElements = window.bpmnInstances.modeler.getDefinitions().rootElements;
|
||||
this.bpmnRootElements
|
||||
.filter(el => el.$type === "bpmn:Message")
|
||||
.forEach(m => {
|
||||
this.bpmnMessageRefsMap[m.id] = m;
|
||||
this.$set(this.messageMap, m.id, m.name);
|
||||
});
|
||||
this.$set(this.messageMap, "-1", "无"); // 添加一个空对象,保证可以取消原消息绑定
|
||||
},
|
||||
methods: {
|
||||
getBindMessage() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.bindMessageId = this.bpmnElement.businessObject?.messageRef?.id || "-1";
|
||||
},
|
||||
openMessageModel() {
|
||||
this.messageModelVisible = true;
|
||||
this.newMessageForm = {};
|
||||
},
|
||||
createNewMessage() {
|
||||
if (this.messageMap[this.newMessageForm.id]) {
|
||||
this.$message.error("该消息已存在,请修改id后重新保存");
|
||||
return;
|
||||
}
|
||||
const newMessage = window.bpmnInstances.moddle.create("bpmn:Message", this.newMessageForm);
|
||||
this.bpmnRootElements.push(newMessage);
|
||||
this.$set(this.messageMap, this.newMessageForm.id, this.newMessageForm.name);
|
||||
this.bpmnMessageRefsMap[this.newMessageForm.id] = newMessage;
|
||||
this.messageModelVisible = false;
|
||||
},
|
||||
updateTaskMessage(messageId) {
|
||||
if (messageId === "-1") {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
messageRef: null
|
||||
});
|
||||
} else {
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
|
||||
messageRef: this.bpmnMessageRefsMap[messageId]
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bpmnElement = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div style="margin-top: 16px">
|
||||
<el-form-item label="脚本格式">
|
||||
<el-input v-model="scriptTaskForm.scriptFormat" clearable @input="updateElementTask()" @change="updateElementTask()" />
|
||||
</el-form-item>
|
||||
<el-form-item label="脚本类型">
|
||||
<el-select v-model="scriptTaskForm.scriptType">
|
||||
<el-option label="内联脚本" value="inline" />
|
||||
<el-option label="外部资源" value="external" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
|
||||
<el-input
|
||||
v-model="scriptTaskForm.script"
|
||||
type="textarea"
|
||||
resize="vertical"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
clearable
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源地址" v-show="scriptTaskForm.scriptType === 'external'">
|
||||
<el-input v-model="scriptTaskForm.resource" clearable @input="updateElementTask()" @change="updateElementTask()" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结果变量">
|
||||
<el-input v-model="scriptTaskForm.resultVariable" clearable @input="updateElementTask()" @change="updateElementTask()" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ScriptTask",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultTaskForm: {
|
||||
scriptFormat: "",
|
||||
script: "",
|
||||
resource: "",
|
||||
resultVariable: ""
|
||||
},
|
||||
scriptTaskForm: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.$nextTick(() => this.resetTaskForm());
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetTaskForm() {
|
||||
for (let key in this.defaultTaskForm) {
|
||||
let value = this.bpmnElement?.businessObject[key] || this.defaultTaskForm[key];
|
||||
this.$set(this.scriptTaskForm, key, value);
|
||||
}
|
||||
this.$set(this.scriptTaskForm, "scriptType", this.scriptTaskForm.script ? "inline" : "external");
|
||||
},
|
||||
updateElementTask() {
|
||||
let taskAttr = Object.create(null);
|
||||
taskAttr.scriptFormat = this.scriptTaskForm.scriptFormat || null;
|
||||
taskAttr.resultVariable = this.scriptTaskForm.resultVariable || null;
|
||||
if (this.scriptTaskForm.scriptType === "inline") {
|
||||
taskAttr.script = this.scriptTaskForm.script || null;
|
||||
taskAttr.resource = null;
|
||||
} else {
|
||||
taskAttr.resource = this.scriptTaskForm.resource || null;
|
||||
taskAttr.script = null;
|
||||
}
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bpmnElement = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div style="margin-top: 16px">
|
||||
<!-- <el-form-item label="处理用户">-->
|
||||
<!-- <el-select v-model="userTaskForm.assignee" @change="updateElementTask('assignee')">-->
|
||||
<!-- <el-option v-for="ak in mockData" :key="'ass-' + ak" :label="`用户${ak}`" :value="`user${ak}`" />-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item label="候选用户">-->
|
||||
<!-- <el-select v-model="userTaskForm.candidateUsers" multiple collapse-tags @change="updateElementTask('candidateUsers')">-->
|
||||
<!-- <el-option v-for="uk in mockData" :key="'user-' + uk" :label="`用户${uk}`" :value="`user${uk}`" />-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item label="候选分组">-->
|
||||
<!-- <el-select v-model="userTaskForm.candidateGroups" multiple collapse-tags @change="updateElementTask('candidateGroups')">-->
|
||||
<!-- <el-option v-for="gk in mockData" :key="'ass-' + gk" :label="`分组${gk}`" :value="`group${gk}`" />-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="到期时间">
|
||||
<el-input v-model="userTaskForm.dueDate" clearable @change="updateElementTask('dueDate')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跟踪时间">
|
||||
<el-input v-model="userTaskForm.followUpDate" clearable @change="updateElementTask('followUpDate')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级">
|
||||
<el-input v-model="userTaskForm.priority" clearable @change="updateElementTask('priority')" />
|
||||
</el-form-item>
|
||||
友情提示:任务的分配规则,使用
|
||||
<router-link target="_blank" :to="{path:'/bpm/manager/model'}"><el-link type="danger">流程模型</el-link> </router-link>
|
||||
下的【分配规则】替代,提供指定角色、部门负责人、部门成员、岗位、工作组、自定义脚本等 7 种维护的任务分配维度,更加灵活!
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "UserTask",
|
||||
props: {
|
||||
id: String,
|
||||
type: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultTaskForm: {
|
||||
assignee: "",
|
||||
candidateUsers: [],
|
||||
candidateGroups: [],
|
||||
dueDate: "",
|
||||
followUpDate: "",
|
||||
priority: ""
|
||||
},
|
||||
userTaskForm: {},
|
||||
mockData: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.bpmnElement = window.bpmnInstances.bpmnElement;
|
||||
this.$nextTick(() => this.resetTaskForm());
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetTaskForm() {
|
||||
for (let key in this.defaultTaskForm) {
|
||||
let value;
|
||||
if (key === "candidateUsers" || key === "candidateGroups") {
|
||||
value = this.bpmnElement?.businessObject[key] ? this.bpmnElement.businessObject[key].split(",") : [];
|
||||
} else {
|
||||
value = this.bpmnElement?.businessObject[key] || this.defaultTaskForm[key];
|
||||
}
|
||||
this.$set(this.userTaskForm, key, value);
|
||||
}
|
||||
},
|
||||
updateElementTask(key) {
|
||||
const taskAttr = Object.create(null);
|
||||
if (key === "candidateUsers" || key === "candidateGroups") {
|
||||
taskAttr[key] = this.userTaskForm[key] && this.userTaskForm[key].length ? this.userTaskForm[key].join() : null;
|
||||
} else {
|
||||
taskAttr[key] = this.userTaskForm[key] || null;
|
||||
}
|
||||
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bpmnElement = null;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,70 @@
|
||||
/* 改变主题色变量 */
|
||||
$--color-primary: #1890ff;
|
||||
$--color-danger: #ff4d4f;
|
||||
|
||||
/* 改变 icon 字体路径变量,必需 */
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import "~element-ui/packages/theme-chalk/src/index";
|
||||
|
||||
.el-table td,
|
||||
.el-table th {
|
||||
color: #333;
|
||||
}
|
||||
.el-drawer__header {
|
||||
padding: 16px 16px 8px 16px;
|
||||
margin: 0;
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
div[class^="el-drawer"]:focus,
|
||||
span:focus {
|
||||
outline: none;
|
||||
}
|
||||
.el-drawer__body {
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
margin-top: 50vh !important;
|
||||
transform: translateY(-50%);
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-dialog__wrapper {
|
||||
overflow: hidden;
|
||||
max-height: 100vh;
|
||||
}
|
||||
.el-dialog__header {
|
||||
padding: 16px 16px 8px 16px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 16px;
|
||||
max-height: 80vh;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
.el-dialog__close {
|
||||
font-weight: 600;
|
||||
}
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
.el-divider:not(.el-divider--horizontal) {
|
||||
margin: 0 8px ;
|
||||
}
|
||||
.el-divider.el-divider--horizontal {
|
||||
margin: 16px 0;
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
@import "./process-designer.scss";
|
||||
@import "./process-panel.scss";
|
@@ -0,0 +1,156 @@
|
||||
@import "~bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css";
|
||||
@import "~bpmn-js-token-simulation/assets/css/font-awesome.min.css";
|
||||
@import "~bpmn-js-token-simulation/assets/css/normalize.css";
|
||||
|
||||
// 边框被 token-simulation 样式覆盖了
|
||||
.djs-palette {
|
||||
background: var(--palette-background-color);
|
||||
border: solid 1px var(--palette-border-color) !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.my-process-designer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
.my-process-designer__header {
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
.el-button {
|
||||
text-align: center;
|
||||
}
|
||||
.el-button-group {
|
||||
margin: 4px;
|
||||
}
|
||||
.el-tooltip__popper {
|
||||
.el-button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.el-button:hover {
|
||||
background: rgba(64, 158, 255, 0.8);
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.align {
|
||||
position: relative;
|
||||
i {
|
||||
&:after {
|
||||
content: "|";
|
||||
position: absolute;
|
||||
transform: rotate(90deg) translate(200%, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.align.align-left i {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.align.align-right i {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.align.align-top i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.align.align-bottom i {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.align.align-center i {
|
||||
transform: rotate(90deg);
|
||||
&:after {
|
||||
transform: rotate(90deg) translate(0, 60%);
|
||||
}
|
||||
}
|
||||
.align.align-middle i {
|
||||
transform: rotate(0deg);
|
||||
&:after {
|
||||
transform: rotate(90deg) translate(0, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.my-process-designer__container {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
.my-process-designer__canvas {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
|
||||
repeat !important;
|
||||
div.toggle-mode {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.my-process-designer__property-panel {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
overflow-y: auto;
|
||||
z-index: 10;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//侧边栏配置
|
||||
.djs-palette.open {
|
||||
.djs-palette-entries {
|
||||
div[class^="bpmn-icon-"]:before,
|
||||
div[class*="bpmn-icon-"]:before {
|
||||
line-height: unset;
|
||||
}
|
||||
div.entry {
|
||||
position: relative;
|
||||
}
|
||||
div.entry:hover {
|
||||
&::after {
|
||||
width: max-content;
|
||||
content: attr(title);
|
||||
vertical-align: text-bottom;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
transform: translateX(100%);
|
||||
font-size: 0.5em;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
background: #fafafa;
|
||||
box-shadow: 0 0 6px #eeeeee;
|
||||
border: 1px solid #cccccc;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
max-height: calc(80vh - 32px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.hljs {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.hljs * {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
.process-panel__container {
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
border-left: 1px solid #eeeeee;
|
||||
box-shadow: 0 0 8px #cccccc;
|
||||
max-height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.panel-tab__title {
|
||||
font-weight: 600;
|
||||
padding: 0 8px;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.2em;
|
||||
i {
|
||||
margin-right: 8px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
.panel-tab__content {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #eeeeee;
|
||||
padding: 8px 16px;
|
||||
.panel-tab__content--title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 8px;
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
.element-property {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 8px 0;
|
||||
.element-property__label {
|
||||
display: block;
|
||||
width: 90px;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
padding-right: 12px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.element-property__value {
|
||||
flex: 1;
|
||||
line-height: 32px;
|
||||
}
|
||||
.el-form-item {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
}
|
||||
.list-property {
|
||||
flex-direction: column;
|
||||
.element-listener-item {
|
||||
width: 100%;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 16px auto 32px 32px;
|
||||
grid-column-gap: 8px;
|
||||
}
|
||||
.element-listener-item + .element-listener-item {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
.listener-filed__title {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 0;
|
||||
span {
|
||||
width: 200px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.element-drawer__button {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.element-drawer__button > .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
color: #999999;
|
||||
}
|
||||
.el-form-item.el-form-item--mini {
|
||||
margin-bottom: 0;
|
||||
& + .el-form-item {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
// 创建监听器实例
|
||||
export function createListenerObject(options, isTask, prefix) {
|
||||
const listenerObj = Object.create(null);
|
||||
listenerObj.event = options.event;
|
||||
isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
|
||||
switch (options.listenerType) {
|
||||
case "scriptListener":
|
||||
listenerObj.script = createScriptObject(options, prefix);
|
||||
break;
|
||||
case "expressionListener":
|
||||
listenerObj.expression = options.expression;
|
||||
break;
|
||||
case "delegateExpressionListener":
|
||||
listenerObj.delegateExpression = options.delegateExpression;
|
||||
break;
|
||||
default:
|
||||
listenerObj.class = options.class;
|
||||
}
|
||||
// 注入字段
|
||||
if (options.fields) {
|
||||
listenerObj.fields = options.fields.map(field => {
|
||||
return createFieldObject(field, prefix);
|
||||
});
|
||||
}
|
||||
// 任务监听器的 定时器 设置
|
||||
if (isTask && options.event === "timeout" && !!options.eventDefinitionType) {
|
||||
const timeDefinition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: options.eventTimeDefinitions });
|
||||
const TimerEventDefinition = window.bpmnInstances.moddle.create("bpmn:TimerEventDefinition", {
|
||||
id: `TimerEventDefinition_${uuid(8)}`,
|
||||
[`time${options.eventDefinitionType.replace(/^\S/, s => s.toUpperCase())}`]: timeDefinition
|
||||
});
|
||||
listenerObj.eventDefinitions = [TimerEventDefinition];
|
||||
}
|
||||
return window.bpmnInstances.moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
|
||||
}
|
||||
|
||||
// 创建 监听器的注入字段 实例
|
||||
export function createFieldObject(option, prefix) {
|
||||
const { name, fieldType, string, expression } = option;
|
||||
const fieldConfig = fieldType === "string" ? { name, string } : { name, expression };
|
||||
return window.bpmnInstances.moddle.create(`${prefix}:Field`, fieldConfig);
|
||||
}
|
||||
|
||||
// 创建脚本实例
|
||||
export function createScriptObject(options, prefix) {
|
||||
const { scriptType, scriptFormat, value, resource } = options;
|
||||
const scriptConfig = scriptType === "inlineScript" ? { scriptFormat, value } : { scriptFormat, resource };
|
||||
return window.bpmnInstances.moddle.create(`${prefix}:Script`, scriptConfig);
|
||||
}
|
||||
|
||||
// 更新元素扩展属性
|
||||
export function updateElementExtensions(element, extensionList) {
|
||||
const extensions = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
|
||||
values: extensionList
|
||||
});
|
||||
window.bpmnInstances.modeling.updateProperties(element, {
|
||||
extensionElements: extensions
|
||||
});
|
||||
}
|
||||
|
||||
// 创建一个id
|
||||
export function uuid(length = 8, chars) {
|
||||
let result = "";
|
||||
let charsString = chars || "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (let i = length; i > 0; --i) {
|
||||
result += charsString[Math.floor(Math.random() * charsString.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
const hljs = require("highlight.js/lib/core");
|
||||
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"));
|
||||
hljs.registerLanguage("json", require("highlight.js/lib/languages/json"));
|
||||
|
||||
module.exports = hljs;
|
@@ -0,0 +1,14 @@
|
||||
import BpmnRenderer from "bpmn-js/lib/draw/BpmnRenderer";
|
||||
|
||||
export default function CustomRenderer(config, eventBus, styles, pathMap, canvas, textRenderer) {
|
||||
BpmnRenderer.call(this, config, eventBus, styles, pathMap, canvas, textRenderer, 2000);
|
||||
|
||||
this.handlers["label"] = function() {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
const F = function() {}; // 核心,利用空对象作为中介;
|
||||
F.prototype = BpmnRenderer.prototype; // 核心,将父类的原型赋值给空对象F;
|
||||
CustomRenderer.prototype = new F(); // 核心,将 F的实例赋值给子类;
|
||||
CustomRenderer.prototype.constructor = CustomRenderer; // 修复子类CustomRenderer的构造器指向,防止原型链的混乱;
|
@@ -0,0 +1,6 @@
|
||||
import CustomRenderer from "./CustomRenderer";
|
||||
|
||||
export default {
|
||||
__init__: ["customRenderer"],
|
||||
customRenderer: ["type", CustomRenderer]
|
||||
};
|
@@ -0,0 +1,16 @@
|
||||
import BpmnRules from "bpmn-js/lib/features/rules/BpmnRules";
|
||||
import inherits from "inherits";
|
||||
|
||||
export default function CustomRules(eventBus) {
|
||||
BpmnRules.call(this, eventBus);
|
||||
}
|
||||
|
||||
inherits(CustomRules, BpmnRules);
|
||||
|
||||
CustomRules.prototype.canDrop = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
CustomRules.prototype.canMove = function() {
|
||||
return false;
|
||||
};
|
@@ -0,0 +1,6 @@
|
||||
import CustomRules from "./CustomRules";
|
||||
|
||||
export default {
|
||||
__init__: ["customRules"],
|
||||
customRules: ["type", CustomRules]
|
||||
};
|
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* This is a sample file that should be replaced with the actual translation.
|
||||
*
|
||||
* Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available
|
||||
* translations and labels to translate.
|
||||
*/
|
||||
export default {
|
||||
"Exclusive Gateway": "Exklusives Gateway",
|
||||
"Parallel Gateway": "Paralleles Gateway",
|
||||
"Inclusive Gateway": "Inklusives Gateway",
|
||||
"Complex Gateway": "Komplexes Gateway",
|
||||
"Event based Gateway": "Ereignis-basiertes Gateway",
|
||||
"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)": "升级启动事件 (非中断)"
|
||||
};
|
@@ -0,0 +1,39 @@
|
||||
//outside.js
|
||||
|
||||
const ctx = "@@clickoutsideContext";
|
||||
|
||||
export default {
|
||||
bind(el, binding, vnode) {
|
||||
const ele = el;
|
||||
const documentHandler = e => {
|
||||
if (!vnode.context || ele.contains(e.target)) {
|
||||
return false;
|
||||
}
|
||||
// 调用指令回调
|
||||
if (binding.expression) {
|
||||
vnode.context[el[ctx].methodName](e);
|
||||
} else {
|
||||
el[ctx].bindingFn(e);
|
||||
}
|
||||
};
|
||||
// 将方法添加到ele
|
||||
ele[ctx] = {
|
||||
documentHandler,
|
||||
methodName: binding.expression,
|
||||
bindingFn: binding.value
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener("touchstart", documentHandler); // 为document绑定事件
|
||||
});
|
||||
},
|
||||
update(el, binding) {
|
||||
const ele = el;
|
||||
ele[ctx].methodName = binding.expression;
|
||||
ele[ctx].bindingFn = binding.value;
|
||||
},
|
||||
unbind(el) {
|
||||
document.removeEventListener("touchstart", el[ctx].documentHandler); // 解绑
|
||||
delete el[ctx];
|
||||
}
|
||||
};
|
@@ -0,0 +1,10 @@
|
||||
export function debounce(fn, delay = 500) {
|
||||
let timer;
|
||||
return function(...args) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
timer = setTimeout(fn.bind(this, ...args), delay);
|
||||
};
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
class Log {
|
||||
static type = ["primary", "success", "warn", "error", "info"];
|
||||
|
||||
static typeColor(type = "default") {
|
||||
let color = "";
|
||||
switch (type) {
|
||||
case "primary":
|
||||
color = "#2d8cf0";
|
||||
break;
|
||||
case "success":
|
||||
color = "#19be6b";
|
||||
break;
|
||||
case "info":
|
||||
color = "#909399";
|
||||
break;
|
||||
case "warn":
|
||||
color = "#ff9900";
|
||||
break;
|
||||
case "error":
|
||||
color = "#f03f14";
|
||||
break;
|
||||
case "default":
|
||||
color = "#35495E";
|
||||
break;
|
||||
default:
|
||||
color = type;
|
||||
break;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
static print(text, type = "default", back = false) {
|
||||
if (typeof text === "object") {
|
||||
// 如果是對象則調用打印對象方式
|
||||
console.dir(text);
|
||||
return;
|
||||
}
|
||||
if (back) {
|
||||
// 如果是打印帶背景圖的
|
||||
console.log(`%c ${text} `, `background:${this.typeColor(type)}; padding: 2px; border-radius: 4px;color: #fff;`);
|
||||
} else {
|
||||
console.log(`%c ${text} `, `color: ${this.typeColor(type)};`);
|
||||
}
|
||||
}
|
||||
|
||||
static pretty(title, text, type = "primary") {
|
||||
if (typeof text === "object") {
|
||||
console.log(
|
||||
`%c ${title} %c`,
|
||||
`background:${this.typeColor(type)};border:1px solid ${this.typeColor(type)}; padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`
|
||||
);
|
||||
console.dir(text);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`%c ${title} %c ${text} %c`,
|
||||
`background:${this.typeColor(type)};border:1px solid ${this.typeColor(type)}; padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`,
|
||||
`border:1px solid ${this.typeColor(type)}; padding: 1px; border-radius: 0 4px 4px 0; color: ${this.typeColor(type)};`,
|
||||
"background:transparent"
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Log;
|
@@ -0,0 +1,50 @@
|
||||
function xmlStr2XmlObj(xmlStr) {
|
||||
let xmlObj = {};
|
||||
if (document.all) {
|
||||
const xmlDom = new window.ActiveXObject("Microsoft.XMLDOM");
|
||||
xmlDom.loadXML(xmlStr);
|
||||
xmlObj = xmlDom;
|
||||
} else {
|
||||
xmlObj = new DOMParser().parseFromString(xmlStr, "text/xml");
|
||||
}
|
||||
return xmlObj;
|
||||
}
|
||||
|
||||
function xml2json(xml) {
|
||||
try {
|
||||
let obj = {};
|
||||
if (xml.children.length > 0) {
|
||||
for (let i = 0; i < xml.children.length; i++) {
|
||||
const item = xml.children.item(i);
|
||||
const nodeName = item.nodeName;
|
||||
if (typeof obj[nodeName] == "undefined") {
|
||||
obj[nodeName] = xml2json(item);
|
||||
} else {
|
||||
if (typeof obj[nodeName].push == "undefined") {
|
||||
const old = obj[nodeName];
|
||||
obj[nodeName] = [];
|
||||
obj[nodeName].push(old);
|
||||
}
|
||||
obj[nodeName].push(xml2json(item));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj = xml.textContent;
|
||||
}
|
||||
return obj;
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function xmlObj2json(xml) {
|
||||
const xmlObj = xmlStr2XmlObj(xml);
|
||||
console.log(xmlObj);
|
||||
let jsonObj = {};
|
||||
if (xmlObj.childNodes.length > 0) {
|
||||
jsonObj = xml2json(xmlObj);
|
||||
}
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
export default xmlObj2json;
|
629
yudao-ui-admin/src/components/generator/config.js
Normal file
629
yudao-ui-admin/src/components/generator/config.js
Normal file
@@ -0,0 +1,629 @@
|
||||
// 表单属性【右面板】
|
||||
export const formConf = {
|
||||
formRef: 'elForm',
|
||||
formModel: 'formData',
|
||||
size: 'medium',
|
||||
labelPosition: 'right',
|
||||
labelWidth: 100,
|
||||
formRules: 'rules',
|
||||
gutter: 15,
|
||||
disabled: false,
|
||||
span: 24,
|
||||
formBtns: true
|
||||
}
|
||||
|
||||
// 输入型组件 【左面板】
|
||||
export const inputComponents = [
|
||||
{
|
||||
// 组件的自定义配置
|
||||
__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: []
|
||||
},
|
||||
// 组件的插槽属性
|
||||
__slot__: {
|
||||
prepend: '',
|
||||
append: ''
|
||||
},
|
||||
// 其余的为可直接写在组件标签上的属性
|
||||
placeholder: '请输入',
|
||||
style: { width: '100%' },
|
||||
clearable: true,
|
||||
'prefix-icon': '',
|
||||
'suffix-icon': '',
|
||||
maxlength: null,
|
||||
'show-word-limit': false,
|
||||
readonly: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '多行文本',
|
||||
labelWidth: null,
|
||||
showLabel: true,
|
||||
tag: 'el-input',
|
||||
tagIcon: 'textarea',
|
||||
defaultValue: undefined,
|
||||
required: true,
|
||||
layout: 'colFormItem',
|
||||
span: 24,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/input'
|
||||
},
|
||||
type: 'textarea',
|
||||
placeholder: '请输入',
|
||||
autosize: {
|
||||
minRows: 4,
|
||||
maxRows: 4
|
||||
},
|
||||
style: { width: '100%' },
|
||||
maxlength: null,
|
||||
'show-word-limit': false,
|
||||
readonly: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '密码',
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
changeTag: true,
|
||||
tag: 'el-input',
|
||||
tagIcon: 'password',
|
||||
defaultValue: undefined,
|
||||
layout: 'colFormItem',
|
||||
span: 24,
|
||||
required: true,
|
||||
regList: [],
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/input'
|
||||
},
|
||||
__slot__: {
|
||||
prepend: '',
|
||||
append: ''
|
||||
},
|
||||
placeholder: '请输入',
|
||||
'show-password': true,
|
||||
style: { width: '100%' },
|
||||
clearable: true,
|
||||
'prefix-icon': '',
|
||||
'suffix-icon': '',
|
||||
maxlength: null,
|
||||
'show-word-limit': false,
|
||||
readonly: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '计数器',
|
||||
showLabel: true,
|
||||
changeTag: true,
|
||||
labelWidth: null,
|
||||
tag: 'el-input-number',
|
||||
tagIcon: 'number',
|
||||
defaultValue: undefined,
|
||||
span: 24,
|
||||
layout: 'colFormItem',
|
||||
required: true,
|
||||
regList: [],
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/input-number'
|
||||
},
|
||||
placeholder: '',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
step: 1,
|
||||
'step-strictly': false,
|
||||
precision: undefined,
|
||||
'controls-position': '',
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '编辑器',
|
||||
showLabel: true,
|
||||
changeTag: true,
|
||||
labelWidth: null,
|
||||
tag: 'tinymce',
|
||||
tagIcon: 'rich-text',
|
||||
defaultValue: null,
|
||||
span: 24,
|
||||
layout: 'colFormItem',
|
||||
required: true,
|
||||
regList: [],
|
||||
document: 'http://tinymce.ax-z.cn'
|
||||
},
|
||||
placeholder: '请输入',
|
||||
height: 300, // 编辑器高度
|
||||
branding: false // 隐藏右下角品牌烙印
|
||||
}
|
||||
]
|
||||
|
||||
// 选择型组件 【左面板】
|
||||
export const selectComponents = [
|
||||
{
|
||||
__config__: {
|
||||
label: '下拉选择',
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
tag: 'el-select',
|
||||
tagIcon: 'select',
|
||||
layout: 'colFormItem',
|
||||
span: 24,
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/select'
|
||||
},
|
||||
__slot__: {
|
||||
options: [{
|
||||
label: '选项一',
|
||||
value: 1
|
||||
}, {
|
||||
label: '选项二',
|
||||
value: 2
|
||||
}]
|
||||
},
|
||||
placeholder: '请选择',
|
||||
style: { width: '100%' },
|
||||
clearable: true,
|
||||
disabled: false,
|
||||
filterable: false,
|
||||
multiple: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '级联选择',
|
||||
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/cascaderList',
|
||||
method: 'get',
|
||||
dataPath: 'list',
|
||||
dataConsumer: 'options',
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
tag: 'el-cascader',
|
||||
tagIcon: 'cascader',
|
||||
layout: 'colFormItem',
|
||||
defaultValue: [],
|
||||
dataType: 'dynamic',
|
||||
span: 24,
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/cascader'
|
||||
},
|
||||
options: [{
|
||||
id: 1,
|
||||
value: 1,
|
||||
label: '选项1',
|
||||
children: [{
|
||||
id: 2,
|
||||
value: 2,
|
||||
label: '选项1-1'
|
||||
}]
|
||||
}],
|
||||
placeholder: '请选择',
|
||||
style: { width: '100%' },
|
||||
props: {
|
||||
props: {
|
||||
multiple: false,
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
children: 'children'
|
||||
}
|
||||
},
|
||||
'show-all-levels': true,
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
filterable: false,
|
||||
separator: '/'
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '单选框组',
|
||||
labelWidth: null,
|
||||
showLabel: true,
|
||||
tag: 'el-radio-group',
|
||||
tagIcon: 'radio',
|
||||
changeTag: true,
|
||||
defaultValue: undefined,
|
||||
layout: 'colFormItem',
|
||||
span: 24,
|
||||
optionType: 'default',
|
||||
regList: [],
|
||||
required: true,
|
||||
border: false,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/radio'
|
||||
},
|
||||
__slot__: {
|
||||
options: [{
|
||||
label: '选项一',
|
||||
value: 1
|
||||
}, {
|
||||
label: '选项二',
|
||||
value: 2
|
||||
}]
|
||||
},
|
||||
style: {},
|
||||
size: 'medium',
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '多选框组',
|
||||
tag: 'el-checkbox-group',
|
||||
tagIcon: 'checkbox',
|
||||
defaultValue: [],
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
layout: 'colFormItem',
|
||||
optionType: 'default',
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
border: false,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'
|
||||
},
|
||||
__slot__: {
|
||||
options: [{
|
||||
label: '选项一',
|
||||
value: 1
|
||||
}, {
|
||||
label: '选项二',
|
||||
value: 2
|
||||
}]
|
||||
},
|
||||
style: {},
|
||||
size: 'medium',
|
||||
min: null,
|
||||
max: null,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '开关',
|
||||
tag: 'el-switch',
|
||||
tagIcon: 'switch',
|
||||
defaultValue: false,
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
layout: 'colFormItem',
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/switch'
|
||||
},
|
||||
style: {},
|
||||
disabled: false,
|
||||
'active-text': '',
|
||||
'inactive-text': '',
|
||||
'active-color': null,
|
||||
'inactive-color': null,
|
||||
'active-value': true,
|
||||
'inactive-value': false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '滑块',
|
||||
tag: 'el-slider',
|
||||
tagIcon: 'slider',
|
||||
defaultValue: null,
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
layout: 'colFormItem',
|
||||
labelWidth: null,
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/slider'
|
||||
},
|
||||
disabled: false,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
'show-stops': false,
|
||||
range: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '时间选择',
|
||||
tag: 'el-time-picker',
|
||||
tagIcon: 'time',
|
||||
defaultValue: null,
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
layout: 'colFormItem',
|
||||
labelWidth: null,
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
|
||||
},
|
||||
placeholder: '请选择',
|
||||
style: { width: '100%' },
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
'picker-options': {
|
||||
selectableRange: '00:00:00-23:59:59'
|
||||
},
|
||||
format: 'HH:mm:ss',
|
||||
'value-format': 'HH:mm:ss'
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '时间范围',
|
||||
tag: 'el-time-picker',
|
||||
tagIcon: 'time-range',
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
layout: 'colFormItem',
|
||||
defaultValue: null,
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
|
||||
},
|
||||
style: { width: '100%' },
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
'is-range': true,
|
||||
'range-separator': '至',
|
||||
'start-placeholder': '开始时间',
|
||||
'end-placeholder': '结束时间',
|
||||
format: 'HH:mm:ss',
|
||||
'value-format': 'HH:mm:ss'
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '日期选择',
|
||||
tag: 'el-date-picker',
|
||||
tagIcon: 'date',
|
||||
defaultValue: null,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
span: 24,
|
||||
layout: 'colFormItem',
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
|
||||
},
|
||||
placeholder: '请选择',
|
||||
type: 'date',
|
||||
style: { width: '100%' },
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
format: 'yyyy-MM-dd',
|
||||
'value-format': 'yyyy-MM-dd',
|
||||
readonly: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '日期范围',
|
||||
tag: 'el-date-picker',
|
||||
tagIcon: 'date-range',
|
||||
defaultValue: null,
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
required: true,
|
||||
layout: 'colFormItem',
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
|
||||
},
|
||||
style: { width: '100%' },
|
||||
type: 'daterange',
|
||||
'range-separator': '至',
|
||||
'start-placeholder': '开始日期',
|
||||
'end-placeholder': '结束日期',
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
format: 'yyyy-MM-dd',
|
||||
'value-format': 'yyyy-MM-dd',
|
||||
readonly: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '评分',
|
||||
tag: 'el-rate',
|
||||
tagIcon: 'rate',
|
||||
defaultValue: 0,
|
||||
span: 24,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
layout: 'colFormItem',
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/rate'
|
||||
},
|
||||
style: {},
|
||||
max: 5,
|
||||
'allow-half': false,
|
||||
'show-text': false,
|
||||
'show-score': false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '颜色选择',
|
||||
tag: 'el-color-picker',
|
||||
tagIcon: 'color',
|
||||
span: 24,
|
||||
defaultValue: null,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
layout: 'colFormItem',
|
||||
required: true,
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/color-picker'
|
||||
},
|
||||
'show-alpha': false,
|
||||
'color-format': '',
|
||||
disabled: false,
|
||||
size: 'medium'
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
label: '上传',
|
||||
tag: 'el-upload',
|
||||
tagIcon: 'upload',
|
||||
layout: 'colFormItem',
|
||||
defaultValue: null,
|
||||
showLabel: true,
|
||||
labelWidth: null,
|
||||
required: true,
|
||||
span: 24,
|
||||
showTip: false,
|
||||
buttonText: '点击上传',
|
||||
regList: [],
|
||||
changeTag: true,
|
||||
fileSize: 2,
|
||||
sizeUnit: 'MB',
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/upload'
|
||||
},
|
||||
__slot__: {
|
||||
'list-type': true
|
||||
},
|
||||
action: 'https://jsonplaceholder.typicode.com/posts/',
|
||||
disabled: false,
|
||||
accept: '',
|
||||
name: 'file',
|
||||
'auto-upload': true,
|
||||
'list-type': 'text',
|
||||
multiple: false
|
||||
}
|
||||
]
|
||||
|
||||
// 布局型组件 【左面板】
|
||||
export const layoutComponents = [
|
||||
{
|
||||
__config__: {
|
||||
layout: 'rowFormItem',
|
||||
tagIcon: 'row',
|
||||
label: '行容器',
|
||||
layoutTree: true,
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/layout#row-attributes'
|
||||
},
|
||||
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'
|
||||
},
|
||||
__slot__: {
|
||||
default: '主要按钮'
|
||||
},
|
||||
type: 'primary',
|
||||
icon: 'el-icon-search',
|
||||
round: false,
|
||||
size: 'medium',
|
||||
plain: false,
|
||||
circle: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
__config__: {
|
||||
layout: 'colFormItem',
|
||||
tagIcon: 'table',
|
||||
tag: 'el-table',
|
||||
document: 'https://element.eleme.cn/#/zh-CN/component/table',
|
||||
span: 24,
|
||||
formId: 101,
|
||||
renderKey: 1595761764203,
|
||||
componentName: 'row101',
|
||||
showLabel: true,
|
||||
changeTag: true,
|
||||
labelWidth: null,
|
||||
label: '表格[开发中]',
|
||||
dataType: 'dynamic',
|
||||
method: 'get',
|
||||
dataPath: 'list',
|
||||
dataConsumer: 'data',
|
||||
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/tableData',
|
||||
children: [{
|
||||
__config__: {
|
||||
layout: 'raw',
|
||||
tag: 'el-table-column',
|
||||
renderKey: 15957617660153
|
||||
},
|
||||
prop: 'date',
|
||||
label: '日期'
|
||||
}, {
|
||||
__config__: {
|
||||
layout: 'raw',
|
||||
tag: 'el-table-column',
|
||||
renderKey: 15957617660152
|
||||
},
|
||||
prop: 'address',
|
||||
label: '地址'
|
||||
}, {
|
||||
__config__: {
|
||||
layout: 'raw',
|
||||
tag: 'el-table-column',
|
||||
renderKey: 15957617660151
|
||||
},
|
||||
prop: 'name',
|
||||
label: '名称'
|
||||
}, {
|
||||
__config__: {
|
||||
layout: 'raw',
|
||||
tag: 'el-table-column',
|
||||
renderKey: 1595774496335,
|
||||
children: [
|
||||
{
|
||||
__config__: {
|
||||
label: '按钮',
|
||||
tag: 'el-button',
|
||||
tagIcon: 'button',
|
||||
layout: 'raw',
|
||||
renderKey: 1595779809901
|
||||
},
|
||||
__slot__: {
|
||||
default: '主要按钮'
|
||||
},
|
||||
type: 'primary',
|
||||
icon: 'el-icon-search',
|
||||
round: false,
|
||||
size: 'medium'
|
||||
}
|
||||
]
|
||||
},
|
||||
label: '操作'
|
||||
}]
|
||||
},
|
||||
data: [],
|
||||
directives: [{
|
||||
name: 'loading',
|
||||
value: true
|
||||
}],
|
||||
border: true,
|
||||
type: 'default',
|
||||
justify: 'start',
|
||||
align: 'top'
|
||||
}
|
||||
]
|
18
yudao-ui-admin/src/components/generator/css.js
Normal file
18
yudao-ui-admin/src/components/generator/css.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const styles = {
|
||||
'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
|
||||
'el-upload': '.el-upload__tip{line-height: 1.2;}'
|
||||
}
|
||||
|
||||
function addCss(cssList, el) {
|
||||
const css = styles[el.__config__.tag]
|
||||
css && cssList.indexOf(css) === -1 && cssList.push(css)
|
||||
if (el.__config__.children) {
|
||||
el.__config__.children.forEach(el2 => addCss(cssList, el2))
|
||||
}
|
||||
}
|
||||
|
||||
export function makeUpCss(conf) {
|
||||
const cssList = []
|
||||
conf.fields.forEach(el => addCss(cssList, el))
|
||||
return cssList.join('\n')
|
||||
}
|
37
yudao-ui-admin/src/components/generator/drawingDefalut.js
Normal file
37
yudao-ui-admin/src/components/generator/drawingDefalut.js
Normal file
@@ -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
|
||||
}
|
||||
]
|
399
yudao-ui-admin/src/components/generator/html.js
Normal file
399
yudao-ui-admin/src/components/generator/html.js
Normal file
@@ -0,0 +1,399 @@
|
||||
/* eslint-disable max-len */
|
||||
import ruleTrigger from './ruleTrigger'
|
||||
|
||||
let confGlobal
|
||||
let someSpanIsNot24
|
||||
|
||||
export function dialogWrapper(str) {
|
||||
return `<el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" title="Dialog Titile">
|
||||
${str}
|
||||
<div slot="footer">
|
||||
<el-button @click="close">取消</el-button>
|
||||
<el-button type="primary" @click="handelConfirm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>`
|
||||
}
|
||||
|
||||
export function vueTemplate(str) {
|
||||
return `<template>
|
||||
<div>
|
||||
${str}
|
||||
</div>
|
||||
</template>`
|
||||
}
|
||||
|
||||
export function vueScript(str) {
|
||||
return `<script>
|
||||
${str}
|
||||
</script>`
|
||||
}
|
||||
|
||||
export function cssStyle(cssStr) {
|
||||
return `<style>
|
||||
${cssStr}
|
||||
</style>`
|
||||
}
|
||||
|
||||
function buildFormTemplate(scheme, child, type) {
|
||||
let labelPosition = ''
|
||||
if (scheme.labelPosition !== 'right') {
|
||||
labelPosition = `label-position="${scheme.labelPosition}"`
|
||||
}
|
||||
const disabled = scheme.disabled ? `:disabled="${scheme.disabled}"` : ''
|
||||
let str = `<el-form ref="${scheme.formRef}" :model="${scheme.formModel}" :rules="${scheme.formRules}" size="${scheme.size}" ${disabled} label-width="${scheme.labelWidth}px" ${labelPosition}>
|
||||
${child}
|
||||
${buildFromBtns(scheme, type)}
|
||||
</el-form>`
|
||||
if (someSpanIsNot24) {
|
||||
str = `<el-row :gutter="${scheme.gutter}">
|
||||
${str}
|
||||
</el-row>`
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function buildFromBtns(scheme, type) {
|
||||
let str = ''
|
||||
if (scheme.formBtns && type === 'file') {
|
||||
str = `<el-form-item size="large">
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
</el-form-item>`
|
||||
if (someSpanIsNot24) {
|
||||
str = `<el-col :span="24">
|
||||
${str}
|
||||
</el-col>`
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// span不为24的用el-col包裹
|
||||
function colWrapper(scheme, str) {
|
||||
if (someSpanIsNot24 || scheme.__config__.span !== 24) {
|
||||
return `<el-col :span="${scheme.__config__.span}">
|
||||
${str}
|
||||
</el-col>`
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
const layouts = {
|
||||
colFormItem(scheme) {
|
||||
const config = scheme.__config__
|
||||
let labelWidth = ''
|
||||
let label = `label="${config.label}"`
|
||||
if (config.labelWidth && config.labelWidth !== confGlobal.labelWidth) {
|
||||
labelWidth = `label-width="${config.labelWidth}px"`
|
||||
}
|
||||
if (config.showLabel === false) {
|
||||
labelWidth = 'label-width="0"'
|
||||
label = ''
|
||||
}
|
||||
const required = !ruleTrigger[config.tag] && config.required ? 'required' : ''
|
||||
const tagDom = tags[config.tag] ? tags[config.tag](scheme) : null
|
||||
let str = `<el-form-item ${labelWidth} ${label} prop="${scheme.__vModel__}" ${required}>
|
||||
${tagDom}
|
||||
</el-form-item>`
|
||||
str = colWrapper(scheme, str)
|
||||
return str
|
||||
},
|
||||
rowFormItem(scheme) {
|
||||
const config = scheme.__config__
|
||||
const type = scheme.type === 'default' ? '' : `type="${scheme.type}"`
|
||||
const justify = scheme.type === 'default' ? '' : `justify="${scheme.justify}"`
|
||||
const align = scheme.type === 'default' ? '' : `align="${scheme.align}"`
|
||||
const gutter = scheme.gutter ? `:gutter="${scheme.gutter}"` : ''
|
||||
const children = config.children.map(el => layouts[el.__config__.layout](el))
|
||||
let str = `<el-row ${type} ${justify} ${align} ${gutter}>
|
||||
${children.join('\n')}
|
||||
</el-row>`
|
||||
str = colWrapper(scheme, str)
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
const tags = {
|
||||
'el-button': el => {
|
||||
const {
|
||||
tag, disabled
|
||||
} = attrBuilder(el)
|
||||
const type = el.type ? `type="${el.type}"` : ''
|
||||
const icon = el.icon ? `icon="${el.icon}"` : ''
|
||||
const round = el.round ? 'round' : ''
|
||||
const size = el.size ? `size="${el.size}"` : ''
|
||||
const plain = el.plain ? 'plain' : ''
|
||||
const circle = el.circle ? 'circle' : ''
|
||||
let child = buildElButtonChild(el)
|
||||
|
||||
if (child) child = `\n${child}\n` // 换行
|
||||
return `<${tag} ${type} ${icon} ${round} ${size} ${plain} ${disabled} ${circle}>${child}</${tag}>`
|
||||
},
|
||||
'el-input': el => {
|
||||
const {
|
||||
tag, disabled, vModel, clearable, placeholder, width
|
||||
} = attrBuilder(el)
|
||||
const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : ''
|
||||
const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : ''
|
||||
const readonly = el.readonly ? 'readonly' : ''
|
||||
const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : ''
|
||||
const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : ''
|
||||
const showPassword = el['show-password'] ? 'show-password' : ''
|
||||
const type = el.type ? `type="${el.type}"` : ''
|
||||
const autosize = el.autosize && el.autosize.minRows
|
||||
? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"`
|
||||
: ''
|
||||
let child = buildElInputChild(el)
|
||||
|
||||
if (child) child = `\n${child}\n` // 换行
|
||||
return `<${tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${tag}>`
|
||||
},
|
||||
'el-input-number': el => {
|
||||
const {
|
||||
tag, disabled, vModel, placeholder
|
||||
} = attrBuilder(el)
|
||||
const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : ''
|
||||
const min = el.min ? `:min='${el.min}'` : ''
|
||||
const max = el.max ? `:max='${el.max}'` : ''
|
||||
const step = el.step ? `:step='${el.step}'` : ''
|
||||
const stepStrictly = el['step-strictly'] ? 'step-strictly' : ''
|
||||
const precision = el.precision ? `:precision='${el.precision}'` : ''
|
||||
|
||||
return `<${tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-select': el => {
|
||||
const {
|
||||
tag, disabled, vModel, clearable, placeholder, width
|
||||
} = attrBuilder(el)
|
||||
const filterable = el.filterable ? 'filterable' : ''
|
||||
const multiple = el.multiple ? 'multiple' : ''
|
||||
let child = buildElSelectChild(el)
|
||||
|
||||
if (child) child = `\n${child}\n` // 换行
|
||||
return `<${tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${tag}>`
|
||||
},
|
||||
'el-radio-group': el => {
|
||||
const { tag, disabled, vModel } = attrBuilder(el)
|
||||
const size = `size="${el.size}"`
|
||||
let child = buildElRadioGroupChild(el)
|
||||
|
||||
if (child) child = `\n${child}\n` // 换行
|
||||
return `<${tag} ${vModel} ${size} ${disabled}>${child}</${tag}>`
|
||||
},
|
||||
'el-checkbox-group': el => {
|
||||
const { tag, disabled, vModel } = attrBuilder(el)
|
||||
const size = `size="${el.size}"`
|
||||
const min = el.min ? `:min="${el.min}"` : ''
|
||||
const max = el.max ? `:max="${el.max}"` : ''
|
||||
let child = buildElCheckboxGroupChild(el)
|
||||
|
||||
if (child) child = `\n${child}\n` // 换行
|
||||
return `<${tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${tag}>`
|
||||
},
|
||||
'el-switch': el => {
|
||||
const { tag, disabled, vModel } = attrBuilder(el)
|
||||
const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : ''
|
||||
const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : ''
|
||||
const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : ''
|
||||
const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : ''
|
||||
const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : ''
|
||||
const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : ''
|
||||
|
||||
return `<${tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-cascader': el => {
|
||||
const {
|
||||
tag, disabled, vModel, clearable, placeholder, width
|
||||
} = attrBuilder(el)
|
||||
const options = el.options ? `:options="${el.__vModel__}Options"` : ''
|
||||
const props = el.props ? `:props="${el.__vModel__}Props"` : ''
|
||||
const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"'
|
||||
const filterable = el.filterable ? 'filterable' : ''
|
||||
const separator = el.separator === '/' ? '' : `separator="${el.separator}"`
|
||||
|
||||
return `<${tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-slider': el => {
|
||||
const { tag, disabled, vModel } = attrBuilder(el)
|
||||
const min = el.min ? `:min='${el.min}'` : ''
|
||||
const max = el.max ? `:max='${el.max}'` : ''
|
||||
const step = el.step ? `:step='${el.step}'` : ''
|
||||
const range = el.range ? 'range' : ''
|
||||
const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : ''
|
||||
|
||||
return `<${tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-time-picker': el => {
|
||||
const {
|
||||
tag, disabled, vModel, clearable, placeholder, width
|
||||
} = attrBuilder(el)
|
||||
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
|
||||
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
|
||||
const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
|
||||
const isRange = el['is-range'] ? 'is-range' : ''
|
||||
const format = el.format ? `format="${el.format}"` : ''
|
||||
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
|
||||
const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : ''
|
||||
|
||||
return `<${tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-date-picker': el => {
|
||||
const {
|
||||
tag, disabled, vModel, clearable, placeholder, width
|
||||
} = attrBuilder(el)
|
||||
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
|
||||
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
|
||||
const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
|
||||
const format = el.format ? `format="${el.format}"` : ''
|
||||
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
|
||||
const type = el.type === 'date' ? '' : `type="${el.type}"`
|
||||
const readonly = el.readonly ? 'readonly' : ''
|
||||
|
||||
return `<${tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-rate': el => {
|
||||
const { tag, disabled, vModel } = attrBuilder(el)
|
||||
const max = el.max ? `:max='${el.max}'` : ''
|
||||
const allowHalf = el['allow-half'] ? 'allow-half' : ''
|
||||
const showText = el['show-text'] ? 'show-text' : ''
|
||||
const showScore = el['show-score'] ? 'show-score' : ''
|
||||
|
||||
return `<${tag} ${vModel} ${max} ${allowHalf} ${showText} ${showScore} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-color-picker': el => {
|
||||
const { tag, disabled, vModel } = attrBuilder(el)
|
||||
const size = `size="${el.size}"`
|
||||
const showAlpha = el['show-alpha'] ? 'show-alpha' : ''
|
||||
const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : ''
|
||||
|
||||
return `<${tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${tag}>`
|
||||
},
|
||||
'el-upload': el => {
|
||||
const { tag } = el.__config__
|
||||
const disabled = el.disabled ? ':disabled=\'true\'' : ''
|
||||
const action = el.action ? `:action="${el.__vModel__}Action"` : ''
|
||||
const multiple = el.multiple ? 'multiple' : ''
|
||||
const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : ''
|
||||
const accept = el.accept ? `accept="${el.accept}"` : ''
|
||||
const name = el.name !== 'file' ? `name="${el.name}"` : ''
|
||||
const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : ''
|
||||
const beforeUpload = `:before-upload="${el.__vModel__}BeforeUpload"`
|
||||
const fileList = `:file-list="${el.__vModel__}fileList"`
|
||||
const ref = `ref="${el.__vModel__}"`
|
||||
let child = buildElUploadChild(el)
|
||||
|
||||
if (child) child = `\n${child}\n` // 换行
|
||||
return `<${tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${tag}>`
|
||||
},
|
||||
tinymce: el => {
|
||||
const { tag, vModel, placeholder } = attrBuilder(el)
|
||||
const height = el.height ? `:height="${el.height}"` : ''
|
||||
const branding = el.branding ? `:branding="${el.branding}"` : ''
|
||||
return `<${tag} ${vModel} ${placeholder} ${height} ${branding}></${tag}>`
|
||||
}
|
||||
}
|
||||
|
||||
function attrBuilder(el) {
|
||||
return {
|
||||
tag: el.__config__.tag,
|
||||
vModel: `v-model="${confGlobal.formModel}.${el.__vModel__}"`,
|
||||
clearable: el.clearable ? 'clearable' : '',
|
||||
placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '',
|
||||
width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '',
|
||||
disabled: el.disabled ? ':disabled=\'true\'' : ''
|
||||
}
|
||||
}
|
||||
|
||||
// el-buttin 子级
|
||||
function buildElButtonChild(scheme) {
|
||||
const children = []
|
||||
const slot = scheme.__slot__ || {}
|
||||
if (slot.default) {
|
||||
children.push(slot.default)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
|
||||
// el-input 子级
|
||||
function buildElInputChild(scheme) {
|
||||
const children = []
|
||||
const slot = scheme.__slot__
|
||||
if (slot && slot.prepend) {
|
||||
children.push(`<template slot="prepend">${slot.prepend}</template>`)
|
||||
}
|
||||
if (slot && slot.append) {
|
||||
children.push(`<template slot="append">${slot.append}</template>`)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
|
||||
// el-select 子级
|
||||
function buildElSelectChild(scheme) {
|
||||
const children = []
|
||||
const slot = scheme.__slot__
|
||||
if (slot && slot.options && slot.options.length) {
|
||||
children.push(`<el-option v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
|
||||
// el-radio-group 子级
|
||||
function buildElRadioGroupChild(scheme) {
|
||||
const children = []
|
||||
const slot = scheme.__slot__
|
||||
const config = scheme.__config__
|
||||
if (slot && slot.options && slot.options.length) {
|
||||
const tag = config.optionType === 'button' ? 'el-radio-button' : 'el-radio'
|
||||
const border = config.border ? 'border' : ''
|
||||
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
|
||||
// el-checkbox-group 子级
|
||||
function buildElCheckboxGroupChild(scheme) {
|
||||
const children = []
|
||||
const slot = scheme.__slot__
|
||||
const config = scheme.__config__
|
||||
if (slot && slot.options && slot.options.length) {
|
||||
const tag = config.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
|
||||
const border = config.border ? 'border' : ''
|
||||
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
|
||||
// el-upload 子级
|
||||
function buildElUploadChild(scheme) {
|
||||
const list = []
|
||||
const config = scheme.__config__
|
||||
if (scheme['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} 的${scheme.accept}文件</div>`)
|
||||
return list.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装html代码。【入口函数】
|
||||
* @param {Object} formConfig 整个表单配置
|
||||
* @param {String} type 生成类型,文件或弹窗等
|
||||
*/
|
||||
export function makeUpHtml(formConfig, type) {
|
||||
const htmlList = []
|
||||
confGlobal = formConfig
|
||||
// 判断布局是否都沾满了24个栅格,以备后续简化代码结构
|
||||
someSpanIsNot24 = formConfig.fields.some(item => item.__config__.span !== 24)
|
||||
// 遍历渲染每个组件成html
|
||||
formConfig.fields.forEach(el => {
|
||||
htmlList.push(layouts[el.__config__.layout](el))
|
||||
})
|
||||
const htmlStr = htmlList.join('\n')
|
||||
// 将组件代码放进form标签
|
||||
let temp = buildFormTemplate(formConfig, htmlStr, type)
|
||||
// dialog标签包裹代码
|
||||
if (type === 'dialog') {
|
||||
temp = dialogWrapper(temp)
|
||||
}
|
||||
confGlobal = null
|
||||
return temp
|
||||
}
|
271
yudao-ui-admin/src/components/generator/js.js
Normal file
271
yudao-ui-admin/src/components/generator/js.js
Normal file
@@ -0,0 +1,271 @@
|
||||
import { isArray } from 'util'
|
||||
import { exportDefault, titleCase, deepClone } from '@/utils/index'
|
||||
import ruleTrigger from './ruleTrigger'
|
||||
|
||||
const units = {
|
||||
KB: '1024',
|
||||
MB: '1024 / 1024',
|
||||
GB: '1024 / 1024 / 1024'
|
||||
}
|
||||
let confGlobal
|
||||
const inheritAttrs = {
|
||||
file: '',
|
||||
dialog: 'inheritAttrs: false,'
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装js 【入口函数】
|
||||
* @param {Object} formConfig 整个表单配置
|
||||
* @param {String} type 生成类型,文件或弹窗等
|
||||
*/
|
||||
export function makeUpJs(formConfig, type) {
|
||||
confGlobal = formConfig = deepClone(formConfig)
|
||||
const dataList = []
|
||||
const ruleList = []
|
||||
const optionsList = []
|
||||
const propsList = []
|
||||
const methodList = mixinMethod(type)
|
||||
const uploadVarList = []
|
||||
const created = []
|
||||
|
||||
formConfig.fields.forEach(el => {
|
||||
buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
|
||||
})
|
||||
|
||||
const script = buildexport(
|
||||
formConfig,
|
||||
type,
|
||||
dataList.join('\n'),
|
||||
ruleList.join('\n'),
|
||||
optionsList.join('\n'),
|
||||
uploadVarList.join('\n'),
|
||||
propsList.join('\n'),
|
||||
methodList.join('\n'),
|
||||
created.join('\n')
|
||||
)
|
||||
confGlobal = null
|
||||
return script
|
||||
}
|
||||
|
||||
// 构建组件属性
|
||||
function buildAttributes(scheme, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created) {
|
||||
const config = scheme.__config__
|
||||
const slot = scheme.__slot__
|
||||
buildData(scheme, dataList)
|
||||
buildRules(scheme, ruleList)
|
||||
|
||||
// 特殊处理options属性
|
||||
if (scheme.options || (slot && slot.options && slot.options.length)) {
|
||||
buildOptions(scheme, optionsList)
|
||||
if (config.dataType === 'dynamic') {
|
||||
const model = `${scheme.__vModel__}Options`
|
||||
const options = titleCase(model)
|
||||
const methodName = `get${options}`
|
||||
buildOptionMethod(methodName, model, methodList, scheme)
|
||||
callInCreated(methodName, created)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理props
|
||||
if (scheme.props && scheme.props.props) {
|
||||
buildProps(scheme, propsList)
|
||||
}
|
||||
|
||||
// 处理el-upload的action
|
||||
if (scheme.action && config.tag === 'el-upload') {
|
||||
uploadVarList.push(
|
||||
`${scheme.__vModel__}Action: '${scheme.action}',
|
||||
${scheme.__vModel__}fileList: [],`
|
||||
)
|
||||
methodList.push(buildBeforeUpload(scheme))
|
||||
// 非自动上传时,生成手动上传的函数
|
||||
if (!scheme['auto-upload']) {
|
||||
methodList.push(buildSubmitUpload(scheme))
|
||||
}
|
||||
}
|
||||
|
||||
// 构建子级组件属性
|
||||
if (config.children) {
|
||||
config.children.forEach(item => {
|
||||
buildAttributes(item, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 在Created调用函数
|
||||
function callInCreated(methodName, created) {
|
||||
created.push(`this.${methodName}()`)
|
||||
}
|
||||
|
||||
// 混入处理函数
|
||||
function mixinMethod(type) {
|
||||
const list = []; const
|
||||
minxins = {
|
||||
file: confGlobal.formBtns ? {
|
||||
submitForm: `submitForm() {
|
||||
this.$refs['${confGlobal.formRef}'].validate(valid => {
|
||||
if(!valid) return
|
||||
// TODO 提交表单
|
||||
})
|
||||
},`,
|
||||
resetForm: `resetForm() {
|
||||
this.$refs['${confGlobal.formRef}'].resetFields()
|
||||
},`
|
||||
} : null,
|
||||
dialog: {
|
||||
onOpen: 'onOpen() {},',
|
||||
onClose: `onClose() {
|
||||
this.$refs['${confGlobal.formRef}'].resetFields()
|
||||
},`,
|
||||
close: `close() {
|
||||
this.$emit('update:visible', false)
|
||||
},`,
|
||||
handelConfirm: `handelConfirm() {
|
||||
this.$refs['${confGlobal.formRef}'].validate(valid => {
|
||||
if(!valid) return
|
||||
this.close()
|
||||
})
|
||||
},`
|
||||
}
|
||||
}
|
||||
|
||||
const methods = minxins[type]
|
||||
if (methods) {
|
||||
Object.keys(methods).forEach(key => {
|
||||
list.push(methods[key])
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
// 构建data
|
||||
function buildData(scheme, dataList) {
|
||||
const config = scheme.__config__
|
||||
if (scheme.__vModel__ === undefined) return
|
||||
const defaultValue = JSON.stringify(config.defaultValue)
|
||||
dataList.push(`${scheme.__vModel__}: ${defaultValue},`)
|
||||
}
|
||||
|
||||
// 构建校验规则
|
||||
function buildRules(scheme, ruleList) {
|
||||
const config = scheme.__config__
|
||||
if (scheme.__vModel__ === undefined) return
|
||||
const rules = []
|
||||
if (ruleTrigger[config.tag]) {
|
||||
if (config.required) {
|
||||
const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
|
||||
let message = isArray(config.defaultValue) ? `请至少选择一个${config.label}` : scheme.placeholder
|
||||
if (message === undefined) message = `${config.label}不能为空`
|
||||
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
|
||||
}
|
||||
if (config.regList && isArray(config.regList)) {
|
||||
config.regList.forEach(item => {
|
||||
if (item.pattern) {
|
||||
rules.push(
|
||||
`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${ruleTrigger[config.tag]}' }`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
ruleList.push(`${scheme.__vModel__}: [${rules.join(',')}],`)
|
||||
}
|
||||
}
|
||||
|
||||
// 构建options
|
||||
function buildOptions(scheme, optionsList) {
|
||||
if (scheme.__vModel__ === undefined) return
|
||||
// el-cascader直接有options属性,其他组件都是定义在slot中,所以有两处判断
|
||||
let { options } = scheme
|
||||
if (!options) options = scheme.__slot__.options
|
||||
if (scheme.__config__.dataType === 'dynamic') { options = [] }
|
||||
const str = `${scheme.__vModel__}Options: ${JSON.stringify(options)},`
|
||||
optionsList.push(str)
|
||||
}
|
||||
|
||||
function buildProps(scheme, propsList) {
|
||||
const str = `${scheme.__vModel__}Props: ${JSON.stringify(scheme.props.props)},`
|
||||
propsList.push(str)
|
||||
}
|
||||
|
||||
// el-upload的BeforeUpload
|
||||
function buildBeforeUpload(scheme) {
|
||||
const config = scheme.__config__
|
||||
const unitNum = units[config.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const
|
||||
returnList = []
|
||||
if (config.fileSize) {
|
||||
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${config.fileSize}
|
||||
if(!isRightSize){
|
||||
this.$message.error('文件大小超过 ${config.fileSize}${config.sizeUnit}')
|
||||
}`
|
||||
returnList.push('isRightSize')
|
||||
}
|
||||
if (scheme.accept) {
|
||||
acceptCode = `let isAccept = new RegExp('${scheme.accept}').test(file.type)
|
||||
if(!isAccept){
|
||||
this.$message.error('应该选择${scheme.accept}类型的文件')
|
||||
}`
|
||||
returnList.push('isAccept')
|
||||
}
|
||||
const str = `${scheme.__vModel__}BeforeUpload(file) {
|
||||
${rightSizeCode}
|
||||
${acceptCode}
|
||||
return ${returnList.join('&&')}
|
||||
},`
|
||||
return returnList.length ? str : ''
|
||||
}
|
||||
|
||||
// el-upload的submit
|
||||
function buildSubmitUpload(scheme) {
|
||||
const str = `submitUpload() {
|
||||
this.$refs['${scheme.__vModel__}'].submit()
|
||||
},`
|
||||
return str
|
||||
}
|
||||
|
||||
function buildOptionMethod(methodName, model, methodList, scheme) {
|
||||
const config = scheme.__config__
|
||||
const str = `${methodName}() {
|
||||
// 注意:this.$axios是通过Vue.prototype.$axios = axios挂载产生的
|
||||
this.$axios({
|
||||
method: '${config.method}',
|
||||
url: '${config.url}'
|
||||
}).then(resp => {
|
||||
var { data } = resp
|
||||
this.${model} = data.${config.dataPath}
|
||||
})
|
||||
},`
|
||||
methodList.push(str)
|
||||
}
|
||||
|
||||
// js整体拼接
|
||||
function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods, created) {
|
||||
const str = `${exportDefault}{
|
||||
${inheritAttrs[type]}
|
||||
components: {},
|
||||
props: [],
|
||||
data () {
|
||||
return {
|
||||
${conf.formModel}: {
|
||||
${data}
|
||||
},
|
||||
${conf.formRules}: {
|
||||
${rules}
|
||||
},
|
||||
${uploadVar}
|
||||
${selectOptions}
|
||||
${props}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
created () {
|
||||
${created}
|
||||
},
|
||||
mounted () {},
|
||||
methods: {
|
||||
${methods}
|
||||
}
|
||||
}`
|
||||
return str
|
||||
}
|
16
yudao-ui-admin/src/components/generator/ruleTrigger.js
Normal file
16
yudao-ui-admin/src/components/generator/ruleTrigger.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 用于生成表单校验,指定正则规则的触发方式。
|
||||
* 未在此处声明无触发方式的组件将不生成rule!!
|
||||
*/
|
||||
export default {
|
||||
'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',
|
||||
tinymce: 'blur'
|
||||
}
|
191
yudao-ui-admin/src/components/parser/Parser.vue
Normal file
191
yudao-ui-admin/src/components/parser/Parser.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<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
|
||||
// 触发 submit 事件
|
||||
// update by 芋道源码
|
||||
// this.$emit('submit', this[this.formConf.formModel])
|
||||
this.$emit('submit', {
|
||||
conf: this.formConfCopy,
|
||||
values: this[this.formConf.formModel]
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
return renderFrom.call(this, h)
|
||||
}
|
||||
}
|
||||
</script>
|
17
yudao-ui-admin/src/components/parser/README.md
Normal file
17
yudao-ui-admin/src/components/parser/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## form-generator JSON 解析器
|
||||
>用于将form-generator导出的JSON解析成一个表单。
|
||||
|
||||
### 安装组件
|
||||
```
|
||||
npm i form-gen-parser
|
||||
```
|
||||
或者
|
||||
```
|
||||
yarn add form-gen-parser
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
> [查看在线示例](https://mrhj.gitee.io/form-generator/#/parser)
|
||||
|
||||
示例代码:
|
||||
> [src\components\parser\example\Index.vue](https://github.com/JakHuang/form-generator/blob/dev/src/components/parser/example/Index.vue)
|
324
yudao-ui-admin/src/components/parser/example/Index.vue
Normal file
324
yudao-ui-admin/src/components/parser/example/Index.vue
Normal file
@@ -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>
|
3
yudao-ui-admin/src/components/parser/index.js
Normal file
3
yudao-ui-admin/src/components/parser/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Parser from './Parser'
|
||||
|
||||
export default Parser
|
25
yudao-ui-admin/src/components/parser/package.json
Normal file
25
yudao-ui-admin/src/components/parser/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "form-gen-parser",
|
||||
"version": "1.0.3",
|
||||
"description": "表单json解析器",
|
||||
"main": "lib/form-gen-parser.umd.js",
|
||||
"directories": {
|
||||
"example": "example"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/JakHuang/form-generator.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-gen-render": "^1.0.0"
|
||||
},
|
||||
"author": "jakHuang",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/JakHuang/form-generator/issues"
|
||||
},
|
||||
"homepage": "https://github.com/JakHuang/form-generator/blob/dev/src/components/parser"
|
||||
}
|
19
yudao-ui-admin/src/components/render/package.json
Normal file
19
yudao-ui-admin/src/components/render/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "form-gen-render",
|
||||
"version": "1.0.4",
|
||||
"description": "表单核心render",
|
||||
"main": "lib/form-gen-render.umd.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/JakHuang/form-generator.git"
|
||||
},
|
||||
"author": "jakhuang",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/JakHuang/form-generator/issues"
|
||||
},
|
||||
"homepage": "https://github.com/JakHuang/form-generator#readme"
|
||||
}
|
122
yudao-ui-admin/src/components/render/render.js
Normal file
122
yudao-ui-admin/src/components/render/render.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { deepClone } from '@/utils/index'
|
||||
|
||||
const componentChild = {}
|
||||
/**
|
||||
* 将./slots中的文件挂载到对象componentChild上
|
||||
* 文件名为key,对应JSON配置中的__config__.tag
|
||||
* 文件内容为value,解析JSON配置中的__slot__
|
||||
*/
|
||||
const slotsFiles = require.context('./slots', false, /\.js$/)
|
||||
const keys = slotsFiles.keys() || []
|
||||
keys.forEach(key => {
|
||||
const tag = key.replace(/^\.\/(.*)\.\w+$/, '$1')
|
||||
const value = slotsFiles(key).default
|
||||
componentChild[tag] = value
|
||||
})
|
||||
|
||||
function vModel(dataObject, defaultValue) {
|
||||
dataObject.props.value = defaultValue
|
||||
|
||||
dataObject.on.input = val => {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
}
|
||||
|
||||
function mountSlotFiles(h, confClone, children) {
|
||||
const childObjs = componentChild[confClone.__config__.tag]
|
||||
if (childObjs) {
|
||||
Object.keys(childObjs).forEach(key => {
|
||||
const childFunc = childObjs[key]
|
||||
if (confClone.__slot__ && confClone.__slot__[key]) {
|
||||
children.push(childFunc(h, confClone, key))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function emitEvents(confClone) {
|
||||
['on', 'nativeOn'].forEach(attr => {
|
||||
const eventKeyList = Object.keys(confClone[attr] || {})
|
||||
eventKeyList.forEach(key => {
|
||||
const val = confClone[attr][key]
|
||||
if (typeof val === 'string') {
|
||||
confClone[attr][key] = event => this.$emit(val, event)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function buildDataObject(confClone, dataObject) {
|
||||
Object.keys(confClone).forEach(key => {
|
||||
const val = confClone[key]
|
||||
if (key === '__vModel__') {
|
||||
vModel.call(this, dataObject, confClone.__config__.defaultValue)
|
||||
} else if (dataObject[key] !== undefined) {
|
||||
if (dataObject[key] === null
|
||||
|| dataObject[key] instanceof RegExp
|
||||
|| ['boolean', 'string', 'number', 'function'].includes(typeof dataObject[key])) {
|
||||
dataObject[key] = val
|
||||
} else if (Array.isArray(dataObject[key])) {
|
||||
dataObject[key] = [...dataObject[key], ...val]
|
||||
} else {
|
||||
dataObject[key] = { ...dataObject[key], ...val }
|
||||
}
|
||||
} else {
|
||||
dataObject.attrs[key] = val
|
||||
}
|
||||
})
|
||||
|
||||
// 清理属性
|
||||
clearAttrs(dataObject)
|
||||
}
|
||||
|
||||
function clearAttrs(dataObject) {
|
||||
delete dataObject.attrs.__config__
|
||||
delete dataObject.attrs.__slot__
|
||||
delete dataObject.attrs.__methods__
|
||||
}
|
||||
|
||||
function makeDataObject() {
|
||||
// 深入数据对象:
|
||||
// https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1
|
||||
return {
|
||||
class: {},
|
||||
attrs: {},
|
||||
props: {},
|
||||
domProps: {},
|
||||
nativeOn: {},
|
||||
on: {},
|
||||
style: {},
|
||||
directives: [],
|
||||
scopedSlots: {},
|
||||
slot: null,
|
||||
key: null,
|
||||
ref: null,
|
||||
refInFor: true
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
conf: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
const dataObject = makeDataObject()
|
||||
const confClone = deepClone(this.conf)
|
||||
const children = this.$slots.default || []
|
||||
|
||||
// 如果slots文件夹存在与当前tag同名的文件,则执行文件中的代码
|
||||
mountSlotFiles.call(this, h, confClone, children)
|
||||
|
||||
// 将字符串类型的事件,发送为消息
|
||||
emitEvents.call(this, confClone)
|
||||
|
||||
// 将json表单配置转化为vue render可以识别的 “数据对象(dataObject)”
|
||||
buildDataObject.call(this, confClone, dataObject)
|
||||
|
||||
return h(this.conf.__config__.tag, dataObject, children)
|
||||
}
|
||||
}
|
5
yudao-ui-admin/src/components/render/slots/el-button.js
Normal file
5
yudao-ui-admin/src/components/render/slots/el-button.js
Normal file
@@ -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
|
||||
}
|
||||
}
|
8
yudao-ui-admin/src/components/render/slots/el-input.js
Normal file
8
yudao-ui-admin/src/components/render/slots/el-input.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
prepend(h, conf, key) {
|
||||
return <template slot="prepend">{conf.__slot__[key]}</template>
|
||||
},
|
||||
append(h, conf, key) {
|
||||
return <template slot="append">{conf.__slot__[key]}</template>
|
||||
}
|
||||
}
|
13
yudao-ui-admin/src/components/render/slots/el-radio-group.js
Normal file
13
yudao-ui-admin/src/components/render/slots/el-radio-group.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export default {
|
||||
options(h, conf, key) {
|
||||
const list = []
|
||||
conf.__slot__.options.forEach(item => {
|
||||
if (conf.__config__.optionType === 'button') {
|
||||
list.push(<el-radio-button label={item.value}>{item.label}</el-radio-button>)
|
||||
} else {
|
||||
list.push(<el-radio label={item.value} border={conf.border}>{item.label}</el-radio>)
|
||||
}
|
||||
})
|
||||
return list
|
||||
}
|
||||
}
|
9
yudao-ui-admin/src/components/render/slots/el-select.js
Normal file
9
yudao-ui-admin/src/components/render/slots/el-select.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
options(h, conf, key) {
|
||||
const list = []
|
||||
conf.__slot__.options.forEach(item => {
|
||||
list.push(<el-option label={item.label} value={item.value} disabled={item.disabled}></el-option>)
|
||||
})
|
||||
return list
|
||||
}
|
||||
}
|
17
yudao-ui-admin/src/components/render/slots/el-upload.js
Normal file
17
yudao-ui-admin/src/components/render/slots/el-upload.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
'list-type': (h, conf, key) => {
|
||||
const list = []
|
||||
const config = conf.__config__
|
||||
if (conf['list-type'] === 'picture-card') {
|
||||
list.push(<i class="el-icon-plus"></i>)
|
||||
} else {
|
||||
list.push(<el-button size="small" type="primary" icon="el-icon-upload">{config.buttonText}</el-button>)
|
||||
}
|
||||
if (config.showTip) {
|
||||
list.push(
|
||||
<div slot="tip" class="el-upload__tip">只能上传不超过 {config.fileSize}{config.sizeUnit} 的{conf.accept}文件</div>
|
||||
)
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
3
yudao-ui-admin/src/components/tinymce/README.md
Normal file
3
yudao-ui-admin/src/components/tinymce/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 简介
|
||||
富文本编辑器tinymce的一个vue版本封装。使用cdn动态脚本引入的方式加载。
|
||||
|
8
yudao-ui-admin/src/components/tinymce/config.js
Normal file
8
yudao-ui-admin/src/components/tinymce/config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable max-len */
|
||||
|
||||
export const plugins = [
|
||||
'advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount'
|
||||
]
|
||||
export const toolbar = [
|
||||
'code searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote removeformat subscript superscript codesample hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'
|
||||
]
|
38
yudao-ui-admin/src/components/tinymce/example/Index.vue
Normal file
38
yudao-ui-admin/src/components/tinymce/example/Index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<Tinymce v-model="defaultValue" :height="300" placeholder="在这里输入文字" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tinymce from '../index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Tinymce
|
||||
},
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultValue: '<p>配置文档参阅:http://tinymce.ax-z.cn</p>'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
3
yudao-ui-admin/src/components/tinymce/index.js
Normal file
3
yudao-ui-admin/src/components/tinymce/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Index from './index.vue'
|
||||
|
||||
export default Index
|
88
yudao-ui-admin/src/components/tinymce/index.vue
Normal file
88
yudao-ui-admin/src/components/tinymce/index.vue
Normal file
@@ -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>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user