first commit

This commit is contained in:
ye1023
2026-04-09 15:09:34 +08:00
commit d8e3a63df1
2246 changed files with 352109 additions and 0 deletions
@@ -0,0 +1,149 @@
<template>
<div v-if="iframeUrl" class="component_div" style="overflow-y: auto">
<DesformView
v-if="designFormInfo.status"
class="desform-view"
:isOnline="false"
mode=""
:url="designFormInfo.url"
:parentNode="parentNode"
:desformCode="designFormInfo.code"
:dataId="designFormInfo.dataId"
/>
<iframe v-else :src="iframeUrl" frameborder="0" width="100%" :height="height" scrolling="auto"></iframe>
</div>
<div v-else class="component_div">
<Suspense v-if="path">
<template #default>
<component v-if="path" :is="currentComponent" :formData="formData" form-bpm></component>
</template>
<template #fallback>
<div style="width: 100%; text-align: center; padding-top: 60px">
<a-spin spinning tip="表单加载中..." />
</div>
</template>
</Suspense>
<div v-else>表单地址不存在</div>
</div>
</template>
<script lang="ts">
/**
* 流程动态表单
*/
import { ref, computed, reactive } from 'vue';
import { getToken } from '/@/utils/auth';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { importViewsFile } from '/@/utils';
import { useGlobSetting } from '../../../../../hooks/setting';
import { getBpmFormUrl } from "/@/utils/is";
export default {
name: 'BpmDynamicForm',
props: {
path: {
type: String,
default: '',
},
formData: {
type: Object,
default: () => {},
},
// 父级html
parentNode: { type: Object as PropType<HTMLElement> },
},
setup(props) {
const height = window.innerHeight - 120 + 'px';
/**
* 如果是表单设计器表单 需要设置一些参数
*/
const designFormInfo = reactive({
status: false,
code: '',
url: '',
dataId: ''
});
/**
* 获取表单地址
* @type {ComputedRef<unknown>}
*/
const iframeUrl = computed(() => {
const { domainUrl } = useGlobSetting();
//update:scott--date:20220830--for:注意未显示使用的const定义变量,ts编译的时候会被删掉,导致动态replace替换变量失效。
// 将任务ID放到计算函数内部 当formData改变的时候会触发iframeUrl重复赋值
let TASKID = props.formData.taskDefKey;
let TOKEN = getToken();
let DOMAIN_URL = domainUrl
// TOKEN = NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
// URL支持{{ window.xxx }}占位符变量
//const URL = (props.path || '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2));
const URL = getBpmFormUrl(props.path, TOKEN, DOMAIN_URL, TASKID);
if (isURL(URL)) {
if(URL.indexOf('desform/edit/')>=0 || URL.indexOf('desform/detail/')>=0){
designFormInfo.url = URL;
designFormInfo.status = true;
designFormInfo.dataId = props.formData.vars['BPM_DES_DATA_ID'];
designFormInfo.code = props.formData.vars['BPM_DES_FORM_CODE'];
console.log('设计器表单参数', designFormInfo)
}else{
designFormInfo.status = false
}
return URL;
} else {
return '';
}
});
// 表单地址兼容vue2
const FORM_PATH_MAP = {
'modules/bpm/task/form/OnlineFormDetail': 'super/bpm/process/components/OnlineFormDetail',
'modules/bpm/task/form/OnlineFormOpt': 'super/bpm/process/components/OnlineFormOpt',
//借款申请表单
'modules/extbpm/joa/modules/JoaLoanApplyForm': 'super/bpm/example/joa/loan/components/LoanApplyForm',
//借款表单
'modules/extbpm/joa/modules/JoaLoanForm': 'super/bpm/example/joa/loan/components/LoanForm',
//出差表单
'modules/extbpm/joa/modules/JoaBusinesStripForm': 'super/bpm/example/joa/businessTrip/components/BusinessTripForm',
//请假表单
'modules/extbpm/joa/modules/JoaEmployeeLeaveForm': 'super/bpm/example/joa/leave/components/LeaveForm',
//公文表单
'modules/extbpm/joa/modules/JoaDocSendingForm': 'super/bpm/example/joa/docSend/components/DocSendForm',
//批量请假单
'modules/extbpm/biz/modules/ExtBizLeaveForm': 'super/bpm/example/batch/components/BizLeaveForm',
};
/**
* 获取组件
* @type {ComputedRef<function(): *>}
*/
const currentComponent = computed(() => {
let temp = props.path;
if (FORM_PATH_MAP[temp]) {
temp = FORM_PATH_MAP[temp];
}
console.log('bpm组件名称:' + temp, 'bpm组件数据:' + props.formData);
return createAsyncComponent(() => importViewsFile(temp));
});
/**
* 判断是否URL地址
* @param {*} s
*/
function isURL(s) {
return /^http[s]?:\/\/.*/.test(s);
}
return {
height,
iframeUrl,
currentComponent,
designFormInfo
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,292 @@
<template>
<div>
<div class="jee-bpm-graphic-containers">
<div class="jee-bpm-graphic-canvas" :id="containerId"></div>
</div>
<bpm-node-info-modal @register="registerModal" @notify="handleModalVisible"></bpm-node-info-modal>
</div>
</template>
<script>
import { watch, ref, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import BpmNodeInfoModal from './BpmNodeInfoModal.vue';
import { useModal } from '/@/components/Modal';
import inherits from 'inherits';
import Viewer from 'bpmn-js/lib/Viewer';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import { append as svgAppend, attr as svgAttr, create as svgCreate } from 'tiny-svg';
import { query as domQuery } from 'min-dom';
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._modules = [].concat(Viewer.prototype._modules, [ZoomScrollModule, MoveCanvasModule]);
export default {
name: 'BpmGraphic',
props: {
// 流程实例ID
instanceId: {
type: String,
required: false,
},
center:{
type: Boolean,
default: false,
required: false
}
},
components: {
BpmNodeInfoModal,
},
emits: ['task'],
setup(props, { emit }) {
const url = {
getProcessInfo: '/act/designer/api/getProcessXmlByInstanceId',
getInstanceInfo: '/act/task/getFlowMsgByProcInstId',
getNodePositionInfo: '/act/task/getNodePositionInfo',
};
const [registerModal, { openModal, closeModal }] = useModal();
const containerId = 'jee-bpm-graphic-canvas';
let bpmViewer = null;
onMounted(() => {
newViewer();
});
let taskList = [];
let currentTaskId = '';
let currentNodeList = [];
let historyNodeList = [];
let historyLineList = [];
let delayHandler = '';
watch(
() => props.instanceId,
(val) => {
if (val) {
init();
}
},
{ immediate: true }
);
async function init() {
let params = { processInstanceId: props.instanceId };
// 1.加载流程设计xml
let xml = await defHttp.get({ url: url.getProcessInfo, params }, { isTransformResponse: false });
//console.log('xml', xml);
// 2.加载流程实例信息
let instanceInfo = await defHttp.get({ url: url.getInstanceInfo, params }, { isTransformResponse: false });
//console.log('instanceInfo', instanceInfo);
// 2.加载节点信息 NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
let nodeInfo = await defHttp.get({ url: url.getNodePositionInfo, params }, { isTransformResponse: false });
//console.log('nodeInfo', nodeInfo);
if (nodeInfo.success) {
taskList = nodeInfo.result.hisTasks;
emit('task', taskList);
}
// console.log('taskList', nodeInfo.result)
try {
// 3.解析流程实例信息
if (instanceInfo.success) {
historyNodeList = instanceInfo.result.highLightedActivitiIdList;
currentNodeList = instanceInfo.result.runningActivitiIdList;
historyLineList = instanceInfo.result.highLightedFlowIds;
}
// 4.绘制流程
newViewer();
const result = await bpmViewer.importXML(xml);
const { warnings } = result;
console.log('bpm graphic warnings', warnings);
// 5.调整图片位置
const canvas = bpmViewer.get('canvas');
if(props.center == true){
canvas.zoom('fit-viewport', true);
}
// 6.创建箭头标记
createArrow();
// 7.设置节点、线的颜色
setColor();
// 8.节点事件
addEvent();
} catch (err) {
console.log(err.message, err.warnings);
}
}
function setColor() {
// access viewer components
const canvas = bpmViewer.get('canvas');
// 获取到全部节点
const allShapes = bpmViewer.get('elementRegistry').getAll();
//循环节点添加class
allShapes.forEach((element) => {
const shapeId = element.businessObject.id;
// const shapeAttrs = element.businessObject.$attrs
//console.info('123element', element)
let type = element.type;
if (type == 'bpmn:ExclusiveGateway' || type == 'bpmn:InclusiveGateway' || type == 'bpmn:ParallelGateway') {
canvas.addMarker(shapeId, 'jee-bpm-gateway');
}
// add marker
if (element.businessObject.$type != 'bpmn:Group') {
if (element.businessObject.$type == 'bpmn:SequenceFlow') {
if (historyLineList.includes(shapeId)) {
canvas.addMarker(shapeId, 'jee-bpm-history-line');
}
} else {
if (historyNodeList.includes(shapeId) && !currentNodeList.includes(shapeId)) {
canvas.addMarker(shapeId, 'jee-bpm-history-node');
}
if (currentNodeList.includes(shapeId)) {
canvas.addMarker(shapeId, 'jee-bpm-current-node');
}
}
}
});
}
// 自定义箭头标记-默认箭头是黑色的
function createArrow() {
const marker = svgCreate('marker');
svgAttr(marker, {
id: 'active-arrow',
viewBox: '0 0 20 20',
refX: '11',
refY: '10',
markerWidth: '10',
markerHeight: '10',
orient: 'auto',
fill: '#408af1',
});
const path = svgCreate('path');
svgAttr(path, {
d: 'M 1 5 L 11 10 L 1 15 Z',
style: 'stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;',
});
const defs = domQuery('defs');
svgAppend(marker, path);
svgAppend(defs, marker);
}
//添加节点事件
function addEvent() {
const eventBus = bpmViewer.get('eventBus');
eventBus.on('element.hover', (e) => {
const { element } = e;
if (!element.parent) {
// 这里关闭modal
delayClose();
currentTaskId = '';
//console.log('鼠标移至空白处', element);
return;
}
if (!e || element.type === 'bpmn:Process') {
return false;
} else {
let temp = element.id;
let type = element.type;
if (currentTaskId != temp && 'bpmn:UserTask' == type && historyNodeList.indexOf(temp) >= 0) {
/**
* 满足3个条件才弹框显示节点信息
* 1.当前节点不是鼠标选中的节点,防止多次调用
* 2.必须是任务节点
* 3.必须是处理过的节点
*/
currentTaskId = temp;
//console.log('准备开启modal', e);
showNodeInfo();
}
}
});
}
function showNodeInfo() {
closeModal();
openModal(true, {
dataList: taskList,
taskId: currentTaskId,
});
}
function handleModalVisible(flag) {
//console.log('handleModalVisible', flag)
if (flag == true) {
clearTimeout(delayHandler);
}
}
function delayClose() {
//console.log('delayClose')
delayHandler = setTimeout(() => {
//console.log('准备关闭modal');
if (currentTaskId) {
showNodeInfo();
} else {
closeModal();
}
}, 500);
}
function newViewer() {
if (bpmViewer == null) {
let dom = document.getElementById(containerId);
bpmViewer = new CustomViewer({
container: dom,
});
}
}
return {
registerModal,
containerId,
handleModalVisible,
};
},
};
</script>
<style lang="less">
.jee-bpm-graphic-containers {
width: 100%;
height: calc(100vh - 250px);
}
.jee-bpm-graphic-canvas {
width: 100%;
height: 100%;
}
.jee-bpm-graphic-canvas .bjs-powered-by {
display: none;
}
/**网关样式*/
.jee-bpm-gateway .djs-visual path {
stroke: none !important;
}
/**走过的分支线样式 */
.jee-bpm-history-line .djs-visual > :nth-child(1) {
stroke: #408af1 !important;
}
.jee-bpm-history-line path {
marker-end: url(#active-arrow) !important;
stroke-width: 2px!important;
}
/**走过的节点样式 */
.jee-bpm-history-node .djs-visual > :nth-child(1) {
fill: #51a2f13b !important;
stroke: #408af1 !important;
}
/**当前节点样式 */
.jee-bpm-current-node .djs-visual > :nth-child(1) {
fill: #f9ca6d !important;
stroke: #cd9423 !important;
}
/* .jee-bpm-current-node .djs-visual > text {
fill: #fff !important;
}*/
</style>
@@ -0,0 +1,68 @@
<template>
<BasicModal title="流程图" @register="registerModal" keyboard maskClosable :bodyStyle="bodyStyle" :width="modalWidth" destroyOnClose :footer="null" @close="handleClose">
<a-spin :spinning="loading">
<BpmGraphic :instanceId="procInsId" center></BpmGraphic>
</a-spin>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, nextTick } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import BpmGraphic from './BpmGraphic.vue';
export default {
name: 'BpmGraphicModal',
components: {
BpmGraphic,
BasicModal,
},
setup() {
const procInsId = ref('');
const loading = ref(true);
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
const { flowCode, dataId } = data;
loading.value = true;
preview(flowCode, dataId);
});
const modalWidth = window.innerWidth * 0.8;
const bodyStyle = ref({});
let height = window.innerHeight - 180;
bodyStyle.value = {
height: height+'px',
overflowY: 'auto'
}
function preview(flowCode, dataId) {
let params = {
flowCode: flowCode,
dataId: dataId,
};
const url = '/act/process/extActFlowData/getProcessInfo';
defHttp.get({ url, params }).then((data) => {
procInsId.value = data.processInstanceId;
loading.value = false;
});
}
function handleClose() {
closeModal();
}
return {
registerModal,
handleClose,
procInsId,
loading,
modalWidth,
bodyStyle
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,137 @@
<template>
<BasicModal
title="任务审批详情"
@register="registerModal"
keyboard
:canFullscreen="false"
:width="280"
:mask="false"
:footer="null"
:centered="true"
@close="handleClose"
wrapClassName="jeecg-bpm-node-detail"
:bodyStyle="{ padding: '0' }"
>
<div style="height: 300px; padding-bottom: 5px; overflow: hidden; overflow-y: auto; overflow-x: auto">
<a-descriptions title="" size="small" :column="1" bordered v-for="item in nodeInfoList" style="margin-bottom: 5px">
<a-descriptions-item v-for="(schema, index) in nodeSchema" :key="index" :label="schema.label" :labelMinWidth="70">{{
item[schema.field]
}}</a-descriptions-item>
</a-descriptions>
</div>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, nextTick } from 'vue';
export default {
name: 'BpmNodeInfoModal',
components: {
BasicModal,
},
emits: ['register', 'notify'],
setup(_p, { emit }) {
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
const { dataList, taskId } = data;
getNodeInfo(dataList, taskId);
});
const nodeSchema = [
{
field: 'taskName',
label: '任务名称',
labelMinWidth: 60,
},
{
field: 'taskAssigneeId',
label: '执行人',
},
{
field: 'taskBeginTime',
label: '开始时间',
},
{
field: 'taskEndTime',
label: '结束时间',
},
{
field: 'durationStr',
label: '耗时',
},
{
field: 'remarks',
label: '意见',
/*style="word-break: break-all;"*/
},
];
const nodeInfoList = ref([]);
function getNodeInfo(dataList, taskId) {
let arr = [];
for (let item of dataList) {
if (item.taskId == taskId) {
arr.push(item);
}
}
nodeInfoList.value = arr;
toggleModalEvent(true);
}
function handleClose() {
closeModal();
nodeInfoList.value = [];
toggleModalEvent(false);
}
function notifyClose() {
emit('notify', false);
}
function notifyVisible() {
emit('notify', true);
}
// 鼠标进入/离开modal,都会通知父组件
function toggleModalEvent(flag) {
nextTick(() => {
const arr = document.getElementsByClassName('jeecg-bpm-node-detail');
let modal = arr[0];
if (modal) {
if (flag == true) {
modal.addEventListener('mouseenter', notifyVisible);
modal.addEventListener('mouseleave', notifyClose);
} else {
modal.removeEventListener('mouseenter', notifyVisible);
modal.removeEventListener('mouseleave', notifyClose);
}
}
});
}
return {
registerModal,
nodeInfoList,
nodeSchema,
handleClose,
};
},
};
</script>
<style lang="less">
.jeecg-bpm-node-detail {
pointer-events: none;
.ant-modal-header {
padding-top: 10px;
padding-bottom: 10px;
}
.ant-modal-close-x {
height: 46px;
}
.ant-descriptions-item-label {
padding: 8px !important;
width: 80px !important;
}
}
</style>
@@ -0,0 +1,46 @@
<template>
<div>
<process-online-form :table-name="tableName" :task-id="taskId" :data-id="dataId" disabled />
</div>
</template>
<script>
import ProcessOnlineForm from '/@/views/super/online/cgform/auto/comp/ProcessOnlineForm.vue';
import { ref } from 'vue';
export default {
name: 'OnlineFormDetail',
components: {
ProcessOnlineForm,
},
props: {
formData: {
type: Object,
default: () => {},
},
},
setup(props) {
const tableName = ref('');
const dataId = ref('');
const taskId = ref('');
//update--begin--autor:scott-----date:20191005------for:流程节点配置组件URL的时候也支持传递参数了,解决TASK #3238流程节点无法与online的复制视图对接------
//节点配置表单URL,VUE组件类型对应的拓展参数
let extendUrlParams = props.formData.extendUrlParams;
if (extendUrlParams && extendUrlParams.view) {
tableName.value = extendUrlParams.view;
} else {
tableName.value = props.formData.tableName;
}
//update--end--autor:scott-----date:20191005------for:流程节点配置组件URL的时候也支持传递参数了,解决TASK #3238流程节点无法与online的复制视图对接------
dataId.value = props.formData.dataId;
taskId.value = props.formData.taskDefKey;
return {
tableName,
dataId,
taskId,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,46 @@
<template>
<div>
<process-online-form :table-name="tableName" :task-id="taskId" :data-id="dataId" :disabled="false" />
</div>
</template>
<script>
import ProcessOnlineForm from '/@/views/super/online/cgform/auto/comp/ProcessOnlineForm.vue';
import { ref } from 'vue';
export default {
name: 'OnlineFormOpt',
components: {
ProcessOnlineForm,
},
props: {
formData: {
type: Object,
default: () => {},
},
},
setup(props) {
const tableName = ref('');
const dataId = ref('');
const taskId = ref('');
//update--begin--autor:scott-----date:20191005------for:流程节点配置组件URL的时候也支持传递参数了,解决TASK #3238流程节点无法与online的复制视图对接------
//节点配置表单URL,VUE组件类型对应的拓展参数
let extendUrlParams = props.formData.extendUrlParams;
if (extendUrlParams && extendUrlParams.view) {
tableName.value = extendUrlParams.view;
} else {
tableName.value = props.formData.tableName;
}
//update--end--autor:scott-----date:20191005------for:流程节点配置组件URL的时候也支持传递参数了,解决TASK #3238流程节点无法与online的复制视图对接------
dataId.value = props.formData.dataId;
taskId.value = props.formData.taskDefKey;
return {
tableName,
dataId,
taskId,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,263 @@
<template>
<BasicModal title="选择用户" @register="registerModal" width="100%" @ok="handleSelectSuccess" :canFullscreen="false" keyboard defaultFullscreen>
<a-row>
<!-- 左侧树-选择部门 -->
<a-col :xs="24" :sm="5">
<a-card title="组织机构" :bordered="true">
<a-alert type="info" :showIcon="true">
<template #message>
当前选择
<span v-if="departInfo.currentSelectRow.title">{{ departInfo.currentSelectRow.title }}</span>
<a v-if="departInfo.currentSelectRow.title" style="margin-left: 10px" @click="onClearSelectedDepart">取消选择</a>
</template>
</a-alert>
<!--组织机构-->
<a-directory-tree
selectable
:selectedKeys="departInfo.selectedKeys"
:checkStrictly="true"
@select="onSelectDepart"
:dropdownStyle="{ maxHeight: '200px', overflow: 'auto' }"
:load-data="onLoadTreeData"
:treeData="departInfo.treeData"
/>
</a-card>
</a-col>
<!-- 中间列表-展示用户信息 -->
<a-col :xs="24" :sm="13">
<a-card title="选择人员" :bordered="true" :bodyStyle="{ paddingTop: '1px' }">
<BasicTable @register="registerTable" :rowSelection="rowSelection" />
</a-card>
</a-col>
<!-- 右侧显示已经选中用户支持调整顺序 -->
<a-col :xs="24" :sm="6">
<a-card title="已选用户" :bordered="true">
<BasicTable @register="registerSelectedUserTable">
<!--操作栏-->
<template #action="{ record }">
<a-button type="primary" size="small" @click="handleDelete(record)" preIcon="ant-design:delete">删除</a-button>
</template>
</BasicTable>
</a-card>
</a-col>
</a-row>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, nextTick, unref, reactive, toRaw, watch } from 'vue';
import { getDepartTreeData, getDepartUserList, getUserList, columns, selectedUserColumns, searchFormSchema } from './useSelectUser';
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
export default {
name: 'BpmSelectUserModal',
components: {
BasicModal,
BasicTable,
},
props: {
multi: {
type: Boolean,
default: true,
},
},
emits: ['selected', 'register'],
setup(props, { emit }) {
const selectedList = ref([]);
const [registerModal, { closeModal }] = useModalInner((data) => {
showSelectedValue(data);
});
/*-----------------部门---begin----------------*/
const departInfo = reactive({
treeData: [],
selectedKeys: [],
currentSelectRow: {
title: '',
},
});
function onSelectDepart(data, { node }) {
departInfo.selectedKeys[0] = data[0];
departInfo.currentSelectRow = toRaw(node.dataRef);
console.log(departInfo);
reload();
}
function onClearSelectedDepart() {
departInfo.selectedKeys = [];
departInfo.currentSelectRow = { title: '' };
reload();
}
async function loadRootDepart() {
const result = await getDepartTreeData();
if (Array.isArray(result)) {
departInfo.treeData = result;
}
}
async function onLoadTreeData(treeNode) {
try {
const result = await getDepartTreeData({
pid: treeNode.dataRef.id,
});
if (result.length == 0) {
treeNode.dataRef.isLeaf = true;
} else {
treeNode.dataRef.children = result;
}
// departInfo.treeData = [...departInfo.treeData]
} catch (e) {
console.error('部门树子节点加载失败', e);
}
return Promise.resolve();
}
/*-----------------部门---end----------------*/
/*-----------------用户列表---begin----------------*/
async function queryUserList(params) {
let arr = departInfo.selectedKeys;
if (arr.length > 0) {
//根据部门查询
params['id'] = arr[0];
let result = await getDepartUserList(params);
if (params.username) {
result.records = result.records.filter((item) => {
return item.username.indexOf(params.username) != -1;
});
}
return Promise.resolve(result);
} else {
return getUserList(params);
}
}
const { tableContext } = useListPage({
designScope: 'bpm-select-user',
pagination: true,
tableProps: {
title: '',
api: queryUserList,
columns: columns,
showActionColumn: false,
showTableSetting: false,
canResize: false,
clickToRowSelect: true,
formConfig: {
labelWidth: '90px',
schemas: searchFormSchema,
autoAdvancedCol: 4,
//update-begin-author:liusq---date:2024-06-11--for: 指定会签人员的弹框 查询遮挡了
baseColProps: { xs: 24, sm: 24, md: 24, lg: 12, xl: 8, xxl: 8 },
actionColOptions: { xs: 24, sm: 24, md: 24, lg: 12, xl: 8, xxl: 8 },
//update-end-author:liusq---date:2024-06-11--for:指定会签人员的弹框 查询遮挡了
},
},
});
const [registerTable, { reload, deleteSelectRowByKey }, { rowSelection, selectedRows, selectedRowKeys }] = tableContext;
watch(
() => props.multi,
(val) => {
if (val === false) {
rowSelection.type = 'radio';
} else {
rowSelection.type = 'checkbox';
}
}
);
/*-----------------用户列表--end-----------------*/
const selectedUserList = ref([]);
//update-begin-author:liusq---date:2024-06-11--for: TV360X-1047 指定下一步操作人/抄送给,选人组件无法多选。
watch(
selectedRows,
() => {
let arr = [];
for (let row of unref(selectedRows)) {
arr.push({
realname: row.realname,
username: row.username,
id: row.id,
});
}
selectedUserList.value = arr;
},
{ deep: true }
);
//update-end-author:liusq---date:2024-06-11--for: TV360X-1047 指定下一步操作人/抄送给,选人组件无法多选。
const { tableContext: selectedTableContext } = useListPage({
designScope: 'bpm-select-user',
pagination: false,
tableProps: {
title: '',
columns: selectedUserColumns,
pagination: false,
dataSource: selectedUserList,
showActionColumn: true,
showTableSetting: false,
canResize: false,
useSearchForm: false,
},
});
const [registerSelectedUserTable] = selectedTableContext;
function handleDelete(record) {
let id = record.id;
let arr = selectedUserList.value;
arr = arr.filter((item) => item.id != id);
selectedUserList.value = arr;
deleteSelectRowByKey(record.id);
}
function handleSelectSuccess() {
let arr = toRaw(selectedUserList.value);
emit('selected', arr);
closeModal();
}
/**
* 弹框打开 回显下拉框选中的数据
* @param data
*/
function showSelectedValue(data) {
let selectedValue = data.selected;
if (!selectedValue || selectedValue.length == 0) {
selectedUserList.value = [];
selectedRows.value = [];
selectedRowKeys.value = [];
} else {
let arr1 = [],
arr2 = [],
arr3 = [];
for (let item of selectedValue) {
arr1.push(item.id);
arr2.push({ ...item });
arr3.push({ ...item });
}
selectedRowKeys.value = arr1;
selectedUserList.value = arr2;
selectedRows.value = arr3;
}
}
loadRootDepart();
return {
registerModal,
handleSelectSuccess,
selectedList,
departInfo,
onSelectDepart,
onClearSelectedDepart,
onLoadTreeData,
registerTable,
rowSelection,
registerSelectedUserTable,
handleDelete,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,101 @@
<template>
<div class="bpmSelectUser" v-bind="$attrs">
<a-select style="width: 300px" mode="multiple" :placeholder="placeholder" :value="selectValue" :options="options" @change="handleChange" />
<a-button type="primary" @click="openSelect" preIcon="ant-design:search-outlined" style="margin-left: 8px">选择</a-button>
<a-button type="primary" @click="clearSelected" preIcon="ant-design:reload-outlined" style="margin-left: 8px">清空</a-button>
</div>
<teleport to="body">
<bpm-select-user-modal @register="registerModal" @selected="onSelected" />
</teleport>
</template>
<script>
/**
* 流程审批中选择用户用 - 只管选择,不管回显
*/
import { ref, toRaw, computed } from 'vue';
import BpmSelectUserModal from './BpmSelectUserModal.vue';
import { useModal } from '/@/components/Modal';
export default {
name: 'BpmSelectUser',
components: {
BpmSelectUserModal,
},
props: {
placeholder: {
type: String,
default: '',
},
},
emits: ['change'],
setup(_p, { emit }) {
let selectedUserList = [];
const options = ref([]);
const selectValue = ref([]);
const [registerModal, { openModal }] = useModal();
const lastSelectedRows = ref([]);
function openSelect() {
let arr = getModalData();
openModal(true, {
selected: arr,
});
selectedUserList = [];
}
function clearSelected() {
selectValue.value = [];
}
function getModalData() {
//找rows
let arr = lastSelectedRows.value;
//找username
let arr2 = selectValue.value;
let dataArray = arr.filter((item) => arr2.indexOf(item.username) >= 0);
return dataArray;
}
function onSelected(data) {
lastSelectedRows.value = data;
let arr1 = [],
arr2 = [];
if (data && data.length > 0) {
data.map((item) => {
arr1.push(item.username);
arr2.push({ value: item.username });
});
}
selectValue.value = arr1;
options.value = arr2;
selectedUserList = data;
emit('change', data);
}
function handleChange(values) {
selectValue.value = values;
let data = selectedUserList.filter((item) => values.indexOf(item.username) >= 0);
emit('change', data);
}
return {
selectValue,
options,
openSelect,
clearSelected,
onSelected,
handleChange,
registerModal,
};
},
};
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-1050】Safari浏览器指定下一步处理人页面控件没对齐
.ant-select {
vertical-align: middle;
}
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-1050】Safari浏览器指定下一步处理人页面控件没对齐
</style>
@@ -0,0 +1,88 @@
import { defHttp } from '/@/utils/http/axios';
import { BasicColumn, FormSchema } from '/@/components/Table';
export enum Api {
departList = '/sys/sysDepart/queryDepartTreeSync',
userList = '/sys/user/list',
departUserList = '/sys/user/queryUserByDepId',
}
/**
* 获取部门树列表
*/
export const getDepartTreeData = (params?) => defHttp.get({ url: Api.departList, params });
/**
* 获取用户列表
*/
export const getUserList = (params?) => defHttp.get({ url: Api.userList, params });
/**
* 获取指定部门用户列表
*/
export const getDepartUserList = (params?) => defHttp.get({ url: Api.departUserList, params });
/**
* 用户列表
*/
export const columns: BasicColumn[] = [
{
title: '用户账号',
align: 'center',
dataIndex: 'username',
ellipsis: true,
width: 130,
},
{
title: '用户姓名',
align: 'center',
width: 150,
dataIndex: 'realname',
ellipsis: true,
},
{
title: '部门',
align: 'center',
width: 150,
dataIndex: 'orgCode',
},
];
/**
* 选中用户列表
*/
export const selectedUserColumns: BasicColumn[] = [
{
title: '用户姓名',
align: 'center',
width: 150,
dataIndex: 'realname',
ellipsis: true,
},
];
/**
* 查询条件
*/
export const searchFormSchema: FormSchema[] = [
{
label: '用户姓名',
field: 'realname',
component: 'Input',
componentProps: {
style: {
width: '150px',
},
},
},
{
label: '用户账号',
field: 'username',
component: 'Input',
componentProps: {
style: {
width: '150px',
},
},
},
];
@@ -0,0 +1,44 @@
<template>
<!--流程图弹窗-->
<BasicModal v-bind="$attrs" :bodyStyle="bodyStyle" :width="900" destroyOnClose :footer="null" @register="registerModal" title="流程图">
<BpmGraphic :instanceId="procInsId"></BpmGraphic>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref, reactive, toRaw } from 'vue';
import { BasicModal, useModalInner } from '/src/components/Modal';
import BpmGraphic from '/src/views/super/bpm/process/components/BpmGraphic.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { getProcessInfo } from './bpm.api';
// Emits声明
const emit = defineEmits(['register']);
// 提示声明
const $message = useMessage();
// 流程实例id
const procInsId = ref('');
//样式
const bodyStyle = {
'overflow-y': 'auto',
'overflow-x': 'auto',
};
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (params) => {
//初始化数据
await initData(params);
});
/**
* 初始化流程数据
* @param params
*/
async function initData(params) {
let res = await getProcessInfo(params);
if (res.success) {
procInsId.value = res.result.processInstanceId;
} else {
$message.warning(res.message);
}
}
</script>
@@ -0,0 +1,126 @@
<template>
<!-- 历史流程任务处理弹出框 -->
<a-modal
width="80%"
style="top: 20px"
destroyOnClose
:title="data.title"
v-model:open="data.visible"
:bodyStyle="data.bodyStyle"
:footer="null"
@cancel="handleModalCancel"
>
<a-tabs defaultActiveKey="1" tabPosition="left">
<a-tab-pane key="1">
<template #tab>
<Icon icon="ant-design:file-text-outlined" />
<span>附加单据</span>
</template>
<div class="component_div">
<template v-if="data.compType == 'comp'">
<DynamicLink :path="path" :formData="formData"></DynamicLink>
</template>
<template v-else-if="data.compType == 'iframe'">
<iframe :src="data.iframeUrl" frameborder="0" width="100%" :height="data.height" scrolling="auto"></iframe>
</template>
</div>
</a-tab-pane>
<a-tab-pane key="2">
<template #tab>
<Icon icon="ant-design:file-text-outlined" />
<span>审批记录</span>
</template>
<HisTaskModule :formData="formData"></HisTaskModule>
</a-tab-pane>
<a-tab-pane key="3">
<template #tab>
<Icon icon="ant-design:sliders-outlined" />
<span>流程跟踪</span>
</template>
<ProcessDiagram :formData="formData"></ProcessDiagram>
</a-tab-pane>
</a-tabs>
</a-modal>
</template>
<script lang="ts" setup>
import { computed, ref, unref, reactive } from 'vue';
import {getBpmFormUrl, isUrl} from '/@/utils/is';
import { getToken } from '/@/utils/auth';
import { useGlobSetting } from '/@/hooks/setting';
import DynamicLink from './DynamicLink.vue';
import ProcessDiagram from './ProcessDiagram.vue';
import HisTaskModule from './HisTaskModule.vue';
import { getBizHisProcessNodeInfo } from './bpm.api';
const { domainUrl } = useGlobSetting();
//数据
const data = reactive({
loading: false,
title: '流程',
visible: false,
bodyStyle: {
padding: '0',
height: window.innerHeight - 80 + 'px',
'overflow-y': 'auto',
},
height: window.innerHeight - 120 + 'px',
iframeUrl: '',
compType: '',
});
const formData = ref({});
const path = ref('');
/**
* 关闭弹窗
*/
function handleModalCancel() {
data.visible = false;
}
/**
* 打开弹窗前处理
* @param record
*/
async function handleTrack(params) {
let res = await getBizHisProcessNodeInfo(params);
if (res.success) {
console.log('获取流程节点信息', res);
formData.value = {
dataId: res.result.dataId,
procInsId: res.result.procInsId,
tableName: res.result.tableName,
vars: res.result.records,
};
console.log('------获取流程节点信息', unref(formData));
path.value = res.result.formUrl;
console.log('获取流程节点信息', path);
let TOKEN = getToken();
let DOMAIN_URL = domainUrl;
let TASKID = unref(formData).taskDefKey;
//let URL = (unref(path) || '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2)); // URL支持{{ window.xxx }}占位符变量
//获取流程审批url
let URL = getBpmFormUrl(unref(path), TOKEN, DOMAIN_URL, TASKID);
if (isUrl(URL)) {
data.iframeUrl = URL;
data.compType = 'iframe';
} else {
data.compType = 'comp';
}
data.visible = true;
}
}
defineExpose({
handleTrack,
data,
});
</script>
<style lang="less" scoped>
.ant-tabs-left-content {
padding-top: 10px !important;
}
</style>
@@ -0,0 +1,59 @@
<template>
<!--委派弹窗-->
<BasicModal
v-bind="$attrs"
@register="registerModal"
destroyOnClose
:bodyStyle="{ minHeight: '100px', maxHeight: '100px' }"
:title="title"
@ok="handleSubmit"
:width="700"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { delegateFormSchema } from './bpm.data';
// 声明Emits
const emit = defineEmits(['success', 'register']);
//组件接受传参
const props = defineProps({
title: { type: String, default: '请选择委托人', required: false },
});
//表单配置
const [registerForm, { resetFields, validate }] = useForm({
labelWidth: 90,
schemas: delegateFormSchema,
showActionButtonGroup: false,
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
});
//表单提交事件
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
//关闭弹窗
closeModal();
//刷新列表
emit('success', values);
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style scoped>
/*update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】3、租户角色下,查询居左显示*/
:deep(.ant-form-item-control-input-content){
text-align: left;
}
/*update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】3、租户角色下,查询居左显示*/
</style>
@@ -0,0 +1,55 @@
<template>
<div>
<Suspense v-if="comp">
<template #default>
<component :is="comp" :formData="formData" v-if="comp" form-bpm></component>
</template>
<template #fallback>
<div style="width: 100%; text-align: center; padding-top: 60px">
<a-spin spinning tip="表单加载中..." />
</div>
</template>
</Suspense>
<div v-else>表单地址不存在</div>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, computed, markRaw } from 'vue';
import { importViewsFile } from '/@/utils';
//组件接受传参
const props = defineProps({
path: { type: String },
formData: { type: Object },
});
// 表单地址兼容vue2
const FORM_PATH_MAP = {
'modules/bpm/task/form/OnlineFormDetail': 'super/bpm/process/components/OnlineFormDetail',
'modules/bpm/task/form/OnlineFormOpt': 'super/bpm/process/components/OnlineFormOpt',
//借款申请表单
'modules/extbpm/joa/modules/JoaLoanApplyForm': 'super/bpm/example/joa/loan/components/LoanApplyForm',
//借款表单
'modules/extbpm/joa/modules/JoaLoanForm': 'super/bpm/example/joa/loan/components/LoanForm',
//出差表单
'modules/extbpm/joa/modules/JoaBusinesStripForm': 'super/bpm/example/joa/businessTrip/components/BusinessTripForm',
//请假表单
'modules/extbpm/joa/modules/JoaEmployeeLeaveForm': 'super/bpm/example/joa/leave/components/LeaveForm',
//公文表单
'modules/extbpm/joa/modules/JoaDocSendingForm': 'super/bpm/example/joa/docSend/components/DocSendForm',
//批量请假单
'modules/extbpm/biz/modules/ExtBizLeaveForm': 'super/bpm/example/batch/components/BizLeaveForm',
};
//组件路径
/**
* 获取组件
* @type {ComputedRef<function(): *>}
*/
const comp = computed(() => {
let temp = props.path;
if (FORM_PATH_MAP[temp]) {
temp = FORM_PATH_MAP[temp];
}
console.log('bpm组件名称:', temp);
console.log('bpm组件数据:', props.formData);
return defineAsyncComponent(() => importViewsFile(temp));
});
</script>
@@ -0,0 +1,126 @@
<template>
<!-- 历史流程任务处理弹出框 -->
<a-modal
width="100%"
style="top: 0"
wrapClassName="full-modal"
destroyOnClose
:title="data.title"
v-model:open="data.visible"
:bodyStyle="data.bodyStyle"
:footer="null"
@cancel="handleModalCancel"
>
<a-tabs defaultActiveKey="1" tabPosition="left">
<a-tab-pane key="1">
<template #tab>
<Icon icon="ant-design:file-text-outlined" />
<span>附加单据</span>
</template>
<div class="component_div">
<template v-if="isComp">
<DynamicLink v-if="path" :path="path" :formData="formData"></DynamicLink>
<div v-else>表单地址不存在</div>
</template>
<template v-else>
<iframe :src="data.iframeUrl" frameborder="0" width="100%" :height="data.height" scrolling="auto"></iframe>
</template>
</div>
</a-tab-pane>
<a-tab-pane key="2">
<template #tab>
<Icon icon="ant-design:user-outlined" />
<span>任务处理</span>
</template>
<HisTaskModule :formData="formData"></HisTaskModule>
</a-tab-pane>
<a-tab-pane key="3">
<template #tab>
<Icon icon="ant-design:sliders-outlined" />
<span>流程图</span>
</template>
<ProcessDiagram :formData="formData"></ProcessDiagram>
</a-tab-pane>
</a-tabs>
</a-modal>
</template>
<script lang="ts" setup>
import { computed, reactive } from 'vue';
import { getBpmFormUrl, isUrl} from '/@/utils/is';
import { getToken } from '/@/utils/auth';
import { useGlobSetting } from '/@/hooks/setting';
import DynamicLink from './DynamicLink.vue';
import ProcessDiagram from './ProcessDiagram.vue';
import HisTaskModule from './HisTaskModule.vue';
const globSetting = useGlobSetting();
//组件接受传参
const props = defineProps({
path: { type: String },
formData: { type: Object },
});
//数据
const data = reactive({
loading: false,
title: '流程',
visible: false,
bodyStyle: {
padding: '0',
height: window.innerHeight - 80 + 'px',
'overflow-y': 'auto',
},
height: window.innerHeight - 120 + 'px',
iframeUrl: '',
});
let TOKEN = getToken();
let DOMAIN_URL = globSetting.domainUrl;
let TASKID = props?.formData?.taskDefKey;
//是否组件
const isComp = computed(() => {
//获取流程审批url
//let URL = (props.path || '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2)); // URL支持{{ window.xxx }}占位符变量
let URL = getBpmFormUrl(props.path, TOKEN, DOMAIN_URL, TASKID);
if (isUrl(URL)) {
data.iframeUrl = URL;
return false;
}
return true;
});
/**
* 关闭弹窗
*/
function handleModalCancel() {
data.visible = false;
}
/**
* 打开弹窗前处理
* @param record
*/
function deal(record) {
data.visible = true;
}
defineExpose({
deal,
data,
});
</script>
<style lang="less" scoped>
.component_div {
margin-top: 5px;
margin-bottom: 5px;
}
:deep(.ant-modal) {
top: 0;
padding: 0;
}
</style>
@@ -0,0 +1,202 @@
<template>
<!--流程任务历史-->
<div>
<!-- 步骤条 -->
<a-spin :spinning="loading">
<a-card>
<a-steps progressDot :current="stepIndex" style="padding: 10px" size="default">
<template v-if="resultObj.bpmLogListCount > 3">
<a-step>
<template #title>
<div class="task-title">...</div>
</template>
</a-step>
</template>
<template v-for="(item, index) in resultObj.bpmLogStepList">
<a-step>
<template #title>
<div class="task-title">{{ item.taskName }}</div>
</template>
<template #description>
<div class="descriptionDiv">
<span>
<a-avatar shape="square" style="background-color: #40a9ff">
<template #icon><UserOutlined /></template>
</a-avatar>
</span>
<span style="margin-left: 5px">
<div class="task-date" style="text-align: left">
<a-tooltip placement="top">
<template #title
><span>{{ item.opTime }}</span></template
>
<span> {{ item.opTime ? item.opTime.substr(0, 10) : item.opTime }}</span>
</a-tooltip>
</div>
<div class="task-user" style="text-align: left"
><span> {{ item.opUserName }}</span></div
>
</span>
</div>
</template>
</a-step>
</template>
<template v-if="resultObj.currTaskName && resultObj.currTaskName != ''">
<a-step>
<template #title>
<div class="task-title">{{ resultObj.currTaskName }}</div>
</template>
<template #description>
<div class="descriptionDiv">
<a-avatar style="background-color: #faad14eb">
<template #icon><UserOutlined /></template>
</a-avatar>
<span style="margin-left: 5px">
<div class="task-date" style="text-align: left">
<a-tooltip placement="top">
<template #title
><span>{{ resultObj.currTaskNameStartTime }}</span></template
>
<span style="color: #ff6d75">
{{
resultObj.currTaskNameStartTime ? resultObj.currTaskNameStartTime.substr(0, 10) : resultObj.currTaskNameStartTime
}}</span
>
</a-tooltip>
</div>
<div class="task-user" style="text-align: left"
><span> {{ resultObj.currTaskNameAssignee }}</span></div
>
</span>
</div>
</template>
</a-step>
<a-step>
<template #title>
<div class="task-title">...</div>
</template>
</a-step>
</template>
</a-steps>
</a-card>
<!-- 意见 -->
<a-card title="意见信息" :bodyStyle="{ padding: '0 20px' }" size="default" style="margin-top: 20px">
<a-list itemLayout="vertical">
<template v-for="(item, index) in resultObj.bpmLogList">
<a-list-item>
<a-list-item-meta :description="item.remarks||'无意见信息'">
<template #title>
<a
><p>{{ item.opUserName }}</p
><span style="color: #ff6d75">[{{ item.taskName }}]</span> {{ item.opTime }}</a
>
</template>
<template #avatar>
<a-avatar :size="36" style="background-color: #51cbff">
<template #icon><UserOutlined /></template>
</a-avatar>
</template>
</a-list-item-meta>
<template v-for="(file, index) in item.bpmFiles" :key="index">
<div class="ant-upload-list ant-upload-list-text">
<div class="ant-upload-list-item ant-upload-list-item-done">
<div class="ant-upload-list-item-info">
<span>
<PaperClipOutlined />
<a
target="_blank"
rel="noopener noreferrer"
:title="file.fileName"
:href="getFileAccessHttpUrl(file.filePath)"
class="ant-upload-list-item-name"
>{{ file.fileName }}</a
>
</span>
</div>
</div>
</div>
</template>
</a-list-item>
</template>
</a-list>
</a-card>
</a-spin>
</div>
</template>
<script lang="ts" setup>
import { ref, unref, onMounted, computed } from 'vue';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { getHisProcessTaskTransInfo } from './bpm.api';
import { UserOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
//组件接受传参
const props = defineProps({
formData: { type: Object },
});
//组件接受传参
const resultObj = ref({});
const loading = ref(false);
//步骤点
const stepIndex = computed(() => {
if (unref(resultObj).bpmLogListCount > 3) {
return unref(resultObj).bpmLogStepListCount + 1;
}
return unref(resultObj).bpmLogStepListCount;
});
/**
* 加载数据
* @param formData
*/
async function loadData(formData) {
var params = { procInstId: formData.procInsId }; //查询条件
loading.value = true;
const res = await getHisProcessTaskTransInfo(params);
loading.value = false;
if (res.success) {
resultObj.value = res.result;
}
}
onMounted(() => {
loadData(props.formData);
});
</script>
<style scoped>
.task-info {
margin: 20px 0;
}
.task-title {
font-weight: bold;
}
.task-date {
text-overflow: ellipsis;
white-space: nowrap;
}
.ant-steps-item-description {
max-width: 200px !important;
}
/** Button按钮间距 */
.ant-btn {
margin-left: 3px;
}
/** 标题和描述对齐 */
:deep(.ant-steps-item-content) {
text-align: left;
margin-left: 50px;
}
/** 描述的样式 */
.descriptionDiv {
display: flex;
justify-content: left;
align-items: center;
margin-top: 5px;
}
</style>
@@ -0,0 +1,133 @@
<template>
<div>
<div class="graphic">
<!--流程图 -->
<BpmGraphic :instanceId="formData.procInsId" @task="getTaskList"></BpmGraphic>
</div>
<a-card title="流程历史跟踪">
<a-table rowKey="taskId" :loading="loading" :dataSource="dataSource" :columns="columns" size="small">
<!-- 字符串超长截取省略号显示-->
<template #remarks="{ record }">
<JEllipsis :value="getNodeInfo(record)" :length="25" />
</template>
</a-table>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, unref, onMounted } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
import BpmGraphic from '/@/views/super/bpm/process/components/BpmGraphic.vue';
import { getProcessHistoryList } from './bpm.api';
//组件接受传参
const props = defineProps({
formData: { type: Object },
});
//提示
const { createMessage } = useMessage();
const loading = ref(false);
//列表数据
const dataSource = ref([]);
const taskList = ref([]);
// 查询数据
async function loadData() {
loading.value = true;
let params = { processInstanceId: props.formData.procInsId };
const res = await getProcessHistoryList(params);
loading.value = false;
if (res.success) {
dataSource.value = res.result.records;
} else {
createMessage.warning('加载失败');
}
}
function getTaskList(result) {
taskList.value = result;
}
/**
* 获取节点备注信息
* @param record
*/
function getNodeInfo(record) {
let arr = taskList.value;
if (arr && arr.length > 0) {
for (let item of arr) {
if (item.id == record.id) {
return item.remarks;
}
}
}
return '';
}
onMounted(() => {
taskList.value = [];
loadData();
});
//定义列
const columns = [
{
title: '#',
dataIndex: '#',
width: 40,
customRender: ({ text, index }) => {
return parseInt(index) + 1;
},
},
{
title: '名称',
dataIndex: 'name',
customRender: ({ text }) => {
if (text == 'start1') {
return '开始';
} else if (text == 'end') {
return '结束';
} else {
return text;
}
},
},
{
title: '流程实例ID',
dataIndex: 'processInstanceId',
},
{
title: '开始时间',
dataIndex: 'startTime',
},
{
title: '结束时间',
dataIndex: 'endTime',
},
{
title: '负责人',
dataIndex: 'assigneeName',
},
{
title: '处理结果',
dataIndex: 'deleteReason',
},
{
title: '处理意见',
fixed: 'right',
width: 350,
dataIndex: 'remarks',
slots: { customRender: 'remarks' },
},
];
</script>
<style lang="less" scoped>
.graphic {
margin-bottom: 20px;
height: 400px;
overflow: hidden;
overflow-y: auto;
overflow-x: auto;
}
</style>
@@ -0,0 +1,53 @@
<!--选择跳转节点弹窗-->
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
destroyOnClose
:bodyStyle="{ minHeight: '100px', maxHeight: '100px' }"
title="选择跳转节点"
@ok="handleSubmit"
:width="700"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { skipNode } from './bpm.api';
import { taskNodeFormSchema } from './bpm.data';
// 声明Emits
const emit = defineEmits(['success', 'register']);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 150,
schemas: taskNodeFormSchema,
showActionButtonGroup: false,
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
setModalProps({ confirmLoading: false });
await setFieldsValue({
...data,
});
});
//表单提交事件
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
//提交跳转
await skipNode(values);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
@@ -0,0 +1,133 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
hisProcessNodeInfo = '/act/process/extActProcessNode/getHisProcessNodeInfo',
processHistoryList = '/act/task/processHistoryList',
hisProcessTaskTransInfo = '/act/task/getHisProcessTaskTransInfo',
skipNode = '/act/processInstance/skipNode',
taskEntrust = '/act/task/taskEntrust',
getAllTask = '/act/processInstance/getAllTask',
reassign = '/act/processInstance/reassign',
bizHisProcessNodeInfo = '/act/process/extActProcessNode/getBizHisProcessNodeInfo',
getNotifyList = '/act/process/extActTaskNotification/mylist',
notifyMeList = '/act/process/extActTaskNotification/list',
taskNotification = '/act/process/extActTaskNotification/taskNotification',
getBizProcessNodeInfo = '/act/process/extActProcessNode/getBizProcessNodeInfo',
getProcessTaskTransInfo = '/act/task/getProcessTaskTransInfo',
processComplete = '/act/task/processComplete',
suspend = '/act/processInstance/suspend',
restart = '/act/processInstance/restart',
claim = '/act/task/claim',
getProcessInfo = '/act/process/extActFlowData/getProcessInfo',
}
/**
* 获取流程节点历史信息
* @param params
*/
export const hisProcessNodeInfo = (params) => {
return defHttp.get({ url: Api.hisProcessNodeInfo, params }, { isTransformResponse: false });
};
/**
* 获取流程历史信息
* @param params
*/
export const getProcessHistoryList = (params) => defHttp.get({ url: Api.processHistoryList, params }, { isTransformResponse: false });
/**
* 获取流程历史流转信息
* @param params
*/
export const getHisProcessTaskTransInfo = (params) => defHttp.get({ url: Api.hisProcessTaskTransInfo, params }, { isTransformResponse: false });
/**
* 委派
* @param params
*/
export const taskEntrust = (params, handleSuccess?) => {
return defHttp.put({ url: Api.taskEntrust, params }, { isTransformResponse: false }).then((res) => {
handleSuccess && handleSuccess(res);
});
};
/**
* 获取所有任务节点
* @param params
*/
export const getAllTask = (params) => defHttp.get({ url: Api.getAllTask, params });
/**
* 跳转节点
* @param params
*/
export const skipNode = (params) => defHttp.get({ url: Api.skipNode, params });
/**
* 获取业务流程节点信息
* @param params
*/
export const getBizHisProcessNodeInfo = (params) => defHttp.get({ url: Api.bizHisProcessNodeInfo, params }, { isTransformResponse: false });
/**
* 获取我催办的流程列表
* @param params
*/
export const getNotifyList = (params) => defHttp.get({ url: Api.getNotifyList, params });
/**
* 获取催办我的流程列表
* @param params
*/
export const getNotifyMeList = (params) => defHttp.get({ url: Api.notifyMeList, params });
/**
* 催办
* @param params
*/
export const saveOrUpdateNotify = (params) => {
return defHttp.post({ url: Api.taskNotification, params });
};
/**
* 获取业务流程节点信息
* @param params
*/
export const getBizProcessNodeInfo = (params) => {
return defHttp.get({ url: Api.getBizProcessNodeInfo, params }, { isTransformResponse: false });
};
/**
* 获取业务流转信息
* @param params
*/
export const getProcessTaskTransInfo = (params) => {
return defHttp.get({ url: Api.getProcessTaskTransInfo, params }, { isTransformResponse: false });
};
/**
* 流程办理
* @param params
*/
export const processComplete = (params) => {
return defHttp.post({ url: Api.processComplete, params }, { isTransformResponse: false });
};
/**
* 挂起
* @param params
*/
export const suspend = (params) => {
return defHttp.get({ url: Api.suspend, params }, { isTransformResponse: false });
};
/**
* 解挂
* @param params
*/
export const restart = (params) => {
return defHttp.get({ url: Api.restart, params }, { isTransformResponse: false });
};
/**
* 签收
* @param params
*/
export const claim = (params) => {
return defHttp.put({ url: Api.claim, params }, { isTransformResponse: false });
};
/**
* 获取流程信息
* @param params
*/
export const getProcessInfo = (params) => {
return defHttp.get({ url: Api.getProcessInfo, params }, { isTransformResponse: false });
};
@@ -0,0 +1,52 @@
import { FormSchema } from '/@/components/Table';
import { getAllTask } from './bpm.api';
/**
* 委派modal的form
*/
export const delegateFormSchema: FormSchema[] = [
{
field: 'username',
label: '用户名',
component: 'JSelectUserByDept',
required: true,
componentProps: {
labelKey: 'realname',
rowKey: 'username',
showButton: false,
isRadioSelection: true,
},
},
];
/**
* 跳转节点form
*/
export const taskNodeFormSchema: FormSchema[] = [
{
field: 'taskId',
label: '',
component: 'Input',
show: false,
},
{
field: 'skipTaskNode',
label: '跳转节点',
component: 'ApiSelect',
required: true,
componentProps: ({ formModel }) => {
return {
api: getAllTask,
params: { taskId: formModel.taskId },
labelField: 'name',
valueField: 'taskKey',
immediate: false,
onChange: (e) => {
console.log('selected:', e);
},
onOptionsChange: (options) => {
console.log('get options', options.length, options);
},
};
},
},
];
@@ -0,0 +1,51 @@
import { ref, unref } from 'vue';
import { hisProcessNodeInfo } from '../bpm.api';
import { getQueryVariable } from '/@/utils';
import { isUrl } from '/@/utils/is';
/**
*
* @param path 路径
* @param taskDealRef 弹窗示例
*/
export function useBpmNodeInfo(path, taskDealRef) {
const formData = ref({});
/**
* 获取流程历史节点信息
* @param record
*/
function getHisProcessNodeInfo(record) {
hisProcessNodeInfo({ procInstId: record.processInstanceId }).then((res) => {
console.log('获取流程节点信息', res);
if (res.success) {
let data = {
dataId: res.result.dataId,
taskId: record.id,
taskDefKey: record.taskId,
procInsId: record.processInstanceId,
tableName: res.result.tableName,
vars: res.result.records,
};
formData.value = data;
//update--begin--autor:scott-----date:20191005------for:流程节点配置组件URL的时候也支持传递参数了,解决TASK #3238流程节点无法与online的复制视图对接------
console.log('获取流程节点表单URL ', res.result.formUrl);
let tempFormUrl = res.result.formUrl;
//节点配置表单URL,VUE组件类型对应的拓展参数
if (tempFormUrl && tempFormUrl.indexOf('?') != -1 && !isUrl(tempFormUrl) && tempFormUrl.indexOf('{{DOMAIN_URL}}') == -1) {
tempFormUrl = res.result.formUrl.split('?')[0];
console.log('获取流程节点表单URL(去掉参数)', tempFormUrl);
formData.value['extendUrlParams'] = getQueryVariable(res.result.formUrl);
}
path.value = tempFormUrl;
//update--end--autor:scott-----date:20191005------for:流程节点配置组件URL的时候也支持传递参数了,解决TASK #3238流程节点无法与online的复制视图对接------
console.log('获取流程节点信息formData', unref(formData));
console.log('获取流程节点信息path', unref(path));
taskDealRef.value.deal(record);
taskDealRef.value.data.title = '流程历史';
console.log('taskDealRef', taskDealRef.value);
}
});
}
return { getHisProcessNodeInfo, formData };
}
@@ -0,0 +1,52 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" :width="700" :min-height="300">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../expression.data';
import { saveOrUpdate } from '../expression.api';
// 声明Emits
const emit = defineEmits(['success', 'register']);
const isUpdate = ref(true);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
// labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
//表单提交事件
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
@@ -0,0 +1,50 @@
import { defHttp } from '/@/utils/http/axios';
import { Modal } from 'ant-design-vue';
enum Api {
list = '/act/process/extActExpression/list',
save = '/act/process/extActExpression/add',
edit = '/act/process/extActExpression/edit',
delete = '/act/process/extActExpression/delete',
deleteBatch = '/act/process/extActExpression/deleteBatch',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 保存或者更新
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
};
/**
* 删除监听
* @param params
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 批量删除
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
Modal.confirm({
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
@@ -0,0 +1,73 @@
import { FormSchema } from '/@/components/Table';
import {render} from "@/utils/common/renderUtils";
export const columns = [
{
title: '表达式名称',
dataIndex: 'name',
width: 180,
ellipsis: true,
},
{
title: '表达式',
width: 180,
dataIndex: 'expression',
},
{
title: '业务类型',
width: 180,
dataIndex: 'bizType',
customRender: ({ text }) => {
return render.renderDict(text, 'processExpressionBizType');
},
},
];
/**
* 列表查询form
*/
export const searchFormSchema: FormSchema[] = [
{
field: 'name',
label: '名称',
component: 'Input',
colProps: { span: 6 },
},
// {
// field: 'expression',
// label: '表达式',
// component: 'Input',
// colProps: { span: 6 },
// },
];
/**
* 表单form
*/
export const formSchema: FormSchema[] = [
{
field: 'id',
label: '',
component: 'Input',
show: false,
},
{
label: '表达式名称',
field: 'name',
required: true,
component: 'Input',
},
{
label: '表达式',
field: 'expression',
required: true,
component: 'InputTextArea',
},
{
label: '业务类型',
field: 'bizType',
required: false,
component: 'JDictSelectTag',
componentProps: {
dictCode: 'processExpressionBizType',
},
},
];
@@ -0,0 +1,117 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="handleBatchDelete">
<Icon icon="ant-design:delete-outlined"></Icon>
删除
</a-menu-item>
</a-menu>
</template>
<a-button
>批量操作
<Icon icon="mdi:chevron-down"></Icon>
</a-button>
</a-dropdown>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!--监听弹窗-->
<ExpressionModal @register="registerModal" @success="reload"></ExpressionModal>
</div>
</template>
<script lang="ts" name="process-expression-list" setup>
import { ref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import ExpressionModal from './components/ExpressionModal.vue';
import { useModal } from '/@/components/Modal';
import { columns, searchFormSchema } from './expression.data';
import { list, deleteOne, batchDelete } from './expression.api';
import { useListPage } from '/@/hooks/system/useListPage';
//弹窗
const [registerModal, { openModal }] = useModal();
// 列表页面公共参数、方法
const { prefixCls, tableContext } = useListPage({
designScope: 'process-expression',
tableProps: {
title: '流程表达式',
api: list,
columns: columns,
formConfig: {
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
/**
* 新增
*/
function handleCreate() {
openModal(true, {
isUpdate: false,
});
}
/**
* 编辑
*/
function handleEdit(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
});
}
/**
* 删除
* @param id
*/
async function handleDelete(id) {
await deleteOne({ id }, reload);
}
/**
* 批量删除事件
*/
async function handleBatchDelete() {
await batchDelete({ ids: selectedRowKeys.value }, () => {
selectedRowKeys.value = [];
reload();
});
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record.id),
},
},
];
}
</script>
@@ -0,0 +1,30 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/task/historyProcessList',
invalidProcess = '/act/task/invalidProcess',
callBackProcess = '/act/task/callBackProcess',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 作废流程
* @param params
*/
export const invalidProcess = (params, handleSuccess) => {
return defHttp.put({ url: Api.invalidProcess, params }).then(() => {
handleSuccess();
});
};
/**
* 取回流程
* @param params
*/
export const callBackProcess = (params, handleSuccess) => {
return defHttp.put({ url: Api.callBackProcess, params }).then(() => {
handleSuccess();
});
};
@@ -0,0 +1,77 @@
import { FormSchema } from '/@/components/Table';
export const columns = [
{
title: '业务标题',
dataIndex: 'bpmBizTitle',
width: 180,
ellipsis: true,
},
{
title: '流程名称',
dataIndex: 'prcocessDefinitionName',
},
{
title: '流程实例',
dataIndex: 'processInstanceId',
width: 180,
},
{
title: '发起人',
dataIndex: 'startUserName',
},
{
title: '开始日期',
dataIndex: 'startTime',
},
{
title: '流程编号',
dataIndex: 'processDefinitionId',
},
{
title: '结束时间',
dataIndex: 'endTime',
},
{
title: '耗时',
dataIndex: 'spendTimes',
},
{
title: '状态',
dataIndex: 'bpmStatus',
customRender: ({ text }) => {
switch (text) {
case '1':
return '待提交';
case '2':
return '处理中';
case '3':
return '已完成';
case 'rejectProcess':
return '已驳回';
case 'callBackProcess':
return '已取回';
case 'invalidProcess':
return '已作废';
}
return text;
},
},
];
/**
* 列表查询form
*/
export const searchFormSchema: FormSchema[] = [
{
field: 'processDefinitionId',
label: '流程编号',
component: 'Input',
colProps: { span: 6 },
},
{
field: 'processName',
label: '流程名称',
component: 'Input',
colProps: { span: 6 },
},
];
@@ -0,0 +1,123 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!--历史-->
<HisTaskDealModal ref="taskDealRef" :path="path" :formData="formData"></HisTaskDealModal>
</div>
</template>
<script lang="ts" name="process-hisprocess-list" setup>
import { ref, unref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import HisTaskDealModal from '../components/HisTaskDealModal.vue';
import { useBpmNodeInfo } from '../components/hooks/useBpmNodeInfo';
import { columns, searchFormSchema } from './hisprocess.data';
import { list, invalidProcess, callBackProcess } from './hisprocess.api';
import { useListPage } from '/@/hooks/system/useListPage';
// 列表页面公共参数、方法
const { prefixCls, tableContext } = useListPage({
designScope: 'process-hisprocess',
tableProps: {
api: list,
columns: columns,
formConfig: {
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
});
const path = ref('');
const taskDealRef = ref(null);
const { getHisProcessNodeInfo, formData } = useBpmNodeInfo(path, taskDealRef);
const [registerTable, rowSelection, { reload }] = tableContext;
/**
* 显示历史
* @param record
*/
function showHistory(record) {
getHisProcessNodeInfo(record);
}
/**
* 作废流程
* @param record
*/
async function handleInvalidProcess(record) {
await invalidProcess(
{
processInstanceId: record.processInstanceId,
},
reload
);
}
/**
* 取回流程
* @param record
*/
async function handleCallBackProcess(record) {
await callBackProcess(
{
processInstanceId: record.processInstanceId,
},
reload
);
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '历史',
onClick: showHistory.bind(null, record),
ifShow: () => {
return record.endTime && record.endTime != '';
},
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '作废流程',
popConfirm: {
title: '确定要作废流程吗?',
confirm: handleInvalidProcess.bind(null, record),
},
ifShow: () => {
return !record.endTime;
},
},
{
label: '取回流程',
popConfirm: {
title: '确定要取回流程吗?',
confirm: handleCallBackProcess.bind(null, record),
},
ifShow: () => {
return !record.endTime;
},
},
{
label: '历史',
onClick: showHistory.bind(null, record),
ifShow: () => {
return !record.endTime;
},
},
];
}
</script>
@@ -0,0 +1,10 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/task/taskAllHistoryList',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
@@ -0,0 +1,65 @@
import { FormSchema } from '/@/components/Table';
export const columns = [
{
title: '业务标题',
dataIndex: 'bpmBizTitle',
width: 180,
ellipsis: true,
},
{
title: '流程名称',
dataIndex: 'processDefinitionName',
width: 180,
},
{
title: '流程实例',
dataIndex: 'processInstanceId',
width: 180,
},
{
title: '任务名称',
dataIndex: 'taskName',
},
{
title: '发起人',
dataIndex: 'processApplyUserName',
},
{
title: '办理人',
dataIndex: 'taskAssigneeName',
},
{
title: '开始时间',
dataIndex: 'taskBeginTime',
},
{
title: '结束时间',
dataIndex: 'taskEndTime',
},
{
title: '耗时',
dataIndex: 'durationStr',
},
{
title: '流程编号',
dataIndex: 'processDefinitionId',
},
];
/**
* 列表查询form
*/
export const searchFormSchema: FormSchema[] = [
{
field: 'processDefinitionId',
label: '流程编号',
component: 'Input',
colProps: { span: 6 },
},
{
field: 'processDefinitionName',
label: '流程名称',
component: 'Input',
colProps: { span: 6 },
},
];
@@ -0,0 +1,60 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
</BasicTable>
<!--历史-->
<HisTaskDealModal ref="taskDealRef" :path="path" :formData="formData"></HisTaskDealModal>
</div>
</template>
<script lang="ts" name="process-histask-list" setup>
import { ref, unref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import HisTaskDealModal from '../components/HisTaskDealModal.vue';
import { columns, searchFormSchema } from './histask.data';
import { list } from './histask.api';
import { useListPage } from '/@/hooks/system/useListPage';
import { useBpmNodeInfo } from '../components/hooks/useBpmNodeInfo';
// 列表页面公共参数、方法
const { prefixCls, tableContext } = useListPage({
designScope: 'process-histask',
tableProps: {
api: list,
columns: columns,
formConfig: {
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
});
const [registerTable, rowSelection, { reload }] = tableContext;
const path = ref('');
const taskDealRef = ref(null);
let { getHisProcessNodeInfo, formData } = useBpmNodeInfo(path, taskDealRef);
/**
* 显示历史
* @param record
*/
function showHistory(record) {
getHisProcessNodeInfo(record);
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '历史',
onClick: showHistory.bind(null, record),
},
];
}
</script>
@@ -0,0 +1,52 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" :width="700">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../listener.data';
import { saveOrUpdate } from '../listener.api';
// 声明Emits
const emit = defineEmits(['success', 'register']);
const isUpdate = ref(true);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
// labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
//表单提交事件
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
@@ -0,0 +1,41 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/process/extActListener/list',
save = '/act/process/extActListener/add',
edit = '/act/process/extActListener/edit',
delete = '/act/process/extActListener/delete',
changeStatus = '/act/process/extActListener/changeStatus',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 保存或者更新
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
};
/**
* 删除监听
* @param params
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 修改状态
* @param params
*/
export const changeStatus = (params, handleSuccess) => {
return defHttp.put({ url: Api.changeStatus, data: params }).then(() => {
handleSuccess();
});
};
@@ -0,0 +1,164 @@
import { FormSchema } from '/@/components/Table';
import { render } from '/@/utils/common/renderUtils';
export const columns = [
{
title: '名称',
dataIndex: 'listenerName',
width: 180,
ellipsis: true,
},
{
title: '监听类型',
dataIndex: 'listenerType',
width: 100,
customRender: ({ text }) => {
return render.renderDictNative(
text,
[
{ label: '执行监听', value: 1 },
{ label: '任务监听', value: 2 },
],
false
);
},
},
{
title: '事件',
dataIndex: 'listenerEvent',
width: 100,
},
{
title: '执行类型',
dataIndex: 'listenerValueType',
width: 150,
customRender: ({ text }) => {
return render.renderDictNative(
text,
[
{ label: '表达式', value: 'expression' },
{ label: 'JAVA类', value: 'javaClass' },
{ label: 'Spring表达式', value: 'delegateExpression' },
],
false
);
},
},
{
title: '执行内容',
dataIndex: 'listenerValue',
width: 360,
ellipsis: true,
},
{
title: '状态',
dataIndex: 'listenerStatus',
width: 100,
customRender: ({ text }) => {
return render.renderDictNative(
text,
[
{ label: '已禁用', value: '0' },
{ label: '已启用', value: '1' },
],
false
);
},
},
];
/**
* 列表查询form
*/
export const searchFormSchema: FormSchema[] = [
{
field: 'listenerName',
label: '名称',
component: 'Input',
colProps: { span: 6 },
},
];
/**
* 表单form
*/
export const formSchema: FormSchema[] = [
{
field: 'id',
label: '',
component: 'Input',
show: false,
},
{
label: '名称',
field: 'listenerName',
required: true,
component: 'Input',
},
{
label: '监听类型',
field: 'listenerType',
component: 'Select',
componentProps: ({ formModel }) => {
return {
options: [
{ label: '执行监听', value: 1 },
{ label: '任务监听', value: 2 },
],
onChange: () => {
formModel.listenerEvent = '';
},
};
},
},
{
label: '事件属性',
field: 'listenerEvent',
component: 'Select',
componentProps: ({ formModel }) => {
const isExecute = [
{ label: 'start', value: 'start' },
{ label: 'end', value: 'end' },
{ label: 'take', value: 'take' },
];
const isTask = [
{ label: 'create', value: 'create' },
{ label: 'assignment', value: 'assignment' },
{ label: 'complete', value: 'complete' },
];
let option = !formModel['listenerType'] ? [] : formModel['listenerType'] == 1 ? isExecute : isTask;
return {
options: option,
};
},
},
{
label: '值类型',
field: 'listenerValueType',
component: 'RadioGroup',
defaultValue: 'javaClass',
componentProps: ({ formActionType }) => {
return {
options: [
{ label: 'JAVA类', value: 'javaClass' },
{ label: '表达式', value: 'expression' },
{ label: '代理表达式', value: 'delegateExpression' },
],
onChange: (e) => {
const { updateSchema } = formActionType;
let value = e.target.value;
const label = value === 'javaClass' ? '类路径' : '表达式';
updateSchema([
{
field: 'listenerValue',
label: label,
},
]);
},
};
},
},
{
label: '类路径',
field: 'listenerValue',
component: 'Input',
},
];
@@ -0,0 +1,125 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!--监听弹窗-->
<ListenerModal @register="registerModal" @success="reload"></ListenerModal>
</div>
</template>
<script lang="ts" name="process-listener-list" setup>
import { ref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import ListenerModal from './components/ListenerModal.vue';
import { useModal } from '/@/components/Modal';
import { columns, searchFormSchema } from './listener.data';
import { list, deleteOne, changeStatus } from './listener.api';
import { useListPage } from '/@/hooks/system/useListPage';
//弹窗
const [registerModal, { openModal }] = useModal();
// 列表页面公共参数、方法
const { prefixCls, tableContext } = useListPage({
designScope: 'process-listener',
tableProps: {
title: '流程监听',
api: list,
columns: columns,
formConfig: {
labelWidth: 50,
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
/**
* 新增
*/
function handleCreate() {
openModal(true, {
isUpdate: false,
});
}
/**
* 编辑
*/
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
openModal(true, {
record,
isUpdate: true,
});
}
/**
* 删除
* @param id
*/
async function handleDelete(id) {
console.log('点击了删除', id);
await deleteOne({ id }, reload);
}
/**
* 修改状态
* @param id
*/
async function handleOpen(id) {
console.log('点击了启用', id);
await changeStatus({ id }, reload);
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
{
label: '启用',
popConfirm: {
title: '是否启用?',
confirm: handleOpen.bind(null, record.id),
},
ifShow: () => {
return record.listenerStatus == 0;
},
},
{
label: '禁用',
popConfirm: {
title: '是否禁用?',
confirm: handleOpen.bind(null, record.id),
},
ifShow: () => {
return record.listenerStatus == 1;
},
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record.id),
},
},
];
}
</script>
@@ -0,0 +1,61 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/processInstance/list',
suspend = '/act/processInstance/suspend',
restart = '/act/processInstance/restart',
close = '/act/processInstance/close',
taskEntrust = '/act/task/taskEntrust',
taskComplaint = '/act/task/taskComplaint',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 激活
* @param params
*/
export const restart = (params, handleSuccess) => {
return defHttp.get({ url: Api.restart, params }).then(() => {
handleSuccess();
});
};
/**
* 挂起
* @param params
*/
export const suspend = (params, handleSuccess) => {
return defHttp.get({ url: Api.suspend, params }).then(() => {
handleSuccess();
});
};
/**
* 关闭
* @param params
*/
export const closeProcess = (params, handleSuccess) => {
return defHttp.get({ url: Api.close, params }).then(() => {
handleSuccess();
});
};
/**
* 委派
* @param params
*/
export const taskEntrust = (params, handleSuccess) => {
return defHttp.put({ url: Api.taskEntrust, params }).then(() => {
handleSuccess();
});
};
/**
* 转办
* @param params
*/
export const taskComplaint = (params, handleSuccess) => {
return defHttp.put({ url: Api.taskComplaint, params }).then(() => {
handleSuccess();
});
};
@@ -0,0 +1,77 @@
import { FormSchema } from '/@/components/Table';
export const columns = [
{
title: '流程名称',
dataIndex: 'prcocessDefinitionName',
width: 180,
ellipsis: true,
},
{
title: '业务标题',
dataIndex: 'bpmBizTitle',
},
{
title: '当前任务',
dataIndex: 'name',
},
{
title: '流程实例',
dataIndex: 'processInstanceId',
width: 180,
},
{
title: '办理人',
dataIndex: 'assigneeName',
width: 100,
},
{
title: '流程ID',
dataIndex: 'processDefinitionId',
width: 150,
},
{
title: '开始时间',
dataIndex: 'startTime',
},
{
title: '发起人',
dataIndex: 'startUserName',
width: 100,
},
{
title: '耗时',
dataIndex: 'spendTimes',
},
{
title: '状态',
dataIndex: 'isSuspended',
width: 80,
customRender: ({ text }) => {
return text === 'true' ? '已暂停' : '已启动';
},
},
];
/**
* 列表查询form
*/
export const searchFormSchema: FormSchema[] = [
{
field: 'processInstanceId',
label: '流程实例ID',
component: 'Input',
colProps: { span: 6 },
},
{
field: 'startUserId',
label: '流程发起人',
component: 'JSelectUserByDept',
componentProps: {
labelKey: 'realname',
rowKey: 'username',
showButton: false,
maxSelectCount: 1,
},
colProps: { span: 6 },
},
];
@@ -0,0 +1,202 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!--委派弹窗-->
<DelegateModal @register="registerModal" @success="handleEntruster" title="请选择委派人"></DelegateModal>
<!--转办弹窗-->
<DelegateModal @register="registerModalComplaint" @success="handleComplaint" title="请选择转办人"></DelegateModal>
<!--跳转弹窗-->
<SelectTaskNodeModal @register="registerSkipModal" @success="reload"></SelectTaskNodeModal>
<!--历史-->
<HisTaskDealModal ref="taskDealRef" :path="path" :formData="formData"></HisTaskDealModal>
</div>
</template>
<script lang="ts" name="process-instance-list" setup>
import { ref, unref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import DelegateModal from '../components/DelegateModal.vue';
import SelectTaskNodeModal from '../components/SelectTaskNodeModal.vue';
import HisTaskDealModal from '../components/HisTaskDealModal.vue';
import { useModal } from '/@/components/Modal';
import { columns, searchFormSchema } from './instance.data';
import { list, suspend, restart, closeProcess, taskEntrust, taskComplaint } from './instance.api';
import { useListPage } from '/@/hooks/system/useListPage';
import { useBpmNodeInfo } from '../components/hooks/useBpmNodeInfo';
//委派弹窗
const [registerModal, { openModal }] = useModal();
//委派弹窗
const [registerModalComplaint, { openModal: openModalComplaint }] = useModal();
//跳转弹窗
const [registerSkipModal, { openModal: openSkipModal }] = useModal();
// 列表页面公共参数、方法
const { prefixCls, tableContext } = useListPage({
designScope: 'process-expression',
tableProps: {
api: list,
isTreeTable: true,
rowKey: 'processInstanceId',
columns: columns,
formConfig: {
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const taskId = ref('');
const path = ref('');
const taskDealRef = ref(null);
let { getHisProcessNodeInfo, formData } = useBpmNodeInfo(path, taskDealRef);
/**
* 激活
* @param id
*/
async function handleRestart(id) {
await restart({ processInstanceId: id }, reload);
}
/**
* 挂起
* @param id
*/
async function handleSuspend(id) {
await suspend({ processInstanceId: id }, reload);
}
/**
* 关闭
* @param id
*/
async function handleClose(id) {
await closeProcess({ processInstanceId: id }, reload);
}
/**
* 选择委派人员弹窗
* @param record
*/
function handleSelectEntruster(record) {
taskId.value = record.taskId;
openModal(true);
}
/**
* 选择转办人员弹窗
* @param record
*/
function handleSelectComplaint(record) {
taskId.value = record.taskId;
openModalComplaint(true);
}
/**
* 跳转
* @param taskId
*/
function handleSkipNode(taskId) {
openSkipModal(true, { taskId });
}
/**
* 显示历史
* @param record
*/
function showHistory(record) {
getHisProcessNodeInfo(record);
}
/**
* 委派
* @data
*/
async function handleEntruster(data) {
console.log('handleEntruster委派返回值data', data);
let params = { taskId: unref(taskId), taskAssignee: data.username };
await taskEntrust(params, reload);
}
/**
* 转办
* @data
*/
async function handleComplaint(data) {
console.log('handleComplaint转办返回值data', data);
let params = { taskId: unref(taskId), taskAssignee: data.username };
await taskComplaint(params, reload);
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '激活',
popConfirm: {
title: '是否激活?',
confirm: handleRestart.bind(null, record.id),
},
ifShow: () => {
return record.isSuspended != '' && record.isSuspended === 'true';
},
},
{
label: '挂起',
popConfirm: {
title: '是否挂起?',
confirm: handleSuspend.bind(null, record.id),
},
ifShow: () => {
return record.isSuspended != '' && record.isSuspended === 'false';
},
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '关闭',
popConfirm: {
title: '是否关闭吗?',
confirm: handleClose.bind(null, record.id),
},
ifShow: () => {
return record.isSuspended != '' && record.isSuspended != 'finished';
},
},
{
label: '转办',
onClick: handleSelectComplaint.bind(null, record),
ifShow: () => {
return record.isSuspended != '' && record.isSuspended === 'false' && record.isSuspended != 'finished';
},
},
{
label: '委派',
onClick: handleSelectEntruster.bind(null, record),
ifShow: () => {
return record.isSuspended != '' && record.isSuspended === 'false' && record.isSuspended != 'finished';
},
},
{
label: '跳转',
onClick: handleSkipNode.bind(null, record.taskId),
ifShow: () => {
return record.isSuspended != '';
},
},
{
label: '历史',
onClick: showHistory.bind(null, record),
ifShow: () => {
return record.isSuspended != '';
},
},
];
}
</script>
@@ -0,0 +1,65 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
add = '/act/process/extActDesignFlowData/add',
addCommUse = '/joa/designform/designFormCommuse/commUseDesignAdd',
queryByCode = '/desform/queryByCode',
roleDegisnList = '/joa/designform/designFormCommuse/roleDegisnList',
commUseList = '/joa/designform/designFormCommuse/getCommuseByUserId',
onlineList = '/joa/designform/designFormCommuse/queryOnlineFormList',
roleOnlineList = '/joa/designform/designFormCommuse/roleOnlineList',
sortChange = '/joa/designform/designFormCommuse/sortChange',
}
/**
* 列表接口
* @param params
*/
export const roleDegisnList = (params?) => defHttp.get({ url: Api.roleDegisnList, params }, { isTransformResponse: false });
/**
* 常用流程
* @param params
*/
export const getCommUseList = () => defHttp.get({ url: Api.commUseList }, { isTransformResponse: false });
/**
* online列表
*/
export const getOnlineList = () => defHttp.get({ url: Api.onlineList }, { isTransformResponse: false });
/**
* roleOnlineList列表
*/
export const roleOnlineList = () => defHttp.get({ url: Api.roleOnlineList }, { isTransformResponse: false });
/**
* 根据流程编码查询
* @param params
*/
export const queryByCode = (params) => defHttp.get({ url: Api.queryByCode, params }, { isTransformResponse: false });
/**
* 往设计表单和流程的关系表中,插入一条数据
* @param params
*/
export const addDesignFlowData = (params) => {
return defHttp.post({ url: Api.add, params }, { isTransformResponse: false });
};
/**
* 保存常用流程
* @param params
*/
export const addCommUse = (params) => {
return defHttp.post({ url: Api.addCommUse, params }, { isTransformResponse: false });
};
/**
* 查询online表单数据
* @param params
*/
export const queryOnlineDynamicData = (config) => {
return defHttp.get(config, { isTransformResponse: false });
};
/**
* 排序修改
* @param params
*/
export const sortChange = (params) => {
return defHttp.post({ url: Api.sortChange, params }, { isTransformResponse: false });
};
@@ -0,0 +1,479 @@
<template>
<div>
<a-card :bordered="false">
<a-spin :spinning="loading">
<template v-if="processTypeDictOptions.length > 0">
<div style="display: flex; justify-content: space-between">
<a-button type="primary" @click="handleSetUse" preIcon="ant-design:setting-outlined">设置常用流程</a-button>
<div v-auth="'sys:order_apply:sort'" style="position: fixed; right: 35px; z-index: 999">
<a-button v-if="!sortStatus" type="primary" @click="sortStatus = !sortStatus" preIcon="ant-design:drag-outlined">激活排序</a-button>
</div>
</div>
<template v-if="commUseList.length > 0">
<a-col :xl="24" :lg="24" :md="24" :sm="24" :xs="24">
<a-card :loading="loading" title="常用流程" style="margin-top: 24px; height: auto" :bodyStyle="{ padding: 0 }">
<draggable
:force-fallback="true"
animation="200"
dragClass="dragClass"
ghostClass="ghostClass"
chosenClass="chosenClass"
v-model="commUseList"
item-key="id"
style="display: flex; flex-wrap: wrap"
@end="dragEnd('common')"
filter=".unmover"
>
<template #item="{ element }">
<a-card-grid :style="{ width: cardWidth }" :class="{ unmover: !sortStatus }" @click="handleOk(element)">
<template v-if="element?.desformIcon">
<Icon v-if="element?.desformIcon.indexOf('ant-design') >= 0" :icon="element.desformIcon" :style="style" />
<a-icon v-else :type="element?.desformIcon" :style="style" />
</template>
<Icon v-else icon="ant-design:file-text-outlined" :style="style" />
<span class="bsSpan no-select" v-if="screenWidth > 700">
<JEllipsis :value="element?.desformName" :length="6" />
</span>
<div v-else class="mobName no-select">
<a-icon v-if="element?.appIcon" :type="element.appIcon" :style="style" />
<Icon v-else icon="ant-design:file-text-outlined" :style="style" />
{{ element?.desformName.length > 4 ? element?.desformName.substr(0, 4) : element.desformName }}
</div>
</a-card-grid>
</template>
</draggable>
</a-card>
</a-col>
</template>
<template v-for="item of processTypeDictOptions">
<a-col :xl="24" :lg="24" :md="24" :sm="24" :xs="24">
<a-card :loading="loading" :title="item.text" :style="{ marginTop: '24px', height: 'auto' }" :bodyStyle="{ padding: 0 }">
<draggable
:force-fallback="true"
animation="200"
dragClass="dragClass"
ghostClass="ghostClass"
chosenClass="chosenClass"
v-model="desformList"
item-key="id"
style="display: flex; flex-wrap: wrap"
@end="dragEnd('desform')"
filter=".unmover"
>
<template #item="{ element }">
<a-card-grid
v-if="element.procType == item.value"
:class="{ unmover: !sortStatus }"
:style="{ width: cardWidth }"
@click="handleOk(element)"
>
<template v-if="element.desformIcon">
<Icon v-if="element.desformIcon.indexOf('ant-design') >= 0" :icon="element.desformIcon" :style="style" />
<a-icon v-else :type="element.desformIcon" :style="style" />
</template>
<Icon v-else icon="ant-design:file-text-outlined" :style="style" />
<span class="bsSpan no-select" v-if="screenWidth > 700">
<JEllipsis :value="element.desformName" :length="6" />
</span>
<div v-else class="mobName no-select">
{{ element.desformName.length > 4 ? element.desformName.substr(0, 4) : element.desformName }}
</div>
</a-card-grid>
</template>
</draggable>
</a-card>
</a-col>
</template>
</template>
<!--设置online流程-->
<template v-if="onlineFormList && onlineFormList.length > 0">
<a-col :xl="24" :lg="24" :md="24" :sm="24" :xs="24">
<a-card :loading="loading" title="online表单" :style="{ marginTop: '24px', height: 'auto' }" :bodyStyle="{ padding: 0 }">
<draggable
:force-fallback="true"
animation="200"
dragClass="dragClass"
ghostClass="ghostClass"
chosenClass="chosenClass"
v-model="onlineFormList"
item-key="id"
@end="dragEnd('online')"
style="display: flex; flex-wrap: wrap"
filter=".unmover"
>
<template #item="{ element }">
<a-card-grid :style="{ width: cardWidth }" :class="{ unmover: !sortStatus }" @click="handleOpenOnlineModal(element)">
<template v-if="element.desformIcon">
<Icon v-if="element.desformIcon.indexOf('ant-design') >= 0" :icon="element.desformIcon" :style="style" />
<a-icon v-else :type="element.desformIcon" :style="style" />
</template>
<Icon v-else icon="ant-design:file-text-outlined" :style="style" />
<span class="bsSpan no-select" v-if="screenWidth > 700">
<JEllipsis :value="element.desformName" :length="20" />
</span>
<div v-else class="mobName no-select">
{{ element.desformName.length > 10 ? element.desformName.substr(0, 10) : element.desformName }}
</div>
</a-card-grid>
</template>
</draggable>
</a-card>
</a-col>
</template>
<template v-if="(!onlineFormList || onlineFormList.length == 0) && (!processTypeDictOptions || processTypeDictOptions.length == 0)">
<span>没有找到配置的流程</span>
</template>
<div class="sticky-button" v-if="sortStatus">
<a-button type="primary" size="middle" @click="saveSort" preIcon="ant-design:save-outlined">保存排序</a-button>
<a-button class="ml-2" size="middle" type="primary" danger @click="sortStatus = !sortStatus" preIcon="ant-design:close-outlined">取消</a-button>
</div>
</a-spin>
</a-card>
<!--online动态弹窗-->
<OnlineDynamicModal ref="onlineModal" @register="registerOnlineModal" />
<!--表单设计弹窗-->
<DesformDataModal ref="desformModal" :dialogOptions="dialogOptions" @added="handleDesformDataAdded" />
<!--常用流程设置-->
<BpmAutoDesformSetUse @register="registerModal" @success="reload" />
</div>
</template>
<script lang="ts" name="order-apply-list" setup>
import draggable from 'vuedraggable';
import { ref, onMounted, computed, unref, reactive } from 'vue';
import { router } from '/@/router';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
import BpmAutoDesformSetUse from './components/BpmAutoDesformSetUse.vue';
import DesformDataModal from '../myApply/components/DesformDataModal.vue';
import OnlineDynamicModal from './components/OnlineDynamicModal.vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { initDictOptions } from '/@/utils/dict/index';
import { roleDegisnList, getCommUseList, queryByCode, addDesignFlowData, getOnlineList, sortChange, roleOnlineList } from './apply.api';
import { cloneDeep } from 'lodash-es';
const commUseList = ref<any>([]);
const loading = ref(false);
const desformList = ref<any>([]);
const processTypeDict = ref<any>([]);
const processTypeDictOptions = ref<any>([]);
const flowCodePre = 'desform_';
const dialogOptions = ref({ top: 60, width: 1000, padding: { top: 25, right: 25, bottom: 30, left: 25 } });
const cardWidth = ref('20%');
const screenWidth = ref();
const sortStatus = ref(false);
const onlineFormList = ref<any>([]);
const desformModal = ref<any>(null);
const onlineModal = ref<any>(null);
const { createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const originalData = reactive({
online: [],
desform: [],
commonUse: [],
});
const style = computed(() => {
let style = { 'vertical-align': 'middle' };
if (screenWidth.value > 700) {
style['font-size'] = '30px';
} else {
style['font-size'] = '25px';
style['margin-left'] = '30%';
}
return style;
});
/** 加载desform */
async function loadDesformList() {
loading.value = true;
let dictRes = await initDictOptions('bpm_process_type');
if (dictRes && dictRes.length > 0) {
processTypeDict.value = dictRes;
}
let res = await roleDegisnList();
if (res.success) {
desformList.value = res.result;
originalData.desform = cloneDeep(res.result);
}
//获取指定属性的数据集合
let procTypeArr = [...new Set(Array.from(unref(desformList), ({ procType }) => procType))];
//工单类型字典项
processTypeDictOptions.value = processTypeDict.value.filter((item) => procTypeArr.indexOf(item.value) != -1);
loading.value = false;
}
async function loadCommUseList() {
loading.value = true;
let res = await getCommUseList();
if (res.success) {
const sortList = res.result.sort(function (a: any, b: any) {
return a.sortNum - b.sortNum;
});
commUseList.value = sortList;
originalData.commonUse = cloneDeep(sortList);
}
loading.value = false;
}
const [registerOnlineModal, { openModal: openOnlineModal }] = useModal();
function handleOpenOnlineModal(item) {
if (sortStatus.value) {
return;
}
openOnlineModal(true, {
id: item.id,
name: item.desformCode,
});
}
function handleOk(desform) {
if (sortStatus.value) {
return;
}
if (desform) {
if (desform.formType == 'online') {
handleOpenOnlineModal(desform);
} else {
handleOkBpmSelect(desform);
}
}
}
/** bmp 选择 ok */
function handleOkBpmSelect(desform) {
let title = '表单【' + desform.desformName + '】发起申请';
openDesformModal('add', desform, title);
}
/** 打开表单设计器弹窗*/
async function openDesformModal(mode, record, title) {
let desform = record,
dataId = null;
if (mode === 'edit' || mode === 'detail') {
let { desformId: id, desformCode, desformDataId } = record;
dataId = desformDataId;
desform = { id, desformCode };
}
let res = await queryByCode({ desformCode: desform.desformCode });
if (res.success) {
let designJson = res.result.desformDesignJson;
let json = JSON.parse(designJson);
// 保存 dialogConfig
let options = json.config.dialogOptions;
if (options) {
dialogOptions.value = options;
}
desformModal.value?.open(mode, desform, dataId, title);
}
}
/** 流程数据保存成功后触发该事件 */
async function handleDesformDataAdded(event) {
// 将流程保存至后台
let { desform, dataId } = event;
loading.value = true;
//发起流程(往设计表单和流程的关系表中,插入一条数据)
let res = await addDesignFlowData({
desformId: desform.id,
desformCode: desform.desformCode,
desformDataId: dataId,
desformName: desform.desformName,
processName: desform.procName,
flowCode: flowCodePre + desform.desformCode,
titleExp: desform.titleExp,
});
loading.value = false;
if (res.success) {
router.push({ path: '/oaOffice/myOrder' });
} else {
createMessage.error(res.message);
}
}
//打开常用流程设计弹窗
function handleSetUse() {
openModal(true, { processTypeDict: unref(processTypeDict) });
}
/**
* 重新加载常用流程
*/
async function reload() {
let res = await getCommUseList();
if (res.success) {
commUseList.value = res.result;
}
}
/**
* 设置卡片size
*/
function resetCardSize() {
console.log('document.body.clientWidth:resetCardSize:', document.body.clientWidth);
screenWidth.value = document.body.clientWidth;
if (unref(screenWidth) <= 1350) {
cardWidth.value = '33.3%';
}
}
/**
* 查询online表单
*/
async function queryOnlineFormList() {
onlineFormList.value = [];
//update-begin-author:liusq---date:2025-04-28--for:【QQYUN-10237】【流程审批】工单授权,没有对online表单的授权
//原来接口 getOnlineLis t查询全部
let res = await roleOnlineList();
//update-end-author:liusq---date:2025-04-28--for:【QQYUN-10237】【流程审批】工单授权,没有对online表单的授权
if (res.success) {
onlineFormList.value = res.result;
originalData.online = cloneDeep(res.result);
}
}
//*********************排序逻辑begin****************************
/**
* 拖拽结束事件
* @param evt
*/
function dragEnd(type) {
if (type == 'online') {
for (let i = 0; i < unref(onlineFormList).length; i++) {
if (unref(onlineFormList)[i].sortNum != i) {
unref(onlineFormList)[i].sortNum = i;
}
}
} else if (type == 'desform') {
for (let i = 0; i < unref(desformList).length; i++) {
if (unref(desformList)[i].sortNum != i) {
unref(desformList)[i].sortNum = i;
}
}
} else {
for (let i = 0; i < unref(commUseList).length; i++) {
if (unref(commUseList)[i].sortNum != i) {
unref(commUseList)[i].sortNum = i;
}
}
}
}
/**
* 保存排序
*/
async function saveSort() {
let changeItem = [] as any[];
unref(onlineFormList).forEach((item) => {
const findObj = originalData.online.find((form: any) => form.id == item.id) as any;
if (item.sortNum != findObj.sortNum) {
changeItem.push(item);
}
});
unref(desformList).forEach((item) => {
const findObj = originalData.desform.find((form: any) => form.id == item.id) as any;
if (item.sortNum != findObj.sortNum) {
changeItem.push(item);
}
});
// for (let i = 0; i < unref(commUseList).length; i++) {
// const findObj = originalData.commonUse.find((form: any) => form.id == unref(commUseList)[i].id) as any;
// if (unref(commUseList)[i].sortNum != findObj.sortNum) {
// changeItem.push(unref(commUseList)[i]);
// }
// }
sortStatus.value = false;
console.log('changeItem', changeItem);
if (changeItem.length > 0) {
let res = await sortChange({ changeItem: changeItem });
if (res.success) {
createMessage.success(res.message);
initData();
}
}
}
//*********************排序逻辑end****************************
function initData() {
loadDesformList();
loadCommUseList();
queryOnlineFormList();
}
onMounted(() => {
initData();
//当页面初始化时,根据屏幕大小来给设置card宽度
resetCardSize();
});
</script>
<style lang="less" scoped>
.bsSpan {
vertical-align: middle;
margin-left: 20px;
display: inline-block;
width: calc(100% - 51px);
overflow: hidden;
:first-child {
white-space: nowrap;
}
}
.mobName {
text-align: center;
font-size: 12px;
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
}
html[data-theme='light'] {
.ant-card {
:deep(.ant-card-head) {
background-color: #eaeaea;
}
}
}
.ant-card .ant-card-grid {
transition: none !important;
}
.ghostClass {
background-color: #b3c9e6 !important;
}
.chosenClass {
background-color: #ffece0 !important;
opacity: 1 !important;
}
.dragClass {
background-color: #b3afe6 !important;
opacity: 1 !important;
box-shadow: none !important;
outline: none !important;
background-image: none !important;
}
.no-select {
user-select: none; /* 对大多数浏览器有效 */
-webkit-user-select: none; /* 对 Safari 有效 */
-moz-user-select: none; /* 对 Firefox 有效 */
-ms-user-select: none; /* 对 Internet Explorer 和 Edge 有效 */
}
.dimensional-button {
border: none; /* 去掉按钮边框 */
box-shadow: 0 5px #097ce5; /* 添加阴影效果 */
color: white; /* 设置字体颜色 */
text-align: center; /* 文字居中 */
text-decoration: none; /* 去掉默认下划线 */
display: inline-block; /* 行内元素 */
font-size: 16px; /* 设置字体大小 */
border-radius: 10px; /* 设置圆角 */
}
.sticky-button {
position: fixed;
bottom: 10px; /* 距离底部10像素 */
left: 50%; /* 水平居中 */
transform: translateX(-50%); /* 水平向左移动自身宽度的50% */
z-index: 999;
}
</style>
@@ -0,0 +1,197 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose title="常用流程设置" @ok="handleSubmit" width="1200px">
<!--工单部分-->
<template v-for="(item, index) of processTypeDictOptions">
<a-card :title="item.text" :style="{ marginTop: index == 0 ? '0px' : '12px', height: 'auto' }" :headStyle="{ backgroundColor: '#eaeaea' }">
<a-checkbox-group v-model:value="designNameValue[index]" style="width: 100%">
<a-row>
<template v-for="des in designNameOption">
<a-col :span="6" v-if="des.procType == item.value">
<a-checkbox :value="des.value">{{ des.text }}</a-checkbox>
</a-col>
</template>
</a-row>
</a-checkbox-group>
</a-card>
</template>
<!--online表单部分-->
<template v-if="onlineFormList && onlineFormList.length > 0">
<a-card title="online表单" :style="{ marginTop: '24px', height: 'auto' }" :headStyle="{ backgroundColor: '#eaeaea' }">
<a-checkbox-group v-model:value="onlineCommonUserList" style="width: 100%">
<a-row>
<template v-for="des in onlineFormList">
<a-col :span="6">
<a-checkbox :value="des.id">{{ des.desformName.length > 10 ? des.desformName.substr(0, 10) : des.desformName }}</a-checkbox>
</a-col>
</template>
</a-row>
</a-checkbox-group>
</a-card>
</template>
<!--树操作部分-->
<template #insertFooter>
<a-dropdown placement="top">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="checkALL">全部勾选</a-menu-item>
<a-menu-item key="2" @click="cancelCheckALL">取消全选</a-menu-item>
</a-menu>
</template>
<a-button style="float: left"> 树操作 <Icon icon="ant-design:up-outlined" /> </a-button>
</a-dropdown>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref, toRaw } from 'vue';
import { BasicModal, useModalInner } from '/src/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { getCommUseList, roleDegisnList, getOnlineList, addCommUse, roleOnlineList } from '../apply.api';
const { createMessage } = useMessage();
// 声明Emits
const emit = defineEmits(['success', 'register', 'ok']);
//原始工单id
const oldDesignId = ref('');
//新工单id
const newDesignId = ref('');
//工单字典类型
const processTypeDict = ref([]);
//工单字典类型项
const processTypeDictOptions = ref([]);
//工单集合
const desformList = ref([]);
//工单名称集合
const designNameOption = ref([]);
//工单数据集合
const designNameValue = ref([]);
//online集合
const onlineFormList = ref([]);
//online数据集合
const onlineCommonUserList = ref([]);
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false });
//初始化数据
processTypeDict.value = data.processTypeDict;
loadDesformList();
queryOnlineFormList();
});
/**
* 初始化工单数据
*/
async function loadDesformList() {
//获取表单设计信息
let res = await roleDegisnList();
if (res.success) {
let designList = res.result;
desformList.value = res.result;
//获取指定属性的数据集合
let procTypeArr = [...new Set(Array.from(unref(desformList), ({ procType }) => procType))];
//工单类型字典项
processTypeDictOptions.value = processTypeDict.value.filter((item) => procTypeArr.indexOf(item.value) != -1);
//工单名称集合
designNameOption.value = designList.map((design) => {
return { value: design.id, text: design.desformName, procType: design.procType };
});
}
//获取表单信息
let useRes = await getCommUseList();
if (useRes.success) {
let commUseList = useRes.result;
if (commUseList.length > 0) {
let onlineList = commUseList.filter((item) => item.formType == 'online');
let designList = commUseList.filter((item) => item.formType !== 'online');
let { designName, designValues } = selectedDesign(designList);
designNameValue.value = designValues;
onlineCommonUserList.value = onlineList.map((item) => item.id);
oldDesignId.value = commUseList.map((item) => item.id).join(',');
} else {
designNameValue.value = [];
onlineCommonUserList.value = [];
oldDesignId.value = '';
}
}
}
/**
* 初始化online数据
*/
async function queryOnlineFormList() {
onlineFormList.value = [];
let res = await roleOnlineList();
if (res.success) {
onlineFormList.value = res.result;
}
}
/**
* 点击修改
*/
function designNameChange(selectedValue) {
newDesignId.value = unref(designNameValue).join(',');
}
/**
* 全选
*/
function checkALL() {
let { designName, designValues } = selectedDesign(toRaw(unref(desformList)));
designNameValue.value = designValues;
onlineCommonUserList.value = onlineFormList.value.map((item) => item.id);
newDesignId.value = [...designName, ...toRaw(unref(onlineCommonUserList))].join(',');
}
/**
* 取消全选
*/
function cancelCheckALL() {
designNameValue.value = [];
onlineCommonUserList.value = [];
newDesignId.value = '';
}
/**
* 选中工单信息
*/
function selectedDesign(selectedList) {
let designName = [];
let designValues = [];
for (let option of unref(processTypeDictOptions)) {
let values = [];
for (let value of selectedList) {
if (option.value == value.procType) {
designName.push(value.id);
values.push(value.id);
}
}
designValues.push(values);
}
return { designName, designValues };
}
/**
* 提交事件
*/
async function handleSubmit() {
try {
setModalProps({ confirmLoading: true });
let formData = {};
//TODO designNameValue的问题
let designValues = [];
unref(designNameValue).forEach((item) => {
designValues.push.apply(designValues, item);
});
formData['newDesignId'] = [...designValues, ...toRaw(unref(onlineCommonUserList))].join(',');
formData['oldDessignId'] = toRaw(unref(oldDesignId));
formData['onlineForm'] = onlineCommonUserList.value.join(',');
//保存常用流程
let res = await addCommUse(formData);
if (res.success) {
createMessage.success(res.message);
emit('success');
emit('ok');
} else {
createMessage.warning(res.message);
}
//关闭弹窗
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
@@ -0,0 +1,149 @@
<template>
<BasicModal :title="title" :width="modalWidth" v-bind="$attrs" @register="registerModal" wrapClassName="jeecg-online-modal" @ok="handleSubmit">
<template #footer>
<a-button
v-for="btn in cgButtonList"
:key="btn.id"
type="primary"
@click="handleCgButtonClick(btn.optType, btn.buttonCode)"
:preIcon="btn.buttonIcon ? 'ant-design:' + btn.buttonIcon : ''"
>
{{ btn.buttonName }}
</a-button>
<a-button v-if="!disableSubmit" key="submit" type="primary" @click="handleSubmit" :loading="submitLoading">确定</a-button>
<a-button key="back" @click="handleCancel">关闭</a-button>
</template>
<online-form
ref="onlineFormCompRef"
:id="tableId"
:disabled="disableSubmit"
:form-template="formTemplate"
:isTree="isTreeForm"
:pidField="pidFieldName"
:submitTip="false"
@rendered="renderSuccess"
@success="handleSuccess"
>
</online-form>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, ref, nextTick } from 'vue';
import { BasicModal } from '/@/components/Modal';
import OnlineForm from '/@/views/super/online/cgform/auto/comp/OnlineForm.vue';
import { useAutoModal } from '/@/views/super/online/cgform/hooks/auto/useAutoModal';
import { startProcess } from '/@/views/super/bpm/example/batch/leave.api';
import { SUBMIT_FLOW_ID } from '/@/views/super/online/cgform/types/onlineRender';
import { useMessage } from '/@/hooks/web/useMessage';
import { getRefPromise } from '/@/views/super/online/cgform/hooks/auto/useAutoForm';
export default defineComponent({
name: 'OnlineDynamicModal',
components: {
BasicModal,
OnlineForm,
},
emits: ['register'],
setup() {
console.log('工单申请-进入表单弹框》》》》modal');
const flow_code_pre = 'onl_';
const tableName = ref('');
const tableId = ref('');
const { createMessage } = useMessage();
let {
title,
modalWidth,
registerModal,
closeModal,
cgButtonList,
handleCgButtonClick,
disableSubmit,
handleSubmit,
submitLoading,
handleCancel,
modalObject,
isUpdate,
handleFormConfig,
onlineFormCompRef,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
formRendered,
} = useAutoModal(true);
/**
* 打开弹窗触发
* @param data
*/
modalObject.handleOpenModal = async (data) => {
const { id, name } = data;
tableId.value = id;
tableName.value = name;
isUpdate.value = false;
disableSubmit.value = false;
formRendered.value = false;
console.log('工单申请-重新渲染表单》》》》modal', data);
await handleFormConfig(id);
await nextTick(async () => {
await getRefPromise(formRendered);
await onlineFormCompRef.value.show(isUpdate);
});
};
/**
* 表单提交完成触发
* @param formData
*/
function handleSuccess(formData) {
handleStartProcess(formData[SUBMIT_FLOW_ID]);
}
/**
* 提交流程
* @param id
*/
async function handleStartProcess(id) {
let param = {
flowCode: flow_code_pre + tableName.value,
id: id,
formUrl: 'super/bpm/process/components/OnlineFormDetail',
formUrlMobile: 'check/onlineForm/detail',
};
let res = await startProcess(param);
if (res.success) {
createMessage.success(res.message);
closeModal();
} else {
createMessage.warning(res.message);
}
}
const that = {
title,
onlineFormCompRef,
renderSuccess,
registerModal,
handleSubmit,
handleSuccess,
handleCancel,
modalWidth,
formTemplate,
disableSubmit,
cgButtonList,
handleCgButtonClick,
isTreeForm,
pidFieldName,
submitLoading,
tableId,
};
return that;
},
});
</script>
<style scoped></style>
@@ -0,0 +1,77 @@
/** 列表上方操作按钮区域 */
.ant-card-body .table-operator {
margin-bottom: 8px;
}
/** Button按钮间距 */
.table-operator .ant-btn {
margin: 0 8px 8px 0;
}
.table-operator .ant-btn-group .ant-btn {
margin: 0;
}
.table-operator .ant-btn-group .ant-btn:last-child {
margin: 0 8px 8px 0;
}
/* 列表td的padding设置 可以控制列表大小 */
.ant-table-tbody .ant-table-row td {
padding-top: 15px;
padding-bottom: 15px;
}
/* 列表页面弹出modal */
.ant-modal-cust-warp {
height: 100%;
}
/* 弹出modal Y轴滚动条 */
.ant-modal-cust-warp .ant-modal-body {
height: calc(100% - 110px) !important;
overflow-y: auto;
}
/* 弹出modal 先有content后有body 故滚动条控制在body上 */
.ant-modal-cust-warp .ant-modal-content {
height: 90% !important;
overflow-y: hidden;
}
/* 列表中有图片的加这个样式 参考用户管理 */
.anty-img-wrap {
height: 25px;
position: relative;
}
.anty-img-wrap > img {
max-height: 100%;
}
/* 列表中范围查询样式 */
.query-group-cust {
width: calc(50% - 10px);
}
.query-group-split-cust::before {
content: '~';
width: 20px;
display: inline-block;
text-align: center;
}
/* erp风格子表外框padding设置 */
.ant-card-wider-padding.cust-erp-sub-tab > .ant-card-body {
padding: 5px 12px;
}
/* 内嵌子表背景颜色 */
.j-inner-table-wrapper :deep(.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody .ant-table-row) {
background-color: #fff;
}
/** 隐藏样式-modal确定按钮 */
.jee-hidden {
display: none;
}
@@ -0,0 +1,109 @@
<template>
<div v-if="visible" class="j-auto-desform-data-full-screen" :style="{ backgroundColor: bgColor }">
<DesformView
class="desform-view"
:mode="mode"
:desformCode="desForm.desformCode"
:dataId="dataId"
height="100vh"
:innerDialog="true"
@close="close"
@forceClose="close"
@success="handleSuccess"
@reload="handleReload"
:isOnline="isOnline"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive } from 'vue';
export default defineComponent({
props: {
dialogOptions: {
type: Object,
default: () => {},
},
},
emits: ['close', 'added', 'edited', 'ok'],
setup(_, { emit }) {
const _data = reactive({
mode: 'add',
title: '操作',
visible: false,
desForm: {},
dataId: null,
bgColor: 'rgba(0,0,0,0.6)',
isOnline: false,
/** 开启表单 */
});
function open(mode, desform, dataId, title) {
_data.mode = mode;
_data.title = title;
_data.dataId = dataId;
_data.desForm = desform;
_data.visible = true;
console.log('_data', _data);
}
/** 开始关闭动画 */
function close() {
_data.bgColor = 'rgba(0,0,0,0)';
setTimeout(() => {
closed();
}, 150);
}
/** 完全关闭,并初始化所有的字段 */
function closed() {
_data.visible = false;
emit('close');
_data.bgColor = 'rgba(0,0,0,0.6)';
// 恢复body的滚动
document.body.style.overflow = _data.bodyOverflow;
_data.bodyOverflow = null;
}
function handleSuccess(event) {
if (_data.dataId == null) {
emit('added', { desform: _data.desForm, dataId: event.dataId });
} else {
emit('edited', { desform: _data.desForm, dataId: _data.dataId });
}
close();
}
function handleReload() {
emit('ok');
}
return {
open,
close,
handleSuccess,
handleReload,
...toRefs(_data),
};
},
});
</script>
<style lang="less" scoped>
.j-auto-desform-data-full-screen {
position: fixed;
z-index: 999;
transition: background-color 150ms;
&,
.desform-view {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.desform-view {
position: absolute;
}
}
</style>
@@ -0,0 +1,61 @@
import { defHttp } from '/@/utils/http/axios';
import { Modal } from 'ant-design-vue';
enum Api {
list = '/act/process/extActDesignFlowData/list',
save = '/act/process/extActDesignFlowData/add',
edit = '/act/process/extActDesignFlowData/edit',
delete = '/act/process/extActDesignFlowData/delete',
queryFormDataById = '/desform/data/queryById',
deleteBatch = '/act/process/extActDesignFlowData/deleteBatch',
startProcess = '/act/process/extActProcess/startDesFormMutilProcess',
}
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 提交流程
* @param params
*/
export const startDesFormProcess = (params, handleSuccess) => {
Modal.confirm({
title: '提示',
content: '确认提交流程吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.get({ url: Api.queryFormDataById, params }, { isTransformResponse: false }).then((res) => {
handleSuccess(res);
});
},
});
};
/**
* 提交流程
* @param params
*/
export const startProcess = (params) => {
return defHttp.post({ url: Api.startProcess, params }, { isTransformResponse: false });
};
/**
* 保存或者更新
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return isUpdate
? defHttp.put({ url: url, params }, { isTransformResponse: false })
: defHttp.post({ url: url, params }, { isTransformResponse: false });
};
/**
* 删除监听
* @param params
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
@@ -0,0 +1,154 @@
import { FormSchema } from '/@/components/Table';
import { render } from '/@/utils/common/renderUtils';
export const columns = [
{
title: '业务申请',
dataIndex: 'bpmTitle',
width: 200,
ellipsis: true,
},
{
title: '表单',
dataIndex: 'desformName',
width: 250,
ellipsis: true,
customRender: ({ text }) => {
return `工单【${text}`;
},
},
{
title: '表单编码',
dataIndex: 'desformCode',
},
{
title: '流程名称',
dataIndex: 'processName',
},
{
title: '流程编码',
dataIndex: 'flowCode',
},
{
title: '创建时间',
dataIndex: 'createTime',
},
{
title: '状态',
dataIndex: 'bpmStatus',
customRender: ({ text }) => {
return render.renderDict(text, 'bpm_status');
},
},
];
/**
* 列表查询form
*/
export const searchFormSchema: FormSchema[] = [
{
field: 'desformCode',
label: '表单名称',
component: 'JSearchSelect',
colProps: { span: 6 },
componentProps: {
dict: 'design_form where parent_id is null,desform_name,desform_code',
},
},
{
field: 'flowCode',
label: '流程编码',
component: 'JInput',
colProps: { span: 6 },
},
{
field: 'processName',
label: '流程名称',
component: 'JInput',
colProps: { span: 6 },
},
];
/**
* 表单form
*/
export const formSchema: FormSchema[] = [
{
field: 'id',
label: '',
component: 'Input',
show: false,
},
{
label: '名称',
field: 'listenerName',
required: true,
component: 'Input',
},
{
label: '监听类型',
field: 'listenerType',
component: 'Select',
componentProps: ({ formModel }) => {
return {
options: [
{ label: '执行监听', value: 1 },
{ label: '任务监听', value: 2 },
],
onChange: () => {
formModel.listenerEvent = '';
},
};
},
},
{
label: '事件属性',
field: 'listenerEvent',
component: 'Select',
componentProps: ({ formModel }) => {
const isExecute = [
{ label: 'start', value: 'start' },
{ label: 'end', value: 'end' },
{ label: 'take', value: 'take' },
];
const isTask = [
{ label: 'create', value: 'create' },
{ label: 'assignment', value: 'assignment' },
{ label: 'complete', value: 'complete' },
];
let option = !formModel['listenerType'] ? [] : formModel['listenerType'] == 1 ? isExecute : isTask;
return {
options: option,
};
},
},
{
label: '值类型',
field: 'listenerValueType',
component: 'RadioGroup',
defaultValue: 'javaClass',
componentProps: ({ formActionType }) => {
return {
options: [
{ label: 'JAVA类', value: 'javaClass' },
{ label: '表达式', value: 'expression' },
{ label: '代理表达式', value: 'delegateExpression' },
],
onChange: (e) => {
const { updateSchema } = formActionType;
let value = e.target.value;
const label = value === 'javaClass' ? '类路径' : '表达式';
updateSchema([
{
field: 'listenerValue',
label: label,
},
]);
},
};
},
},
{
label: '类路径',
field: 'listenerValue',
component: 'Input',
},
];
@@ -0,0 +1,216 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!-- 审批记录 -->
<BpmProcessFormTrackModal ref="trackRef"></BpmProcessFormTrackModal>
<!-- 表单区域 -->
<DesformDataModal ref="desformModal" @added="handleDesformDataAdded" @edited="handleDesformDataEdited" @close="reload" />
</div>
</template>
<script lang="ts" name="process-order-list" setup>
import { ref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import BpmProcessFormTrackModal from '/@/views/super/bpm/process/manage/components/BpmProcessFormTrackModal.vue';
import DesformDataModal from './components/DesformDataModal.vue';
import { columns, searchFormSchema } from './my.apply.data';
import { list, startProcess, startDesFormProcess, deleteOne, saveOrUpdate } from './my.apply.api';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage();
// 列表页面公共参数、方法
const { prefixCls, tableContext } = useListPage({
designScope: 'my-process-order',
tableProps: {
title: '我的工单',
api: list,
columns: columns,
canResize: false,
scroll: { x: 1800 },
formConfig: {
schemas: searchFormSchema,
},
actionColumn: {
width: 150,
fixed: 'right',
},
},
});
const [registerTable, { reload, clearSelectedRowKeys }, { rowSelection, selectedRowKeys }] = tableContext;
const flowCodePre = 'desform_';
const trackRef = ref();
const desformModal = ref();
/**
* 提交流程
* @param record
*/
async function handleStartProcess(record) {
const success = async (res) => {
if (res && res.success) {
let jsonData = res.result.desformDataJson;
let param = {
flowCode: flowCodePre + record.desformCode,
id: record.id,
formUrl: '{{DOMAIN_URL}}/desform/detail/' + record.desformCode + '/${BPM_DES_DATA_ID}?token={{TOKEN}}&taskId={{TASKID}}',
formUrlMobile: '{{DOMAIN_URL}}/desform/detail/' + record.desformCode + '/${BPM_DES_DATA_ID}?token={{TOKEN}}&taskId={{TASKID}}',
jsonData: jsonData,
};
let result = await startProcess(param);
if (result && result.success) {
createMessage.success(result.message);
reload();
clearSelectedRowKeys();
} else {
createMessage.warning(res.message || '流程启动异常');
}
} else {
createMessage.warning(res?.message || '数据加载失败');
}
};
await startDesFormProcess({ desformCode: record.desformCode,id: record.desformDataId }, success);
}
/**
* 编辑
* @param record
*/
function handleEdit(record) {
let title = '【' + record.desformName + '】详情';
openDesformModal('edit', record, title);
}
/**
* 详情
* @param record
*/
function handleDetail(record) {
let title = '详情【' + record.desformName + '】';
openDesformModal('detail', record, title);
}
function openDesformModal(mode, record, title) {
let desform = record,
dataId = null;
if (mode === 'edit' || mode === 'detail') {
let { desformId: id, desformCode, desformDataId } = record;
dataId = desformDataId;
desform = { id, desformCode };
}
desformModal.value.open(mode, desform, dataId, title);
}
/**
* 删除
* @param id
*/
async function handleDelete(id) {
await deleteOne({ id }, reload);
}
/**
* 审批进度
* @param record
*/
function handleTrack(record) {
console.log('审批进度', record);
let flowCode = flowCodePre + record.desformCode;
let params = { flowCode: flowCode, dataId: record.id }; //查询条件
trackRef.value.handleTrack(params);
trackRef.value.data.title = '审批跟踪记录';
}
/** 流程数据保存成功后触发该事件 */
async function handleDesformDataAdded(event) {
// 将流程保存至后台
let { desform, dataId } = event;
//发起流程(往设计表单和流程的关系表中,插入一条数据)
let res = await saveOrUpdate(
{
desformId: desform.id,
desformCode: desform.desformCode,
desformDataId: dataId,
desformName: desform.desformName,
processName: desform.procName,
flowCode: flowCodePre + desform.desformCode,
titleExp: desform.titleExp,
},
false
);
if (!res.success) {
createMessage.error(res.message);
} else {
reload();
}
}
/** 流程数据更新成功后触发该事件 */
function handleDesformDataEdited(event) {
// 将流程保存至后台
let { desform, dataId } = event;
saveOrUpdate(
{
desformDataId: dataId,
},
true
).then((res) => {
console.log('res', res);
if (!res.success) {
createMessage.error(res.message);
} else {
reload();
}
});
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '提交流程',
onClick: handleStartProcess.bind(null, record),
ifShow: () => {
return record.bpmStatus === '1';
},
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
ifShow: () => {
return record.bpmStatus === '1';
},
},
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record.id),
},
ifShow: () => {
return record.bpmStatus === '1';
},
},
{
label: '审批进度',
onClick: handleTrack.bind(null, record),
ifShow: () => {
return record.bpmStatus !== '1';
},
},
];
}
</script>
@@ -0,0 +1,96 @@
<template>
<div v-if="_data.visible" class="j-auto-desform-data-full-screen" :style="{ backgroundColor: _data.bgColor }">
<desform-view
class="desform-view"
:mode="_data.mode"
:desformCode="_data.desformCode"
:dataId="_data.dataId"
height="100vh"
:innerDialog="true"
@close="close"
@success="handleSuccess"
@reload="handleReload"
:isOnline="_data.isOnline"
/>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
const emit = defineEmits(['ok']);
const _data = reactive({
mode: 'add',
title: '操作',
visible: false,
desformCode: null,
dataId: null,
bodyOverflow: null,
bgColor: 'rgba(0,0,0,0.6)',
isOnline: false,
});
/** 开启表单 */
function open(mode, desformCode, dataId, title, isOnline) {
_data.isOnline = isOnline;
_data.mode = mode;
_data.title = title;
_data.dataId = dataId;
_data.desformCode = desformCode;
_data.visible = true;
// 禁止body滚动,防止滚动穿透
_data.bodyOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
}
/** 开始关闭动画 */
function close() {
_data.bgColor = 'rgba(0,0,0,0)';
setTimeout(() => {
closed();
}, 150);
}
/** 完全关闭,并初始化所有的字段 */
function closed() {
_data.visible = false;
emit('ok');
_data.bgColor = 'rgba(0,0,0,0.6)';
// 恢复body的滚动
document.body.style.overflow = _data.bodyOverflow;
_data.bodyOverflow = null;
}
function handleSuccess() {
emit('ok');
close();
}
function handleReload() {
emit('ok');
}
defineExpose({
open,
});
</script>
<style lang="less" scoped>
.j-auto-desform-data-full-screen {
position: fixed;
z-index: 999;
transition: background-color 150ms;
&,
.desform-view {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.desform-view {
position: absolute;
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
import { Switch, Slider, Rate } from 'ant-design-vue';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
//设置特殊列类型(仅用于工单查询)
export function setCustomRender(item, column, options) {
// TODO 开关特殊处理
if (item.type === 'switch') {
column.customRender = ({ text }) => {
let activeValue = options.activeValue || true;
return <Switch size="small" checked={text === activeValue} disabled />;
};
}
// TODO 滑块特殊处理
if (item.type === 'slider') {
let { min, max } = options;
column.customRender = ({ text }) => {
return <Slider value={text} min={min} max={max} disabled style="margin:0;" />;
};
}
// TODO 评分组件
if (item.type === 'rate') {
let { max, allowHalf } = options;
column.customRender = ({ text }) => {
let val = parseInt(text);
return <Rate value={val} count={max} allowHalf={allowHalf} disabled style="margin:0;font-size: 16px;" />;
};
}
// TODO 超长截取显示
if (!column.slots && !column.customRender) {
column.customRender = ({ text }) => {
let txt = text;
// 如果是数组,就显示为逗号分割
if (Array.isArray(text)) {
txt = text.join(',');
}
return <JEllipsis length={50} value={txt} />;
};
}
return column;
}
@@ -0,0 +1,63 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/desform/data/list',
queryById = '/desform/queryById',
getColumns = '/desform/getColumns',
queryByCode = '/desform/queryByCode',
delete = '/desform/data/delete',
deleteBatch = '/desform/data/deleteBatch',
exportXls = '/desform/data/exportXls/',
importXls = '/desform/data/importXls/',
// 对接流程地址
startProcess = '/act/process/extActProcess/startDesFormMutilProcess',
}
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params }, { isTransformResponse: false });
/**
* 直接请求
* @param url
*/
export const getAction = (url) => defHttp.get({ url: url }, { isTransformResponse: false });
/**
* 获取列信息
* @param params
*/
export const getColumns = (params) => defHttp.get({ url: Api.getColumns, params }, { isTransformResponse: false });
const getTransitURL = (url) => `/desform/api/transitRESTful?url=${encodeURIComponent(url)}`;
// 中转HTTP请求
export const transitRESTful = {
get: (url, params?) => defHttp.get({ url: getTransitURL(url), params }, { isTransformResponse: false }),
post: (url, params?) => defHttp.post({ url: getTransitURL(url), params }, { isTransformResponse: false }),
put: (url, params?) => defHttp.put({ url: getTransitURL(url), params }, { isTransformResponse: false }),
};
/**
* 提交流程
* @param params
*/
export const startProcess = (params) => {
return defHttp.post({ url: Api.startProcess, params }, { isTransformResponse: false });
};
/**
* 删除
* @param params
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.delete, data: params }, { isTransformResponse: false, joinParamsToUrl: true }).then((res) => {
handleSuccess(res);
});
};
/**
* 批量删除
* @param params
*/
export const deleteBatch = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { isTransformResponse: false, joinParamsToUrl: true }).then((res) => {
handleSuccess(res);
});
};
@@ -0,0 +1,75 @@
<template>
<div :class="['p-4']">
<DesignFormDataTable v-if="showListTable" :queryDesformCode="data.desformCode" :customButtonsAuth="data.buttonsAuth">
<template #buttonBefore>
<span style="color: #060606">请选择工单 </span>
<a-select
v-model:value="data.desformCode"
class="search-input"
showSearch
:showArrow="false"
:options="data.desFormOptions"
placeholder="搜索表单"
optionFilterProp="text"
:filterOption="filterOption"
@change="onDesformChange"
>
</a-select>
</template>
</DesignFormDataTable>
</div>
</template>
<script lang="ts" setup>
import { initDictOptions } from '/@/utils/dict/index';
import { nextTick, reactive, computed } from 'vue';
import DesignFormDataTable from './components/DesignFormDataTable.vue';
const data = reactive({
reloading: false,
desformCode: '',
desFormOptions: [],
buttonsAuth: {
detail: true,
superQuery: true,
customColumn: true,
},
});
/*初始化字典*/
initDictConfig();
/*是否显示列表*/
const showListTable = computed(() => {
return data.desformCode && !data.reloading;
});
//初始化字典 - 表单数据
async function initDictConfig() {
let result = await initDictOptions('design_form,desform_name,desform_code,desform_type=1');
if (result) {
data.desFormOptions = result;
let code = data.desFormOptions[0].value;
onDesformChange(code);
}
}
// 刷新表格
async function reload() {
data.reloading = true;
await nextTick();
data.reloading = false;
await nextTick();
}
/*表单切换*/
function onDesformChange(code) {
data.desformCode = code;
reload();
}
/*是否根据输入项进行筛选*/
function filterOption(inputValue, option) {
return option.text.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0;
}
</script>
<style lang="less" scoped>
.table-operator .search-input {
width: 180px;
margin-right: 8px;
}
</style>
@@ -0,0 +1,161 @@
<template>
<BasicTable @register="registerTable">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!-- 查看历史 -->
<TaskHandleModal @register="registerHistoryModal"></TaskHandleModal>
<!-- 催办 -->
<task-notify-modal @register="registerNotifyModal"></task-notify-modal>
</template>
<script>
import { invalidProcess, backProcess, list } from './task.apply.api';
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, searchFormSchema } from './task.apply.data';
import TaskHandleModal from '../myHandleTask/modal/TaskHandleModal.vue';
import { useModal } from '/@/components/Modal';
import { getTaskInfoForHistory } from '../myHandleTask/useTaskList';
import TaskNotifyModal from './notify/TaskNotifyModal.vue';
import { ref } from 'vue';
export default {
name: 'MyApplyTaskList',
components: {
BasicTable,
TableAction,
TaskHandleModal,
TaskNotifyModal,
},
setup() {
const { tableContext } = useListPage({
designScope: 'my-apply-task-list',
pagination: true,
tableProps: {
api: list,
columns: columns,
showIndexColumn: true,
showTableSetting: true,
canResize: false,
scroll: { x: 1600 },
actionColumn: { dataIndex: 'action', fixed: 'right' },
formConfig: {
schemas: searchFormSchema,
autoAdvancedCol: 4,
baseColProps: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
actionColOptions: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
},
},
});
const [registerTable, { reload }] = tableContext;
const [registerHistoryModal, { openModal: openHistoryModal }] = useModal();
const [registerNotifyModal, { openModal: openNotifyModal }] = useModal();
function getTableAction(record) {
if (record.endTime && record.endTime != '') {
return [
{
label: '历史',
onClick: showHistory.bind(null, record),
},
];
} else {
return [];
}
}
async function showHistory(record) {
let { formData, formUrl } = await getTaskInfoForHistory(record);
formData['PROCESS_TAB_TYPE'] = 'history';
openHistoryModal(true, {
formData,
formUrl,
title: '流程历史',
});
}
function getDropDownAction(record) {
if (!record.endTime || record.endTime == '') {
let arr = [];
if(record.urgeStatus!=='0'){
arr.push({
label: '催办',
onClick: handleTaskNotify.bind(null, record),
})
}
arr.push({
label: '作废流程',
popConfirm: {
title: '确定要作废流程吗?',
placement: 'left',
confirm: handleInvalidTask.bind(null, record),
},
});
if(record.backStatus!=='0'){
arr.push({
label: '取回流程',
popConfirm: {
title: '确定要取回流程吗?',
placement: 'left',
confirm: handleBackTask.bind(null, record),
},
})
}
arr.push({
label: '历史',
onClick: showHistory.bind(null, record)
});
return arr;
} else {
return [];
}
}
// 流程作废 NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
async function handleInvalidTask(record) {
let params = {
processInstanceId: record.processInstanceId,
};
await invalidProcess(params);
reload();
}
// 流程取回
async function handleBackTask(record) {
let params = {
processInstanceId: record.processInstanceId,
};
await backProcess(params);
reload();
}
//催办
function handleTaskNotify(record) {
openNotifyModal(true, {
title: '催办提醒',
procInstId: record.processInstanceId,
});
}
return {
registerTable,
registerHistoryModal,
registerNotifyModal,
getTableAction,
getDropDownAction,
reload,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,91 @@
<template>
<BasicForm @register="registerForm" />
<div style="text-align: center; margin-top: 10px; width: 100%">
<a-button type="primary" @click="handleOk()" :loading="loading">保存</a-button>
</div>
</template>
<script>
/**
* 展示催办表单
*/
import { BasicForm, useForm } from '/@/components/Form/index';
import { taskNotification } from '../task.apply.api';
import {ref} from 'vue'
export default {
name: 'NotifyForm',
props: {
procInstId: {
type: String,
default: '',
},
},
emits: ['ok'],
components: {
BasicForm,
},
setup(props, { emit }) {
const formSchema = [
{
field: 'notifyType',
label: '催办类型',
component: 'JCheckbox',
defaultValue: '1,2',
componentProps: {
options: [
{ value: '1', label: '系统通知' },
{ value: '2', label: '邮件' },
],
},
},
{
field: 'remarks',
label: '催办内容',
component: 'InputTextArea',
required: true,
componentProps: {
placeholder: '请输入催办内容',
rows: 3,
},
},
];
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema, getFieldsValue }] = useForm({
schemas: formSchema,
showActionButtonGroup: false,
showSubmitButton: true,
baseColProps: { span: 24 },
});
const loading = ref(false)
async function handleOk() {
try {
loading.value = true
let formData = await validate();
let params = {
...formData,
procInstId: props.procInstId,
};
await taskNotification(params);
emit('ok');
setTimeout(()=>{
loading.value = false
}, 200)
}catch (e) {
console.log('催办出错',e)
loading.value = false
}
}
return {
registerForm,
handleOk,
loading
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,56 @@
<template>
<BasicTable @register="registerTable" />
</template>
<script>
/**
* 展示指定任务的催办列表
*/
import { BasicTable } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { taskNotifyList } from '../task.apply.api';
import { notifyColumns } from '../task.apply.data';
export default {
name: 'NotifyList',
components: {
BasicTable,
},
props: {
procInstId: {
type: String,
default: '',
},
},
setup(props) {
const { tableContext } = useListPage({
designScope: 'notify-list',
pagination: true,
tableProps: {
api: taskNotifyList,
columns: notifyColumns,
showIndexColumn: true,
showTableSetting: false,
useSearchForm: false,
canResize: false,
showActionColumn: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable] = tableContext;
function addQueryParams(params) {
params['procInstId'] = props.procInstId;
return params;
}
return {
registerTable,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,67 @@
<template>
<BasicModal :title="title" width="60%" destroyOnClose :bodyStyle="bodyStyle" :footer="null" @register="registerModal">
<a-tabs defaultActiveKey="1" tabPosition="top">
<a-tab-pane key="1">
<template #tab> <file-text-outlined /><span>催办</span> </template>
<notify-form :procInstId="procInstId" @ok="notifyOk"></notify-form>
<!--<ext-act-task-notification-modal ref="extActTaskNotificationModal" :procInstId="procInstId" @ok="handleOk"></ext-act-task-notification-modal>-->
<p></p>
</a-tab-pane>
<a-tab-pane key="2">
<template #tab> <user-outlined /><span>我提醒的</span> </template>
<notify-list :procInstId="procInstId"></notify-list>
</a-tab-pane>
</a-tabs>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import NotifyForm from './NotifyForm.vue';
import NotifyList from './NotifyList.vue';
import { UserOutlined, FileTextOutlined } from '@ant-design/icons-vue';
import { ref } from 'vue';
export default {
name: 'TaskNotifyModal',
emits: ['register'],
components: {
BasicModal,
NotifyForm,
NotifyList,
UserOutlined,
FileTextOutlined,
},
setup(_p, { emit }) {
const title = ref('');
const bodyStyle = {
padding: '0 5px',
'overflow-y': 'auto',
};
const procInstId = ref('');
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
console.log('data', data);
title.value = data.title;
procInstId.value = data.procInstId;
});
function notifyOk() {
//emit('success')
closeModal();
}
return {
title,
registerModal,
bodyStyle,
procInstId,
notifyOk,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,39 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/task/myApplyProcessList',
invalidProcess = '/act/task/invalidProcess',
backProcess = '/act/task/callBackProcess',
taskNotification = '/act/process/extActTaskNotification/taskNotification',
notifyList = '/act/process/extActTaskNotification/mylist',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 作废
* @param params
*/
export const invalidProcess = (params) => defHttp.put({ url: Api.invalidProcess, params });
/**
* 取回
* @param params
*/
export const backProcess = (params) => defHttp.put({ url: Api.backProcess, params });
/**
* 催办
* @param params
*/
export const taskNotification = (params) => defHttp.post({ url: Api.taskNotification, params });
/**
* 列表查询
* @param params
*/
export const taskNotifyList = (params) => defHttp.get({ url: Api.notifyList, params });
@@ -0,0 +1,155 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
/**
* 列表
*/
export const columns: BasicColumn[] = [
{
title: '业务标题',
align: 'center',
dataIndex: 'bpmBizTitle',
ellipsis: true,
},
{
title: '流程名称',
align: 'center',
dataIndex: 'prcocessDefinitionName',
ellipsis: true,
},
{
title: '流程实例',
align: 'center',
dataIndex: 'processInstanceId',
},
{
title: '发起人',
align: 'center',
width: 120,
dataIndex: 'startUserName',
},
{
title: '办理人',
align: 'center',
width: 120,
dataIndex: 'assigneeName',
},
{
title: '开始日期',
align: 'center',
dataIndex: 'startTime',
},
{
title: '结束时间',
align: 'center',
dataIndex: 'endTime',
},
{
title: '耗时',
align: 'center',
width: 150,
dataIndex: 'spendTimes',
},
{
title: '状态',
align: 'center',
dataIndex: 'bpmStatus',
width: 120,
customRender: ({ text }) => {
switch (text) {
case '1':
return '待提交';
case '2':
return '处理中';
case '3':
return '已完成';
case 'rejectProcess':
return '已驳回';
case 'callBackProcess':
return '已取回';
case 'invalidProcess':
return '已作废';
}
return text;
},
},
{
title: '流程编号',
align: 'center',
dataIndex: 'processDefinitionId',
ellipsis: true,
},
];
/**
* 查询条件
*/
export const searchFormSchema: FormSchema[] = [
{
label: '业务标题',
field: 'bpmBizTitle',
component: 'Input',
},
{
label: '流程名称',
field: 'processName',
component: 'Input',
},
{
label: '流程编号',
field: 'processDefinitionId',
component: 'Input',
},
// {
// label: '应用ID',
// field: 'lowAppId',
// component: 'Input',
// },
];
/**
* 催办列表
*/
export const notifyColumns: BasicColumn[] = [
{
title: '流程名称',
align: 'center',
dataIndex: 'procName',
},
{
title: '任务名称',
align: 'center',
dataIndex: 'taskName',
},
{
title: '任务处理人',
align: 'center',
dataIndex: 'taskAssignee',
},
{
title: '催办时间',
align: 'center',
dataIndex: 'opTime',
},
{
title: '催办类型',
align: 'center',
dataIndex: 'notifyType',
customRender: function ({ text }) {
var srtArr = text.split(',');
var value = '';
if (srtArr.includes('1')) {
value += ',页面通知';
}
if (srtArr.includes('2')) {
value += ',邮件';
}
return value.substring(1);
},
},
{
title: '催办说明',
align: 'center',
dataIndex: 'remarks',
},
];
@@ -0,0 +1,81 @@
<template>
<BasicTable @register="registerTable">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
</BasicTable>
<!-- 查看历史 -->
<task-handle-modal @register="registerHistoryModal"></task-handle-modal>
</template>
<script>
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { ref } from 'vue';
import { columns, searchFormSchema } from './task.cc.data';
import { list } from './task.cc.api';
import { getTaskInfoForHistory } from '../myHandleTask/useTaskList';
import TaskHandleModal from '../myHandleTask/modal/TaskHandleModal.vue';
import { useModal } from '/@/components/Modal';
export default {
name: 'MyCcTaskList',
components: {
BasicTable,
TaskHandleModal,
TableAction,
},
setup() {
const { tableContext } = useListPage({
designScope: 'my-apply-task-list',
pagination: true,
tableProps: {
api: list,
columns: columns,
showIndexColumn: true,
showTableSetting: true,
canResize: false,
scroll: { x: 1600 },
actionColumn: { dataIndex: 'action', fixed: 'right' },
formConfig: {
schemas: searchFormSchema,
autoAdvancedCol: 4,
baseColProps: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
actionColOptions: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
},
},
});
const [registerTable, { reload }] = tableContext;
const [registerHistoryModal, { openModal: openHistoryModal }] = useModal();
function getTableAction(record) {
return [
{
label: '查看审批',
onClick: showHistory.bind(null, record),
},
];
}
// NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
async function showHistory(record) {
let { formData, formUrl } = await getTaskInfoForHistory(record);
formData['PROCESS_TAB_TYPE'] = 'history';
openHistoryModal(true, {
formData,
formUrl,
title: '流程历史',
});
}
return {
registerTable,
getTableAction,
registerHistoryModal,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,11 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/task/taskAllCcHistoryList',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
@@ -0,0 +1,84 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
/**
* 列表
*/
export const columns: BasicColumn[] = [
{
title: '业务标题',
align: 'center',
dataIndex: 'bpmBizTitle',
ellipsis: true,
},
{
title: '流程编号',
align: 'center',
dataIndex: 'processDefinitionId',
},
{
title: '流程名称',
align: 'center',
dataIndex: 'processDefinitionName',
},
{
title: '流程实例',
align: 'center',
dataIndex: 'processInstanceId',
},
{
title: '任务名称',
align: 'center',
dataIndex: 'taskName',
},
{
title: '发起人',
align: 'center',
dataIndex: 'processApplyUserName',
},
{
title: '办理人',
align: 'center',
dataIndex: 'taskAssigneeName',
},
{
title: '开始时间',
align: 'center',
dataIndex: 'taskBeginTime',
},
{
title: '结束时间',
align: 'center',
dataIndex: 'taskEndTime',
},
{
title: '耗时',
align: 'center',
dataIndex: 'durationStr',
},
];
/**
* 查询条件
*/
export const searchFormSchema: FormSchema[] = [
{
label: '业务标题',
field: 'bpmBizTitle',
component: 'Input',
},
{
label: '流程名称',
field: 'processDefinitionName',
component: 'Input',
},
{
label: '流程编号',
field: 'processDefinitionId',
component: 'Input',
},
// {
// label: '应用ID',
// field: 'lowAppId',
// component: 'Input',
// },
];
@@ -0,0 +1,151 @@
<template>
<div style="padding: 10px">
<a-card>
<a-spin :spinning="loading">
<a-tabs v-model="activeKey" tabPosition="left">
<a-tab-pane key="1">
<template #tab> <file-text-outlined /><span>附加单据</span> </template>
<BpmDynamicForm :path="taskFormUrl" :form-data="taskFormData" />
</a-tab-pane>
<a-tab-pane key="2">
<template #tab> <user-outlined /><span>任务处理</span> </template>
<task-handle-inner-content @success="handleSuccess" :form-data="taskFormData" :claim="taskClaimStatus" @claimSuccess="handleClaimSuccess"></task-handle-inner-content>
</a-tab-pane>
<a-tab-pane key="3">
<template #tab> <partition-outlined /><span>流程图</span> </template>
<task-trace-content :form-data="taskFormData" />
</a-tab-pane>
</a-tabs>
</a-spin>
</a-card>
</div>
</template>
<script lang="ts">
import { useRoute } from 'vue-router';
import { useTaskList, getTaskInfoForHistory } from './myHandleTask/useTaskList';
import { ref } from 'vue';
import { UserOutlined, PartitionOutlined, FileTextOutlined } from '@ant-design/icons-vue';
import TaskHandleInnerContent from './myHandleTask/content/TaskHandleInnerContent.vue';
import TaskTraceContent from './myHandleTask/content/TaskTraceContent.vue';
import BpmDynamicForm from '/@/views/super/bpm/process/components/BpmDynamicForm.vue';
import { useTabs } from '/@/hooks/web/useTabs';
import { useMessage } from '/@/hooks/web/useMessage';
/**
* 用于办理跳转页面 独立菜单
*/
export default {
name: "myHandlePage",
components:{
BpmDynamicForm,
TaskHandleInnerContent,
TaskTraceContent,
UserOutlined,
PartitionOutlined,
FileTextOutlined
},
setup(){
const { createMessage } = useMessage();
//关闭当前tab
const { closeCurrent } = useTabs();
function handleSuccess(){
closeCurrent();
}
const taskFormData = ref({});
const taskFormUrl = ref('');
const taskClaimStatus = ref(false)
const loading = ref(false)
const activeKey = ref('1')
const route = useRoute();
let taskId = route.params.id as string;
let taskType = 'run';
// 加载节点信息
const { getTaskNodeInfo } = useTaskList('run');
loadNodeInfo();
async function loadNodeInfo(){
loading.value = true
const routeQuery:any = route.query;
if(routeQuery && routeQuery.history=='1'){
// 查看历史信息 抄送查看界面
await loadHistoryInfo(routeQuery)
}else{
// 办理界面
let { formData, formUrl, isSignTask, taskIsHandel } = await getTaskNodeInfo({id: taskId });
if(taskIsHandel==true){
//如果已经处理过了
//createMessage.warning("当前任务已被处理!");
await closeCurrent();
return;
}
console.log('formData', formData)
if(routeQuery){
// taskId taskDefKey procInsId
formData['taskId'] = routeQuery.taskId;
formData['taskDefKey'] = routeQuery.taskDefKey;
formData['procInsId'] = routeQuery.procInsId;
if(routeQuery.claim == 1 && isSignTask==false){
//如果已经签收了
// NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
createMessage.warning("当前任务已被他人签收!");
await closeCurrent();
return;
}
// 需要签收
if(isSignTask==true){
taskType = 'group';
taskClaimStatus.value = true
}
}
formData['PROCESS_TAB_TYPE'] = taskType;
taskFormData.value = formData
taskFormUrl.value = formUrl
}
loading.value = false
}
async function loadHistoryInfo(routeQuery){
let record = {
id: routeQuery.taskId,
taskId: routeQuery.taskDefKey,
processInstanceId: routeQuery.procInsId
}
let { formData, formUrl } = await getTaskInfoForHistory(record);
formData['PROCESS_TAB_TYPE'] = 'history';
taskClaimStatus.value = false
taskFormData.value = formData
taskFormUrl.value = formUrl
}
async function handleClaimSuccess(){
taskFormData.value['PROCESS_TAB_TYPE'] = 'run';
taskClaimStatus.value = false
}
return {
loading,
activeKey,
taskFormData,
taskFormUrl,
handleSuccess,
taskClaimStatus,
handleClaimSuccess
}
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,81 @@
<template>
<a-card :class="'jeecg-my-handle-task-info'">
<a-tabs :activeKey="activeKey" tabPosition="left" @tabClick="handleChangePanel">
<a-tab-pane tab="我的任务" key="run">
<task-running-list ref="taskRef" v-if="activeKey==='run'"></task-running-list>
</a-tab-pane>
<a-tab-pane tab="组任务" key="group">
<task-group-list v-if="activeKey==='group'"></task-group-list>
</a-tab-pane>
<a-tab-pane tab="历史任务" key="history">
<task-history-list v-if="activeKey==='history'"></task-history-list>
</a-tab-pane>
</a-tabs>
</a-card>
</template>
<script>
/**
* 我处理的任务信息
* - 正在处理的任务
* - 组任务
* - 历史任务
*/
import { ref, onMounted, nextTick } from 'vue';
import TaskRunningList from './TaskRunningList.vue';
import TaskGroupList from './TaskGroupList.vue';
import TaskHistoryList from './TaskHistoryList.vue';
import { useRouter } from 'vue-router';
import { useAppStore } from '/@/store/modules/app';
export default {
name: 'MyHandleTaskInfo',
components: {
TaskRunningList,
TaskGroupList,
TaskHistoryList,
},
setup() {
const bodyStyle = {
padding: '10px',
};
const activeKey = ref('run');
function handleChangePanel(key) {
activeKey.value = key;
}
// 消息跳转处理页面参数
const taskRef = ref()
const appStore = useAppStore();
onMounted(()=>{
activeKey.value = 'run';
let params = appStore.getMessageHrefParams;
if(params) {
let taskId = params.detailId;
if(taskId){
nextTick(()=>{
taskRef.value.openHrefModal(taskId);
appStore.setMessageHrefParams('');
})
}
}
});
return {
bodyStyle,
activeKey,
handleChangePanel,
taskRef
};
},
};
</script>
<style scoped>
.jeecg-my-handle-task-info {
margin: 10px;
}
</style>
@@ -0,0 +1,79 @@
<template>
<BasicTable @register="registerTable">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"></TableAction>
</template>
</BasicTable>
<!-- 办理 -->
<task-handle-modal @register="registerHandleModal" @success="reload"></task-handle-modal>
</template>
<script>
import { BasicTable, TableAction } from '/@/components/Table';
import { useTaskList } from './useTaskList';
import { useModal } from '/@/components/Modal';
import TaskHandleModal from './modal/TaskHandleModal.vue';
import { taskClaim } from './task.handle.api';
export default {
name: 'TaskGroupList',
components: {
BasicTable,
TableAction,
TaskHandleModal,
},
setup() {
const { registerTable, reload, getTaskNodeInfo } = useTaskList('group');
//办理
const [registerHandleModal, { openModal: openHandleModal }] = useModal();
async function handleProcess(record) {
let { formData, formUrl } = await getTaskNodeInfo(record);
formData['PROCESS_TAB_TYPE'] = 'run';
openHandleModal(true, {
formData,
formUrl,
title: '流程办理',
});
}
//签收
async function handleClaim(record) {
let params = { taskId: record.id };
await taskClaim(params);
await reload();
}
function getTableAction(record) {
let arr = [];
if (record.taskAssigneeId && record.taskAssigneeId != '') {
arr.push({
label: '办理',
onClick: handleProcess.bind(null, record),
});
} else {
arr.push({
label: '签收',
popConfirm: {
title: '确定签收吗?',
placement: 'left',
confirm: handleClaim.bind(null, record),
},
});
}
return arr;
}
return {
registerTable,
getTableAction,
registerHandleModal,
reload,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,58 @@
<template>
<BasicTable @register="registerTable">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"></TableAction>
</template>
</BasicTable>
<task-handle-modal @register="registerModal"></task-handle-modal>
</template>
<script>
import { BasicTable, TableAction } from '/@/components/Table';
import { useTaskList } from './useTaskList';
import { useModal } from '/@/components/Modal';
import TaskHandleModal from './modal/TaskHandleModal.vue';
export default {
name: 'TaskHistoryList',
components: {
BasicTable,
TableAction,
TaskHandleModal,
},
setup() {
const { registerTable, getHistoryTaskInfo } = useTaskList('history');
const [registerModal, { openModal }] = useModal();
async function showHistoryInfo(record) {
let { formData, formUrl } = await getHistoryTaskInfo(record);
formData['PROCESS_TAB_TYPE'] = 'history';
openModal(true, {
formData,
formUrl,
title: '流程历史',
});
}
function getTableAction(record) {
return [
{
label: '历史',
onClick: showHistoryInfo.bind(null, record),
},
];
}
return {
registerTable,
registerModal,
getTableAction,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,186 @@
<template>
<BasicTable @register="registerTable">
<template #bpmBizTitle="{ text, record }">
<notification-two-tone v-if="record.taskUrge" title="催办提醒" twoToneColor="#eb2f96" @click="taskNotify(record)" />
<j-ellipsis :value="text" :length="15" />
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"></TableAction>
</template>
</BasicTable>
<!-- 催办 -->
<task-notify-me-modal @register="registerNotifyModal"></task-notify-me-modal>
<!-- 办理 -->
<task-handle-modal @register="registerHandleModal" @success="reload"></task-handle-modal>
<!-- 委托 -->
<select-entruster-modal @register="registerEntrusterModal" @selected="selectedEntruster"></select-entruster-modal>
<!-- 转办 -->
<select-entruster-modal @register="registerComplaintModal" @selected="selectedComplaint"></select-entruster-modal>
</template>
<script>
import { BasicTable, TableAction } from '/@/components/Table';
import { useTaskList } from './useTaskList';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
import { NotificationTwoTone } from '@ant-design/icons-vue';
import TaskNotifyMeModal from './modal/TaskNotifyMeModal.vue';
import TaskHandleModal from './modal/TaskHandleModal.vue';
import { useModal } from '/@/components/Modal';
import SelectEntrusterModal from './modal/SelectEntrusterModal.vue';
import { taskEntrust, taskClaim, taskComplaint } from './task.handle.api';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
export default {
name: 'TaskRunningList',
components: {
BasicTable,
TableAction,
JEllipsis,
NotificationTwoTone,
TaskNotifyMeModal,
TaskHandleModal,
SelectEntrusterModal,
},
setup() {
const { registerTable, reload, getTaskNodeInfo } = useTaskList('run');
const userStore = useUserStore();
const { createMessage } = useMessage();
//催办
const [registerNotifyModal, { openModal: openNotifyModal }] = useModal();
function taskNotify(record) {
openNotifyModal(true, {
procInstId: record.processInstanceId,
});
}
//办理
const [registerHandleModal, { openModal: openHandleModal }] = useModal();
async function handleProcess(record) {
let { formData, formUrl } = await getTaskNodeInfo(record);
formData['PROCESS_TAB_TYPE'] = 'run';
openHandleModal(true, {
formData,
formUrl,
title: '流程办理',
});
}
// 委托
const [registerEntrusterModal, { openModal: openEntrusterModal }] = useModal();
// 转办
const [registerComplaintModal, { openModal: openComplaintModal }] = useModal();
// 委托——弹出选择界面
function handleSelectEntruster(record) {
openEntrusterModal(true, {
taskId: record.id,
});
}
// 转办——弹出选择界面
function handleComplaintEntruster(record) {
openComplaintModal(true, {
taskId: record.id,
});
}
// 委托——回调处理
async function selectedEntruster(params) {
console.log('委托', params);
await taskEntrust(params);
await reload();
}
// 转办——回调处理
async function selectedComplaint(params) {
console.log('转办', params);
await taskComplaint(params);
await reload();
}
//签收
async function handleClaim(record) {
let params = { taskId: record.id };
await taskClaim(params);
await reload();
}
function getTableAction(record) {
let arr = [];
if (record.taskAssigneeId && record.taskAssigneeId != '') {
arr.push({
label: '办理',
onClick: handleProcess.bind(null, record),
});
} else {
arr.push({
label: '签收',
popConfirm: {
title: '确定签收吗?',
placement: 'left',
confirm: handleClaim.bind(null, record),
},
});
}
arr.push({
label: '转办',
onClick: handleComplaintEntruster.bind(null, record),
});
arr.push({
label: '委托',
onClick: handleSelectEntruster.bind(null, record),
});
return arr;
}
/**
* 消息跳转时打开表单页面的逻辑
* @param taskId
*/
async function openHrefModal(taskId){
let { formData, formUrl, isSignTask, taskIsHandel, assignee } = await getTaskNodeInfo({id: taskId });
if(taskIsHandel==true){
//如果已经处理过了
return;
}
let username = userStore.getUserInfo.username;
if(assignee && username!=assignee){
//不是你办理的任务
createMessage.warning('任务已被他人签收~')
return;
}
formData['PROCESS_TAB_TYPE'] = 'run';
openHandleModal(true, {
formData,
formUrl,
isSignTask,
title: '流程办理',
});
}
return {
registerTable,
taskNotify,
registerNotifyModal,
registerHandleModal,
getTableAction,
reload,
selectedEntruster,
selectedComplaint,
registerEntrusterModal,
registerComplaintModal,
openHrefModal
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,502 @@
<template>
<a-card style="margin-top: 10px">
<template #title> <audit-outlined /><span style="margin-left: 10px">我的审批</span> </template>
<a-spin :spinning="loading">
<a-list itemLayout="vertical">
<a-list-item>
<div style="width: 100%">
<div style="margin-bottom: 5px">
处理意见
<a-select
style="width: 300px"
placeholder="常用审批语"
:getPopupContainer="(target) => target.parentNode"
@change="changeReasonSelection"
>
<a-select-option v-for="(item, key) in remarksDictOptions" :key="key" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
</div>
<a-textarea :rows="3" v-model:value="model.reason" />
</div>
</a-list-item>
<a-list-item>
<j-upload text="添加文件" bizPath="bpm" :returnUrl="false" v-model:value="model.fileList"></j-upload>
</a-list-item>
<!-- 选择分支 -->
<a-list-item>
<div style="width: 100%">
<a-radio-group v-model:value="model.processModel">
<a-radio :checked="true" :value="1">单分支模式</a-radio>
<a-radio :value="2">多分支模式</a-radio>
<a-radio :value="3" v-if="historyCount > 0 && allowReject">驳回</a-radio>
</a-radio-group>
<span v-show="model.processModel == 2">
<span style="color: red">多分支模式默认执行所有分支</span>
<template v-for="(item, index) in branchList">
<a-checkbox :checked="true" :value="item.nextnode">{{ item.Transition }}</a-checkbox>
</template>
</span>
<span v-show="model.processModel == 3" v-if="historyCount > 0">
<a-select v-model:value="model.rejectModelNode" :getPopupContainer="(target) => target?.parentNode" style="width: 150px">
<template v-for="(item, index) in historyList">
<a-select-option v-if="item.NAME_ != currentTaskName" :value="item.TASK_DEF_KEY_">{{ item.NAME_ }}</a-select-option>
</template>
</a-select>
</span>
</div>
</a-list-item>
<!-- 下一步操作人 -->
<a-list-item class="flex-center" v-if="buttonStatus.selnextUserStatus || buttonStatus.ccStatus || allowAddSign || allowCounterSignAddUser">
<a-checkbox v-if="buttonStatus.selnextUserStatus" :checked="checkedNext" @change="handleCheckedNext">指定下一步操作人指定下一步会签人员</a-checkbox>
<a-checkbox v-if="buttonStatus.ccStatus" :checked="checkedCc" @change="handleCheckedCc">是否抄送</a-checkbox>
<a-dropdown v-if="allowAddSign || allowCounterSignAddUser" :trigger="['click']" :overlayStyle="{ width: '120px' }" placement="bottomRight">
<div class="dot-more" title="更多操作"> <div></div><div></div><div></div> </div>
<template #overlay>
<a-menu @click="onMoreAction">
<a-menu-item key="add-sign" v-if="allowAddSign">
<div class="flex-center">
<Icon icon="ant-design:sisternode-outlined" size="22" />
<span style="margin-left: 10px">动态加签</span>
</div>
</a-menu-item>
<a-menu-item key="add-user" v-if="allowCounterSignAddUser">
<div class="flex-center">
<Icon icon="ant-design:usergroup-add-outlined" size="22" />
<span style="margin-left: 10px">追加审批人</span>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-list-item>
<a-list-item style="line-height: 32px" v-show="checkedNext">
<span>指定下一步操作人指定下一步会签人员</span>
<bpm-select-user style="display: inline-block" placeholder="请选择下一步操作人" @change="handleSelectNextUser"></bpm-select-user>
</a-list-item>
<!-- 抄送 -->
<a-list-item style="line-height: 32px" v-show="checkedCc">
<span>抄送给</span>
<bpm-select-user style="display: inline-block" placeholder="请选择抄送人" @change="handleSelectCcUser"></bpm-select-user>
</a-list-item>
</a-list>
<!-- 流转按钮 -->
<div style="margin-top: 20px; text-align: center">
<template v-if="model.processModel == 1">
<template v-for="(item, index) in branchList">
<a-button type="primary" @click="handleProcessComplete(item.nextnode)">{{ item.Transition }}</a-button>
</template>
</template>
<template v-else>
<a-button type="primary" @click="handleManyProcessComplete()">确认提交</a-button>
</template>
</div>
<br />
</a-spin>
</a-card>
<!-- 加签节点弹窗 -->
<add-sign-task-modal @register="registerAddSignTaskModal" @selected="selectedAddSignTask" />
<!-- 审批人选择人员 -->
<user-select-modal :multi="multiSelectUser" @register="registerUserModal" @selected="onSelectedUserOk" />
</template>
<script>
import { ref, reactive, computed, toRaw, unref, watchEffect } from 'vue';
import { initDictOptions } from '/@/utils/dict';
import { useMessage } from '/@/hooks/web/useMessage';
import { taskComplaint, taskComplete, beforeAddSignTask, afterAddSignTask, addMultiInstance} from "../task.handle.api";
import { useModal } from '/@/components/Modal';
import { JSelectUserByDept } from '/@/components/Form';
import { AuditOutlined } from '@ant-design/icons-vue';
import { JUpload } from '/@/components/Form/src/jeecg/components/JUpload';
import BpmSelectUser from '/@/views/super/bpm/process/components/bpmSelectUser/index.vue';
import AddSignTaskModal from '/@/views/super/bpm/process/personalOffice/myHandleTask/modal/AddSignTaskModal.vue';
import UserSelectModal from '/@/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue';
import { useUserStore } from '/@/store/modules/user';
import { isString } from '/@/utils/is';
export default {
name: 'MyHandleContent',
components: {
UserSelectModal,
AddSignTaskModal,
JSelectUserByDept,
JUpload,
AuditOutlined,
BpmSelectUser,
},
props: {
historyList: {
type: Array,
default: () => [],
},
branchList: {
type: Array,
default: () => [],
},
currentTaskName: {
type: String,
default: '',
},
taskId: {
type: String,
default: '',
},
ccStatus:{
type: Boolean,
default: true,
},
allowAddSign:{
type: Boolean,
default: false,
},
allowCounterSignAddUser: {
type: Boolean,
default: false,
},
allowReject: {
type: Boolean,
default: true,
},
turnbackTaskId: {
type: String,
default: '',
},
selnextUserStatus:{
type: Boolean,
default: true,
}
},
emits: ['success'],
setup(props, { emit }) {
console.log('进入MyHandleContent');
const loading = ref(false);
const { createMessage: $message, createConfirm: $confirm } = useMessage();
const userStore = useUserStore();
// 转办
const [registerAddSignTaskModal, { openModal: openAddSignTaskModal }] = useModal();
// 选择用户
const [registerUserModal, { openModal: openUserModal, closeModal: closeUserModal }] = useModal();
/**
* 转审(change-user)和添加审批人(add-user) 都需要选择用户,此处记录不同的类型
*/
const selectUserType = ref('');
const remarksDictOptions = ref([]);
const historyCount = computed(() => {
return props.historyList.length;
});
const buttonStatus = computed(()=>{
return {
ccStatus: props.ccStatus,
selnextUserStatus: props.selnextUserStatus
}
})
const model = reactive({
taskId: '',
nextnode: '',
nextCodeCount: '',
reason: '',
processModel: 1,
rejectModelNode: '',
nextUserName: '',
nextUserId: '',
ccUserIds: '',
ccUserRealNames: '',
fileList: '',
});
//审批人用户是否多选
const multiSelectUser = computed(() => {
return unref(selectUserType) == 'change-user' ? false : true;
});
//监听返回节点变化
watchEffect(() => {
props.turnbackTaskId && (model.rejectModelNode = props.turnbackTaskId);
});
// 选择下一步操作人
const checkedNext = ref(false);
const nextPersonList = ref([]);
function handleCheckedNext(e) {
checkedNext.value = e.target.checked;
nextPersonList.value = [];
}
// 选择抄送人
const checkedCc = ref(false);
const ccPersonList = ref([]);
function handleCheckedCc(e) {
checkedCc.value = e.target.checked;
ccPersonList.value = [];
}
function changeReasonSelection(value) {
model.reason = value;
}
function handleProcessComplete(nextnode) {
if (!model.reason || model.reason.length == 0) {
//update-begin-author:taoyan date:2022-9-5 for: VUEN-2157 2. 意见允许为空
//$message.warning('请填写处理意见');
//return;
//update-end-author:taoyan date:2022-9-5 for: VUEN-2157 2. 意见允许为空
}
if (nextnode) {
model.nextnode = nextnode;
}
//设置下个节点的数量
if (props.branchList) {
model.nextCodeCount = props.branchList.length;
}
$confirm({
title: '提示',
content: '确认提交审批吗?',
onOk: commitTaskHandleInfo,
});
}
function handleManyProcessComplete() {
if (model.processModel == 3) {
if (!model.rejectModelNode || model.rejectModelNode.length == 0) {
$message.warning('请选择驳回节点');
return;
}
}
handleProcessComplete();
}
// 加签——弹出选择界面
function handleAddSignTask() {
openAddSignTaskModal(true, {
taskId: props.taskId
});
}
// 加签——回调处理
async function selectedAddSignTask(params,addSignType) {
console.log('加签', params);
console.log('加签', addSignType);
if(addSignType=='after'){
await afterAddSignTask(params);
}
if(addSignType=='before'){
await beforeAddSignTask(params);
}
//加签完,关闭办理弹窗
emit('success');
}
//================[20230707会签节点增加审批人]begin=============================
/**
* 更多操作
*/
function onMoreAction(e) {
selectUserType.value = 'add-user';
if ('add-user' == e.key) {
//多实例加签(支持串行和并行会签)
onAddHandlePerson();
} else if ('add-sign' == e.key) {
//加签
handleAddSignTask();
}
}
/**
* 添加审批人 事件
*/
function onAddHandlePerson() {
let id = userStore.getUserInfo.id;
selectUserType.value = 'add-user';
openUserModal(true, {
list: [],
excludeUserIdList: [id],
});
}
/**
* 人员选中事件
* @param data
*/
async function onSelectedUserOk(data) {
console.log('onSelectedUserOk', data);
if (data && data.length > 0) {
if (!props.taskId) {
console.error('节点信息不存在');
return;
}
if (selectUserType.value === 'change-user') {
//await doChangeHandlePerson(node.taskId, data)
} else if (selectUserType.value === 'add-user') {
await doAddHandlePerson(props.taskId, data);
} else {
console.error('不识别的类型', selectUserType.value);
}
}
}
/**
* 添加审批人 请求
*/
async function doAddHandlePerson(taskId, userList) {
let usernameList = userList.map((item) => item.username);
let params = {
taskId: taskId,
assignees: usernameList,
//简流模式,串行会签专用变量值
assigneeListKey: 'loopAssigneeCollection',
};
const result = await addMultiInstance(params);
if (result.success) {
closeUserModal();
emit('success');
$message.success('操作成功!');
} else {
$message.warning(result.message);
}
}
//================[20230707会签节点增加审批人]end=============================
/**
* 流程处理提交
* @returns {Promise<void>}
*/
async function commitTaskHandleInfo() {
loading.value = true;
const params = toRaw(model);
params.taskId = props.taskId;
if (params.fileList) {
params.fileList = isString(params.fileList) ? params.fileList : JSON.stringify(params.fileList);
}
try {
console.log('流程提交->params', params);
let json = await taskComplete(params);
console.log('流程提交-result', json);
loading.value = false;
emit('success');
} catch (e) {
console.error('流程处理失败', e);
loading.value = false;
}
}
async function initDict() {
const dictData = await initDictOptions('approval_remarks');
console.log('dic', dictData);
remarksDictOptions.value = dictData;
}
function handleSelectNextUser(arr) {
let { usernames, realnames } = getInfo(arr);
model.nextUserId = usernames.join(',');
model.nextUserName = realnames.join(',');
}
function handleSelectCcUser(arr) {
let { usernames, realnames } = getInfo(arr);
model.ccUserIds = usernames.join(',');
model.ccUserRealNames = realnames.join(',');
}
function getInfo(arr) {
let usernames = [];
let realnames = [];
if (arr && arr.length > 0) {
for (let item of arr) {
usernames.push(item.username);
realnames.push(item.realname);
}
}
return {
usernames,
realnames,
};
}
initDict();
return {
handleSelectNextUser,
handleSelectCcUser,
model,
remarksDictOptions,
changeReasonSelection,
historyCount,
checkedNext,
nextPersonList,
handleCheckedNext,
checkedCc,
ccPersonList,
handleCheckedCc,
loading,
handleManyProcessComplete,
handleProcessComplete,
buttonStatus,
registerAddSignTaskModal,
selectedAddSignTask,
handleAddSignTask,
onMoreAction,
openUserModal,
closeUserModal,
registerUserModal,
onSelectedUserOk,
multiSelectUser
};
},
};
</script>
<style lang="less" scoped>
:deep(.ant-card-head-title) {
padding: 10px 0;
}
:deep(.ant-collapse-header) {
font-size: 16px !important;
}
:deep(.ant-card-head) {
padding-left: 12px;
}
html[data-theme='light'] {
background-color: #fafafa;
}
:deep(.ant-card-body) {
padding-top: 6px;
}
//会签节点增加审批人下拉显示更多样式begin
.flex-center {
display: flex;
justify-content: flex-start;
align-items: center;
}
.dot-more {
display: flex;
flex-direction: row;
height: 30px;
width: 30px;
padding: 0 4px;
border-radius: 2px;
cursor: pointer;
align-items: center;
justify-content: space-between;
margin-left: 8px;
& > div {
width: 4px;
height: 4px;
background-color: #9e9e9e;
margin: 0 1px;
border-radius: 3px;
}
&:hover {
background-color: #f7f7f7;
& > div {
background-color: #0a9fe5;
}
}
}
//会签节点增加审批人下拉显示更多样式end
</style>
@@ -0,0 +1,89 @@
<template>
<a-collapse v-model:activeKey="activeKey" style="margin-top: 10px">
<a-collapse-panel key="1" header="意见信息">
<a-list itemLayout="vertical">
<template v-for="(item, index) in bpmLogList">
<a-list-item>
<a-list-item-meta :description="item.remarks||'无意见信息'">
<template #title>
{{ item.opUserName }}
<span style="color: #ff6d75">[{{ item.taskName }}]</span>
{{ item.opTime }}
</template>
<template #avatar>
<a-avatar :size="36" style="background-color: #51cbff"
><template #icon><UserOutlined /></template
></a-avatar>
</template>
</a-list-item-meta>
<template v-for="(file, index) in item.bpmFiles">
<div class="ant-upload-list ant-upload-list-text">
<div class="ant-upload-list-item ant-upload-list-item-done">
<div class="ant-upload-list-item-info">
<span>
<paper-clip-outlined />
<a
target="_blank"
rel="noopener noreferrer"
:title="file.fileName"
:href="getFileDownloadUrl(file.filePath)"
class="ant-upload-list-item-name"
>{{ file.fileName }}</a
>
</span>
</div>
</div>
</div>
</template>
</a-list-item>
</template>
</a-list>
</a-collapse-panel>
</a-collapse>
</template>
<script>
import { UserOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { ref, watch } from 'vue';
export default {
name: 'TaskCommentList',
components: {
UserOutlined,
PaperClipOutlined,
},
props: {
bpmLogList: {
type: Array,
default: () => [],
},
},
setup(props) {
const activeKey = ref('1');
watch(
() => props.bpmLogList,
() => {
if (props.bpmLogList.length > 0) {
activeKey.value = '1';
} else {
activeKey.value = '';
}
}
);
function getFileDownloadUrl(path) {
return getFileAccessHttpUrl(path);
}
return {
activeKey,
getFileDownloadUrl,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,244 @@
<template>
<div class="jeecg-task-handle-content">
<!-- 横向步骤条显示节点 -->
<task-node-step-list :taskStepList="taskStepList" :stepCount="bpmLogList.length" :currentNode="currentNode"></task-node-step-list>
<!-- 意见信息 -->
<task-comment-list :bpmLogList="bpmLogList"></task-comment-list>
<!-- 填写处理意见并提交 -->
<my-handle-content
v-if="isRunningTask"
@success="handleSuccess"
:taskId="taskId"
:historyList="historyList"
:turnbackTaskId="turnbackTaskId"
:branchList="branchList"
:selnextUserStatus="selnextUserStatus"
:ccStatus="ccStatus"
:allowAddSign="allowAddSign"
:allowCounterSignAddUser="allowCounterSignAddUser"
:allowReject="allowReject"
:currentTaskName="currentNode.taskName">
</my-handle-content>
<!-- 签收 -->
<div v-if="claim" style="width: 100%;text-align: center; padding-top: 10px">
<a-button type="primary" @click="handleClaim" :loading="loading"><AuditOutlined/>确认签收</a-button>
</div>
</div>
</template>
<script lang="ts">
import { taskTransInfo, taskComplete, taskClaim } from '../task.handle.api';
import { ref, watch } from 'vue';
import TaskNodeStepList from './TaskNodeStepList.vue';
import TaskCommentList from './TaskCommentList.vue';
import MyHandleContent from './MyHandleContent.vue';
import { pick } from 'lodash-es';
import { AuditOutlined } from '@ant-design/icons-vue';
export default {
name: 'TaskHandleInnerContent',
components: {
TaskNodeStepList,
TaskCommentList,
MyHandleContent,
AuditOutlined
},
props: {
formData: {
type: Object,
default: () => {},
},
//签收状态
claim:{
type: Boolean,
default: false
}
},
emits: ['success', 'claimSuccess'],
setup(props, { emit }) {
console.log('进入tab panel-content');
const taskId = ref('');
// 任务步骤条
const taskStepList = ref([]);
// 当前任务节点
const currentNode = ref({});
// 审批记录/意见信息
const bpmLogList = ref([]);
// 历史节点
const historyList = ref([]);
// 获取上一步的节点
const turnbackTaskId = ref('');
// 任务分支
const branchList = ref([]);
const isRunningTask = ref(true);
const allowAddSign = ref(false);
//允许驳回
const allowReject = ref(true);
const allowCounterSignAddUser = ref(false);
const loading = ref(false);
// 选择下一步处理人
const selnextUserStatus = ref(true);
// 选择抄送人
const ccStatus = ref(true);
getTaskTransInfo();
async function getTaskTransInfo(type?) {
let taskType = props.formData['PROCESS_TAB_TYPE'];
console.log('taskType>>', taskType)
if(type){
taskType = type;
}
isRunningTask.value = taskType == 'run';
//查询条件-run只需要taskId history只需要procInstId
let params = { taskId: props.formData.taskId, procInstId: props.formData.procInsId };
let data = await taskTransInfo(params, taskType);
console.log('获取流程流转信息', data);
//update-begin-author:taoyan date:2022-7-5 for:
//选择下一步操作人
if(!data.selnextUserStatus || data.selnextUserStatus=='1'){
//如果没有该值 或是该值为1
selnextUserStatus.value = true;
}else{
selnextUserStatus.value = false;
}
// 选择抄送人
if(!data.ccStatus || data.ccStatus=='1'){
//如果没有该值 或是该值为1
ccStatus.value = true;
}else{
ccStatus.value = false;
}
//是否允许加签
allowAddSign.value= data.allowAddSign;
//是否允许驳回
allowReject.value= data?.rejectStatus && data?.rejectStatus =='0'?false:true;
//是否多实例- 会签加签 是否允许会签节点加人0否、1允许
allowCounterSignAddUser.value = (data?.allowCounterSignAddUser && data.allowCounterSignAddUser =='1');
taskStepList.value = data.bpmLogStepList;
bpmLogList.value = data.bpmLogList;
let nodeObject = pick(data, 'taskName', 'taskNameStartTime', 'taskAssigneeName');
if (isRunningTask.value === false) {
// 'taskName', 'taskNameStartTime', 'taskAssigneeName' 是正在运行流程的当前节点信息
// 'currTaskName', 'currTaskNameAssignee', 'currTaskNameStartTime'这三个是历史查看中的节点信息
nodeObject['taskName'] = data['currTaskName'];
nodeObject['taskNameStartTime'] = data['currTaskNameStartTime'];
nodeObject['taskAssigneeName'] = data['currTaskNameAssignee'];
}
currentNode.value = nodeObject;
historyList.value = data.histListNode;
branchList.value = data.transitionList;
turnbackTaskId.value = data.turnbackTaskId;
taskId.value = props.formData.taskId;
}
function handleSuccess() {
emit('success');
}
/**
* 签收调用
*/
async function handleClaim(){
loading.value = true;
let params = { taskId: taskId.value };
await taskClaim(params);
await getTaskTransInfo('run');
emit('claimSuccess')
setTimeout(()=>{
loading.value = false;
}, 200)
}
watch(()=>props.claim, (val)=>{
if(val===true){
// 如果是签收状态 则定义isRunningTask为false
isRunningTask.value = false
}else{
let taskType = props.formData['PROCESS_TAB_TYPE'];
isRunningTask.value = taskType == 'run';
}
}, {immediate: true})
return {
taskId,
bpmLogList,
taskStepList,
currentNode,
historyList,
branchList,
handleSuccess,
isRunningTask,
handleClaim,
loading,
ccStatus,
allowAddSign,
allowCounterSignAddUser,
allowReject,
selnextUserStatus,
turnbackTaskId
};
},
};
</script>
<style lang="less">
.jeecg-task-handle-content {
.task-info {
margin: 20px 0;
}
.task-title {
font-weight: bold;
}
.task-date {
text-overflow: ellipsis;
white-space: nowrap;
}
.task-date span {
/* color: #ff6d75;*/
}
.ant-steps-item-description {
max-width: 200px !important;
}
/** Button按钮间距 */
.ant-btn {
margin-left: 3px;
}
/** 标题和描述对齐 */
.ant-steps-item-content {
text-align: left;
margin-left: 50px;
}
/** 描述的样式 */
.descriptionDiv {
display: flex;
justify-content: left;
align-items: center;
margin-top: 5px;
}
.jee-cust-selector .ant-select-selection__choice {
padding-right: 10px !important;
}
}
</style>
@@ -0,0 +1,124 @@
<template>
<a-card>
<a-steps progressDot :current="stepIndex" style="padding: 10px" size="default">
<template v-if="stepCount > 3">
<a-step>
<template #title>
<div class="task-title">...</div>
</template>
</a-step>
</template>
<template v-for="(item, index) in taskStepList">
<a-step>
<template #title>
<div class="task-title">{{ item.taskName }}</div>
</template>
<template #description>
<div class="descriptionDiv">
<span>
<!--#40a9ff-->
<a-avatar shape="square" style="background-color: #40a9ff"
><template #icon><UserOutlined /></template
></a-avatar>
</span>
<span style="margin-left: 5px">
<div class="task-date" style="text-align: left">
<a-tooltip placement="top">
<template #title>
<span>{{ item.opTime }}</span>
</template>
<span> {{ item.opTime ? item.opTime.substr(0, 10) : item.opTime }}</span>
</a-tooltip>
</div>
<div class="task-user" style="text-align: left">
<span> {{ item.opUserName }}</span>
</div>
</span>
</div>
</template>
</a-step>
</template>
<a-step v-if="currentNode.taskName && currentNode.taskName != ''">
<template #title>
<div class="task-title">{{ currentNode.taskName }}</div>
</template>
<template #description
><!--#faad14eb-->
<div class="descriptionDiv">
<span>
<a-avatar style="background-color: #faad14eb"
><template #icon><UserOutlined /></template
></a-avatar>
</span>
<span style="margin-left: 5px">
<div class="task-date" style="text-align: left">
<a-tooltip placement="top">
<template #title>
<span>{{ currentNode.taskNameStartTime }}</span>
</template>
<span style="color: #ff6d75">
{{ currentNode.taskNameStartTime ? currentNode.taskNameStartTime.substr(0, 10) : currentNode.taskNameStartTime }}
</span>
</a-tooltip>
</div>
<div class="task-user" style="text-align: left">
<span> {{ currentNode.taskAssigneeName }}</span>
</div>
</span>
</div>
</template>
</a-step>
<a-step>
<template #title>
<div class="task-title">...</div>
</template>
</a-step>
</a-steps>
</a-card>
</template>
<script>
/**
* top上节点步骤
*/
import { computed } from 'vue';
import { UserOutlined } from '@ant-design/icons-vue';
export default {
name: 'TaskNodeStepList',
components: {
UserOutlined,
},
props: {
stepCount: {
type: Number,
default: 0,
},
taskStepList: {
type: Array,
default: () => [],
},
currentNode: {
type: Object,
default: () => {},
},
},
setup(props) {
const stepIndex = computed(() => {
if (props.taskStepList.length > 3) {
return props.taskStepList.length + 1;
}
return props.taskStepList.length;
});
return {
stepIndex,
};
},
};
</script>
@@ -0,0 +1,93 @@
<template>
<div>
<div style="margin-bottom: 20px; height: 400px; overflow: hidden; overflow-y: auto; overflow-x: auto">
<bpm-graphic :instanceId="formData.procInsId" @task="getTaskList"></bpm-graphic>
</div>
<a-card title="流程历史跟踪">
<BasicTable @register="registerTable">
<template #remarks="{ record }">
<j-ellipsis :value="getNodeInfo(record)" :length="25" />
</template>
</BasicTable>
</a-card>
</div>
</template>
<script>
/**
* 历史任务跟踪-查看历史节点-流程图
*/
import BpmGraphic from '/@/views/super/bpm/process/components/BpmGraphic.vue';
import { BasicTable } from '/@/components/Table';
import { taskTraceColumns } from '../task.handle.data';
import { taskTraceList } from '../task.handle.api';
import { useListPage } from '/@/hooks/system/useListPage';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
import { ref } from 'vue';
export default {
name: 'TaskTraceContent',
components: {
BpmGraphic,
BasicTable,
JEllipsis,
},
props: {
formData: {
type: Object,
default: () => {},
},
},
setup(props) {
console.log('进入TaskTraceContent>>');
const { tableContext } = useListPage({
designScope: 'task-trace-content',
pagination: true,
tableProps: {
title: '',
api: taskTraceList,
columns: taskTraceColumns,
showIndexColumn: true,
showTableSetting: false,
canResize: false,
showActionColumn: false,
useSearchForm: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable] = tableContext;
function addQueryParams(params) {
params.processInstanceId = props.formData.procInsId;
return params;
}
const taskList = ref([]);
function getTaskList(arr) {
console.log('aaa', arr);
taskList.value = arr;
}
function getNodeInfo(record) {
let arr = taskList.value;
if (arr && arr.length > 0) {
for (let item of arr) {
if (item.id == record.id) {
return item.remarks;
}
}
}
return '';
}
return {
registerTable,
getTaskList,
getNodeInfo,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,88 @@
<template>
<BasicModal title="选择节点审批人" @register="registerModal" :minHeight="80" :width="900" @ok="selected" :canFullscreen="false">
<tr>
<td>
<span> 加签方式</span>
</td>
<td colspan="3" style="width: 600px; margin: 0 auto; padding-top: 25px">
<a-radio-group v-model:value="addSignType">
<a-radio value="before">
<a-tooltip>
<template #title>需要他人核对流程其他人核对完成后回到当前节点处理人手中</template>
向前加签
</a-tooltip>
</a-radio>
<a-radio value="after">
<a-tooltip>
<template #title>需要让他人核对流程其他人核对完成后直接进入下一节点</template>
向后加签
</a-tooltip>
</a-radio>
</a-radio-group>
</td>
</tr>
<tr>
<td>
<span> 选择用户</span>
</td>
<td colspan="3" style="width: 600px; margin: 0 auto; padding-top: 25px">
<j-select-user-by-dept v-model:value="person" button-icon="ant-design:search" placeholder="请选择人员" />
</td>
</tr>
</BasicModal>
</template>
<script>
/**
* 选择加签节点审批人
*/
import { BasicModal, useModalInner } from "/@/components/Modal";
import { ref, nextTick, unref } from "vue";
import { JSelectUserByDept } from "/@/components/Form";
import { useUserStore } from "/@/store/modules/user";
import { useMessage } from "/@/hooks/web/useMessage";
export default {
name: "AddSignTaskModal",
components: {
BasicModal,
JSelectUserByDept
},
emits: ["selected", "register"],
setup(_p, { emit }) {
//useModalInner
const person = ref("");
const taskId = ref("");
const addSignType = ref("after");
const { userInfo } = useUserStore();
const { createMessage } = useMessage();
const [registerModal, { closeModal }] = useModalInner((data) => {
person.value = "";
taskId.value = data.taskId;
});
function selected() {
let signUserIds= unref(person);
if(signUserIds.length==0){
createMessage.warning('请选择审批人')
return;
}
emit("selected", {
currentTaskId: taskId.value,
signUserIds: signUserIds,
userCode: userInfo.username
},addSignType.value);
closeModal();
}
return {
registerModal,
person,
selected,
addSignType
};
}
};
</script>
<style scoped></style>
@@ -0,0 +1,57 @@
<template>
<BasicModal title="选择委托/转办人" @register="registerModal" :minHeight="80" :width="900" @ok="selected" :canFullscreen="false">
<tr>
<td>
<span style="margin-left: 30px"> 选择用户</span>
</td>
<td colspan="3" style="width: 600px; margin: 0 auto; padding-top: 25px">
<j-select-user-by-dept v-model:value="person" button-icon="ant-design:search" :isRadioSelection="true" placeholder="请选择人员" />
</td>
</tr>
</BasicModal>
</template>
<script>
/**
* 选择委托人
*/
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, nextTick, unref } from 'vue';
import { JSelectUserByDept } from '/@/components/Form';
export default {
name: 'SelectEntrusterModal',
components: {
BasicModal,
JSelectUserByDept,
},
emits: ['selected', 'register'],
setup(_p, { emit }) {
//useModalInner
const person = ref('');
const taskId = ref('');
const [registerModal, { closeModal }] = useModalInner((data) => {
person.value = '';
taskId.value = data.taskId;
});
function selected() {
let temp = unref(person);
if (temp instanceof Array) {
temp = temp[0];
}
emit('selected', {
taskId: taskId.value,
taskAssignee: temp,
});
closeModal();
}
return {
registerModal,
person,
selected,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,133 @@
<template>
<BasicModal
:title="title"
@register="registerModal"
destroyOnClose
wrapClassName="jeecg-online-modal"
style="top: 0px"
:footer="null"
keyboard
defaultFullscreen
:canFullscreen="false"
ref="modalRef"
>
<a-tabs v-model="activeKey" tabPosition="left">
<a-tab-pane key="1">
<template #tab> <file-text-outlined /><span>附加单据</span> </template>
<BpmDynamicForm :path="taskFormUrl" :form-data="taskFormData" :parentNode="parentNode"/>
</a-tab-pane>
<a-tab-pane key="2">
<template #tab> <user-outlined /><span>任务处理</span> </template>
<task-handle-inner-content @success="handleSuccess" :form-data="taskFormData" :claim="claimStatus" @claimSuccess="claimSuccess"></task-handle-inner-content>
</a-tab-pane>
<a-tab-pane key="3">
<template #tab> <partition-outlined /><span>流程图</span> </template>
<task-trace-content :form-data="taskFormData" />
</a-tab-pane>
</a-tabs>
</BasicModal>
</template>
<script lang="ts">
/**
* 处理页面
*/
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref , nextTick , computed } from 'vue';
import { UserOutlined, PartitionOutlined, FileTextOutlined } from '@ant-design/icons-vue';
import TaskHandleInnerContent from '../content/TaskHandleInnerContent.vue';
import TaskTraceContent from '../content/TaskTraceContent.vue';
import BpmDynamicForm from '/@/views/super/bpm/process/components/BpmDynamicForm.vue';
export default {
name: 'TaskHandleModal',
components: {
BasicModal,
UserOutlined,
FileTextOutlined,
PartitionOutlined,
TaskHandleInnerContent,
TaskTraceContent,
BpmDynamicForm,
},
emits: ['success', 'register'],
setup(_p, { emit }) {
const title = ref('流程办理');
const taskFormData = ref({});
const taskFormUrl = ref('');
//是否需要签收
const claimStatus = ref(false);
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
console.log('流程办理', data);
taskFormData.value = data.formData;
taskFormUrl.value = data.formUrl;
title.value = data.title;
if(data.isSignTask==true){
claimStatus.value = true
}else{
claimStatus.value = false;
}
});
const activeKey = ref('1');
// desformView 父级滚动条,只有传了此参数才会突破内部弹窗
const modalRef = ref();
const parentNode = computed(() => {
return modalRef.value?.modalWrapperRef?.wrapperRef?.scrollbarRef?.wrap;
});
console.log("parentNode",parentNode)
function handleSuccess() {
emit('success');
closeModal();
}
function claimSuccess(){
claimStatus.value = false;
}
return {
title,
registerModal,
activeKey,
taskFormData,
taskFormUrl,
handleSuccess,
claimStatus,
claimSuccess,
modalRef,
parentNode
};
},
};
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20231220---for:【QQYUN-7670】antd4兼容改造,附加单据页面loading图标偏上
.ant-tabs {
:deep(.ant-spin-nested-loading) {
min-height: 200px;
}
}
// update-end--author:liaozhiyang---date:20231220---for:【QQYUN-7670】antd4兼容改造,附加单据页面loading图标偏上
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-1022】流程办理页面左侧固定不随滚动而滚动
.ant-tabs {
height: 100%;
:deep(.ant-tabs-content-holder) {
height: 100%;
.ant-tabs-content {
height: 100%;
.ant-tabs-tabpane {
height: 100%;
padding-right: 6px;
overflow-y: auto;
}
}
}
}
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-1022】流程办理页面左侧固定不随滚动而滚动
</style>
@@ -0,0 +1,70 @@
<template>
<BasicModal title="催办提醒" @register="registerModal" width="60%">
<a-tabs defaultActiveKey="1" tabPosition="top">
<a-tab-pane key="1">
<template #tab> <alert-outlined /><span>提醒我的</span> </template>
<BasicTable @register="registerTable" />
</a-tab-pane>
</a-tabs>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, nextTick } from 'vue';
import { AlertOutlined } from '@ant-design/icons-vue';
import { BasicTable } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { taskNotifyMeList } from '../task.handle.api';
import { notifyMeColumns } from '../task.handle.data';
export default {
name: 'TaskNotifyMeModal',
components: {
BasicModal,
AlertOutlined,
BasicTable,
},
setup() {
//useModalInner
const procInstId = ref('');
const { tableContext } = useListPage({
designScope: 'notify-me-list',
pagination: true,
tableProps: {
api: taskNotifyMeList,
columns: notifyMeColumns,
immediate: false,
showIndexColumn: true,
showTableSetting: false,
useSearchForm: false,
canResize: false,
showActionColumn: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable, { reload }] = tableContext;
const [registerModal] = useModalInner((data) => {
console.log(data);
procInstId.value = data.procInstId;
reload();
});
function addQueryParams(params) {
params['procInstId'] = procInstId.value;
return params;
}
return {
registerModal,
registerTable,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,103 @@
import { defHttp } from '/@/utils/http/axios';
const URL = {
run: {
list: '/act/task/list',
claim: '/act/task/claim',
taskEntrust: '/act/task/taskEntrust',
getProcessNodeInfo: '/act/process/extActProcessNode/getProcessNodeInfo',
getProcessTaskTransInfo: '/act/task/getProcessTaskTransInfo',
},
history: {
list: '/act/task/taskHistoryList',
getProcessNodeInfo: '/act/process/extActProcessNode/getHisProcessNodeInfo',
getProcessTaskTransInfo: '/act/task/getHisProcessTaskTransInfo',
},
group: {
list: '/act/task/taskGroupList',
claim: '/act/task/claim',
getProcessTaskTransInfo: '/act/task/getProcessTaskTransInfo',
},
processComplete: '/act/task/processComplete',
processHistoryList: '/act/task/processHistoryList',
taskEntrust: '/act/task/taskEntrust',
taskComplaint: '/act/task/taskComplaint',
afterAddSignTask: '/act/task/afterAddSignTask',
beforeAddSignTask: '/act/task/beforeAddSignTask',
claim: '/act/task/claim',
notifyMeList: '/act/process/extActTaskNotification/list',
// 添加审批人
addMultiInstance: '/act/task/addMultiInstance',
};
/**
* 列表
* @param params
*/
export const list = (type, params) => defHttp.get({ url: URL[type].list, params });
/**
* 列表
* @param params
*/
export const taskNodeInfo = (type, params) => defHttp.get({ url: URL[type].getProcessNodeInfo, params });
/**
* 任务流转信息
* @param params
* @param type
*/
export const taskTransInfo = (params, type = 'run') => defHttp.get({ url: URL[type].getProcessTaskTransInfo, params });
/**
* 流程任务办理
* @param params
*/
export const taskComplete = (params) => defHttp.post({ url: URL.processComplete, params });
/**
* 流程历史跟踪
* @param params
*/
export const taskTraceList = (params) => defHttp.get({ url: URL.processHistoryList, params });
/**
* 任务委托
* @param params
*/
export const taskEntrust = (params) => defHttp.put({ url: URL.taskEntrust, params });
/**
* 任务转办
* @param params
*/
export const taskComplaint = (params) => defHttp.put({ url: URL.taskComplaint, params });
/**
* 任务签收
* @param params
*/
export const taskClaim = (params) => defHttp.put({ url: URL.claim, params });
/**
* 催办-提醒我的记录
* @param params
*/
export const taskNotifyMeList = (params) => defHttp.get({ url: URL.notifyMeList, params });
/**
* 任务向后加签
* @param params
*/
export const afterAddSignTask = (params) => defHttp.put({ url: URL.afterAddSignTask, params });
/**
* 任务向前加签
* @param params
*/
export const beforeAddSignTask = (params) => defHttp.put({ url: URL.beforeAddSignTask, params });
/**
* 添加审批人
* @param params
*/
export const addMultiInstance = (params) => defHttp.put({ url: URL.addMultiInstance, params },{isTransformResponse: false});
@@ -0,0 +1,308 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
export enum TaskType {
RUN = 'run',
HIS = 'history',
GROUP = 'group',
}
/**
* 列表 列--running
*/
export const columns: BasicColumn[] = [
{
title: '业务标题',
align: 'center',
dataIndex: 'bpmBizTitle',
slots: { customRender: 'bpmBizTitle' },
},
{
title: '当前环节',
align: 'center',
width: 130,
dataIndex: 'taskName',
},
{
title: '流程名称',
align: 'center',
dataIndex: 'processDefinitionName',
ellipsis: true,
},
{
title: '流程实例',
align: 'center',
dataIndex: 'processInstanceId',
},
{
title: '发起人',
width: 110,
align: 'center',
dataIndex: 'processApplyUserName',
},
{
title: '开始时间',
align: 'center',
dataIndex: 'taskBeginTime',
},
{
title: '流程编号',
align: 'center',
dataIndex: 'processDefinitionId',
ellipsis: true,
},
{
title: '任务ID',
align: 'center',
dataIndex: 'taskId',
},
];
/**
* 列表 列--group
*/
export const columns_group: BasicColumn[] = [
{
title: '业务标题',
align: 'center',
dataIndex: 'bpmBizTitle',
ellipsis: true,
},
{
title: '流程编号',
align: 'center',
dataIndex: 'processDefinitionId',
},
{
title: '流程名称',
align: 'center',
dataIndex: 'processDefinitionName',
},
{
title: '流程实例',
align: 'center',
dataIndex: 'processInstanceId',
},
{
title: '任务ID',
align: 'center',
dataIndex: 'taskId',
},
{
title: '发起人',
align: 'center',
dataIndex: 'processApplyUserName',
},
{
title: '开始时间',
align: 'center',
dataIndex: 'taskBeginTime',
},
{
title: '结束时间',
align: 'center',
dataIndex: 'taskEndTime',
},
{
title: '当前环节',
align: 'center',
dataIndex: 'taskName',
},
];
/**
* 列表 列-history
*/
export const columns_history: BasicColumn[] = [
{
title: '业务标题',
align: 'center',
dataIndex: 'bpmBizTitle',
ellipsis: true,
},
{
title: '流程编号',
align: 'center',
dataIndex: 'processDefinitionId',
ellipsis: true,
},
{
title: '流程名称',
align: 'center',
dataIndex: 'processDefinitionName',
ellipsis: true,
},
{
title: '流程实例',
align: 'center',
width: 100,
dataIndex: 'processInstanceId',
},
{
title: '任务ID',
align: 'center',
dataIndex: 'taskId',
},
{
title: '发起人',
align: 'center',
width: 100,
dataIndex: 'processApplyUserName',
},
{
title: '办理人',
align: 'center',
width: 100,
dataIndex: 'taskAssigneeName',
},
{
title: '开始时间',
align: 'center',
dataIndex: 'taskBeginTime',
},
{
title: '结束时间',
align: 'center',
dataIndex: 'taskEndTime',
},
{
title: '耗时',
align: 'center',
width: 100,
dataIndex: 'durationStr',
},
// {
// title: '当前环节',
// align: 'center',
// width: 120,
// dataIndex: 'taskName',
// },
];
/**
* 查询条件
*/
export const searchFormSchema: FormSchema[] = [
{
label: '业务标题',
field: 'bpmBizTitle',
component: 'Input',
},
{
label: '流程名',
field: 'processDefinitionName',
component: 'Input',
},
{
label: '发起人',
field: 'userName',
component: 'JSelectUserByDept',
componentProps: {
labelKey: 'realname',
rowKey: 'username',
showButton: false,
isRadioSelection: true,
},
buss: 'run',
},
{
label: '流程编号',
field: 'processDefinitionId',
component: 'Input',
},
// {
// label: '应用ID',
// field: 'lowAppId',
// component: 'Input',
// },
];
/**
* 列表 流程历史跟踪
*/
export const taskTraceColumns: BasicColumn[] = [
{
title: '名称',
dataIndex: 'name',
customRender: function ({ text }) {
if (text == 'start1') {
return '开始';
} else if (text == 'end') {
return '结束';
} else {
return text;
}
},
},
{
title: '流程实例ID',
dataIndex: 'processInstanceId',
},
{
title: '开始时间',
dataIndex: 'startTime',
},
{
title: '结束时间',
dataIndex: 'endTime',
},
{
title: '负责人',
dataIndex: 'assigneeName',
},
{
title: '处理结果',
dataIndex: 'deleteReason',
},
{
title: '处理意见',
fixed: 'right',
width: 350,
dataIndex: 'remarks',
slots: { customRender: 'remarks' },
},
];
/**
* 提醒我的列表(催办)
*/
export const notifyMeColumns: BasicColumn[] = [
{
title: '流程名称',
align: 'center',
dataIndex: 'procName',
},
{
title: '任务名称',
align: 'center',
dataIndex: 'taskName',
},
{
title: '催办时间',
align: 'center',
dataIndex: 'opTime',
},
{
title: '催办类型',
align: 'center',
dataIndex: 'notifyType',
customRender: function ({ text }) {
if (!text) {
return '';
}
let srtArr = text.split(',');
let value = '';
if (srtArr.includes('1')) {
value += ',页面通知';
}
if (srtArr.includes('2')) {
value += ',邮件';
}
return value.substring(1);
},
},
{
title: '催办说明',
align: 'center',
dataIndex: 'remarks',
},
];
@@ -0,0 +1,179 @@
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, columns_group, columns_history, TaskType, searchFormSchema } from './task.handle.data';
import { list, taskNodeInfo } from './task.handle.api';
/**
* 用于列表渲染
* @param urlObject
*/
export function useTaskList(type) {
const { tableContext } = useListPage({
designScope: 'process-design',
pagination: true,
tableProps: {
title: '',
api: getDataList,
columns: getColumns(),
showIndexColumn: true,
showTableSetting: false,
canResize: false,
scroll: { x: 1800 },
actionColumn: { dataIndex: 'action', fixed: 'right',width:150 },
useSearchForm: TaskType.GROUP != type,
formConfig: {
schemas: getSearchFormSchema(),
autoAdvancedCol: 3,
baseColProps: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
actionColOptions: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
},
},
});
const [registerTable, { reload }] = tableContext;
/**
* 数据请求接口
* @param params
*/
function getDataList(params) {
return list(type, params);
}
/**
* 列
* 不同类型 列表列有少许差别
*/
function getColumns() {
if (TaskType.RUN == type) {
return columns;
} else if (TaskType.GROUP == type) {
return columns_group;
} else {
return columns_history;
}
}
/**
* 查询条件
*/
function getSearchFormSchema() {
return searchFormSchema.filter((item) => !item.buss || item.buss === type);
}
/**
* 获取流程节点信息
*/
async function getTaskNodeInfo(record) {
//查询条件
let params = { taskId: record.id };
const result = await taskNodeInfo(type, params);
console.log('获取流程节点信息', result);
let procInsId = record.processInstanceId || (result.records?result.records.BPM_INST_ID: '');
let formData: any = {
taskId: record.id,
taskDefKey: result.taskDefKey,
procInsId: procInsId,
dataId: result.dataId,
tableName: result.tableName,
permissionList: result.permissionList,
subPermissionList: result.subPermissionList,
vars: result.records,
};
let tempFormUrl = result.formUrl;
console.log('获取流程节点表单URL', tempFormUrl);
//节点配置表单URL,VUE组件类型对应的拓展参数
if (tempFormUrl && tempFormUrl.indexOf('?') != -1 && !isURL(tempFormUrl) && tempFormUrl.indexOf('{{DOMAIN_URL}}') == -1) {
tempFormUrl = result.formUrl.split('?')[0];
console.log('获取流程节点表单URL(去掉参数)', tempFormUrl);
let qv: any = getQueryVariable(result.formUrl);
if (qv.edit == 1) {
formData['disabled'] = false;
}
formData.extendUrlParams = qv;
}
//如果没有taskId参数,程序自动追加,用于设计器表单节点权限
if (tempFormUrl != null && tempFormUrl.indexOf('{{DOMAIN_URL}}/desform/') != -1 && tempFormUrl.indexOf('taskId') == -1) {
tempFormUrl = tempFormUrl.trim();
if (tempFormUrl.endsWith('?')) {
tempFormUrl = tempFormUrl + 'taskId=' + result.taskDefKey;
} else {
tempFormUrl = tempFormUrl + '&taskId=' + result.taskDefKey;
}
}
return {
formData,
formUrl: tempFormUrl,
isSignTask: result.isSignTask,
assignee: result.assignee,
taskIsHandel: result.taskIsHandel||false,
};
}
/**
* 获取历史任务信息
*/
async function getHistoryTaskInfo(record) {
return await getTaskInfoForHistory(record);
}
return {
registerTable,
reload,
getTaskNodeInfo,
getHistoryTaskInfo,
};
}
export async function getTaskInfoForHistory(record) {
//查询条件
let params = { procInstId: record.processInstanceId };
const result = await taskNodeInfo('history', params);
console.log('获取历史任务信息', result);
let formData: any = {
dataId: result.dataId,
taskId: record.id,
taskDefKey: record.taskId,
procInsId: record.processInstanceId,
tableName: result.tableName,
vars: result.records,
};
let tempFormUrl = result.formUrl;
console.log('获取流程节点表单URL', tempFormUrl);
//节点配置表单URL,VUE组件类型对应的拓展参数
if (tempFormUrl && tempFormUrl.indexOf('?') != -1 && !isURL(tempFormUrl) && tempFormUrl.indexOf('{{DOMAIN_URL}}') == -1) {
tempFormUrl = result.formUrl.split('?')[0];
console.log('获取流程节点表单URL(去掉参数)', tempFormUrl);
formData.extendUrlParams = getQueryVariable(result.formUrl);
}
return {
formData,
formUrl: tempFormUrl,
};
}
/**
* 获取URL上参数
* @param url
*/
function getQueryVariable(url) {
if (!url) return;
let t,
n,
r,
i = url.split('?')[1],
s = {};
(t = i.split('&')), (r = null), (n = null);
for (let o in t) {
let u = t[o].indexOf('=');
u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n));
}
return s;
}
/**
* URL地址
* @param {*} s
*/
function isURL(s) {
return /^http[s]?:\/\/.*/.test(s);
}
@@ -0,0 +1,370 @@
<template>
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate" v-if="isMiniDesgin=='default'">新建流程</a-button>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreateMini" v-if="isMiniDesgin=='mini'">新建简流程</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)">
<template #updateProcess>
<a-upload
:showUploadList="false"
:action="processUpload.action(record.id)"
:data="processUpload.data"
:headers="processUpload.headers"
@change="processUpload.onChange"
>
<span>上传流程</span>
</a-upload>
</template>
</TableAction>
</template>
</BasicTable>
<!-- 流程设计modal -->
<process-design-modal @register="registerDesignModal" @success="loadTable"></process-design-modal>
<!-- 简流设计器modal -->
<mini-des-flow-modal @register="registerMiniDesFlowModal" :isLowApp="false" @success="loadTable"></mini-des-flow-modal>
<!-- 流程配置modal -->
<process-config-modal @register="registerDesignConfigModal"></process-config-modal>
<!-- 图标配置 -->
<process-icon-modal @register="registerIconModal" @success="loadTable"></process-icon-modal>
<!-- 版本监控 -->
<process-deployment-modal @register="registerDeploymentModal"></process-deployment-modal>
<!-- 流程属性配置 -->
<process-other-config-drawer @register="registerOtherDrawer" @success="loadTable"></process-other-config-drawer>
</template>
<script>
import {ref, unref, reactive, nextTick} from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { list, publish, deleteOne, copy, uploadProcess } from './process.design.api';
import { columns, searchFormSchema } from './process.design.data';
import { initDictOptions } from '/@/utils/dict';
import { filterDictText } from '/@/utils/dict/JDictSelectUtil';
import { getToken } from '/@/utils/auth';
import { useMessage } from '/@/hooks/web/useMessage';
import { useRouter } from 'vue-router'
import { useDrawer } from '/@/components/Drawer';
import { useModal } from '/@/components/Modal';
import ProcessDesignModal from './modal/ProcessDesignModal.vue';
import ProcessConfigModal from './modal/ProcessConfigModal.vue';
import ProcessIconModal from './modal/ProcessIconModal.vue';
import ProcessDeploymentModal from './modal/ProcessDeploymentModal.vue';
import ProcessOtherConfigDrawer from './modal/ProcessOtherConfigDrawer.vue'
export default {
name: 'ProcessDesignList',
components: {
BasicTable,
TableAction,
ProcessDesignModal,
ProcessConfigModal,
ProcessIconModal,
ProcessDeploymentModal,
ProcessOtherConfigDrawer
},
setup() {
const { createMessage: $message } = useMessage();
//update-begin---author:scott ---date:2022-10-21 for:默认流程和简版流程,拆分成两个菜单,方便维护-----------
//自定义查询条件默认值
let searchFormSchemaNew = searchFormSchema;
const isMiniDesgin = ref("default");
//获取路由地址
let router = useRouter()
console.log('router.currentRoute.value.path', router.currentRoute.value.path)
if(router.currentRoute.value.path.endsWith("/mini")){
isMiniDesgin.value = 'mini'
searchFormSchemaNew = searchFormSchema.map(item => {
if(item.field == 'processDesginType'){
item.defaultValue = 'mini';
}
})
}else if(router.currentRoute.value.path.endsWith("/list/desflow")){
isMiniDesgin.value = 'mini'
searchFormSchemaNew = searchFormSchema.map(item => {
if(item.field == 'processDesginType'){
item.defaultValue = 'mini';
}
})
}else{
isMiniDesgin.value = 'default'
searchFormSchemaNew = searchFormSchema.map(item => {
if(item.field == 'processDesginType'){
item.defaultValue = 'default';
}
})
}
//update-end---author:scott ---date::2022-10-21 for:默认流程和简版流程,拆分成两个菜单,方便维护--------------
const { tableContext } = useListPage({
designScope: 'process-design',
pagination: true,
tableProps: {
title: '流程设计',
api: list,
columns: columns,
actionColumn: {
width: 300,
dataIndex: 'action',
slots: { customRender: 'action' },
},
formConfig: {
schemas: searchFormSchema,
autoAdvancedCol: 4,
baseColProps: { xs: 24, sm: 12, md: 8, lg: 8, xl: 8, xxl: 5 },
actionColOptions: {
xs: 24,
sm: 12,
md: 8,
lg: 8,
xl: 8,
xxl: 4,
},
},
},
});
const [registerTable, { getForm , reload }] = tableContext;
nextTick(() => {
console.log("getForm() data", getForm().getFieldsValue());
});
const [registerDesignModal, { openModal: openDesignModal }] = useModal();
const [registerMiniDesFlowModal, { openModal: openDesignModalMini }] = useModal();
const [registerDesignConfigModal, { openModal: openDesignConfigModal }] = useModal();
const [registerIconModal, { openModal: openIconModal }] = useModal();
const [registerDeploymentModal, { openModal: openDeploymentModal }] = useModal();
const [registerOtherDrawer, { openDrawer }] = useDrawer();
/**
* 打开流程设计表单
*/
function handleCreate() {
openDesignModal(true, {
isUpdate: false,
});
}
/**
* 打开流程设计表单
*/
function handleCreateMini() {
openDesignModalMini(true, {
isUpdate: false,
});
}
function handleUpdate(record) {
openDesignModal(true, {
isUpdate: true,
id: record.id,
});
}
function handleUpdateTest(record) {
openDesignModalMini(true, {
isUpdate: true,
id: record.id,
});
}
function handleOpenConfigModal(record) {
openDesignConfigModal(true, record);
}
async function handlePublish(record) {
await publish({ id: record.id });
reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '设计流程',
onClick: handleUpdate.bind(null, record),
ifShow: () => {
return record.processJson == null || record.processJson == '';
},
},
{
label: '简流设计',
onClick: handleUpdateTest.bind(null, record),
ifShow: () => {
return isMiniDesgin.value =='mini';
},
},
{
label: '配置',
onClick: handleOpenConfigModal.bind(null, record),
// ifShow: () => {
// return record.processJson == null || record.processJson == '';
// },
},
{
label: '发布',
popConfirm: {
title: '是否确认发布该流程?',
confirm: handlePublish.bind(null, record),
},
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
let arr = [];
// arr.push( {
// label: '设计 · 简',
// onClick: handleUpdateTest.bind(null, record),
// ifShow: () => {
// return record.processJson !== null && record.processJson !== '';
// },
// });
if (record.processStatus === 1) {
arr.push({
label: '版本监控',
onClick: showDeploymentList.bind(null, record),
});
}
arr.push(
{
label: '高级配置',
onClick: handleOtherConfig.bind(null, record),
},
{
label: '流程复制',
popConfirm: {
title: '确定复制该流程吗?',
confirm: handleCopy.bind(null, record),
},
},
{
label: '上传流程',
slot: 'updateProcess',
},
/* {
label: '表单图标',
onClick: handleIcon.bind(null, record),
},*/
);
if (record.processStatus === 0) {
arr.push({
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
});
}
return arr;
}
function handleOtherConfig(record){
openDrawer(true, record)
}
async function handleDelete(record) {
await deleteOne({ id: record.id });
reload();
}
async function handleCopy(record) {
await copy({ id: record.id });
reload();
}
function handleIcon(record) {
openIconModal(true, record);
}
function showDeploymentList(record) {
openDeploymentModal(true, record);
}
const processUpload = reactive({
action: function (id) {
let url = window._CONFIG['domianURL'] + uploadProcess + '?id=' + id;
// console.log('----------------', url);
return url;
},
data: { isup: 1 },
headers: { 'X-Access-Token': getToken() },
onChange: function (info) {
if (info.file.status === 'done') {
if (info.file.response.success) {
$message.success(`流程上传成功`);
} else {
$message.error(`${info.file.name} ${info.file.response.message}.`);
}
} else if (info.file.status === 'error') {
$message.error(`流程上传失败: ${info.file.msg} `);
}
},
});
function loadTable() {
reload();
}
return {
registerTable,
handleCreate,
handleCreateMini,
getTableAction,
getDropDownAction,
processUpload,
loadTable,
//流程设计
registerDesignModal,
//测试流程设计
registerMiniDesFlowModal,
//流程配置
registerDesignConfigModal,
//图标配置
registerIconModal,
//版本监控
registerDeploymentModal,
registerOtherDrawer,
isMiniDesgin
};
},
};
</script>
<style lang="less">
/*QQYUN-3131【流程列表】删除效果,为什么跟复制流程效果不一样*/
.ant-popconfirm {
.ant-popover-buttons{
min-width: 120px;
text-align: center;
}
}
</style>
@@ -0,0 +1,81 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
wrapClassName="jee-process-config-modal"
width="90%"
:footer="null"
:title="title"
keyboard
defaultFullscreen
:canFullscreen="false"
destroyOnClose
>
<a-card class="card" :bordered="false">
<a-tabs :activeKey="activeKey" tabPosition="left" @tabClick="handleChangePanel">
<a-tab-pane tab="流程节点" key="1">
<process-node-list :process-id="processId"></process-node-list>
</a-tab-pane>
<a-tab-pane tab="业务关联" key="2">
<process-form-list :process-id="processId"></process-form-list>
</a-tab-pane>
</a-tabs>
</a-card>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { getToken } from '/@/utils/auth';
import ProcessNodeList from '../processNode/ProcessNodeList.vue';
import ProcessFormList from '../processForm/ProcessFormList.vue';
export default {
name: 'ProcessConfigModal',
components: {
BasicModal,
ProcessNodeList,
ProcessFormList,
},
emits: ['success', 'register'],
setup(props, { emit }) {
const bodyStyle = {
padding: '0',
height: window.innerHeight - 25 + 'px',
};
const processId = ref('');
const activeKey = ref('1');
const title = ref('');
//useModalInner
const [registerModal] = useModalInner((data) => {
console.log('data', data);
title.value = '流程【' + data.processName + '】配置';
processId.value = data.id;
});
function handleChangePanel(key) {
activeKey.value = key;
}
return {
registerModal,
bodyStyle,
processId,
activeKey,
title,
handleChangePanel,
};
},
};
</script>
<style lang="less">
// update-begin--author:liaozhiyang---date:20240521---for:【TV360X-241】流程设计的表单关联页面样式调整
.card {
height: 100%;
}
// update-end--author:liaozhiyang---date:20240521---for:【TV360X-241】流程设计的表单关联页面样式调整
</style>
@@ -0,0 +1,53 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:footer="null"
:title="title"
keyboard
defaultFullscreen
:canFullscreen="false"
destroyOnClose
>
<a-spin :spinning="spinningLoading">
<process-deployment-list ref="deploymentListRef" @loaded="spinningLoading = false"></process-deployment-list>
</a-spin>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref } from 'vue';
import processDeploymentList from '../processDeployment/ProcessDeploymentList.vue';
export default {
name: 'ProcessDeploymentModal',
emits: ['success', 'register'],
components: {
BasicModal,
processDeploymentList,
},
setup() {
const spinningLoading = ref(false);
const title = ref('');
const deploymentListRef = ref();
//useModalInner
const [registerModal] = useModalInner((data) => {
console.log('data', data);
spinningLoading.value = true;
title.value = `流程【${data.processName}】版本监控`;
deploymentListRef.value.init(data.processKey);
});
return {
spinningLoading,
title,
registerModal,
deploymentListRef,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,138 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
wrapClassName="jee-process-modal"
width="100%"
:style="{ top: '0', padding: '0' }"
:bodyStyle="bodyStyle"
:footer="null"
:canFullscreen="false"
keyboard
defaultFullscreen
destroyOnClose
@cancel="handleClose"
>
<a-spin :spinning="spinningLoading">
<iframe id="processDesign" :src="iframeSrc" :height="iframeHeight" @load="iframeLoaded" frameborder="0" width="100%" scrolling="auto"> </iframe>
</a-spin>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { getToken, getTenantId } from '/@/utils/auth';
export default {
name: 'ProcessDesignModal',
components: {
BasicModal,
},
emits: ['success', 'register'],
setup(props, { emit }) {
const bodyStyle = {
padding: '0',
height: window.innerHeight + 'px',
};
const spinningLoading = ref(false);
const iframeSrc = ref('');
const iframeHeight = window.innerHeight - 5 + 'px';
// NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY=
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
console.log('data', data);
spinningLoading.value = true;
resetIframeUrl(data.id);
});
/**
* 根据iframe加载完成,关闭spinning
*/
function iframeLoaded() {
spinningLoading.value = false;
}
/**
* 每次进入页面 需重新设置iframe的地址
*/
function resetIframeUrl(id) {
//1.TOKEN获取
let token = getToken();
//2.租户ID获取
let tenantIdUrlFragment = '&tenantId=' + getTenantId();
const baseUrl = window._CONFIG['domianURL'] + '/act/designer/index';
if (id != null && id != undefined && id != '') {
iframeSrc.value = baseUrl + '?id=' + id + '&token=' + token + tenantIdUrlFragment;
} else {
iframeSrc.value = baseUrl + '?token=' + token + tenantIdUrlFragment;
}
}
/**
* iframe消息绑定-如果当前路由被缓存,需修改语法
* 进入列表页面就会触发
*/
onMounted(() => {
window.addEventListener('message', handleMessage);
});
/**
* iframe消息解绑-如果当前路由被缓存,需修改语法
* 如果不解绑,会绑定多次
* 切换路由页面就会触发
*/
onBeforeUnmount(() => {
window.removeEventListener('message', handleMessage);
});
/**
* 处理iframe消息
* @param event
*/
function handleMessage(event) {
const data = event.data;
console.log('iframe message', data);
if (data.cmd == 'saveProcessDef') {
emit('success', data);
}
}
/**
* 关闭设计器窗口
*
* @param event
*/
function handleClose() {
console.log(' --- 关闭设计器窗口 ---');
//关闭窗口,清空历史流程设计URL
iframeSrc.value = '';
closeModal();
}
return {
registerModal,
bodyStyle,
spinningLoading,
iframeSrc,
iframeHeight,
iframeLoaded,
handleClose
};
},
};
</script>
<style lang="less">
.jee-process-modal {
.ant-modal-header {
padding: 0 !important;
}
.ant-modal-body > .scrollbar {
padding-top: 0;
}
}
</style>
@@ -0,0 +1,92 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :minHeight="90" :title="title" @ok="handleSubmit" :width="700" destroyOnClose>
<div style="padding-top: 10px">
<BasicForm @register="registerForm">
<template #pcIcon>
<IconPicker v-model:value="iconInfo.pcIcon" :disabled="false" placeholder="点击选择表单图标" />
</template>
<template #appIcon>
<IconPicker v-model:value="iconInfo.appIcon" :disabled="false" placeholder="点击选择移动表单图标" />
</template>
</BasicForm>
</div>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref, reactive, toRaw, h } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { SettingOutlined } from '@ant-design/icons-vue';
import { IconPicker } from '/@/components/Icon';
import { edit } from '../process.design.api';
import { DesktopOutlined, MobileOutlined } from '@ant-design/icons-vue';
export default {
name: 'ProcessIconModal',
components: {
BasicModal,
BasicForm,
SettingOutlined,
IconPicker,
},
emits: ['success', 'register'],
setup(_props, { emit }) {
const title = ref('');
const iconInfo = reactive({
id: '',
pcIcon: '',
appIcon: '',
});
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
console.log('data', data);
title.value = `流程【${data.processName}】图标设置`;
iconInfo.id = data.id;
iconInfo.pcIcon = data.pcIcon || '';
iconInfo.appIcon = data.appIcon || '';
});
const formSchema = [
{
field: 'pcIcon',
slot: 'pcIcon',
component: 'Input',
label: h('span', {}, ['PC表单图标 ', h(DesktopOutlined)]),
},
{
field: 'appIcon',
slot: 'appIcon',
component: 'Input',
label: h('span', {}, ['APP表单图标 ', h(MobileOutlined)]),
},
];
//表单配置
const [registerForm] = useForm({
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
async function handleSubmit() {
let data = toRaw(iconInfo);
console.log('修改流程图标', data);
await edit(data);
emit('success');
closeModal();
}
return {
title,
handleSubmit,
registerModal,
iconInfo,
registerForm,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,250 @@
<template>
<BasicDrawer @register="register" showFooter v-bind="$attrs" title="流程高级配置" width="330" height="300" @ok="handleSubmit" @close="handleClose">
<div class="process-config">
<div class="chunk">
<div class="header">流程提醒</div>
<div class="content">
<a-checkbox-group v-model:value="config.notifyWay">
<div><a-checkbox value="system">系统消息</a-checkbox></div>
<div><a-checkbox value="email">邮件消息</a-checkbox></div>
<div><a-checkbox value="dingtalk">钉钉消息</a-checkbox></div>
<div><a-checkbox value="wechat_enterprise">企业微信</a-checkbox></div>
</a-checkbox-group>
</div>
</div>
<a-divider />
<div class="chunk">
<div class="header switch">
<div>
流程发起后允许撤回
<a-tooltip title="流程发起后允许撤回">
<question-circle-outlined />
</a-tooltip>
</div>
<div>
<a-switch v-model:checked="config.backStatus" checkedValue="1" unCheckedValue="0" checked-children="" un-checked-children="" />
</div>
</div>
</div>
<a-divider />
<div class="chunk">
<div class="header switch">
<div>
允许流程发起人催办
<a-tooltip title="允许流程发起人催办">
<question-circle-outlined />
</a-tooltip>
</div>
<div>
<a-switch v-model:checked="config.urgeStatus" checkedValue="1" unCheckedValue="0" checked-children="" un-checked-children="" />
</div>
</div>
</div>
<a-divider />
<div class="chunk" style="display: none">>
<div class="header switch">
<div>
允许查看流程动图和流转图
<a-tooltip title="允许查看流程动图和流转图">
<question-circle-outlined />
</a-tooltip>
</div>
<div>
<a-switch v-model:checked="config.graphicStatus" checkedValue="1" unCheckedValue="0" checked-children="" un-checked-children="" />
</div>
</div>
</div>
<a-divider style="display: none"/>
<div class="chunk" style="display: none">
<div class="header switch">
<div>
自动提交规则
<a-tooltip title="保存数据后,自动提交流程">
<question-circle-outlined />
</a-tooltip>
</div>
</div>
<div class="nextline-input">
<a-select v-model:value="config.autoSubmitStatus" style="width: 100%">
<a-select-option value="0">不启用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</div>
</div>
<a-divider style="display: none"/>
<div class="chunk">
<div class="header switch">
<div>桌面图标<DesktopOutlined /> </div>
</div>
<div class="nextline-input">
<IconPicker v-model:value="config.pcIcon" :disabled="false" placeholder="点击选择表单图标" />
</div>
</div>
<a-divider />
<div class="chunk">
<div class="header switch">
<div>移动图标<MobileOutlined /> </div>
</div>
<div class="nextline-input">
<IconPicker v-model:value="config.appIcon" :disabled="false" placeholder="点击选择表单图标" />
</div>
</div>
</div>
</BasicDrawer>
</template>
<script lang="ts">
/**
* 高级配置 vue3新增功能点
*/
import { defineComponent, reactive, toRaw } from 'vue';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { QuestionCircleOutlined, DesktopOutlined, MobileOutlined } from '@ant-design/icons-vue';
import { IconPicker } from '/@/components/Icon';
import { edit } from '../process.design.api';
interface ProcessConfig {
id?: string;
notifyWay?: string[] | string;
urgeStatus?: string;
backStatus?: string;
graphicStatus?: string;
autoSubmitStatus?: string;
pcIcon?: string;
appIcon?: string;
messageTemplate?: string
}
export default defineComponent({
name: 'ProcessOtherConfigDrawer',
components: {
BasicDrawer,
QuestionCircleOutlined,
DesktopOutlined,
MobileOutlined,
IconPicker,
},
emits: ['success', 'register'],
setup(_p, { emit }) {
//高级配置属性
const config = reactive<ProcessConfig>({
id: '',
notifyWay: [],
urgeStatus: '1',
backStatus: '1',
graphicStatus: '1',
autoSubmitStatus: '0',
pcIcon: '',
appIcon: '',
messageTemplate: ''
});
// 弹窗
const [register, { closeDrawer }] = useDrawerInner((data) => {
console.log('data', data);
resetForm(data);
});
//赋值
function resetForm(data) {
Object.keys(config).map((k) => {
//通知类型是数组格式
if (k == 'notifyWay') {
if (!data[k]) {
config[k] = [];
} else {
config[k] = data[k].split(',');
}
} else {
if(k=='messageTemplate'){
config[k] = data[k] || 'bpm_node_notify';
}else{
config[k] = data[k] || '';
}
}
});
}
//保存配置
async function handleSubmit() {
let data = {
...toRaw(config),
};
if (data.notifyWay && data.notifyWay.length > 0) {
let temp = (data.notifyWay as string[]).join(',');
data.notifyWay = temp;
}else{
data.notifyWay = ''
}
console.log('修改流程高级配置属性', data);
await edit(data);
emit('success');
closeDrawer();
}
//handleClose
function handleClose() {
resetForm({});
}
return {
register,
closeDrawer,
config,
handleSubmit,
handleClose,
};
}
});
</script>
<style lang="less" scoped>
.process-config {
.ant-divider {
margin: 10px 0;
}
.header {
color: #000000d9;
font-weight: 700;
font-size: 14px;
line-height: 1.5715;
white-space: nowrap;
text-overflow: ellipsis;
&.switch {
display: flex;
justify-content: space-between;
.anticon {
font-size: 14px;
color: @primary-color;
margin-left: 5px;
}
}
}
.content {
margin-top: 8px;
.ant-checkbox-group > div {
margin: 5px;
}
}
.chunk {
.nextline-input {
margin: 7px 0 3px 0;
}
}
}
html[data-theme='dark'] {
.process-config .header {
color: rgba(255, 255, 255, 0.88);
}
}
</style>
@@ -0,0 +1,39 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/process/extActProcess/listProcess',
delete = '/act/process/extActProcess/delete',
deleteBatch = '/act/process/extActProcess/deleteBatch',
deployProcess = '/act/process/extActProcess/deployProcess',
uploadProcess = '/act/process/extActProcess/uploadProcess',
copyProcess = '/act/process/extActProcess/copyProcess',
edit = '/act/process/extActProcess/edit',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 编辑
*/
export const edit = (params) => defHttp.put({ url: Api.edit, params });
/**
* 发布
*/
export const publish = (params) => defHttp.put({ url: Api.deployProcess, params });
/**
* 删除
*/
export const deleteOne = (params) => defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true });
/**
* 复制
*/
export const copy = (params) => defHttp.get({ url: Api.copyProcess, params });
export const uploadProcess = Api.uploadProcess;
@@ -0,0 +1,109 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { ajaxGetDictItems } from '/@/views/system/menu/menu.api';
import { render } from '/@/utils/common/renderUtils';
/**
* 列表
*/
export const columns: BasicColumn[] = [
{
title: '#',
dataIndex: '',
key: 'rowIndex',
width: 60,
align: 'center',
customRender: function ({ index }) {
return parseInt(index) + 1;
},
},
{
title: '流程名称',
align: 'center',
dataIndex: 'processName',
sorter: true,
},
{
title: '流程编码',
align: 'center',
dataIndex: 'processKey',
sorter: true,
},
{
title: '流程类型',
align: 'center',
dataIndex: 'processType',
sorter: true,
customRender: function ({ text }) {
return render.renderDict(text, 'bpm_process_type');
},
},
{
title: '发布状态',
align: 'center',
sorter: true,
dataIndex: 'processStatus',
customRender: function ({ text }) {
if (text == 1) {
return '已发布';
} else if (text == 0) {
return '未发布';
} else {
return text;
}
},
},
{
title: '创建时间',
align: 'center',
dataIndex: 'createTime',
sorter: true,
},
];
/**
* 查询条件
*/
export const searchFormSchema: FormSchema[] = [
{
label: '流程名称',
field: 'processName',
component: 'Input',
},
{
label: '流程编码',
field: 'processKey',
component: 'Input',
},
{
label: '流程类型',
field: 'processType',
component: 'ApiSelect',
componentProps: {
api: ajaxGetDictItems,
params: { code: 'bpm_process_type' },
labelField: 'text',
valueField: 'value',
},
},
{
label: '对接编码',
field: 'flowCode',
component: 'Input',
},
{
label: '设计风格',
field: 'processDesginType',
component: 'Select',
defaultValue: 'default',
componentProps: () => {
return {
options: [
{ label: '请选择', value: '' },
{ label: '默认设计', value: 'default' },
{ label: '简版设计', value: 'mini' },
],
};
},
},
];
@@ -0,0 +1,168 @@
<template>
<BasicTable @register="registerTable">
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"></TableAction>
</template>
</BasicTable>
<!-- 流程图 -->
<process-graph-modal @register="registerGraphModal"></process-graph-modal>
<!-- 流程节点 -->
<process-node-list-deployment-modal @register="registerNodeListModal"></process-node-list-deployment-modal>
</template>
<script>
import { ref, unref, reactive } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns } from './process.deployment.data';
import { list, active, suspend, deleteOne, downProcessXmlUrl } from './process.deployment.api';
import ProcessGraphModal from './ProcessGraphModal.vue';
import ProcessNodeListDeploymentModal from './ProcessNodeListDeploymentModal.vue';
import { useModal } from '/@/components/Modal';
import { getToken } from '/@/utils/auth';
export default {
name: 'ProcessDeploymentList',
components: {
BasicTable,
TableAction,
ProcessGraphModal,
ProcessNodeListDeploymentModal,
},
emits: ['loaded'],
setup(_props, { emit }) {
const processKey = ref('');
async function init(dataKey) {
console.log('init', dataKey);
processKey.value = dataKey;
await reload();
emit('loaded');
}
const { tableContext } = useListPage({
designScope: 'process-deployment',
pagination: false,
tableProps: {
api: list,
immediate: false,
showIndexColumn: false,
columns: columns,
useSearchForm: false,
canResize: false,
actionColumn: {
width: 360,
dataIndex: 'action',
slots: { customRender: 'action' },
},
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable, { reload }] = tableContext;
const [registerGraphModal, { openModal: openGraphModal }] = useModal();
const [registerNodeListModal, { openModal: openNodeListModal }] = useModal();
function getTableAction(record) {
let arr = [];
arr.push({
label: '流程图',
onClick: showPic.bind(null, record),
});
if (record.suspensionState == 0) {
arr.push({
label: '激活',
popConfirm: {
title: '确定激活吗?',
confirm: handleActive.bind(null, record),
},
});
}
if (record.suspensionState == 1) {
arr.push({
label: '挂起',
popConfirm: {
title: '确定挂起吗?',
confirm: handleSuspend.bind(null, record),
},
});
}
arr.push({
label: '流程节点',
onClick: showNodes.bind(null, record),
});
return arr;
}
function getDropDownAction(record) {
return [
{
label: '删除',
popConfirm: {
title: '确定删除吗?',
confirm: handleDelete.bind(null, record),
},
},
{
label: '下载',
onClick: handleDownload.bind(null, record),
},
];
}
function showPic(record) {
openGraphModal(true, record);
}
async function handleActive(record) {
await active(record.id);
reload();
}
async function handleSuspend(record) {
await suspend(record.id);
reload();
}
function showNodes(record) {
openNodeListModal(true, record);
}
async function handleDelete(record) {
await deleteOne({ ids: record.deploymentId, processKey: processKey.value });
reload();
}
function handleDownload(record) {
let deploymentId = record.deploymentId;
let resourceName = record.resourceName;
const token = getToken();
let url =
window._CONFIG['domianURL'] + downProcessXmlUrl + '?deploymentId=' + deploymentId + '&resourceName=' + resourceName + '&token=' + token;
window.location.href = url;
}
function addQueryParams(params) {
params.processKey = processKey.value;
return params;
}
return {
init,
registerTable,
getDropDownAction,
getTableAction,
registerGraphModal,
registerNodeListModal,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,47 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="流程图" :footer="null" :width="800" canFullscreen destroyOnClose>
<img :src="imageSrc" @load="imgLoad" />
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import qs from 'qs';
import { ref } from 'vue';
export default {
name: 'ProcessGraphModal',
components: {
BasicModal,
},
setup() {
const imageSrc = ref('');
const [registerModal, { redoModalHeight }] = useModalInner((data) => {
const { deploymentId, diagramResourceName } = data;
imageSrc.value = getResourceURL(deploymentId, diagramResourceName);
});
// 获取静态资源访问地址
function getResourceURL(id, name) {
let params = qs.stringify({
_t: Date.parse(new Date()) / 1000,
deploymentId: id,
resourceName: name,
});
return `${window._CONFIG['domianURL']}/act/process/resource?${params}`;
}
function imgLoad() {
redoModalHeight();
}
return {
registerModal,
imageSrc,
imgLoad,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,82 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:footer="null"
width="90%"
title="流程节点"
keyboard
useWrapper
:minHeight="50"
:canFullscreen="false"
destroyOnClose
>
<a-spin :spinning="spinningLoading">
<BasicTable @register="registerTable" />
</a-spin>
</BasicModal>
</template>
<script>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { ref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { deploymentNodeColumn } from './process.deployment.data';
import { getNodeList } from './process.deployment.api';
export default {
name: 'ProcessNodeListDeploymentModal',
components: {
BasicModal,
BasicTable,
},
setup() {
const spinningLoading = ref('');
const deploymentId = ref('');
const { tableContext } = useListPage({
designScope: 'process-deployment',
pagination: false,
tableProps: {
api: getNodeList,
immediate: false,
showIndexColumn: true,
showTableSetting: false,
columns: deploymentNodeColumn,
useSearchForm: false,
showActionColumn: false,
// 这属性效果是相反的
canResize: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable, { reload }] = tableContext;
const [registerModal] = useModalInner(async (data) => {
console.log('data', data);
spinningLoading.value = true;
deploymentId.value = data.deploymentId;
await reload();
spinningLoading.value = false;
});
function addQueryParams(params) {
params.deploymentId = deploymentId.value;
params.column = 'id';
params.order = 'asc';
return params;
}
return {
registerModal,
spinningLoading,
registerTable,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,50 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/process/extActProcess/processDeploymentList',
delete = '/act/process/extActProcess/deleteDeployment',
active = '/act/process/active/',
suspend = '/act/process/suspend/',
downProcessXml = '/act/process/downProcessXml',
getNodeList = '/act/process/extActProcessNodeDeployment/list',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除
*/
export const deleteOne = (params) => defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true });
/**
* 激活
* @param params
*/
export const active = (id) => {
let url = Api.active + id;
return defHttp.get({ url });
};
/**
* 挂起
* @param params
*/
export const suspend = (id) => {
let url = Api.suspend + id;
return defHttp.get({ url });
};
/**
* 下载地址
*/
export const downProcessXmlUrl = Api.downProcessXml;
/**
* 流程节点列表
* @param params
*/
export const getNodeList = (params) => defHttp.get({ url: Api.getNodeList, params });
@@ -0,0 +1,74 @@
import { BasicColumn } from '/@/components/Table';
/**
* 列表
*/
export const columns: BasicColumn[] = [
{
title: '名称',
align: 'center',
dataIndex: 'name',
},
{
title: '流程KEY',
align: 'center',
dataIndex: 'key',
},
{
title: '版本',
align: 'center',
dataIndex: 'version',
},
{
title: '状态',
align: 'center',
dataIndex: 'suspensionState',
customRender: function ({ text }) {
if (text == 1) {
return '已激活';
} else if (text == 0) {
return '挂起';
} else {
return text;
}
},
},
];
/**
* 版本监控节点列表
*/
export const deploymentNodeColumn: BasicColumn[] = [
{
title: '节点名称',
align: 'center',
dataIndex: 'processNodeName',
},
{
title: '节点编码',
align: 'center',
dataIndex: 'processNodeCode',
},
{
title: 'PC表单地址',
align: 'center',
dataIndex: 'modelAndView',
},
{
title: '移动表单地址',
align: 'center',
dataIndex: 'modelAndViewMobile',
},
{
title: '超时提醒(时)',
align: 'center',
dataIndex: 'nodeTimeout',
customRender: function ({ text }) {
if (!text) {
return '';
}
return text + '小时';
},
},
];
@@ -0,0 +1,121 @@
<template>
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleAddForm"> 新增 </a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"></TableAction>
</template>
</BasicTable>
<process-form-modal @register="registerFormModal" @success="reload"></process-form-modal>
</template>
<script>
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns } from './process.form.data';
import { list, deleteOne } from './process.form.api';
import { useModal } from '/@/components/Modal';
import ProcessFormModal from './ProcessFormModal.vue';
export default {
name: 'ProcessFormList',
components: {
BasicTable,
TableAction,
ProcessFormModal,
},
props: {
processId: {
type: String,
default: '',
},
},
setup(props) {
const { tableContext } = useListPage({
designScope: 'process-form',
pagination: true,
tableProps: {
api: list,
columns: columns,
pagination: false,
useSearchForm: false,
canResize: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable, { reload }] = tableContext;
const [registerFormModal, { openModal: openFormModal }] = useModal();
function addQueryParams(params) {
params.processId = props.processId;
params.column = 'id';
params.order = 'desc';
return params;
}
/**
* 新增
*/
function handleAddForm() {
openFormModal(true, {
isUpdate: false,
processId: props.processId,
formDealStyle: 'default',
});
}
/**
* 编辑弹框
*/
function handleUpdate(record) {
let data = Object.assign({}, record, { isUpdate: true });
openFormModal(true, data);
}
/**
* 删除
*/
async function handleDelete(record) {
await deleteOne({ id: record.id });
reload();
}
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleUpdate.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
];
}
return {
registerTable,
getTableAction,
handleAddForm,
registerFormModal,
reload,
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,86 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" wrapClassName="process-form-modal" @ok="handleSubmit" :width="800" >
<BasicForm @register="registerForm">
<template #titleExp="{ model, field }">
<a-input v-model:value="model[field]" placeholder="请输入标题表达式"></a-input>
<span style="color: red; font-size: 12px">参考XXXX${busname}-XXXX${name}其中${}表达式取流程变量的值</span>
</template>
</BasicForm>
</BasicModal>
</template>
<script>
import { ref, computed } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { useProcessFormSchema } from './useProcessFormSchema';
import { saveOrUpdate } from './process.form.api';
export default {
name: 'ProcessFormModal',
components: {
BasicModal,
BasicForm,
},
emits: ['success', 'register'],
setup(_props, { emit }) {
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value === true ? '编辑' : '新增';
});
const { processFormSchema, changeFormType } = useProcessFormSchema();
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema, getFieldsValue }] = useForm({
schemas: processFormSchema,
showActionButtonGroup: false,
baseColProps: { span: 24, style: { marginTop: '10px' } },
});
//useModalInner
const [registerModal, { closeModal }] = useModalInner((data) => {
console.log('data', data);
isUpdate.value = data.isUpdate;
resetFields();
if (!data.isUpdate) {
//新增页面 设置表单的 processId,formDealStyle
const { formDealStyle, processId } = data;
setFieldsValue({ processId, formDealStyle });
} else {
delete data.isUpdate;
let temp = {...data}
changeFormType(data.formType);
if(data.formType == '1'){
temp['formTableName1'] = temp.formTableName;
}else if(data.formType == '2'){
temp['formTableName2'] = temp.formTableName;
}else if(data.formType == '3'){
temp['formTableName3'] = temp.formTableName;
}
delete temp.formTableName;
setFieldsValue(temp);
}
});
async function handleSubmit() {
const values = await validate();
Object.keys(values).map(k=>{
if(k.indexOf('formTableName')>=0){
values['formTableName'] = values[k];
delete values[k]
}
});
await saveOrUpdate(values, isUpdate.value);
emit('success');
closeModal();
}
return {
registerModal,
title,
handleSubmit,
registerForm,
};
},
};
</script>
@@ -0,0 +1,32 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/process/extActProcessForm/list',
delete = '/act/process/extActProcessForm/delete',
deleteBatch = '/act/process/extActProcessForm/deleteBatch',
add = '/act/process/extActProcessForm/add',
edit = '/act/process/extActProcessForm/edit',
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除
*/
export const deleteOne = (params) => defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true });
/**
* 表单操作
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
if (isUpdate) {
return defHttp.put({ url: Api.edit, params });
} else {
return defHttp.post({ url: Api.add, params });
}
};
@@ -0,0 +1,64 @@
import { BasicColumn } from '/@/components/Table';
import { filterDictText } from '/@/utils/dict/JDictSelectUtil';
export const processFormTypeOptions = [
{
value: '1',
label: 'Online表单',
title: 'Online表单',
},
{
value: '2',
label: '表单设计器',
title: '表单设计器',
},
{
value: '3',
label: '自定义开发',
title: '自定义开发',
},
];
/**
* 列表
*/
export const columns: BasicColumn[] = [
{
title: '#',
dataIndex: '',
key: 'rowIndex',
width: 60,
align: 'center',
customRender: function ({ index }) {
return parseInt(index) + 1;
},
},
{
title: '唯一编码',
align: 'center',
dataIndex: 'relationCode',
},
{
title: '表名/自定义表单CODE',
align: 'center',
dataIndex: 'formTableName',
},
{
title: '表单类型',
align: 'center',
dataIndex: 'formType',
customRender: function ({ text }) {
return filterDictText(processFormTypeOptions, text);
},
},
{
title: '业务标题表达式',
align: 'center',
dataIndex: 'titleExp',
},
{
title: '流程状态列名',
align: 'center',
dataIndex: 'flowStatusCol',
},
];
@@ -0,0 +1,318 @@
import { defHttp } from '/@/utils/http/axios';
import { FormSchema } from '/@/components/Table';
import { processFormTypeOptions } from './process.form.data';
import { ref } from 'vue'
/**
* 获取自定义开发的流程编码
*/
const GET_FLOW_CODE_FOR_CUTOM = '/act/process/extActProcessForm/genDefaultCode';
/**
* 给表单添加相关事件
*/
export function useProcessFormSchema() {
let showOnline = ref(false);
let showDesigner = ref(false);
let showCustom = ref(false);
showOnline.value = true;
function changeFormType(type){
if (type == '1') {
showOnline.value = true;
showDesigner.value = false;
showCustom.value = false;
} else if (type == '2') {
showOnline.value = false;
showDesigner.value = true;
showCustom.value = false;
} else {
showOnline.value = false;
showDesigner.value = false;
showCustom.value = true;
}
}
/**
* 表单类型改变触发
* @param type
* @param updateSchema
* @param setFieldsValue
*/
async function handleFormTypeChange(type, formAction) {
const { updateSchema, setFieldsValue, clearValidate } = formAction;
await clearValidate(['relationCode', 'formTableName1', 'formTableName2', 'formTableName3']);
changeFormType(type)
if (type == '1') {
await resetFormForOnline(updateSchema, setFieldsValue);
} else if (type == '2') {
await resetFormForDesigner(updateSchema, setFieldsValue);
} else {
await resetFormForCode(updateSchema);
}
// 重置表单的值
setFieldsValue({
relationCode: '',
formTableName1: '',
formTableName2: '',
formTableName3: '',
});
}
/**
* 表单类型改变触发-online
* @param updateSchema
*/
async function resetFormForOnline(updateSchema, setFieldsValue) {
await updateSchema([
{
field: 'relationCode',
required: false,
componentProps: {
readOnly: true,
},
},
{
field: 'flowStatusCol',
required: false,
componentProps: {
readOnly: true,
},
},
]);
setFieldsValue({ flowStatusCol: 'bpm_status' });
}
/**
* 表单类型改变触发-表单设计器
* @param updateSchema
*/
async function resetFormForDesigner(updateSchema, setFieldsValue) {
await updateSchema([
{
field: 'relationCode',
required: false,
componentProps: {
readOnly: true,
},
},
{
field: 'flowStatusCol',
required: false,
componentProps: {
readOnly: true,
},
},
]);
setFieldsValue({ flowStatusCol: 'bpm_status' });
}
/**
* 表单类型改变触发-自定义编码
* @param updateSchema
*/
async function resetFormForCode(updateSchema) {
await updateSchema([
{
field: 'relationCode',
required: true,
componentProps: {
readOnly: false,
},
},
{
field: 'flowStatusCol',
required: true,
componentProps: {
readOnly: false,
},
},
]);
}
/**
* 当 表名改变的时候 对应的流程编码需要动态改变,
* change事件 只对online和表单设计器有效
* @param e
* @param formModel
*/
function handleTableNameChange(e, formModel) {
let val = e;
if(e && e.target){
val = e.target.value;
}
if (!formModel.flowStatusCol) {
formModel.flowStatusCol = 'bpm_status';
}
if (!val) {
formModel.relationCode = '';
} else {
if (formModel.formType == '1') {
formModel.relationCode = 'onl_' + val;
} else if (formModel.formType == '2') {
formModel.relationCode = 'desform_' + val;
}
}
}
/**
* 当 表名改变的时候 对应的流程编码需要动态改变,
* blur事件 自定义开发
* @param e
* @param formModel
*/
function handleTableNameBlur(e, formModel, setFieldsValue) {
if (formModel.formType == '3') {
let val = e.target.value;
if (!val) {
return;
}
let relationCode = formModel.relationCode;
if (!relationCode) {
//只有编码不存在的时候才需要请求后台获取最新的流程编码
let params = { tabeName: val };
defHttp.get({ url: GET_FLOW_CODE_FOR_CUTOM, params }, { isTransformResponse: false }).then((res) => {
if (res.success) {
setFieldsValue({
relationCode: res.result,
});
}
});
}
}
}
/**
* 表单
*/
const processFormSchema: FormSchema[] = [
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
{
label: 'processId',
field: 'processId',
component: 'Input',
show: false,
},
{
label: 'formDealStyle',
field: 'formDealStyle',
component: 'Input',
show: false,
},
{
label: '表单类型',
field: 'formType',
component: 'Select',
defaultValue: '1',
componentProps: ({ formActionType }) => {
return {
allowClear: false,
options: processFormTypeOptions,
onChange: async (value) => {
await handleFormTypeChange(value, formActionType);
},
};
},
},
{
label: '表名',
field: 'formTableName3',
component: 'Input',
required: true,
ifShow: ()=>showCustom.value,
componentProps: ({ formModel, formActionType: { setFieldsValue } }) => {
return {
allowClear: false,
onChange: (e) => {
handleTableNameChange(e, formModel);
},
onBlur: (e) => {
handleTableNameBlur(e, formModel, setFieldsValue);
},
};
},
},
{
label: '表名',
field: 'formTableName1',
component: 'JSearchSelect',
required: true,
ifShow: ()=>showOnline.value,
componentProps: ({ formModel, formActionType: { setFieldsValue } }) => {
return {
dict: 'onl_cgform_head where table_type!=3 and copy_type=0,table_txt,table_name',
pageSize: 10,
async: true,
popContainer: '.process-form-modal',
params:{order: 'desc', column: 'create_time'},
onChange: (e) => {
handleTableNameChange(e, formModel);
handleTableNameBlur(e, formModel, setFieldsValue);
}
};
}
},
{
label: '表单编码',
field: 'formTableName2',
component: 'JSearchSelect',
required: true,
ifShow: ()=>showDesigner.value,
componentProps: ({ formModel, formActionType: { setFieldsValue } }) => {
return {
dict: 'design_form where desform_type=1,desform_name,desform_code',
pageSize: 10,
async: true,
popContainer: '.process-form-modal',
params:{order: 'desc', column: 'create_time'},
onChange: (e) => {
handleTableNameChange(e, formModel);
handleTableNameBlur(e, formModel, setFieldsValue);
}
};
}
},
{
label: '唯一编码',
field: 'relationCode',
component: 'Input',
required: true,
componentProps: {
readOnly: true,
allowClear: false,
},
},
{
label: '流程状态列名',
field: 'flowStatusCol',
component: 'Input',
required: true,
componentProps: {
readOnly: true,
allowClear: false,
},
},
{
label: '标题表达式',
field: 'titleExp',
component: 'Input',
slot: 'titleExp',
required: true,
},
];
return { processFormSchema, changeFormType };
}
@@ -0,0 +1,58 @@
<template>
<BasicDrawer v-bind="$attrs" @register="registerModal" title="节点页面权限" :width="drawerWidth">
<process-node-auth-list ref="nodeAuthRef"></process-node-auth-list>
</BasicDrawer>
</template>
<script>
import { ref } from 'vue';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import ProcessNodeAuthList from '../processNodeAuth/ProcessNodeAuthList.vue';
export default {
name: 'ProcessNodeAuthDrawer',
components: {
BasicDrawer,
ProcessNodeAuthList,
},
emits: ['register'],
setup(_props, { emit }) {
const processId = ref('');
const processNodeCode = ref('');
const drawerWidth = ref(900);
const nodeAuthRef = ref();
// 注册
const [registerModal, { closeDrawer }] = useDrawerInner(async (data) => {
console.log('data1111', data);
// this.queryParam.processId = record.processId;
// this.queryParam.processNodeCode = record.processNodeCode;
const { processId, processNodeCode } = data;
nodeAuthRef.value.init(processId, processNodeCode);
resetScreenSize();
});
function resetScreenSize() {
let screenWidth = document.body.clientWidth;
if (screenWidth < 500) {
drawerWidth.value = screenWidth;
} else {
drawerWidth.value = 900;
}
}
return {
registerModal,
nodeAuthRef,
drawerWidth,
};
},
};
</script>
<style scoped>
:deep(.ant-modal-body) {
padding-bottom: 5px !important;
padding-top: 5px !important;
}
</style>
@@ -0,0 +1,172 @@
<template>
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleAddNode"> 新增 </a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"></TableAction>
</template>
</BasicTable>
<process-node-modal @register="registerNodeModal" @success="reload"></process-node-modal>
<process-node-auth-drawer @register="registerDrawer"></process-node-auth-drawer>
<!-- 节点权限新配置 -->
<node-auth-easy-modal @register="registerNodeAuthModal" @success="reload"></node-auth-easy-modal>
</template>
<script>
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns } from './process.node.data';
import { list, deleteOne } from './process.node.api';
import { useModal } from '/@/components/Modal';
import { useDrawer } from '/@/components/Drawer';
import ProcessNodeModal from './ProcessNodeModal.vue';
import ProcessNodeAuthDrawer from './ProcessNodeAuthDrawer.vue';
import NodeAuthEasyModal from '../processNodeAuth/NodeAuthEasyModal.vue'
export default {
name: 'ProcessNodeList',
components: {
BasicTable,
TableAction,
ProcessNodeModal,
ProcessNodeAuthDrawer,
NodeAuthEasyModal
},
props: {
processId: {
type: String,
default: '',
},
},
setup(props) {
const { tableContext } = useListPage({
designScope: 'process-node',
tableProps: {
api: list,
columns: columns,
useSearchForm: false,
canResize: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable, { reload }, { selectedRowKeys }] = tableContext;
const [registerDrawer, { openDrawer }] = useDrawer();
const [registerNodeModal, { openModal: openNodeModal }] = useModal();
const [registerNodeAuthModal, { openModal: openNodeAuthModal }] = useModal();
function addQueryParams(params) {
params.processId = props.processId;
return params;
}
/**
* 新增
*/
function handleAddNode() {
openNodeModal(true, {
isUpdate: false,
processId: props.processId,
});
}
/**
* 编辑弹框
*/
function handleUpdate(record) {
let data = Object.assign({}, record, { isUpdate: true });
openNodeModal(true, data);
}
/**
* 删除
*/
async function handleDelete(record) {
await deleteOne({ id: record.id });
reload();
}
/**
* 权限设置
* 1.获取业务表单
* 2.如果是单个业务表单且表单是 online表单或者设计器表单 跳转新的权限设置页面
* 3.如果是多个业务表单或自定义开发表单, 只支持旧的权限设置页面
*/
function handleAuthConfig(record) {
openDrawer(true, record);
}
/**
* 权限设置简洁版
*/
function easyAuthConfig(record) {
openNodeAuthModal(true, record);
}
function getDropDownAction(record) {
return [
/* {
label: '权限设置',
onClick: handleAuthConfig.bind(null, record),
},*/
{
label: '自定义',
onClick: handleUpdate.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
];
}
function getTableAction(record) {
return [
{
label: '节点规则',
onClick: easyAuthConfig.bind(null, record),
},
];
}
function openAuthModal(isNewAuthModal, row) {
if(isNewAuthModal===true){
openNodeAuthModal(true, row);
}else{
openDrawer(true, row);
}
}
return {
registerTable,
getDropDownAction,
getTableAction,
handleAddNode,
registerNodeModal,
registerDrawer,
registerNodeAuthModal,
reload,
openAuthModal
};
},
};
</script>
<style scoped></style>
@@ -0,0 +1,150 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" :width="800" destroyOnClose>
<BasicForm @register="registerForm">
<template #nodeTimeout="{ model, field }">
<a-input-number v-model:value="model[field]" :min="0"></a-input-number>单位小时0表示不提醒
</template>
<template #modelAndView="{ model, field }">
<a-input style="width: calc(100% - 20px);margin-right: 5px" v-model:value="model[field]"></a-input>
<a-tooltip overlayClassName="process-node-tip">
<template #title>
<div class="tip-content">
参考配置如下<br>
1.onlinesuper/bpm/process/components/OnlineFormOpt<br>
2.自定义编码super/bpm/example/joa/leave/components/LeaveForm
<div>{{designerUrl}}</div>
详情参考 <a target="_blank" href="https://www.kancloud.cn/zhangdaiscott/jeecgboot_business/3214567">官方文档</a>
</div>
</template>
<question-circle-outlined style="color: rgb(110 110 110)"/>
</a-tooltip>
</template>
<template #modelAndViewMobile="{ model, field }">
<a-input style="width: calc(100% - 20px);margin-right: 5px" v-model:value="model[field]"></a-input>
<a-tooltip overlayClassName="process-node-tip">
<template #title>
<div class="tip-content">
参考配置如下<br>
1.onlinecheck/onlineForm/flowedit<br>
2.自定义编码applyform/scottNote
<div>{{designerUrl}}</div>
详情参考 <a target="_blank" href="https://www.kancloud.cn/zhangdaiscott/jeecgboot_business/2476430">官方文档</a>
</div>
</template>
<question-circle-outlined style="color: rgb(110 110 110)"/>
</a-tooltip>
</template>
</BasicForm>
</BasicModal>
</template>
<script>
import { ref, computed } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { nodeFormSchema } from './process.node.data';
import { saveOrUpdate } from './process.node.api';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
export default {
name: 'ProcessNodeModal',
components: {
BasicModal,
BasicForm,
QuestionCircleOutlined
},
emits: ['success', 'register'],
setup(_props, { emit }) {
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value === true ? '编辑' : '新增';
});
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema, getFieldsValue }] = useForm({
schemas: nodeFormSchema,
showActionButtonGroup: false,
baseColProps: { span: 24, style: { marginTop: '10px' } },
});
//useModalInner
const [registerModal, { closeModal }] = useModalInner(async (data) => {
console.log('data', data);
isUpdate.value = data.isUpdate;
if (!data.isUpdate) {
//新增页面 设置表单的 processId
setFieldsValue({ processId: data.processId });
//update-begin-author:taoyan date:2023-2-14 for: QQYUN-4266【老版流程】新增节点 节点名称和编码不能输入,子流程无法添加start节点
await updateSchema([
{
field: 'processNodeName',
componentProps: {
readOnly: false,
}
}, {
field: 'processNodeCode',
componentProps: {
readOnly: false,
}
}
]);
//update-end-author:taoyan date:2023-2-14 for: QQYUN-4266【老版流程】新增节点 节点名称和编码不能输入,子流程无法添加start节点
} else {
delete data.isUpdate;
setFieldsValue({ ...data });
//update-begin-author:taoyan date:2023-2-14 for: QQYUN-4266【老版流程】新增节点 节点名称和编码不能输入,子流程无法添加start节点
await updateSchema([
{
field: 'processNodeName',
componentProps: {
readOnly: true,
}
}, {
field: 'processNodeCode',
componentProps: {
readOnly: true,
}
}
]);
//update-end-author:taoyan date:2023-2-14 for: QQYUN-4266【老版流程】新增节点 节点名称和编码不能输入,子流程无法添加start节点
}
});
async function handleSubmit() {
const values = await validate();
await saveOrUpdate(values, isUpdate.value);
emit('success');
closeModal();
}
const designerUrl = "3.表单设计器:{{DOMAIN_URL}}/desform/edit/设计器编码/${BPM_DES_DATA_ID}?token={{TOKEN}}&taskId={{TASKID}}"
return {
registerModal,
title,
handleSubmit,
registerForm,
designerUrl
};
},
};
</script>
<style lang="less">
.process-node-tip {
.ant-tooltip-inner{
width: 410px;
.tip-content{
width: 400px;
}
}
}
</style>
<style scoped>
:deep(.ant-modal-body) {
padding-bottom: 5px !important;
padding-top: 5px !important;
}
</style>
@@ -0,0 +1,41 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/act/process/extActProcessNode/list',
delete = '/act/process/extActProcessNode/delete',
deleteBatch = '/act/process/extActProcessNode/deleteBatch',
add = '/act/process/extActProcessNode/add',
edit = '/act/process/extActProcessNode/edit',
batchSavePermission = '/act/process/extActProcessNodePermission/saveOrUpdateBatch'
}
/**
* 列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除
*/
export const deleteOne = (params) => defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true });
/**
* 表单操作
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
if (isUpdate) {
return defHttp.put({ url: Api.edit, params });
} else {
return defHttp.post({ url: Api.add, params });
}
};
/**
* 批量保存节点权限
* @param params
*/
export const batchSavePermission = (params) => {
return defHttp.post({ url: Api.batchSavePermission, params });
}

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