初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

View File

@ -0,0 +1,129 @@
<template>
<FormItem :label="t('自动同意:')">
<a-tree-select
:value="props.autoAgreeRule"
style="width: 100%"
:tree-data="autoAgreeRuleOptions"
tree-checkable
allow-clear
:placeholder="t('请选择自动同意规则')"
@change="changeAutoAgreeRule"
/>
</FormItem>
<FormItem :label="t('无处理人:')">
<a-select
:value="props.noHandler"
style="width: 100%"
:options="noHandlerOptions"
@change="changeNoHandler"
/>
</FormItem>
<FormItem :label="t('指定审批人:')">
<a-select
:value="props.isPrevChooseNext"
style="width: 100%"
:options="designatedApproverOptions"
@change="changeDesignatedApprover"
/>
</FormItem>
<FormItem
:tip="
t(
'临时审批人是指由上一节点审批人指定下一节点审批人过程中,是否允许在审批人基础上添加组织架构人员。',
)
"
:label="t('临时审批人:')"
v-if="props.provisionalApprover == true || props.provisionalApprover == false"
>
<a-switch :checked="props.provisionalApprover" @change="changeProvisionalApprover" />
</FormItem>
</template>
<script setup lang="ts" name="ProcessBasic">
import FormItem from '/@bpmn/layout/FormItem.vue';
import { AutoAgreeRule, DesignatedApprover, NoHandler } from '/@/enums/workflowEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits([
'update:autoAgreeRule',
'update:noHandler',
'update:isPrevChooseNext',
'update:provisionalApprover',
]);
const props = defineProps({
autoAgreeRule: Array,
noHandler: Number || String || Boolean,
isPrevChooseNext: Number || String || Boolean,
provisionalApprover: {
type: Boolean || undefined,
default: undefined,
},
});
// 自动同意规则
const autoAgreeRuleOptions = [
{
value: AutoAgreeRule.ORIGINATOR,
label: t('候选审批人包含流程任务发起人'),
},
{
value: AutoAgreeRule.PREVIOUS_NODE,
label: t('候选审批人包含上一节点审批人'),
},
{
value: AutoAgreeRule.APPROVED,
label: t('候选审批人在此流程中审批过'),
},
];
function changeAutoAgreeRule(val: Array<AutoAgreeRule>) {
let autoAgreeRule = val;
if (val.length > 0) {
// 选择了自动同意规则后,无处理人 只能由 超级管理员处理 且 指定审批人 只能 不指定审批人
emits('update:noHandler', NoHandler.ADMIN);
emits('update:isPrevChooseNext', DesignatedApprover.NOT_SPECIFIED);
}
emits('update:autoAgreeRule', autoAgreeRule);
}
// 无处理人
const noHandlerOptions = [
{
value: NoHandler.ADMIN,
label: t('由超级管理员处理'),
},
{
value: NoHandler.PREVIOUS_NODE,
label: t('由上一节点审批人指定审批人'),
},
];
function changeNoHandler(val: NoHandler) {
emits('update:noHandler', val);
if (val == NoHandler.PREVIOUS_NODE) {
// 无处理人 选择了 由上一节点审批人指定审批人;那么自动同意规则必须为空
emits('update:autoAgreeRule', []);
}
}
// 指定审批人
const designatedApproverOptions = [
{
value: DesignatedApprover.NOT_SPECIFIED,
label: t('不指定审批人'),
},
{
value: DesignatedApprover.PREVIOUS_NODE,
label: t('由上一节点审批人指定'),
},
];
function changeDesignatedApprover(val: DesignatedApprover) {
emits('update:isPrevChooseNext', val);
if (val == DesignatedApprover.PREVIOUS_NODE) {
// 指定审批人 选择了 由上一节点审批人指定;那么自动同意规则必须为空
emits('update:autoAgreeRule', []);
}
}
// 临时审批人
function changeProvisionalApprover(val: Boolean) {
emits('update:provisionalApprover', val);
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,298 @@
<template>
<div v-if="showPanel">
<NodeHead class="mb-3" :nodeName="nodeName" />
<a-tabs>
<a-tab-pane key="1" :tab="t('基础配置')">
<FormItem :required="formInfo.type !== 'bpmn:SequenceFlow'" :label="t('节点名称:')">
<a-input
v-model:value="formInfo.name"
:placeholder="t('请填写节点名称')"
@change="updateElementName"
/>
</FormItem>
<slot name="basic"></slot>
<FormItem :label="t('节点备注:')">
<a-textarea v-model:value="formInfo.remark" :rows="8" />
</FormItem>
<slot name="noticePolicy"></slot>
</a-tab-pane>
<slot></slot>
<a-tab-pane key="99" :tab="t('事件监听')">
<a-tabs>
<a-tab-pane key="1" :tab="t('开始事件')">
<div class="process-top">
<a-button type="primary" @click="addStartEvent">
{{ t('添加开始事件') }}
</a-button>
</div>
<a-table
:columns="columns"
:data-source="formInfo.startEventConfigs"
:pagination="false"
>
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.START, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteStartEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="2" :tab="t('结束事件')">
<div class="process-top">
<a-button type="primary" @click="addEndEvent"> {{ t('添加结束事件') }} </a-button>
</div>
<a-table :columns="columns" :data-source="formInfo.endEventConfigs" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.END, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteEndEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-tab-pane>
</a-tabs>
<!-- <ApiConfig
v-if="apiConfigDialog"
:title="t('API配置')"
v-model:apiConfigDialog="apiConfigDialog"
v-model:apiConfig="
formInfo[eventType === NodeEventType.START ? 'startEventConfigs' : 'endEventConfigs'][
configIndex
].apiConfig
"
/> -->
</div>
</template>
<script setup lang="ts">
import useStateFormInfo from '/@bpmn/hooks/useStateFormInfo';
import { NodeHead } from '/@/components/ModalPanel/index';
import FormItem from '/@bpmn/layout/FormItem.vue';
import { inject, nextTick, onMounted, ref, watch } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { DeleteTwoTone } from '@ant-design/icons-vue';
import { getLiteflowList } from '/@/api/liteflow';
// import { ApiConfig } from '/@/components/ApiConfig';
import ScriptApiSelect from '/@bpmn/components/arguments/ScriptApiSelect.vue';
import Sortable from 'sortablejs';
import { NodeEventExType } from '/@/enums/workflowEnum';
import { NodeEventConfig } from '/@/model/workflow/workflowConfig';
const { t } = useI18n();
const { showPanel, formInfo, nodeName } = useStateFormInfo();
const updateElementName = inject('updateElementName') as any;
const liteFlowOptions = ref();
const columns = ref([
{
dataIndex: 'sort',
key: 'sort',
},
{
title: t('操作类别'),
dataIndex: 'type',
key: 'type',
width: '35%',
align: 'center',
},
{
title: t('操作配置'),
dataIndex: 'operateConfig',
key: 'operateConfig',
width: '50%',
align: 'center',
},
{
title: t('操作'),
dataIndex: 'action',
key: 'action',
width: '25%',
align: 'center',
},
]);
onMounted(() => {
getList();
});
const addStartEvent = () => {
formInfo.value.startEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
const addEndEvent = () => {
formInfo.value.endEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
watch(
() => formInfo.value,
() => {
if (
formInfo.value.endEventConfigs?.length > 0 ||
formInfo.value.startEventConfigs?.length > 0
) {
nextTick(() => {
const tbody: any = document.querySelector('.ant-table-tbody');
Sortable.create(tbody, {
handle: '.draggable-icon',
});
});
}
},
{ deep: true },
);
const deleteStartEvent = (index) => {
formInfo.value.startEventConfigs.splice(index, 1);
};
const deleteEndEvent = (index) => {
formInfo.value.endEventConfigs.splice(index, 1);
};
const getList = async () => {
liteFlowOptions.value = (await getLiteflowList()) || [];
};
</script>
<style lang="less" scoped>
.node-box {
position: absolute;
top: 0;
right: 0;
box-shadow: -7px -1px 7px #dadcde;
padding: 20px 30px 20px 20px;
height: 100%;
width: 410px;
.node-title {
line-height: 20px;
margin-bottom: 10px;
padding-left: 6px;
border-left: 6px solid #5e95ff;
}
:deep(.ant-form-item) {
margin-bottom: 10px;
}
.process-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.process-title {
line-height: 18px;
padding-left: 6px;
border-left: 6px solid #5e95ff;
}
}
:deep(.ant-select) {
width: 100%;
}
}
:deep(.ant-table-cell) {
padding: 10px !important;
}
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentcolor;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<div class="overflow-hidden">
<NodeHead class="header-title" :node-name="t('流程分类')" />
<BasicTree
:clickRowToExpand="true"
:treeData="treeData"
:fieldNames="{ key: 'id', title: 'name' }"
@select="handleSelect"
/>
</div>
</template>
<script setup lang="ts">
import { getDicDetailList } from '/@/api/system/dic';
import { TreeItem } from '/@/components/Tree';
import { BasicTree } from '/@/components/Tree';
import { NodeHead } from '/@/components/ModalPanel/index';
import { onMounted, ref } from 'vue';
import { FlowCategory } from '/@/enums/workflowEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['select']);
const treeData = ref<TreeItem[]>([]);
async function getList() {
treeData.value = (await getDicDetailList({
itemId: FlowCategory.ID,
})) as unknown as TreeItem[];
}
function handleSelect(keys: string) {
emits('select', keys[0]);
}
onMounted(() => {
getList();
});
</script>
<style scoped>
.header-title {
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div :class="hasSlot ? 'rule-box' : ''">
<slot></slot>
<template v-if="hasSlot">
<span v-if="!value" class="rule-color">{{ placeholder }}</span>
</template>
<template v-else>
<Input class="rule-box" :value="value" :placeholder="placeholder" readonly>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" class="rule-color" />
</template>
</Input>
</template>
<Icon icon="ant-design:ellipsis-outlined" class="rule-color" v-if="hasSlot" />
</div>
</template>
<script setup lang="ts">
import { computed, useSlots } from 'vue';
import Icon from '/@/components/Icon/index';
import { Input } from 'ant-design-vue';
defineProps({
value: [String, Number],
placeholder: String,
});
const hasSlot = computed(() => {
return !!useSlots().default;
});
</script>
<style lang="less" scoped>
.rule-box {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 30px;
border: 1px solid #d9d9d9;
padding: 0 4px;
.rule-color {
color: #d0cfd0;
}
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<a-checkbox-group :value="props.modelValue" style="width: 100%" @change="change">
<a-checkbox v-for="(item, index) in options" :key="index" :value="item.value">{{
item.label
}}</a-checkbox>
</a-checkbox-group>
</template>
<script setup lang="ts">
import { NoticePolicyType } from '/@/enums/workflowEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emit = defineEmits(['update:modelValue']);
const props = defineProps(['modelValue']);
const options = [
{ label: t('系统消息'), value: NoticePolicyType.SYSTEM_MESSAGES },
{ label: t('短信'), value: NoticePolicyType.SHORT_MESSAGE },
{ label: t('企业微信'), value: NoticePolicyType.ENTERPRISE_WECHAT },
{ label: t('钉钉'), value: NoticePolicyType.DING_TALK },
{ label: t('邮箱'), value: NoticePolicyType.MAILBOX },
];
function change(value) {
emit('update:modelValue', value);
}
</script>
<style scoped></style>

View File

@ -0,0 +1,38 @@
<template>
<div class="overflow-hidden">
<NodeHead class="header-title" node-name="组织架构" />
<BasicTree
title=""
:treeData="treeData"
:fieldNames="{ key: 'id', title: 'name' }"
@select="handleSelect"
/>
</div>
</template>
<script setup lang="ts">
import { getDepartmentTree } from '/@/api/system/department';
import { TreeItem } from '/@/components/Tree';
import { BasicTree } from '/@/components/Tree';
import { NodeHead } from '/@/components/ModalPanel/index';
import { onMounted, ref } from 'vue';
const emits = defineEmits(['select']);
const treeData = ref<TreeItem[]>([]);
async function getList() {
treeData.value = (await getDepartmentTree()) as unknown as TreeItem[];
}
function handleSelect(keys: string) {
emits('select', keys[0]);
}
onMounted(() => {
getList();
});
</script>
<style scoped>
.header-title {
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
}
</style>

View File

@ -0,0 +1,217 @@
<template>
<div>
<a-button type="primary" class="bpmn-button" @click="show">{{ t('更新表单') }}</a-button>
<a-modal
v-model:visible="visible"
:title="t('更新表单配置')"
:forceRender="false"
:closable="false"
:width="1000"
>
<div class="box">
<NodeHead class="box-head" :node-name="t('更新表单配置')" />
<a-table :columns="columns" :data-source="formData.list" :pagination="false">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-spin v-if="!record.status" />
<span v-else>{{ t('成功') }}</span>
</template>
<template v-if="column.key === 'formType'">
<span v-if="record.formType === 1">{{ t('自定义表单') }}</span>
<span v-else>{{ t('系统表单') }}</span>
</template>
</template>
</a-table>
</div>
<template #footer>
<a-button key="back" @click="submit">{{ t('取消') }}</a-button>
<a-button key="submit" type="primary" :loading="loading" @click="submit">{{
t('确定')
}}</a-button>
</template>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import { getFormConfig, compareFormInfo } from '/@bpmn/config/formPermission';
import { BpmnNodeKey } from '/@/enums/workflowEnum';
import { NodeHead } from '/@/components/ModalPanel/index';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import { setProperties } from '/@bpmn/config/property';
import {
FormConfigItem,
FormSettingItem,
UpdateFormConfigItem,
} from '/@/model/workflow/formSetting';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const visible = ref(false);
const formData: {
list: Array<UpdateFormConfigItem>;
indexMap: Map<string, number>;
} = reactive({
okDisabled: true,
cancelDisabled: true,
list: [],
indexMap: new Map(),
});
const columns = [
{
title: t('序号'),
dataIndex: 'index',
key: 'index',
customRender: (column) => {
return column.index + 1;
},
},
{
title: t('表单名称'),
dataIndex: 'formName',
key: 'formName',
},
{
title: t('表单ID'),
dataIndex: 'formId',
key: 'formId',
},
{
title: t('表单类别'),
key: 'formType',
dataIndex: 'formType',
},
{
title: t('所属节点'),
key: 'nodeName',
dataIndex: 'nodeName',
},
{
title: t('更新状态'),
key: 'status',
},
{
title: t('备注'),
key: 'remark',
dataIndex: 'remark',
},
];
const loading = computed(() => {
if (
formData.list.filter((ele) => {
return ele.status;
}).length == 0
) {
return true;
} else {
return false;
}
});
function show() {
visible.value = true;
updateFormData();
}
function submit() {
formData.list = [];
formData.indexMap = new Map();
visible.value = false;
}
async function updateFormData() {
let formInfoData = new Map(); // formId 表单数据
const store = useBpmnStore();
const { info, defaultFormList, setDefaultFormList } = store;
//更新默认表单
if (defaultFormList.length > 0) {
let newDefaultFormList: Array<FormSettingItem> = [];
for (let index = 0; index < defaultFormList.length; index++) {
const element = defaultFormList[index];
let item = {
key: element.key,
formId: element.formId,
formName: element.formName,
formType: element.formType,
};
try {
let result = await getFormConfig(item);
newDefaultFormList.push(result);
} catch (error) {}
}
setDefaultFormList(newDefaultFormList);
}
for (const item of info.values()) {
const showNodeTypes = [BpmnNodeKey.START, BpmnNodeKey.USER];
if (showNodeTypes.includes(item.type)) {
if (item.formConfigs) {
item.formConfigs.forEach((ele, index) => {
formInfoData.set(ele.formId, {
key: ele.key,
formType: ele.formType,
formId: ele.formId,
formName: ele.formName,
});
let formKey = item.id + ele.formId + index;
let formKeyIndx = formData.list.length + 1;
formData.indexMap.set(formKey, formKeyIndx);
formData.list[formKeyIndx] = {
formKey,
formName: ele.formName,
formId: ele.formId,
formType: ele.formType,
nodeName: item.name,
status: false,
remark: '',
};
});
}
}
}
for (let [formId, value] of formInfoData) {
let item = {
formId: formId,
key: value.key,
formName: value.formName,
formType: value.formType,
};
try {
let result = await getFormConfig(item);
formInfoData.set(formId, result);
} catch (error) {}
}
for (const item of info.values()) {
const showNodeTypes = [BpmnNodeKey.START, BpmnNodeKey.USER];
let properties = item;
if (showNodeTypes.includes(item.type)) {
let formConfigs: Array<FormConfigItem> = [];
if (item.formConfigs) {
item.formConfigs.forEach((ele, index) => {
let formKeyIndx = formData.indexMap.get(item.id + ele.formId + index);
if (formInfoData.has(ele.formId)) {
let formConfig: FormConfigItem = compareFormInfo(ele, formInfoData.get(ele.formId));
formConfigs.push(formConfig);
if (formKeyIndx) formData.list[formKeyIndx].status = true;
} else {
if (formKeyIndx) formData.list[formKeyIndx].status = false;
}
});
}
if (formConfigs.length > 0) {
properties.formConfigs = formConfigs;
}
setProperties(item.id, properties); // setInfo 合并新数据与旧数据
}
}
}
</script>
<style scoped>
.box {
padding: 10px 20px;
}
.box-head {
margin: 10px 0;
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<SelectApiConfig
v-model="data.config"
:paramTree="ProcessDataVariable"
:exampleStr="props.exampleStr"
@update:modelValue="submit"
>
<InputModel
:value="data.config.name"
:placeholder="t('配置API')"
style="width: 100%; min-width: 100px"
/>
</SelectApiConfig>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { SelectApiConfig, InputModel } from '/@/components/ApiConfig';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
import { ApiConfig } from '/@/components/ApiConfig/src/interface';
import { ProcessDataVariable } from '/@bpmn/config/rules';
const { t } = useI18n();
const emit = defineEmits(['update:modelValue']);
let props = withDefaults(defineProps<{ modelValue: ApiConfig; exampleStr?: string }>(), {
modelValue: () => {
return {
id: '',
name: '',
method: '',
requestParamsConfigs: [], //Query Params
requestHeaderConfigs: [], //Header
requestBodyConfigs: [], //Body
};
},
exampleStr: `{
code: 0,
msg: 'success',
data: 'value',
}`,
});
let data: {
config: ApiConfig;
} = reactive({
config: props.modelValue,
});
function submit() {
emit('update:modelValue', cloneDeep(data.config));
}
</script>
<style lang="less" scoped>
.title {
border-bottom: 1px solid #f0f0f0;
margin-bottom: 10px;
}
.padding {
padding: 10px 20px;
}
.btn-item {
position: absolute;
bottom: 10px;
}
.editor-box {
width: 300px;
position: absolute;
height: 350px;
left: 140px;
bottom: 13px;
border: 1px solid #ccc;
box-shadow: 0 0 6px 3px #ccc;
.editor-close {
position: absolute;
top: -3px;
right: 13px;
font-size: 16px;
cursor: pointer;
}
.editor-copy {
position: absolute;
bottom: 5px;
right: 15px;
cursor: pointer;
z-index: 999;
color: #5e95ff;
}
}
</style>

View File

@ -0,0 +1,245 @@
<template>
<div @click.stop="open">
<InputModel
:value="data.config.name"
:placeholder="t('配置API')"
style="width: 100%; min-width: 100px"
/>
<ModalPanel
:visible="visible"
:width="1100"
:title="t('API配置')"
@submit="submit"
@close="close"
>
<NodeHead :nodeName="t('API信息')" class="title" />
<SelectInterfaceAddress :config="data.config" @set-api-config="setApiConfig" />
<ScriptInputParams
class="padding"
v-if="visible"
:apiParams="data.apiParams"
:paramTree="data.paramTree"
:need-hide-components="needHideComponents"
/>
<a-button
type="primary"
class="btn-item"
@click.stop="data.isShowExample = false"
@mouseenter="data.isShowExample = true"
>{{ t('返回出参示例') }}</a-button
>
<div class="editor-box" v-if="data.isShowExample">
<CodeEditor
v-if="data.isShowExample"
:value="exampleStr"
language="json"
readonly
style="font-size: 12px"
/>
<span class="editor-close" @click.stop="data.isShowExample = false"> x </span>
<span class="editor-copy" @click.stop="copy">{{ t('复制代码') }}</span>
</div>
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { NodeHead, ModalPanel } from '/@/components/ModalPanel/index';
import { SelectInterfaceAddress, InputModel } from '/@/components/ApiConfig';
import { CodeEditor } from '/@/components/CodeEditor';
import ScriptInputParams from './ScriptInputParams.vue';
import { TreeProps } from 'ant-design-vue';
import { cloneDeep } from 'lodash-es';
import { InterfaceListInfo } from '/@/api/system/interface/model';
import useClipboard from 'vue-clipboard3';
import { useI18n } from '/@/hooks/web/useI18n';
import { ApiConfig, ApiParams } from '/@/components/ApiConfig/src/interface';
const { t } = useI18n();
const emit = defineEmits(['update:modelValue']);
let props = withDefaults(
defineProps<{
paramTree?: TreeProps['treeData'];
modelValue: ApiConfig;
exampleStr?: string;
needHideComponents?: Boolean;
}>(),
{
modelValue: () => {
return {
id: '',
name: '',
method: '',
requestParamsConfigs: [], //Query Params
requestHeaderConfigs: [], //Header
requestBodyConfigs: [], //Body
};
},
exampleStr: `{
code: 0,
msg: 'success',
data: 'value',
}`,
needHideComponents: () => {
return false;
},
},
);
const { toClipboard } = useClipboard();
let data: {
isShowExample: boolean;
config: ApiConfig;
paramTree: TreeProps['treeData'];
apiParams: Array<ApiParams>;
} = reactive({
isShowExample: false,
config: props.modelValue,
paramTree: [],
apiParams: [
{
key: '1',
title: 'Query Params',
tableInfo: [],
},
{
key: '2',
title: 'Header',
tableInfo: [],
},
{
key: '3',
title: 'Body',
tableInfo: [],
},
],
});
const visible = ref<boolean>(false);
function open() {
data.config = cloneDeep(props.modelValue);
if (props.paramTree) {
data.paramTree = props.paramTree;
}
if (data.config.requestParamsConfigs)
data.apiParams[0].tableInfo = data.config.requestParamsConfigs;
if (data.config.requestHeaderConfigs)
data.apiParams[1].tableInfo = data.config.requestHeaderConfigs;
if (data.config.requestBodyConfigs)
data.apiParams[2].tableInfo = data.config.requestBodyConfigs;
visible.value = true;
}
function close() {
visible.value = false;
}
async function copy() {
try {
await toClipboard(props.exampleStr);
} catch (e) {
console.error(e);
}
}
function submit() {
emit('update:modelValue', cloneDeep(data.config));
close();
}
function setApiConfig(config: InterfaceListInfo) {
if (data.config.id !== config.id) {
data.config.id = config.id;
data.config.name = config.name;
data.config.method = config.method;
data.config.path = config.path;
data.config.requestParamsConfigs = [];
data.config.requestHeaderConfigs = [];
data.config.requestBodyConfigs = [];
}
if (data.config.requestParamsConfigs.length <= 0) {
if (config.parameters) {
config.parameters.forEach((element) => {
data.config.requestParamsConfigs.push({
name: element.name, //API入参名称
dataType: element.dataType, //API入参类型
assignmentType: 'value', //赋值类型
value: element.value, //值
config: '', //赋值配置
});
});
}
data.apiParams[0].tableInfo = data.config.requestParamsConfigs;
}
if (data.config.requestHeaderConfigs.length <= 0) {
if (config.headers) {
config.headers.forEach((element) => {
data.config.requestHeaderConfigs.push({
name: element.name, //API入参名称
dataType: element.dataType, //API入参类型
assignmentType: 'value', //赋值类型
value: element.value, //值
config: '', //赋值配置
});
});
}
data.apiParams[1].tableInfo = data.config.requestHeaderConfigs;
}
if (data.config.requestBodyConfigs.length <= 0) {
if (config.requestBodyDefinition && config.requestBodyDefinition.children) {
config.requestBodyDefinition.children.forEach((element) => {
data.config.requestBodyConfigs.push({
name: element.name, //API入参名称
dataType: element.dataType, //API入参类型
assignmentType: 'value', //赋值类型
value: element.value, //值
config: '', //赋值配置
});
});
}
data.apiParams[2].tableInfo = data.config.requestBodyConfigs;
}
}
</script>
<style lang="less" scoped>
.title {
border-bottom: 1px solid #f0f0f0;
margin-bottom: 10px;
}
.padding {
padding: 10px 20px;
}
.btn-item {
position: absolute;
bottom: 10px;
}
.editor-box {
width: 300px;
position: absolute;
height: 350px;
left: 140px;
bottom: 13px;
border: 1px solid #ccc;
box-shadow: 0 0 6px 3px #ccc;
.editor-close {
position: absolute;
top: -3px;
right: 13px;
font-size: 16px;
cursor: pointer;
}
.editor-copy {
position: absolute;
bottom: 5px;
right: 15px;
cursor: pointer;
z-index: 999;
color: #5e95ff;
}
}
</style>

View File

@ -0,0 +1,177 @@
<template>
<a-tabs>
<a-tab-pane :key="item.key" :tab="item.title" v-for="item in apiParams">
<a-table
:dataSource="item.tableInfo"
:columns="apiConfigColumns"
:pagination="false"
:scroll="{ y: '400px' }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'assignmentType'">
<a-select
v-model:value="record.assignmentType"
style="width: 100%"
:placeholder="t('请选择赋值类型')"
@change="
() => {
record.config = null;
}
"
allowClear
>
<a-select-option :value="bind.value" v-for="bind in bindType" :key="bind.value">
{{ bind.label }}
</a-select-option>
</a-select>
</template>
<template v-else-if="column.key === 'value'">
<!-- 流程参数 -->
<a-select
v-if="record.assignmentType === 'processParameter'"
v-model:value="record.config"
style="width: 100%"
:placeholder="t('请选择流程参数')"
allowClear
>
<a-select-option :value="it.id" v-for="it in data.processParameterTree" :key="it.id">
{{ it.name }}
</a-select-option>
</a-select>
<!-- 发起人信息 -->
<a-tree-select
v-else-if="record.assignmentType === 'originator'"
v-model:value="record.config"
show-search
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="t('请选择发起人信息')"
allow-clear
tree-default-expand-all
:tree-data="data.originatorTree"
:field-names="{
children: 'children',
label: 'title',
value: 'key',
}"
/>
<!-- 表单数据 -->
<a-tree-select
v-else-if="record.assignmentType === 'formData'"
v-model:value="record.config"
show-search
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="t('请选择表单数据')"
allow-clear
tree-default-expand-all
:tree-data="data.formDataTree"
:field-names="{
children: 'children',
label: 'title',
value: 'key',
}"
/>
<a-input
v-else
v-model:value="record.value"
:placeholder="record.type ? t('请填写值') : t('请先选择赋值类型后再配置值')"
/>
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</template>
<script setup lang="ts">
import { TreeProps } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { ApiParams } from '/@/components/ApiConfig/src/interface';
import { reactive } from 'vue';
import { getProcessParamConfigs, getVariablesTree } from '../../config/info';
const { t } = useI18n();
const props = withDefaults(
defineProps<{
paramTree: TreeProps['treeData'];
apiParams: Array<ApiParams>;
needHideComponents?: Boolean;
}>(),
{
paramTree: () => {
return [];
},
apiParams: () => {
return [];
},
needHideComponents: () => {
return false;
},
},
);
let data: {
processParameterTree: Array<{ id: string; name: string }>;
originatorTree: TreeProps['treeData'];
formDataTree: TreeProps['treeData'];
} = reactive({
processParameterTree: getProcessParamConfigs(),
originatorTree: [
{
title: t('发起人ID'),
key: '#{initiator_id}#',
},
{
title: t('发起人所属组织架构名称'),
key: '#{initiator_dept_name}#',
},
],
formDataTree: getVariablesTree({ needHideComponents: props.needHideComponents as boolean }),
});
let bindType = [
{
label: t('值'),
value: 'value',
},
{
label: t('流程参数'),
value: 'processParameter',
},
{
label: t('发起人信息'),
value: 'originator',
},
{
label: t('表单数据'),
value: 'formData',
},
];
let apiConfigColumns = [
{
title: t('API入参名称'),
dataIndex: 'name',
key: 'name',
align: 'center',
},
{
title: t('API入参类型'),
dataIndex: 'dataType',
key: 'dataType',
align: 'center',
},
{
title: t('赋值类型'),
dataIndex: 'assignmentType',
key: 'assignmentType',
align: 'center',
},
{
title: t('赋值配置'),
dataIndex: 'value',
key: 'value',
align: 'center',
},
];
</script>
<style scoped></style>

View File

@ -0,0 +1,161 @@
<template>
<div class="list-item">
<div class="item-box">
<div class="item-left"><img :src="FormImg" /></div>
<div class="item-right">
<div class="item-title">{{ t('功能名称') }}</div>
<a-tooltip v-if="props.item.formName.length > 12" :title="props.item.formName">
<div class="item-form-name"> {{ `${props.item.formName.slice(0, 12)}...` }}</div>
</a-tooltip>
<div class="item-form-name" v-else> {{ props.item.formName }}</div>
</div>
<div class="fixed-checked">
<slot name="check"></slot>
</div>
<div class="fixed-circle"><PreviewForm :item="props.item" /></div>
<div class="form-icon">
<IconFontSymbol icon="form" :fill-color="fillColor" />
</div>
<div class="fixed-title"> {{ bgTitle }} </div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import customFormImg from '/@/assets/workflow/custom-form.png';
import systemFormImg from '/@/assets/workflow/system-form.png';
import PreviewForm from '/@bpmn/components/formSettings/PreviewForm.vue';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { FormType } from '/@/enums/workflowEnum';
import { FormSettingItem } from '/@/model/workflow/formSetting';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
defineEmits(['preview']);
let props = withDefaults(defineProps<{ item: FormSettingItem }>(), {
item: () => {
return { formName: '', key: '', formType: FormType.CUSTOM, formId: '' };
},
});
const FormImg = computed(() => {
return props.item.formType == FormType.CUSTOM ? customFormImg : systemFormImg;
});
const bgTitle = computed(() => {
return props.item.formType == FormType.CUSTOM ? 'CUSTOM' : 'SYSTEM';
});
const fontColor = computed(() => {
return props.item.formType == FormType.CUSTOM ? '#ab7efe' : '#18a1f8';
});
const fillColor = computed(() => {
return props.item.formType == FormType.CUSTOM ? '#eceaff' : '#e8f6ff';
});
const bgColor = computed(() => {
return props.item.formType == FormType.CUSTOM ? '#f4f3ff' : '#f4fafe';
});
</script>
<style lang="less" scoped>
.list-item {
width: 30%;
height: 100px;
background: v-bind(bgColor);
border-radius: 8px;
margin-left: 20px;
margin-bottom: 20px;
overflow: hidden;
border-color: v-bind(fontColor);
&:hover {
border: 1px dotted v-bind(fontColor);
}
.item-box {
display: flex;
margin: 14px;
position: relative;
.item-left {
margin-right: 14px;
}
.item-right {
.item-title {
font-size: 12px;
font-weight: bold;
color: #999999;
margin: 10px 0 4px 0;
opacity: 0.8;
}
.item-form-name {
color: #303133;
font-size: 14px;
font-weight: bold;
opacity: 0.8;
}
}
.fixed-circle {
position: absolute;
right: 0;
top: 0;
border-radius: 8px;
border: 1px solid v-bind(fontColor);
padding: 2px 10px;
color: v-bind(fontColor);
font-size: 12px;
cursor: pointer;
}
.fixed-circle:hover {
background-color: v-bind(fontColor);
color: #fff;
}
.fixed-title {
position: absolute;
top: 50px;
left: 50px;
color: #eaf1f5;
font-size: 40px;
font-weight: 500;
}
.fixed-checked {
position: absolute;
bottom: -16px;
z-index: 1;
right: -6px;
}
.form-icon {
position: absolute;
right: -20px;
bottom: -28px;
font-size: 48px;
transform: rotate(310deg);
}
}
}
:deep(.ant-checkbox-inner) {
border-radius: 50%;
border-color: v-bind(fontColor);
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
border-radius: 50%;
background-color: v-bind(fontColor);
}
:deep(.ant-checkbox-checked::after),
:deep(.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover),
:deep(.ant-checkbox-inner),
:deep(.ant-checkbox:hover),
:deep(.ant-checkbox-input:focus + .ant-checkbox-inner) {
border-color: v-bind(fontColor);
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div class="list-item">
<div class="item-box">
<div class="item-left"><img :src="CardImg" /></div>
<div class="item-right">
<div class="template-box">
<span class="item-title">{{ t('编码') }}</span>
<span class="item-form-name">{{ props.item.code }}</span>
</div>
<div class="template-box">
<span class="item-title">{{ t('名称') }}</span>
<span class="item-form-name">{{ props.item.name }}</span>
</div>
</div>
<div class="fixed-checked">
<slot name="pick"></slot>
</div>
<div class="fixed-icon">
<IconFontSymbol icon="liucheng" fill-color="#ece8ff" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import CardImg from '/@/assets/workflow/template.png';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
interface Obj {
name: string;
code: string;
}
let props = withDefaults(defineProps<{ item: Obj }>(), {
item: () => {
return { name: '', code: '' };
},
});
</script>
<style lang="less" scoped>
@custom-color: #5332f5;
@bg-color: #f3f0ff;
.list-item {
width: 31%;
height: 92px;
background: @bg-color;
border-color: @custom-color;
border-radius: 8px;
margin: 1%;
overflow: hidden;
box-sizing: border-box;
border: 3px solid transparent;
&:hover {
border: 3px dotted @custom-color;
}
.item-box {
display: flex;
margin: 14px;
position: relative;
height: 62px;
align-items: center;
.item-left {
margin-right: 14px;
min-width: 70px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
font-size: 14px;
width: 100%;
z-index: 9;
.item-title {
// font-weight: bold;
color: #999999;
flex: 1;
}
.item-form-name {
width: calc(100% - 38px);
margin-left: 4px;
color: #999999;
// font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.fixed-checked {
position: absolute;
bottom: -6px;
z-index: 1;
right: -6px;
}
.fixed-icon {
position: absolute;
right: -40px;
font-size: 70px;
transform: rotate(48deg);
top: -40px;
z-index: 0;
}
}
}
.template-box {
display: flex;
margin: 4px 0;
width: 100%;
}
.img-box {
width: 100%;
height: 100%;
}
:deep(.ant-checkbox-inner) {
border-color: @custom-color;
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
background-color: @custom-color;
}
:deep(.ant-checkbox-checked::after),
:deep(.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover),
:deep(.ant-checkbox-inner),
:deep(.ant-checkbox:hover),
:deep(.ant-checkbox-input:focus + .ant-checkbox-inner) {
border-color: @custom-color;
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<div>
<div @click="show"> <slot></slot></div>
<ModalPanel
:visible="visible"
:width="1200"
:title="t('添加流程')"
@submit="submit"
@close="close"
>
<template #left>
<CategoryTree @select="handleSelect" />
</template>
<Selected :list="template.selected" @change="selectedListChecked" />
<SearchBox @search="searchList" />
<div class="list-page-box" v-if="template.list.length > 0">
<TemplateCard
:class="selectIds.includes(item.id) ? 'picked' : 'not-picked'"
v-for="(item, index) in template.list"
:key="index"
:item="item"
@click="checked(item)"
>
<template #pick>
<a-checkbox size="small" :checked="selectIds.includes(item.id)" />
</template>
</TemplateCard>
<div class="page-box">
<a-pagination
v-model:current="page.current"
:pageSize="page.pageSize"
:total="page.total"
show-less-items
@change="getList"
/></div>
</div>
<div v-else>
<EmptyBox />
</div>
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import CategoryTree from '/@bpmn/components/CategoryTree.vue';
import TemplateCard from '/@bpmn/components/card/TemplateCard.vue';
import { ModalPanel, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import Selected from './Selected.vue';
import { RelationProcessConfig } from '/@/model/workflow/workflowConfig';
import { getDesignPage } from '/@/api/workflow/design';
import { WorkflowPageModel } from '/@/api/workflow/model';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
list: Array<RelationProcessConfig>;
single: Boolean;
}>(),
{
list: () => {
return [];
},
single: () => {
return false;
},
},
);
const visible = ref(false);
let category = ref('');
let template: {
list: Array<WorkflowPageModel>;
selected: Array<RelationProcessConfig>;
keyword: string;
} = reactive({ list: [], selected: [], keyword: '' });
let page: { current: number; total: number; pageSize: number } = reactive({
current: 1,
total: 0,
pageSize: 9,
});
async function show() {
page.current = 1;
page.total = 0;
category.value = '';
template.selected = [];
if (props.list.length > 0) {
template.selected = cloneDeep(props.list).filter((ele) => {
return ele.id;
});
}
await getList();
visible.value = true;
}
let selectIds = computed(() => {
if (template.selected && template.selected.length > 0) {
return template.selected.map((ele: RelationProcessConfig) => {
return ele.id;
});
}
return [];
});
async function getList() {
try {
let params = {
limit: page.current,
size: page.pageSize,
category: category.value,
keyword: template.keyword,
};
let res = await getDesignPage(params);
page.total = res.total;
template.list = res.list;
} catch (error) {}
}
function submit() {
emits('change', template.selected);
close();
}
function close() {
template.list = [];
visible.value = false;
}
function handleSelect(deptId = '') {
category.value = deptId;
getList();
}
function checked(item: WorkflowPageModel) {
if (props.single) {
if (selectIds.value.includes(item.id)) {
template.selected = [];
} else {
template.selected = [
{
name: item.name,
id: item.id,
code: item.code,
definitionKey: item.definitionKey,
},
];
}
} else {
if (selectIds.value.includes(item.id)) {
template.selected = template.selected.filter((ele) => {
return ele.id != item.id;
});
} else {
template.selected.push({
name: item.name,
id: item.id,
code: item.code,
definitionKey: item.definitionKey,
processStatus: 0, //流程状态
processAuth: 0, //任务权限
});
}
}
}
function selectedListChecked(item: RelationProcessConfig) {
template.selected = template.selected.filter((ele) => {
return ele.id != item.id;
});
}
function searchList(searchKeywords: string) {
template.keyword = searchKeywords;
console.log('searchKeywords: ', searchKeywords);
getList();
}
</script>

View File

@ -0,0 +1,140 @@
<template>
<div class="title">
<NodeHead :node-name="t('流程模板列表')" />
<a-button class="selected-btn" @click="open">{{ t('已选流程模板列表') }}</a-button>
</div>
<a-drawer
placement="right"
:visible="data.visible"
:get-container="false"
:closable="false"
:mask="false"
>
<div class="selected-head title">
<NodeHead :node-name="t('已选流程模板列表')" />
<div class="close-icon" @click="close">+</div>
</div>
<SearchBox @search="search" />
<div class="list-box" v-if="data.list && data.list.length > 0">
<TemplateCard
class="picked"
v-for="(item, index) in data.list"
:key="index"
:item="item"
@click="listChecked(item)"
>
<template #pick> <a-checkbox size="small" :checked="true" /> </template
></TemplateCard>
</div>
<EmptyBox v-else />
</a-drawer>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { NodeHead, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import TemplateCard from '/@bpmn/components/card/TemplateCard.vue';
import { cloneDeep } from 'lodash-es';
import { RelationProcessConfig } from '/@/model/workflow/workflowConfig';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
list: Array<RelationProcessConfig>;
}>(),
{
list: () => {
return [];
},
},
);
let data: {
visible: Boolean;
list: Array<RelationProcessConfig>;
keyword: string;
} = reactive({
visible: false,
list: [],
keyword: '',
});
watch(props, (val) => {
data.list = cloneDeep(val.list);
});
function open() {
data.list = cloneDeep(props.list);
data.visible = true;
}
function close() {
data.visible = false;
}
function search(keyword = '') {
data.keyword = keyword;
data.list = cloneDeep(
props.list.filter((ele) => {
return ele.name.includes(data.keyword);
}),
);
}
function listChecked(item) {
emits('change', item);
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-drawer-content-wrapper) {
width: 100% !important;
box-shadow: 0px 2px 2px 2px rgb(0 0 0 / 4%);
}
:deep(.ant-drawer-open) {
position: absolute;
width: calc(100% - 244px) !important;
top: 50px;
left: 240px;
box-shadow: -5px 5px 4px 1px rgb(0 0 0 / 6%);
height: calc(100% - 110px);
z-index: 2;
}
.list-box {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
padding: 10px 0;
}
.selected-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.selected-btn {
position: absolute;
right: 30px;
}
.close-icon {
font-size: 24px;
transform: rotate(45deg);
}
.picked {
border-width: 3px;
border-style: dotted;
border-color: #5332f5;
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div class="form-category">
<NodeHead class="title" :node-name="t('表单类型')" />
<div class="tree-box">
<div
class="item"
:class="props.modelValue == FormType.WORKFLOW ? 'checked' : ''"
@click="change(FormType.WORKFLOW)"
>
<IconFontSymbol class="item-icon" icon="xitong" />
<span class="item-text">{{ t('流程已添加表单') }}</span>
</div>
<div
class="item"
:class="props.modelValue == FormType.CUSTOM ? 'checked' : ''"
@click="change(FormType.CUSTOM)"
>
<IconFontSymbol class="item-icon" icon="zidingyi" />
<span class="item-text">{{ t('自定义表单') }}</span>
</div>
<div
class="item"
:class="props.modelValue == FormType.SYSTEM ? 'checked' : ''"
@click="change(FormType.SYSTEM)"
>
<IconFontSymbol class="item-icon" icon="xitong" />
<span class="item-text">{{ t('系统表单') }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { FormType } from '/@/enums/workflowEnum';
import { NodeHead } from '/@/components/ModalPanel/index';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emit = defineEmits(['update:modelValue', 'change']);
const props = defineProps(['modelValue']);
function change(type: FormType) {
emit('update:modelValue', type);
emit('change');
}
</script>
<style lang="less" scoped>
.form-category {
.title {
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
}
.tree-box {
padding: 10px 0;
.item {
display: flex;
align-items: center;
height: 40px;
font-size: 14px;
color: #606266;
padding: 10px;
.item-icon {
font-size: 14px;
color: #606266;
}
.item-text {
margin-left: 20px;
}
}
.checked {
background: #e5f1ff;
}
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<SettingModal
:list="[
{
formId: config.formId,
formName: config.formName,
formType: config.formType,
key: config.key,
},
]"
:isSingle="true"
@submit="submit"
>
<InputModel :value="config.formName" :placeholder="t('请选择功能表单')" />
</SettingModal>
</template>
<script setup lang="ts">
import SettingModal from '/@bpmn/components/formSettings/SettingModal.vue';
import InputModel from '/@bpmn/components/InputModel.vue';
import { FormType } from '/@/enums/workflowEnum';
import { FormSettingItem } from '/@/model/workflow/formSetting';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let emits = defineEmits(['update:config']);
withDefaults(defineProps<{ config: FormSettingItem }>(), {
config: () => {
return { formType: FormType.CUSTOM, formId: '', formName: '', key: '' };
},
});
// FormSettingItem
function submit(list: Array<FormSettingItem>) {
let submitJson = {
enabled: true,
formId: '',
formName: '',
formType: FormType.CUSTOM,
};
if (list.length > 0) {
submitJson.formId = list[0].formId;
submitJson.formName = list[0].formName;
submitJson.formType = list[0].formType;
}
emits('update:config', submitJson);
}
</script>
<style lang="less" scoped>
:deep(.slot-item) {
display: flex;
justify-content: space-between;
align-items: center;
}
.open-icon {
text-align: center;
height: 30px;
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<div @click.stop="open">{{ t('表单预览') }}</div>
<a-modal v-model:visible="visible" :title="t('预览')" :width="1100" :footer="null">
<PreviewSystemForm
v-if="visible && props.item.formType == FormType.SYSTEM"
:systemComponent="getFormProps.systemComponent"
/>
<SimpleForm
v-else
ref="formRef"
class="form"
:formProps="getFormProps"
:formModel="formData"
:isWorkFlow="true"
/>
</a-modal>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { getSchemasList } from '/@bpmn/config/formPermission';
import { FormSchema } from '/@/components/Form';
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
import { FormSettingItem } from '/@/model/workflow/formSetting';
import { FormType } from '/@/enums/workflowEnum';
import { useI18n } from '/@/hooks/web/useI18n';
import { PreviewSystemForm } from '/@/components/SystemForm/index';
import { SystemComponentConfig } from '/@/model/workflow/bpmnConfig';
const { t } = useI18n();
let props = withDefaults(defineProps<{ item: FormSettingItem }>(), {
item: () => {
return { formType: FormType.CUSTOM, formId: '', key: '', formName: '' };
},
});
const visible = ref(false);
let getFormProps: {
schemas: FormSchema[];
showResetButton: boolean;
showSubmitButton: boolean;
hiddenComponent: any;
systemComponent: SystemComponentConfig;
} = reactive({
schemas: [],
showResetButton: false,
showSubmitButton: false,
hiddenComponent: [],
systemComponent: {
functionalModule: '',
functionName: '',
functionFormName: '',
},
});
const formData = reactive<Recordable>({});
async function open() {
if (props.item.formType == FormType.CUSTOM) {
getFormProps.schemas = await getSchemasList(props.item.formId, props.item.formType);
} else {
let systemJson = await getSchemasList(props.item.formId, props.item.formType);
getFormProps.systemComponent = systemJson.systemComponent;
}
visible.value = true;
}
</script>
<style lang="less" scoped>
.form {
min-height: 400px;
padding: 20px;
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<div class="title">
<NodeHead :node-name="t('表单列表')" />
<a-button class="selected-btn" @click="open">{{ t('已选表单') }}</a-button>
</div>
<a-drawer
v-if="visible"
placement="right"
:visible="true"
:get-container="false"
:closable="false"
:mask="false"
>
<div class="selected-head title">
<NodeHead :node-name="t('已选表单')" />
<div class="close-icon" @click="close">+</div>
</div>
<div class="list-box" v-if="props.list.length > 0">
<FormCard
v-for="(item, index) in props.list"
:key="index"
:item="item"
class="picked"
@click="abolish(item)"
>
<template #check>
<a-checkbox size="small" :checked="true" />
</template>
</FormCard>
</div>
<EmptyBox v-else />
</a-drawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { NodeHead, EmptyBox } from '/@/components/ModalPanel/index';
import { FormSettingItem } from '/@/model/workflow/formSetting';
import FormCard from '/@bpmn/components/card/FormCard.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let emits = defineEmits(['abolish']);
let props = withDefaults(defineProps<{ type: string; list: Array<FormSettingItem> }>(), {
type: '',
list: () => {
return [];
},
});
let visible = ref(false);
function open() {
visible.value = true;
}
function close() {
visible.value = false;
}
function abolish(item) {
emits('abolish', item);
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
height: 40px;
font-size: 16px;
color: #333;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-drawer-content-wrapper) {
width: 100% !important;
box-shadow: 0 2px 2px 2px rgb(0 0 0 / 4%);
}
:deep(.ant-drawer-open) {
position: absolute;
width: calc(100% - 244px) !important;
top: 50px;
left: 240px;
box-shadow: -5px 5px 4px 1px rgb(0 0 0 / 6%);
height: calc(100% - 110px);
z-index: 2;
}
.list-box {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
padding: 10px 0;
}
.selected-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.selected-btn {
position: absolute;
right: 30px;
}
.close-icon {
font-size: 24px;
transform: rotate(45deg);
}
.picked {
border-width: 1px;
border-style: dotted;
}
</style>

View File

@ -0,0 +1,500 @@
<template>
<div class="list-box">
<div class="opr-box">
<NodeHead :nodeName="t('表单列表')" />
<div class="button-box">
<SettingModal :list="[]" :isSingle="false" @submit="addItem">
<a-button type="primary">{{ t('添加表单') }}</a-button>
</SettingModal>
</div>
</div>
<div class="list">
<div class="row head">
<span class="up-or-down"></span>
<span class="form-name">{{ t('表单名称') }}</span>
<span class="form-type">{{ t('表单类型') }}</span>
<span class="opr">{{ t('操作') }}</span>
</div>
<div class="body" v-if="formSetting.list.length > 0">
<div class="box" v-for="(item, index) in formSetting.list" :key="index">
<div class="row item">
<div class="up-or-down" @click="item.showChildren = !item.showChildren">
<Icon
class="icon"
:icon="item.showChildren ? 'ant-design:up-outlined' : 'ant-design:down-outlined'"
/>
</div>
<span class="form-name">{{ item.formName }}</span>
<span class="form-type">{{ formTypeOptions[item.formType] }}</span>
<span class="opr">
<a-popconfirm @confirm="deleteItem(index)">
<template #title>
<div style="width: 300px">
<p>{{ t('删除表单') }}</p>
<p>{{ t('删除表单会清空已引用该表单数据的所有配置,请确认是否继续?') }}</p>
<p class="pop-desc">{{
t('如果引用该表单的配置较多,清空时间会相应变长,请耐心等待。')
}}</p>
</div>
</template>
<Icon icon="ant-design:delete-outlined" class="delete-icon" />
</a-popconfirm>
</span>
</div>
<div
class="children list"
v-if="item.showChildren && item.children && item.children.length > 0"
>
<div class="row head">
<span class="flex-baisc-4"></span>
<span>{{ t('字段名') }}</span>
<span>{{ t('字段ID') }}</span>
<span
><a-checkbox
v-model:checked="item.requiredAll"
size="small"
@change="requiredAll(item)"
>{{ t('必填') }}</a-checkbox
></span
>
<span
><a-checkbox v-model:checked="item.viewAll" size="small" @change="viewAll(item)">{{
t('查看')
}}</a-checkbox></span
>
<span
><a-checkbox v-model:checked="item.editAll" size="small" @change="editAll(item)">{{
t('编辑')
}}</a-checkbox></span
>
</div>
<div class="body" v-if="item.children.length > 0">
<div
v-for="(child, childIndex) in item.children"
:key="childIndex"
class="padding-left"
>
<div class="row item" v-if="child.type != hiddenComponentType">
<span
><em
class="flex-baisc-4"
v-if="child.isSubTable"
@click="child.showChildren = !child.showChildren"
>
<Icon
class="icon"
:icon="
child.showChildren ? 'ant-design:up-outlined' : 'ant-design:down-outlined'
"
/> </em
>{{ child.fieldName }}</span
>
<span>{{ child.fieldId }}</span>
<span>
<a-switch
v-if="!child.isSubTable && !doNotShowControl.includes(child.type)"
v-model:checked="child.required"
:disabled="requiredDisabled.includes(child.type) || child.isSaveTable"
@change="TableRequired(child.required, child, item)"
size="small"
/></span>
<span>
<a-switch
v-model:checked="child.view"
size="small"
@change="TableView(child.view, child, item)"
/></span>
<span
><a-switch
v-if="!child.isSubTable && !doNotShowControl.includes(child.type)"
:disabled="child.disabled"
v-model:checked="child.edit"
@change="TableEdit(child.edit, child, item)"
size="small"
/></span>
</div>
<template
v-if="child.showChildren && child.isSubTable && child.children.length > 0"
>
<div
class="row item padding-left"
v-for="(child2, childIndex2) in child.children"
:key="childIndex2"
>
<span>{{ child2.fieldName }}</span>
<span>{{ child2.fieldId }}</span>
<span
><a-switch
:disabled="child2.disabled || child2.isSaveTable"
v-model:checked="child2.required"
@change="TableRequired(child2.required, child2, item, child)"
size="small"
/></span>
<span
><a-switch
:disabled="child2.disabled"
v-model:checked="child2.view"
@change="TableView(child2.view, child2, item, child)"
size="small"
/></span>
<span
><a-switch
:disabled="child2.disabled || child2.isSaveTable"
v-model:checked="child2.edit"
@change="TableEdit(child2.edit, child2, item, child)"
size="small"
/></span>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
<EmptyBox v-else :has-icon="false" />
</div>
</div>
</template>
<script setup lang="ts">
import useStateFormInfo from '/@bpmn/hooks/useStateFormInfo';
import Icon from '/@/components/Icon/index';
import { NodeHead, EmptyBox } from '/@/components/ModalPanel/index';
import SettingModal from '/@bpmn/components/formSettings/SettingModal.vue';
import { FormType } from '/@/enums/workflowEnum';
import { FormConfigItem } from '/@/model/workflow/formSetting';
import { onMounted, reactive } from 'vue';
import {
formPermissionList,
hiddenComponentType,
requiredDisabled,
doNotShowControl,
} from '/@bpmn/config/formPermission';
import { updateFormDataInfo } from '/@bpmn/config/useUpdateAllFormInfo';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const { formInfo } = useStateFormInfo();
const formTypeOptions = {};
formTypeOptions[FormType.CUSTOM] = t('自定义表单');
formTypeOptions[FormType.SYSTEM] = t('系统表单');
let formSetting: { list: Array<FormConfigItem> } = reactive({ list: [] });
onMounted(() => {
if (formInfo.value.formConfigs) formSetting.list = formInfo.value.formConfigs;
formSetting.list.forEach((val) => {
checkIsAll(val);
});
});
function deleteItem(index: number) {
updateFormDataInfo(formSetting.list[index]['key']);
formSetting.list.splice(index, 1);
}
async function addItem(list: Array<FormConfigItem>) {
let returnArr = await formPermissionList(list);
returnArr.forEach((val) => {
checkIsAll(val);
});
formSetting.list.push(...returnArr);
}
function requiredAll(item) {
// 必填所有为true 所有的必填编辑查看都为true除了disabled
// 必填所有为false 所有必填都取消除了disabled
let requiredAll = item.requiredAll;
if (requiredAll) {
item.viewAll = true;
item.editAll = true;
}
item.children = item.children.map((ele) => {
if (!ele.disabled) {
if (requiredAll) {
ele.required = true;
ele.edit = true;
} else {
ele.required = false;
}
}
if (requiredAll) ele.view = true;
if (ele.children && ele.children.length > 0) {
ele.children.map(
(ele2: { disabled: any; required: boolean; view: boolean; edit: boolean }) => {
if (!ele2.disabled) {
if (requiredAll) {
ele2.required = true;
ele2.edit = true;
} else {
ele2.required = false;
}
}
if (requiredAll) ele2.view = true;
},
);
}
return ele;
});
}
function editAll(item) {
// 编辑所有为true 所有的编辑查看都为true除了disabled
// 编辑所有为false 所有编辑都为false除了disabled
let editAll = item.editAll;
if (editAll) {
item.viewAll = true;
} else {
item.requiredAll = false;
}
item.children = item.children.map((ele) => {
if (!ele.disabled) {
if (editAll) {
ele.edit = true;
} else {
ele.edit = false;
ele.required = false;
}
}
if (editAll) ele.view = true;
if (ele.children && ele.children.length > 0) {
ele.children.map(
(ele2: { disabled: any; required: boolean; view: boolean; edit: boolean }) => {
if (!ele2.disabled) {
if (editAll) {
ele2.edit = true;
} else {
ele2.edit = false;
ele2.required = false;
}
}
if (editAll) ele2.view = true;
},
);
}
return ele;
});
}
function viewAll(item) {
// 查看所有为true 所有的查看都为true除了disabled
// 查看所有为false 所有查看都为false除了disabled
let viewAll = item.viewAll;
if (!viewAll) {
item.requiredAll = false;
item.editAll = false;
}
item.children = item.children.map((ele) => {
if (viewAll) {
ele.view = true;
} else {
ele.view = false;
ele.edit = false;
ele.required = false;
}
if (ele.children && ele.children.length > 0) {
ele.children.map((ele2) => {
if (viewAll) {
ele2.view = true;
} else {
ele2.view = false;
ele2.edit = false;
ele2.required = false;
}
});
}
return ele;
});
}
function TableRequired(required, child, item, cur?) {
if (required) {
child.view = true;
child.edit = true;
}
checkIsAll(item);
if (cur) {
checkCurTableIsEdit(cur);
}
}
function TableView(view, child, item, cur?) {
if (!view) {
child.required = view;
child.view = view;
child.edit = view;
}
if (child.children?.length > 0) {
child.children.forEach((o) => {
if (!view) {
o.required = view;
o.view = view;
o.edit = view;
} else {
o.view = view;
o.edit = view;
}
});
}
checkIsAll(item);
if (cur) {
checkCurTableIsEdit(cur);
}
}
function TableEdit(edit, child, item, cur?) {
if (edit) {
child.view = true;
} else {
child.required = false;
}
checkIsAll(item);
if (cur) {
checkCurTableIsEdit(cur);
}
}
function checkCurTableIsEdit(item) {
let view = 0;
let edit = 0;
item.children.map((ele) => {
if (ele.view) view += 1;
if (ele.edit || ele.disabled) edit += 1;
});
item.view = view > 0 ? true : false;
item.edit = edit > 0 ? true : false;
}
function checkIsAll(item) {
let all = 0;
let required = 0;
let view = 0;
let edit = 0;
item.children.map((ele) => {
all += 1;
if (ele.required || ele.disabled) required += 1;
if (ele.view) view += 1;
if (ele.edit || ele.disabled) edit += 1;
if (ele.children && ele.children.length > 0) {
let childView = 0;
ele.children.map((ele2) => {
all += 1;
if (ele2.required || ele2.disabled) required += 1;
if (ele2.view) {
view += 1;
childView += 1;
}
if (ele2.edit || ele2.disabled) edit += 1;
});
ele.view = childView > 0 ? true : false;
}
});
item.requiredAll = required == all ? true : false;
item.viewAll = view == all ? true : false;
item.editAll = edit == all ? true : false;
// if (item.editAll) {
// item.viewAll = true;
// viewAll(item);
// }
//if (item.requiredAll) {
// item.viewAll = true;
// item.editAll = true;
// viewAll(item);
// editAll(item);
//}
}
</script>
<style lang="less" scoped>
.list-box {
.opr-box {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
}
.list {
.row {
height: 40px;
line-height: 30px;
display: flex;
justify-content: center;
align-items: center;
span {
flex-basis: 20%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.up-or-down {
flex-basis: 30px;
margin-left: 8px;
}
.form-name {
flex: 1;
}
.form-type {
flex-basis: 80px;
}
.opr {
flex-basis: 80px;
}
.small {
flex-basis: 60px;
}
.flex-baisc-35 {
flex-basis: 120px;
}
.flex-baisc-4 {
flex-basis: 40px;
}
.icon {
color: #ccc;
}
}
.padding-left {
padding-left: 40px;
}
.head {
background-color: #f9f9f9;
}
.item {
border-bottom: 1px solid #f9f9f9;
}
.delete-icon {
color: @clear-color;
}
.empty-box {
height: 200px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #f9f9f9;
border-top: none;
}
}
:deep(.slot-item) {
display: inline;
}
.pop-desc {
font-size: 12px;
color: rgb(0 0 0 / 40%);
}
</style>

View File

@ -0,0 +1,424 @@
<template>
<div @click="show" class="slot-item">
<slot></slot>
</div>
<ModalPanel :visible="visible" :title="t('表单设置')" @submit="submit" @close="close">
<template #left>
<Category v-model="setting.checkFormType" @change="changeType" />
</template>
<SelectedList type="form" :list="setting.selectedList" @abolish="abolishChecked" />
<SearchBox @search="searchList" />
<div class="flow-used" v-if="setting.checkFormType == FormType.WORKFLOW">
<EmptyBox v-if="showUsedEmpty" />
<!-- 仅仅显示未重复表单 -->
<div class="list-page-box" v-if="showRepeatedList">
<FormCard
v-for="(noUsedItem, index) in setting.usedNotRepeatedList"
:key="index"
:item="setting.formKeyList[noUsedItem]"
@click="usedChecked(noUsedItem)"
:class="selectKeys.includes(noUsedItem) ? 'picked' : 'notPicked'"
class="notPicked"
>
<template #check>
<a-checkbox size="small" :checked="selectKeys.includes(noUsedItem)" />
</template>
</FormCard>
</div>
<!-- 既有重复表单又有未重复表单 显示面板折叠分类 -->
<a-collapse v-else v-model:activeKey="activeKey" ghost expandIconPosition="right">
<a-collapse-panel
v-for="(formIds, formIndex) in setting.usedRepeatedList"
:key="formIndex"
:header="t('相同表单重复选用-') + setting.formKeyList[formIds[0]].name"
>
<div class="list-page-box" v-if="formIds.length > 0">
<FormCard
v-for="(key, index) in formIds"
:key="index"
:item="setting.formKeyList[key]"
:class="selectKeys.includes(key) ? 'picked' : 'notPicked'"
@click="usedChecked(key)"
>
<template #check>
<a-checkbox size="small" :checked="selectKeys.includes(key)" />
</template>
</FormCard>
</div>
</a-collapse-panel>
<a-collapse-panel
key="2000"
:header="t('未重复选用的表单')"
v-if="setting.usedNotRepeatedList.length > 0"
>
<div class="list-page-box">
<FormCard
v-for="(noUsedItem, index) in setting.usedNotRepeatedList"
:key="index"
:item="setting.formKeyList[noUsedItem]"
@click="usedChecked(noUsedItem)"
:class="selectKeys.includes(noUsedItem) ? 'picked' : 'notPicked'"
class="notPicked"
>
<template #check>
<a-checkbox size="small" :checked="selectKeys.includes(noUsedItem)" />
</template>
</FormCard>
</div>
</a-collapse-panel>
</a-collapse>
</div>
<template v-else>
<div class="list-page-box" v-if="setting.list.length > 0">
<FormCard
v-for="(item, index) in setting.list"
:key="index"
:item="item"
:class="setting.checkedFormId[item.formId] ? 'picked' : 'notPicked'"
@click="checked(item)"
>
<template #check>
<a-checkbox
size="small"
:checked="setting.checkedFormId[item.formId] ? true : false"
:disabled="item.enabledMark == 1 ? false : true"
/>
</template>
</FormCard>
<div class="page-box">
<a-pagination
v-model:current="setting.page.current"
:pageSize="setting.page.pageSize"
:total="setting.page.total"
show-less-items
@change="getList"
/>
</div>
</div>
<EmptyBox v-else />
</template>
</ModalPanel>
<a-modal
v-model:visible="setting.operationVisible"
:width="400"
:title="t('操作确认')"
@ok="submitOperation"
>
<div class="opr-box">
<div class="opr-icon">
<Icon icon="ant-design:question-circle-outlined" :size="30" color="#ff9900" />
</div>
<div class="opr-content">
<div class="title">{{ t('该表单已经在流程中引用过,是否重复引用?') }}</div>
<div class="desc">{{ t('重复引用是在发起或审批中,会出现两张相同结构的表单。') }}</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import { ModalPanel, SearchBox, EmptyBox } from '/@/components/ModalPanel/index';
import Category from '/@bpmn/components/formSettings/Category.vue';
import SelectedList from '/@bpmn/components/formSettings/SelectedList.vue';
import FormCard from '/@bpmn/components/card/FormCard.vue';
import { FormType } from '/@/enums/workflowEnum';
import { FormSettingItem } from '/@/model/workflow/formSetting';
import { getFormTemplateEnabledPage } from '/@/api/form/design';
import { randomFormNameStr, randomTime } from '/@bpmn/util/random';
import { getUsedFormIds, getUsedFormList } from '/@bpmn/config/info';
import { Icon } from '/@/components/Icon';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let props = withDefaults(defineProps<{ isSingle: Boolean; list: Array<FormSettingItem> }>(), {
isSingle: () => {
return false;
},
list: () => {
return [];
},
});
let emits = defineEmits(['submit']);
const activeKey = ref(['2000']);
const visible = ref(false);
let setting: {
checkFormType: FormType;
selectedList: Array<FormSettingItem>;
list: Array<FormSettingItem>;
searchKeyword: string;
page: { current: number; total: number; pageSize: number };
formKeyList: Object;
checkedFormId: Object;
usedRepeatedList: Object;
usedNotRepeatedList: Array<string>;
operationVisible: boolean;
operationCloneItem: FormSettingItem;
} = reactive({
checkFormType: FormType.CUSTOM,
selectedList: [],
list: [],
searchKeyword: '',
page: { current: 1, total: 0, pageSize: 9 },
formKeyList: {},
checkedFormId: {},
usedRepeatedList: {},
usedNotRepeatedList: [],
operationVisible: false,
operationCloneItem: {
key: '', //formId_key
formType: FormType.CUSTOM, //表单类型
formId: '', //表单ID 系统表单为文件夹名
formName: '', //表单名称
},
});
const showRepeatedList = computed(() => {
return (
setting.usedNotRepeatedList.length != 0 && JSON.stringify(setting.usedRepeatedList) == '{}'
);
});
const showUsedEmpty = computed(() => {
return (
setting.usedNotRepeatedList.length == 0 && JSON.stringify(setting.usedRepeatedList) == '{}'
);
});
let selectKeys = computed(() => {
if (setting.selectedList && setting.selectedList.length > 0) {
return setting.selectedList.map((ele: FormSettingItem) => {
return ele.key;
});
}
return [];
});
function show() {
setting.checkFormType = FormType.CUSTOM;
setting.list = [];
setting.page.total = 0;
setting.selectedList = [];
getList();
visible.value = true;
}
function changeType() {
setting.list = [];
setting.page.total = 0;
getList();
}
async function getList() {
if (setting.checkFormType == FormType.WORKFLOW) {
getUsedList();
} else {
await getTemplateList();
}
}
function getUsedList() {
let { formKeyList, repeatedList, notRepeatedList } = getUsedFormList();
setting.formKeyList = formKeyList;
setting.usedNotRepeatedList = notRepeatedList;
setting.usedRepeatedList = repeatedList;
let arr: Array<string> = [];
for (let k of Object.keys(setting.usedRepeatedList)) {
arr.push(k);
}
arr.push('2000'); //未重复的表单的卡片key
activeKey.value = arr;
}
async function getTemplateList() {
let params = {
limit: setting.page.current,
size: setting.page.pageSize,
type: setting.checkFormType,
keyword: setting.searchKeyword,
};
let res = await getFormTemplateEnabledPage(params);
if (res.total) {
setting.page.total = res.total;
}
setting.list = [];
if (res.list.length > 0) {
res.list.forEach((ele) => {
if (setting.checkFormType == FormType.CUSTOM) {
setting.list.push({
key: 'form_' + ele.id + '_' + randomTime(),
formType: FormType.CUSTOM,
formName: ele.name ? ele.name : ele.id,
formId: ele.id,
enabledMark: ele.enabledMark,
});
} else {
setting.list.push({
key: 'form_' + ele.id + '_' + randomTime(),
formType: FormType.SYSTEM,
formName: ele.name,
formId: ele.id,
enabledMark: ele.enabledMark,
});
}
});
}
}
async function submit() {
if (setting.selectedList.length === 0) {
message.error(t('请至少选择一个表单'));
return false;
}
emits('submit', setting.selectedList);
close();
}
function close() {
visible.value = false;
}
function searchList(keyword: string) {
setting.searchKeyword = keyword;
setting.page.current = 1;
getList();
}
function checked(item: FormSettingItem) {
if (item.enabledMark != 1) return;
let cloneItem: FormSettingItem = cloneDeep(item);
cloneItem.formName = hasFormIdFromSelectedList(cloneItem.formId)
? cloneItem.formName + '_' + randomFormNameStr()
: cloneItem.formName;
cloneItem.key = 'form_' + cloneItem.formId + '_' + randomTime();
setting.operationCloneItem = cloneItem;
if (props.isSingle) {
// 单选
setting.selectedList = [];
setting.checkedFormId = {};
if (!setting.checkedFormId[item.formId]) {
if (hasFormIdFromSelectedList(cloneItem.formId)) {
setting.operationVisible = true;
} else {
submitOperation();
}
}
} else {
// 多选
if (setting.checkedFormId[item.formId]) {
// 取消选中
let formKey = setting.checkedFormId[item.formId];
if (selectKeys.value.includes(formKey)) {
let index = setting.selectedList.findIndex((ele) => {
return ele.key === formKey;
});
setting.selectedList.splice(index, 1);
}
setting.checkedFormId[item.formId] = '';
} else {
if (hasFormIdFromSelectedList(cloneItem.formId)) {
setting.operationVisible = true;
} else {
submitOperation();
}
}
}
}
function usedChecked(key: string) {
if (props.isSingle) {
let cloneItem: FormSettingItem = cloneDeep(setting.formKeyList[key]);
setting.selectedList = [cloneItem];
} else {
if (selectKeys.value.includes(key)) {
let index = setting.selectedList.findIndex((ele) => {
return ele.key === key;
});
setting.selectedList.splice(index, 1);
} else {
let cloneItem: FormSettingItem = cloneDeep(setting.formKeyList[key]);
setting.selectedList.push(cloneItem);
}
}
}
function hasFormIdFromSelectedList(formId) {
let usedIds = [
...getUsedFormIds(),
...setting.selectedList.map((ele) => {
return ele.formId;
}),
];
return usedIds.includes(formId);
}
function submitOperation() {
let cloneItem: FormSettingItem = setting.operationCloneItem;
setting.checkedFormId[cloneItem.formId] = cloneItem.key;
setting.selectedList.push(cloneItem);
setting.operationVisible = false;
}
function abolishChecked(item: FormSettingItem) {
setting.selectedList = setting.selectedList.filter((ele) => {
return ele.key != item.key;
});
setting.checkedFormId[item.formId] = '';
}
</script>
<style lang="less" scoped>
.list-box {
display: flex;
flex-wrap: wrap;
padding: 10px 0;
}
.page-box {
position: absolute;
bottom: 80px;
right: 20px;
}
.picked {
border-width: 1px;
border-style: dotted;
}
.notPicked {
border-width: 1px;
border-style: dotted;
border-color: transparent;
}
.flow-used {
height: calc(100% - 100px);
overflow: auto;
}
:deep(.ant-collapse-ghost > .ant-collapse-item) {
border-bottom: 1px solid #f5f5f5;
}
:deep(.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header) {
padding: 10px 16px;
}
:deep(.ant-collapse-content-box .ant-form-item) {
margin-bottom: 12px;
}
:deep(.ant-collapse-content-box) {
padding-left: 30px;
}
:deep(.ant-input-number) {
width: 100%;
}
.opr-box {
display: flex;
padding: 30px 20px;
.opr-icon {
margin-right: 10px;
}
.opr-content {
.title {
font-weight: 700;
}
.desc {
color: #a0a0a0;
}
}
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<SelectApiConfig
v-model="config"
:paramTree="ProcessDataVariable"
:exampleStr="exampleStr"
@update:modelValue="submit"
>
<slot></slot>
</SelectApiConfig>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { SelectApiConfig } from '/@/components/ApiConfig';
import { useMessage } from '/@/hooks/web/useMessage';
import { ApiConfig } from '/@/components/ApiConfig/src/interface';
import { ProcessDataVariable } from '/@bpmn/config/rules';
import { MemberConfig } from '/@/model/workflow/memberSetting';
import { MemberType } from '/@/enums/workflowEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emit = defineEmits(['change']);
let props = withDefaults(defineProps<{ memberList: Array<MemberConfig> }>(), {
memberList: () => {
return [];
},
});
let config = ref<ApiConfig>();
const exampleStr = `{
code: 0,
msg: 'success',
data: '1830999999983899309,2835993035383895389', //用户ID可以多个用逗号分隔
}`;
const memberType = MemberType.API;
const { notification } = useMessage();
function submit() {
let list: Array<MemberConfig> = [];
if (props.memberList.length > 0) {
list = props.memberList.filter((ele: MemberConfig) => {
if (ele.memberType != memberType) return ele;
});
}
if (config.value?.path) {
list.push({
memberType: memberType,
id: config.value.id,
name: config.value.path,
apiConfig: config.value,
});
emit('change', [...list]);
} else {
notification.warning({
message: t('提示'),
description: t('您没有选择API'),
});
}
}
</script>

View File

@ -0,0 +1,185 @@
<template>
<div>
<div @click="show"><slot></slot></div>
<ModalPanel
:visible="visible"
:width="400"
:title="t('表单字段配置人员')"
@submit="submit"
@close="close"
>
<div class="title">
<NodeHead :node-name="t('表单字段列表')" />
</div>
<div class="list-box" v-if="Object.keys(expandedNames).length > 0">
<a-tree
checkable
:tree-data="nodes.treeData"
autoExpandParent
defaultExpandAll
v-model:checkedKeys="checkedKeys"
/>
</div>
<EmptyBox v-else />
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import { BpmnNodeKey, MemberType } from '/@/enums/workflowEnum';
import { FormFiledConfig, MemberConfig } from '/@/model/workflow/memberSetting';
import { ModalPanel, EmptyBox } from '/@/components/ModalPanel/index';
import { NodeHead } from '/@/components/ModalPanel/index';
import { TreeProps } from 'ant-design-vue';
import { separator } from '../../config/info';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
memberList: Array<MemberConfig>;
}>(),
{
memberList: () => {
return [];
},
},
);
const visible = ref(false);
const memberType = MemberType.FORM_FIELD;
let nodes: { treeData: TreeProps['treeData'] } = reactive({
treeData: [],
});
const checkedKeys = ref<string[]>([]);
const expandedNames = ref<{}>({});
let selectedList: { list: Array<MemberConfig> } = reactive({ list: [] });
function show() {
checkedKeys.value = [];
nodes.treeData = [];
if (props.memberList.length > 0) {
selectedList.list = props.memberList.filter((ele: MemberConfig) => {
if (ele.memberType === memberType) return ele;
});
if (selectedList.list.length > 0) {
selectedList.list.forEach((ele) => {
if (ele.formFieldConfig) {
checkedKeys.value.push(
ele.formFieldConfig.nodeId +
separator +
ele.formFieldConfig.formId +
separator +
ele.formFieldConfig.formField +
separator +
ele.formFieldConfig.formKey,
);
}
});
}
}
const store = useBpmnStore();
const { info } = store;
for (let item of info.values()) {
let showNodeTypes = [BpmnNodeKey.START, BpmnNodeKey.USER];
if (showNodeTypes.includes(item.type)) {
let name = item.name ? item.name : item.type;
let id = item.id;
let info: FormFiledConfig = {
title: name,
key: id,
disabled: true,
children: [],
};
if (item.formConfigs && item.formConfigs.length > 0) {
item.formConfigs.forEach((ele1) => {
let formChildren: Array<FormFiledConfig> = [];
if (ele1.children && ele1.children.length > 0) {
ele1.children.forEach((ele2) => {
let key =
id + separator + ele1.formId + separator + ele2.fieldId + separator + ele1.key;
formChildren.push({
title: ele2.fieldName,
key,
disabled: false,
children: [],
});
expandedNames.value[key] = name + '-' + ele1.formName + '-' + ele2.fieldName;
});
}
info.children.push({
title: ele1.formName,
key: id + separator + ele1.formId,
disabled: true,
children: formChildren,
});
});
}
nodes.treeData.push(info);
}
}
visible.value = true;
}
function submit() {
let list: Array<MemberConfig> = [];
if (props.memberList.length > 0) {
list = props.memberList.filter((ele: MemberConfig) => {
if (ele.memberType != memberType) return ele;
});
}
console.log(checkedKeys.value, 'sss');
for (const item of checkedKeys.value) {
let arr = item.split(separator);
if (arr.length === 4) {
let nodeId = arr[0];
let formId = arr[1];
let formField = arr[2];
let formKey = arr[3];
list.push({
memberType: memberType,
id: nodeId,
name: expandedNames.value[item],
formFieldConfig: { nodeId, formId, formField, formKey },
});
}
}
console.log(list, 'list');
emits('change', [...list]);
close();
}
function close() {
visible.value = false;
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 10px;
}
.list-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
overflow: auto;
}
:deep(.ant-tree-treenode) {
margin-left: 8px;
margin-bottom: 10px;
}
:deep(.ant-tree-list) {
height: 460px;
}
</style>

View File

@ -0,0 +1,153 @@
<template>
<div class="list-box">
<div class="opr-box">
<div class="header-box"><NodeHead :nodeName="t('人员列表')" /></div>
<div class="button-box">
<div v-for="(item, index) in allComponentList" :key="index">
<component
v-if="item.show"
:is="item.component"
:memberList="props.memberList"
@change="changeList"
>
<a-button type="primary">{{ item.name }}</a-button>
</component>
</div>
</div>
</div>
<a-table :dataSource="props.memberList" :columns="configColumns" :pagination="false">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'memberType'">
{{ getMemberType(record.memberType) }}
</template>
<template v-if="column.key === 'operation'">
<Icon icon="ant-design:delete-outlined" class="delete-icon" @click="deleteItem(index)" />
</template>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { NodeHead } from '/@/components/ModalPanel/index';
import Icon from '/@/components/Icon/index';
import Posts from '/@bpmn/components/member/Posts.vue';
import Roles from '/@bpmn/components/member/Roles.vue';
import Users from '/@bpmn/components/member/Users.vue';
import NodeApprover from '/@bpmn/components/member/NodeApprover.vue';
import UpperManagement from '/@bpmn/components/member/UpperManagement.vue';
import FormFields from '/@bpmn/components/member/FormFields.vue';
import { MemberType } from '/@/enums/workflowEnum';
import { MemberConfig } from '/@/model/workflow/memberSetting';
import { computed } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import ApiSelect from '/@bpmn/components/member/ApiSelect.vue';
const { t } = useI18n();
const emits = defineEmits(['update:memberList']);
const props = withDefaults(
defineProps<{
isCommonType: Boolean;
isUpper?: Boolean;
isApiApprover?: Boolean;
memberList: Array<MemberConfig>;
}>(),
{
isCommonType: () => false,
isUpper: () => false,
isApiApprover: () => false,
memberList: () => {
return [];
},
},
);
const configColumns = [
{
title: t('类型'),
dataIndex: 'memberType',
key: 'memberType',
align: 'center',
},
{
title: t('名称'),
dataIndex: 'name',
key: 'name',
align: 'center',
},
{
title: t('操作'),
dataIndex: 'operation',
key: 'operation',
align: 'center',
},
];
const allComponentList = computed(() => {
return [
{ name: t('添加岗位'), component: Posts, show: true },
{ name: t('添加角色'), component: Roles, show: true },
{ name: t('添加人员'), component: Users, show: true },
{ name: t('节点审批人'), component: NodeApprover, show: props.isCommonType ? false : true },
{
name: t('上级领导'),
component: UpperManagement,
show: props.isCommonType ? false : props.isUpper ? false : true,
},
{ name: t('表单字段'), component: FormFields, show: props.isCommonType ? false : true },
{ name: t('API审批人'), component: ApiSelect, show: props.isApiApprover ? true : false },
];
});
// 类型
function getMemberType(val: MemberType) {
if (val === MemberType.POST) return t('岗位');
if (val === MemberType.ROLE) return t('角色');
if (val === MemberType.USER) return t('人员');
if (val === MemberType.SPECIFY_NODE_APPROVER) return t('指定审批人');
if (val === MemberType.SUPERIOR_LEADERS) return t('上级领导');
if (val === MemberType.FORM_FIELD) return t('表单字段');
if (val === MemberType.API) return t('API审批人');
return val;
}
function changeList(list: Array<MemberConfig>) {
emits('update:memberList', list);
}
function deleteItem(index: number) {
let list = props.memberList;
list.splice(index, 1);
emits('update:memberList', list);
}
</script>
<style lang="less" scoped>
.list-box {
.opr-box {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
.header-box {
flex-basis: 220px;
align-items: flex-start;
margin-bottom: -10px;
}
}
.button-box {
display: flex;
align-items: center;
flex-wrap: wrap;
:deep(button) {
margin: 4px;
width: 88px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
.delete-icon {
color: @clear-color;
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<div>
<div @click="show"><slot></slot></div>
<ModalPanel
:visible="visible"
:width="400"
:title="t('指定节点审批人')"
@submit="submit"
@close="close"
>
<div class="title">
<NodeHead :node-name="t('节点列表')" />
</div>
<div class="list-box">
<a-checkbox
v-for="(item, index) in nodes.list"
:key="index"
:value="item.id"
size="small"
v-model:checked="item.checked"
>{{ item.name }}</a-checkbox
>
</div>
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { MemberType } from '/@/enums/workflowEnum';
import { MemberConfig, NodesConfig } from '/@/model/workflow/memberSetting';
import { NodeHead, ModalPanel } from '/@/components/ModalPanel/index';
import { InfoId } from '/@/model/workflow/bpmnConfig';
import { getNodeList } from '/@bpmn/config/info';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
memberList: Array<MemberConfig>;
}>(),
{
memberList: () => {
return [];
},
},
);
const visible = ref(false);
const memberType = MemberType.SPECIFY_NODE_APPROVER;
let nodes: { list: Array<NodesConfig> } = reactive({
list: [],
});
function show() {
nodes.list = [];
let selectIds: Array<InfoId> = [];
if (props.memberList.length > 0) {
selectIds = props.memberList
.filter((ele: MemberConfig) => {
if (ele.memberType && ele.memberType === memberType) return ele;
})
.map((ele: MemberConfig) => {
return ele.id;
});
}
getNodeList().map((item) => {
nodes.list.push({ id: item.id, name: item.name, checked: selectIds.includes(item.id) });
});
visible.value = true;
}
function submit() {
let list: Array<MemberConfig> = [];
if (props.memberList.length > 0) {
list = props.memberList.filter((ele: MemberConfig) => {
if (ele.memberType != memberType) return ele;
});
}
nodes.list.forEach((item: NodesConfig) => {
if (item.checked) {
list.push({
memberType: memberType,
name: item.name,
id: item.id,
});
}
});
emits('change', [...list]);
close();
}
function close() {
visible.value = false;
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 10px;
}
.list-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
margin: 0 20px;
}
:deep(.ant-checkbox-wrapper) {
margin-left: 8px;
margin-bottom: 10px;
}
:deep(.ant-checkbox-inner) {
border-radius: unset;
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
border-radius: unset;
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div>
<SelectPost :selectedIds="selectedIds" :multiple="true" @change="submit">
<a-button type="primary">{{ t('添加岗位') }}</a-button>
</SelectPost>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getPostMulti } from '/@/api/system/post';
import { SelectPost } from '/@/components/SelectOrganizational/index';
import { MemberType } from '/@/enums/workflowEnum';
import { MemberConfig } from '/@/model/workflow/memberSetting';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
memberList: Array<MemberConfig>;
}>(),
{
memberList: () => {
return [];
},
},
);
let selectedIds = computed(() => {
if (props.memberList && props.memberList.length > 0) {
return props.memberList
.filter((ele: MemberConfig) => {
return ele.memberType === MemberType.POST;
})
.map((ele: MemberConfig) => {
return ele.id;
});
} else {
return [];
}
});
async function submit(ids: Array<string>) {
let list: Array<MemberConfig> = [];
if (props.memberList && props.memberList.length > 0) {
props.memberList.forEach((ele) => {
if (ele.memberType != MemberType.POST)
list.push({
name: ele.name,
id: ele.id,
memberType: ele.memberType,
});
});
}
try {
let postRes = await getPostMulti(ids.join(','));
if (postRes.length > 0) {
postRes.forEach((ele) => {
list.push({
name: ele.name,
id: ele.id,
memberType: MemberType.POST,
});
});
}
} catch (error) {
ids.forEach((id) => {
if (id)
list.push({
name: id,
id: id,
memberType: MemberType.POST,
});
});
}
emits('change', list);
}
</script>

View File

@ -0,0 +1,78 @@
<template>
<div>
<SelectRole :selectedIds="selectedIds" :multiple="true" @change="submit">
<a-button type="primary">{{ t('添加角色') }}</a-button>
</SelectRole>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getRoleMulti } from '/@/api/system/role';
import { SelectRole } from '/@/components/SelectOrganizational/index';
import { MemberType } from '/@/enums/workflowEnum';
import { MemberConfig } from '/@/model/workflow/memberSetting';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
memberList: Array<MemberConfig>;
}>(),
{
memberList: () => {
return [];
},
},
);
let selectedIds = computed(() => {
if (props.memberList && props.memberList.length > 0) {
return props.memberList
.filter((ele: MemberConfig) => {
return ele.memberType === MemberType.ROLE;
})
.map((ele: MemberConfig) => {
return ele.id;
});
} else {
return [];
}
});
async function submit(ids: Array<string>) {
let list: Array<MemberConfig> = [];
if (props.memberList && props.memberList.length > 0) {
props.memberList.forEach((ele) => {
if (ele.memberType != MemberType.ROLE)
list.push({
name: ele.name,
id: ele.id,
memberType: ele.memberType,
});
});
}
try {
let roleRes = await getRoleMulti(ids.join(','));
if (roleRes.length > 0) {
roleRes.forEach((ele) => {
list.push({
name: ele.name,
id: ele.id,
memberType: MemberType.ROLE,
});
});
}
} catch (error) {
ids.forEach((id) => {
if (id)
list.push({
name: id,
id: id,
memberType: MemberType.ROLE,
});
});
}
emits('change', list);
}
</script>

View File

@ -0,0 +1,190 @@
<template>
<div>
<div @click="show"><slot></slot></div>
<ModalPanel
:visible="visible"
:width="400"
:title="t('上级领导配置')"
@submit="submit"
@close="close"
>
<div class="title">
<NodeHead :node-name="t('上级领导列表')" />
</div>
<div class="list-box">
<a-tree
checkable
:tree-data="data.treeData"
autoExpandParent
defaultExpandAll
v-model:checkedKeys="data.checkedKeys"
>
<template #title="{ title, key }">
<span v-if="key === '0-0-1-0'" style="color: #1890ff">{{ title }}</span>
<template v-else>{{ title }}</template>
</template>
</a-tree>
</div>
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import { BpmnNodeKey, LevelEnum, MemberType } from '/@/enums/workflowEnum';
import { MemberConfig } from '/@/model/workflow/memberSetting';
import { ModalPanel } from '/@/components/ModalPanel/index';
import { NodeHead } from '/@/components/ModalPanel/index';
import { TreeProps } from 'ant-design-vue';
import { separator } from '../../config/info';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
memberList: Array<MemberConfig>;
}>(),
{
memberList: () => {
return [];
},
},
);
const visible = ref(false);
const memberType = MemberType.SUPERIOR_LEADERS;
let data: {
treeData: TreeProps['treeData'];
checkedKeys: string[];
expandedNames: Object;
selected: Array<MemberConfig>;
} = reactive({ treeData: [], checkedKeys: [], expandedNames: {}, selected: [] });
function show() {
data.checkedKeys = [];
data.treeData = [];
if (props.memberList.length > 0) {
data.selected = props.memberList.filter((ele: MemberConfig) => {
if (ele.memberType === memberType) return ele;
});
if (data.selected.length > 0) {
data.selected.forEach((ele) => {
if (ele.leaderConfig?.level)
data.checkedKeys.push(ele.leaderConfig.nodeId + separator + ele.leaderConfig.level);
});
}
}
const store = useBpmnStore();
const { info } = store;
for (let item of info.values()) {
let showNodeTypes = [BpmnNodeKey.START, BpmnNodeKey.USER];
if (showNodeTypes.includes(item.type)) {
let name = item.name ? item.name : item.type;
data.treeData.push({
title: name,
key: item.id,
children: getLeaveChildren(item.id),
});
data.expandedNames[item.id] = name;
}
}
visible.value = true;
}
function submit() {
let list: Array<MemberConfig> = [];
if (props.memberList.length > 0) {
list = props.memberList.filter((ele: MemberConfig) => {
if (ele.memberType != memberType) return ele;
});
}
for (const leader of data.checkedKeys) {
let arr = leader.split(separator);
if (arr.length == 2) {
let nodeId = arr[0];
let level = Number(arr[1]);
list.push({
memberType: memberType,
id: nodeId,
name: data.expandedNames[nodeId] + '-' + getLeaveName(level),
leaderConfig: { nodeId, level },
});
}
}
emits('change', [...list]);
close();
}
function close() {
visible.value = false;
}
function getLeaveName(level: LevelEnum) {
switch (level) {
case LevelEnum.ONE:
return '上一级领导';
case LevelEnum.SECOND:
return '上二级领导';
case LevelEnum.THREE:
return '上三级领导';
case LevelEnum.FOUR:
return '上四级领导';
case LevelEnum.FIVE:
return '上五级领导';
default:
break;
}
}
function getLeaveChildren(id: string) {
return [
{
title: getLeaveName(LevelEnum.ONE),
key: id + separator + LevelEnum.ONE,
},
{
title: getLeaveName(LevelEnum.SECOND),
key: id + separator + LevelEnum.SECOND,
},
{
title: getLeaveName(LevelEnum.THREE),
key: id + separator + LevelEnum.THREE,
},
{
title: getLeaveName(LevelEnum.FOUR),
key: id + separator + LevelEnum.FOUR,
},
{
title: getLeaveName(LevelEnum.FIVE),
key: id + separator + LevelEnum.FIVE,
},
];
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
height: 40px;
font-size: 16px;
color: #333333;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 10px;
}
.list-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
overflow: auto;
}
:deep(.ant-tree-treenode) {
margin-left: 8px;
margin-bottom: 10px;
}
:deep(.ant-tree-list) {
height: 460px;
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<div>
<SelectUser :selectedIds="selectedIds" :multiple="true" @change="submit">
<a-button type="primary">{{ t('添加人员') }}</a-button>
</SelectUser>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getUserMulti } from '/@/api/system/user';
import { SelectUser } from '/@/components/SelectOrganizational/index';
import { MemberType } from '/@/enums/workflowEnum';
import { MemberConfig } from '/@/model/workflow/memberSetting';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
memberList: Array<MemberConfig>;
}>(),
{
memberList: () => {
return [];
},
},
);
let selectedIds = computed(() => {
if (props.memberList && props.memberList.length > 0) {
return props.memberList
.filter((ele: MemberConfig) => {
return ele.memberType === MemberType.USER;
})
.map((ele: MemberConfig) => {
return ele.id;
});
} else {
return [];
}
});
async function submit(ids: Array<string>) {
let list: Array<MemberConfig> = [];
if (props.memberList && props.memberList.length > 0) {
props.memberList.forEach((ele) => {
if (ele.memberType != MemberType.USER)
list.push({
name: ele.name,
id: ele.id,
memberType: ele.memberType,
});
});
}
let users = await getUserMulti(ids.join(','));
if (users.length > 0) {
users.forEach((ele) => {
list.push({
name: ele.name,
id: ele.id,
memberType: MemberType.USER,
});
});
}
emits('change', list);
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<div class="list-box">
<div class="opr-box">
<NodeHead :nodeName="t('表单操作列表')" />
<div class="button-box">
<a-button type="primary" @click="addItem">{{ t('添加') }}</a-button>
</div>
</div>
<div class="list">
<div class="row head">
<span class="common">{{ t('赋值来源') }}</span>
<span class="common">{{ t('目标表单字段') }}</span>
<span class="small">{{ t('操作') }}</span>
</div>
<div class="body" v-if="formInfo.assignmentConfig.formAssignmentConfigs.length > 0">
<div
class="row item"
v-for="(item, index) in formInfo.assignmentConfig.formAssignmentConfigs"
:key="index"
>
<span class="common">
<a-tree-select
v-model:value="item.source"
autoExpandParent
treeDefaultExpandAll
:tree-data="props.processParameter"
style="width: 100%"
:field-names="{
children: 'children',
label: 'name',
value: 'id',
}"
/>
</span>
<span class="common">
<FormTargetItem v-model:target="item.target" :formSettingTree="props.formSettingTree" />
</span>
<span @click="deleteItem(index)" class="small">
<Icon icon="ant-design:delete-outlined" class="delete-icon" />
</span>
</div>
</div>
<EmptyBox v-else :has-icon="false" />
</div>
</div>
</template>
<script setup lang="ts">
import { NodeHead, EmptyBox } from '/@/components/ModalPanel/index';
import Icon from '/@/components/Icon/index';
import FormTargetItem from './FormTargetItem.vue';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const props = defineProps({
formSettingTree: Array,
processParameter: Array,
});
const store = useBpmnStore();
const { infoId } = store;
const { info } = storeToRefs(store);
const formInfo = ref();
formInfo.value = info.value.get(infoId);
function addItem() {
formInfo.value.assignmentConfig.formAssignmentConfigs.push({
source: '',
target: { key: '', formId: '', formField: '' },
});
}
function deleteItem(index: number) {
formInfo.value.assignmentConfig.formAssignmentConfigs.splice(index, 1);
}
</script>
<style lang="less" scoped>
.list-box {
.opr-box {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
}
.list {
.row {
height: 40px;
line-height: 30px;
display: flex;
justify-content: space-around;
align-items: center;
span {
display: flex;
justify-content: center;
align-items: center;
}
.common {
flex-basis: 40%;
margin-right: 4px;
}
.small {
flex-basis: 10%;
}
}
.head {
background-color: #f9f9f9;
}
.item {
border-bottom: 1px solid #f9f9f9;
}
.delete-icon {
color: @clear-color;
}
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<a-tree-select
:value="props.target.key"
autoExpandParent
treeDefaultExpandAll
:tree-data="props.formSettingTree"
style="width: 100%"
:field-names="{
children: 'children',
label: 'title',
value: 'key',
}"
@select="changeTarget"
/>
</template>
<script setup lang="ts">
const emit = defineEmits(['update:target']);
const props = defineProps({
target: {
type: Object, //类型
default: null, //默认值
},
formSettingTree: {
type: Array, //类型
default: null, //默认值
},
});
function changeTarget(value, node) {
let target = props.target;
target.formId = node.formId;
target.key = value;
target.formField = node.formField;
emit('update:target', target);
}
</script>
<style scoped></style>

View File

@ -0,0 +1,193 @@
<template>
<div class="list-box">
<div class="opr-box">
<NodeHead :nodeName="t('参数赋值列表')" />
<div class="button-box">
<a-button type="primary" @click="add">{{ t('添加') }}</a-button>
</div>
</div>
<div class="list">
<div class="row head">
<span class="source">{{ t('赋值来源') }}</span>
<span class="target">{{ t('赋值配置') }}</span>
<span class="config">{{ t('目标参数') }}</span>
<span class="small">{{ t('操作') }}</span>
</div>
<div class="body" v-if="formInfo.assignmentConfig.paramAssignmentConfigs.length > 0">
<div
class="row item"
v-for="(item, index) in formInfo.assignmentConfig.paramAssignmentConfigs"
:key="index"
>
<span class="source"
><a-select v-model:value="item.type" :placeholder="t('请选择')" style="width: 100%">
<a-select-option :value="ParamType.VALUE"> {{ t('值') }} </a-select-option>
<a-select-option :value="ParamType.VARIABLE"> {{ t('变量') }} </a-select-option>
<a-select-option v-if="props.needFromData" :value="ParamType.FORM_DATA">
{{ t('表单数据') }}
</a-select-option>
<a-select-option :value="ParamType.API"> API </a-select-option>
</a-select></span
>
<span class="target">
<a-input
v-if="item.type == ParamType.VALUE"
v-model:value="item.value"
:placeholder="t('请填写参数值')"
/>
<a-tree-select
v-if="item.type == ParamType.VARIABLE"
v-model:value="item.varValue"
autoExpandParent
treeDefaultExpandAll
:tree-data="variableTree"
style="width: 100%"
:field-names="{
children: 'children',
label: 'title',
value: 'key',
}"
/>
<ScriptApiSelect
style="width: 100%"
v-if="item.type == ParamType.API"
v-model="item.apiConfig"
:need-hide-components="true"
/>
<FormTargetItem
v-if="item.type == ParamType.FORM_DATA"
v-model:target="item.formConfig"
:formSettingTree="props.formSettingTree"
/>
</span>
<span class="config">
<a-tree-select
v-model:value="item.target"
autoExpandParent
treeDefaultExpandAll
:tree-data="props.processParameter"
style="width: 100%"
:field-names="{
children: 'children',
label: 'name',
value: 'id',
}"
/></span>
<span @click="deleteItem(index)" class="small">
<Icon icon="ant-design:delete-outlined" class="delete-icon" />
</span>
</div>
</div>
<EmptyBox v-else :has-icon="false" />
</div>
</div>
</template>
<script setup lang="ts">
import { NodeHead, EmptyBox } from '/@/components/ModalPanel/index';
import Icon from '/@/components/Icon/index';
//import ScriptApiSelect from '/@bpmn/components/arguments/ApiSelect.vue'; //之前是调的这个现在换成ScriptApiSelect.vue
import ScriptApiSelect from '/@bpmn/components/arguments/ScriptApiSelect.vue';
import FormTargetItem from './FormTargetItem.vue';
import { ProcessArgumentTreeData } from '/@bpmn/config/rules';
import { ParamType } from '/@/enums/workflowEnum';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const props = defineProps({
formSettingTree: Array,
processParameter: Array,
needFromData: Boolean,
});
const store = useBpmnStore();
const { infoId } = store;
const { info } = storeToRefs(store);
const formInfo = ref();
formInfo.value = info.value.get(infoId);
const variableTree = ProcessArgumentTreeData;
// 新增
function add() {
formInfo.value.assignmentConfig.paramAssignmentConfigs.push({
type: ParamType.VARIABLE,
value: '',
varValue: '',
apiConfig: {
id: '',
name: '',
method: '',
requestParamsConfigs: [], //Query Params
requestHeaderConfigs: [], //Header
requestBodyConfigs: [], //Body
},
formConfig: { key: '', formId: '', formField: '' },
target: '',
});
}
//删除
function deleteItem(index: number) {
formInfo.value.assignmentConfig.paramAssignmentConfigs.splice(index, 1);
}
</script>
<style lang="less" scoped>
.list-box {
.opr-box {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
}
.list {
.row {
height: 40px;
line-height: 30px;
display: flex;
justify-content: space-around;
align-items: center;
span {
display: flex;
justify-content: center;
align-items: center;
}
.source {
flex-basis: 120px;
margin: 0 4px;
}
.config {
min-width: 120px;
margin: 0 4px;
}
.target {
flex: 1;
margin: 0 4px;
}
.small {
flex-basis: 10%;
}
}
.head {
background-color: #f9f9f9;
}
.item {
border-bottom: 1px solid #f9f9f9;
}
.delete-icon {
color: @clear-color;
}
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<a-tabs>
<a-tab-pane key="1" :tab="t('表单赋值')">
<FormAssignmentConfig :formSettingTree="targetTree" :processParameter="processParameter" />
</a-tab-pane>
<a-tab-pane key="2" :tab="t('参数赋值')">
<ParamAssignmentConfig
:needFromData="true"
:processParameter="processParameter"
:formSettingTree="sourceTree"
/>
</a-tab-pane>
</a-tabs>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getFormSettingTree, getProcessParamConfigs } from '/@bpmn/config/info';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import FormAssignmentConfig from './FormAssignmentConfig.vue';
import ParamAssignmentConfig from './ParamAssignmentConfig.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const bpmnStore = useBpmnStore();
const targetTree = computed(() => {
const { info, infoId } = bpmnStore;
if (infoId && info.has(infoId)) {
let value = info.get(infoId);
if (value?.formConfigs) {
return getFormSettingTree(value.formConfigs);
} else {
return [];
}
}
return [];
});
const sourceTree = computed(() => {
const { info, infoId } = bpmnStore;
if (infoId && info.has(infoId)) {
let value = info.get(infoId);
if (value?.formConfigs) {
return getFormSettingTree(value.formConfigs, infoId, value.name, true);
} else {
return [];
}
}
return [];
});
const processParameter = computed(() => {
return [
{
name: t('流程参数'),
id: 'processParameter',
disabled: true,
children: getProcessParamConfigs(),
},
];
});
</script>
<style scoped></style>

View File

@ -0,0 +1,174 @@
<template>
<div class="list-box">
<div class="opr-box">
<NodeHead :nodeName="t('流程参数')" />
<div class="button-box">
<a-button type="primary" @click="add">{{ t('添加参数') }}</a-button>
</div>
</div>
<a-table
:dataSource="processInfo.processParamConfigs"
:columns="configColumns"
:pagination="false"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'name'">
<a-input v-model:value="record.name" :placeholder="t('参数名称')" />
</template>
<template v-if="column.key === 'type'">
<a-select
v-model:value="record.type"
:placeholder="t('请选择类型')"
style="width: 80px"
@change="
() => {
record.value = null;
}
"
>
<a-select-option v-for="item in typeOptions" :key="item.id" :value="item.id">
{{ item.name }}
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'value'">
<a-input
v-if="record.type == OperationType.VALUE"
v-model:value="record.value"
:placeholder="t('请填写参数值')"
/>
<a-tree-select
v-if="record.type == OperationType.VARIABLE"
v-model:value="record.value"
autoExpandParent
treeDefaultExpandAll
:tree-data="variableTree"
style="width: 100%"
:placeholder="t('请选择参数值')"
:field-names="{
children: 'children',
label: 'title',
value: 'key',
}"
/>
<ScriptApiSelect
style="width: 100%"
v-if="record.type == OperationType.API"
v-model="record.apiConfig"
:need-hide-components="true"
/>
</template>
<template v-if="column.key === 'operation'">
<a-popconfirm @confirm="deleteItem(index)">
<template #title>
<p>{{ t('删除参数') }}</p>
<p>{{ t('删除流程参数会清空已引用该参数的所有配置,请确认是否继续?') }}</p>
<p class="pop-desc">{{
t('如果引用该流程参数的配置较多,清空时间会相应变长,请耐心等待。')
}}</p>
</template>
<Icon icon="ant-design:delete-outlined" class="delete-icon" />
</a-popconfirm>
</template>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { NodeHead } from '/@/components/ModalPanel/index';
import Icon from '/@/components/Icon/index';
//import ScriptApiSelect from '/@bpmn/components/arguments/ApiSelect.vue'; //之前是调的这个现在换成ScriptApiSelect.vue
import ScriptApiSelect from '/@bpmn/components/arguments/ScriptApiSelect.vue';
import { ProcessArgumentTreeData } from '/@bpmn/config/rules';
import { OperationType } from '/@/enums/workflowEnum';
import { useBpmnStore } from '/@bpmn/store/bpmn';
import { storeToRefs } from 'pinia';
import { randomNum } from '/@bpmn/util/random';
import { updateProcessParameterRelevance } from '/@bpmn/config/useUpdateAllFormInfo';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const configColumns = [
{
title: t('参数名称'),
dataIndex: 'name',
key: 'name',
width: 120,
},
{
title: t('类型'),
dataIndex: 'type',
key: 'type',
width: 80,
},
{
title: t('参数值'),
dataIndex: 'value',
key: 'value',
},
{
title: t('操作'),
dataIndex: 'operation',
key: 'operation',
width: 60,
},
];
const store = useBpmnStore();
const { processInfo } = storeToRefs(store);
let typeOptions = [
{
id: OperationType.VALUE,
name: t('值'),
},
{
id: OperationType.VARIABLE,
name: t('变量'),
},
{
id: OperationType.API,
name: 'API',
},
];
let variableTree = ProcessArgumentTreeData;
// 新增
function add() {
processInfo.value.processParamConfigs.push({
id: randomNum(),
name: '',
type: OperationType.VALUE,
value: '',
apiConfig: {
id: '',
name: '',
method: '',
requestParamsConfigs: [], //Query Params
requestHeaderConfigs: [], //Header
requestBodyConfigs: [], //Body
},
});
}
//删除
function deleteItem(index: number) {
updateProcessParameterRelevance(processInfo.value.processParamConfigs[index].id);
processInfo.value.processParamConfigs.splice(index, 1);
}
</script>
<style lang="less" scoped>
.list-box {
.opr-box {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
}
.delete-icon {
color: @clear-color;
}
.pop-desc {
font-size: 12px;
color: rgb(0 0 0 / 40%);
}
</style>