初始版本提交
This commit is contained in:
581
src/views/workflow/task/components/ApprovalProcess.vue
Normal file
581
src/views/workflow/task/components/ApprovalProcess.vue
Normal file
@ -0,0 +1,581 @@
|
||||
<template>
|
||||
<span @click="approval"
|
||||
><slot></slot>
|
||||
<LoadingBox v-if="showLoading" />
|
||||
<ProcessLayout class="wrap" v-if="showVisible" @click.stop="">
|
||||
<template #title> {{ t('审批流程') }}【{{ data.item.name }}】 </template>
|
||||
<template #close>
|
||||
<a-button type="primary" class="clean-icon" @click.stop="close">{{ t('关闭') }}</a-button>
|
||||
</template>
|
||||
<template #left>
|
||||
<FlowPanel
|
||||
:xml="data.xml"
|
||||
:taskRecords="data.taskRecords"
|
||||
:predecessorTasks="selectedPredecessorTasks"
|
||||
:processId="props.processId"
|
||||
position="top"
|
||||
>
|
||||
<FormInformation
|
||||
:opinionsComponents="data.opinionsComponents"
|
||||
:opinions="data.opinions"
|
||||
:disabled="false"
|
||||
:formInfos="data.formInfos"
|
||||
:formAssignmentData="data.formAssignmentData"
|
||||
ref="formInformation"
|
||||
@get-form-configs="(config) => (formConfigs = config)"
|
||||
/>
|
||||
</FlowPanel>
|
||||
</template>
|
||||
<template #right>
|
||||
<a-tabs>
|
||||
<a-tab-pane key="1" :tab="t('审批信息')">
|
||||
<NodeHead :nodeName="t('基础信息')" />
|
||||
|
||||
<div class="description-box">
|
||||
<ProcessInfo class="item-box" :item="data.item">
|
||||
<NodeHead :nodeName="t('审批信息')" />
|
||||
<div
|
||||
class="text-box"
|
||||
v-if="approvalData.buttonConfigs && approvalData.buttonConfigs.length > 0"
|
||||
>
|
||||
<div class="text-label">{{ t('审批结果:') }}</div>
|
||||
<span class="flex-1">
|
||||
<a-radio-group
|
||||
class="approve-group"
|
||||
v-model:value="approvedType"
|
||||
name="approvedType"
|
||||
@change="changeApprovedType"
|
||||
>
|
||||
<span v-for="(item, index) in approvalData.buttonConfigs" :key="index">
|
||||
<a-radio
|
||||
v-if="item.approveType !== ApproveType.OTHER"
|
||||
:value="item.approveType"
|
||||
>
|
||||
{{ item.buttonName }}
|
||||
</a-radio>
|
||||
</span>
|
||||
</a-radio-group>
|
||||
<a-radio-group
|
||||
class="approve-group"
|
||||
v-model:value="approvedType"
|
||||
name="buttonCode"
|
||||
>
|
||||
<span v-for="(item, index) in approvalData.buttonConfigs" :key="index">
|
||||
<a-radio
|
||||
v-if="item.approveType === ApproveType.OTHER"
|
||||
:value="item.buttonCode"
|
||||
@change="changeButtonCodeType"
|
||||
>
|
||||
{{ item.buttonName }}
|
||||
</a-radio>
|
||||
</span>
|
||||
</a-radio-group>
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-box" v-if="approvalData.approvedType === ApproveType.REJECT">
|
||||
<div class="text-label">{{ t('驳回节点:') }}</div>
|
||||
<a-select class="w-full flex-1" v-model:value="approvalData.rejectNodeActivityId">
|
||||
<a-select-option
|
||||
v-for="(item, index) in approvalData.rejectNodeActivityIds"
|
||||
:key="index"
|
||||
:value="item.activityId"
|
||||
>{{ item.activityName }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="text-box">
|
||||
<div class="text-label">{{ t('审批内容:') }}</div>
|
||||
<a-textarea
|
||||
class="flex-1"
|
||||
v-model:value="approvalData.approvedContent"
|
||||
:rows="6"
|
||||
:maxlength="100"
|
||||
/>
|
||||
</div>
|
||||
</ProcessInfo>
|
||||
</div>
|
||||
<a-form
|
||||
class="approval-form"
|
||||
:model="approvalData.stampInfo"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
@finish="onFinish"
|
||||
@finish-failed="onFinishFailed"
|
||||
>
|
||||
<!-- 电子签章 -->
|
||||
<a-form-item
|
||||
:label="t('电子签章')"
|
||||
name="stampId"
|
||||
:rules="[{ required: true, message: t('请选择电子签章') }]"
|
||||
v-if="data.hasStamp"
|
||||
>
|
||||
<SelectStamp
|
||||
v-if="data.hasStamp"
|
||||
v-model:stampId="approvalData.stampInfo.stampId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('签章密码')"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: t('请输入签章密码') }]"
|
||||
v-if="data.hasStampPassword"
|
||||
>
|
||||
<a-input-password v-model:value="approvalData.stampInfo.password" />
|
||||
</a-form-item>
|
||||
|
||||
<ApproveUser
|
||||
v-if="approveUserData.visible"
|
||||
:taskList="approveUserData.list"
|
||||
:schemaId="approveUserData.schemaId"
|
||||
@change="changeApproveUserData"
|
||||
/>
|
||||
<div class="button-box">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
class="mr-2"
|
||||
:loading="data.submitLoading"
|
||||
>{{ t('审批') }}</a-button
|
||||
>
|
||||
<!-- 转办 -->
|
||||
<TransferUser :taskId="props.taskId" @close="close" />
|
||||
<!-- 加签减签 -->
|
||||
<AddOrSubtract
|
||||
v-if="approvalData.isAddOrSubSign"
|
||||
:schemaId="props.schemaId"
|
||||
:taskId="props.taskId"
|
||||
/>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" :tab="t('传阅信息')" force-render>
|
||||
<MemberTable
|
||||
v-model:memberList="approvalData.circulateConfigs"
|
||||
:isCommonType="true"
|
||||
:isApiApprover="true"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" :tab="t('打印表单')" force-render>
|
||||
<NodeHead :nodeName="t('打印属性')" />
|
||||
<a-form
|
||||
:model="printData"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
@finish="printForm"
|
||||
>
|
||||
<a-form-item :label="t('边框颜色')" name="borderColor">
|
||||
<ColorPicker v-model:value="printData.borderColor" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('页面风格')" name="style">
|
||||
<a-select v-model:value="printData.style" style="width: 100%">
|
||||
<a-select-option value="1">默认风格</a-select-option>
|
||||
<a-select-option value="2">公文风格</a-select-option>
|
||||
<a-select-option value="3">发文稿纸风格</a-select-option>
|
||||
<a-select-option value="4">下划线风格</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="标题下划线" name="underline">
|
||||
<a-select v-model:value="printData.underline" style="width: 100%">
|
||||
<a-select-option value="1">是,标题带有下划线</a-select-option>
|
||||
<a-select-option value="0">否,标题不带下划线</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<div class="button-box">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
class="mr-2"
|
||||
:loading="printData.submitLoading"
|
||||
>{{ t('打印当前页表单') }}</a-button
|
||||
>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
</ProcessLayout>
|
||||
<FormPrint
|
||||
v-if="isShowPrint"
|
||||
v-model:isShowPrint="isShowPrint"
|
||||
:formConfigs="formConfigs"
|
||||
:printData="printData"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, defineAsyncComponent, onMounted, provide, Ref } from 'vue';
|
||||
import ProcessLayout from './flow/Layout.vue';
|
||||
import FormInformation from './flow/FormInformation.vue';
|
||||
import FlowPanel from './flow/FlowPanel.vue';
|
||||
import ProcessInfo from './flow/ProcessInfo.vue';
|
||||
import FormPrint from './print/FormPrint.vue';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import { getApprovalProcess, getRejectNode, postApproval } from '/@/api/workflow/task';
|
||||
import userTaskItem from './../hooks/userTaskItem';
|
||||
import { ApproveTask, PostApprovalData, rejectNodeItem } from '/@/model/workflow/bpmnConfig';
|
||||
import { LoadingBox } from '/@/components/ModalPanel/index';
|
||||
import { ButtonConfigItem } from '/@/model/workflow/workflowConfig';
|
||||
import { MemberConfig } from '/@/model/workflow/memberSetting';
|
||||
import { ApproveCode, ApproveType } from '/@/enums/workflowEnum';
|
||||
import { separator } from '../../design/bpmn/config/info';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { ColorPicker } from '/@/components/ColorPicker';
|
||||
const { t } = useI18n();
|
||||
const ApproveUser = defineAsyncComponent(() => import('./flow/ApproveUser.vue'));
|
||||
const AddOrSubtract = defineAsyncComponent(() => import('./flow/AddOrSubtract.vue'));
|
||||
const TransferUser = defineAsyncComponent(() => import('./flow/TransferUser.vue'));
|
||||
const MemberTable = defineAsyncComponent(
|
||||
() => import('/@bpmn/components/member/MemberTable.vue'),
|
||||
);
|
||||
const SelectStamp = defineAsyncComponent(() => import('./stamp/SelectStamp.vue'));
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
schemaId: string;
|
||||
processId: string;
|
||||
taskId: string;
|
||||
visible?: boolean;
|
||||
}>(),
|
||||
{
|
||||
schemaId: '',
|
||||
processId: '',
|
||||
taskId: '',
|
||||
visible: false,
|
||||
},
|
||||
);
|
||||
let emits = defineEmits(['close']);
|
||||
|
||||
let formInformation = ref();
|
||||
let showVisible = ref(false);
|
||||
let showLoading = ref(false);
|
||||
let approvedType = ref(ApproveType.AGREE);
|
||||
let approvalData: {
|
||||
isCountersign: Boolean; //是否会签节点
|
||||
isAddOrSubSign: Boolean; //是否加签减签
|
||||
stampInfo: {
|
||||
stampId: string;
|
||||
password: string;
|
||||
};
|
||||
buttonConfigs: Array<ButtonConfigItem>;
|
||||
approvedType: ApproveType;
|
||||
approvedContent: string;
|
||||
approvedResult: string;
|
||||
rejectNodeActivityId: string;
|
||||
rejectNodeActivityIds: Array<rejectNodeItem>;
|
||||
circulateConfigs: Array<MemberConfig>;
|
||||
} = reactive({
|
||||
isCountersign: false,
|
||||
isAddOrSubSign: false,
|
||||
stampInfo: {
|
||||
stampId: '',
|
||||
password: '',
|
||||
},
|
||||
buttonConfigs: [],
|
||||
approvedType: ApproveType.AGREE,
|
||||
approvedResult: ApproveCode.AGREE,
|
||||
approvedContent: '',
|
||||
rejectNodeActivityId: '',
|
||||
rejectNodeActivityIds: [],
|
||||
circulateConfigs: [],
|
||||
});
|
||||
|
||||
const printData = ref({
|
||||
borderColor: '#000000',
|
||||
style: '1',
|
||||
underline: '0',
|
||||
submitLoading: false,
|
||||
});
|
||||
const formConfigs = ref();
|
||||
const tabActiveKey = ref<number>(0);
|
||||
const isShowPrint = ref<boolean>(false);
|
||||
provide<Ref<number>>('tabActiveKey', tabActiveKey);
|
||||
const { data, approveUserData, initProcessData, notificationError, notificationSuccess } =
|
||||
userTaskItem();
|
||||
const validateSuccess = ref(false);
|
||||
onMounted(() => {
|
||||
if (props.visible) {
|
||||
approval();
|
||||
}
|
||||
});
|
||||
const selectedPredecessorTasks = computed(() => {
|
||||
return data.predecessorTasks.filter((ele) => {
|
||||
return ele.taskId;
|
||||
});
|
||||
});
|
||||
|
||||
// 审批
|
||||
async function approval() {
|
||||
showLoading.value = true;
|
||||
reset();
|
||||
if (props.taskId) {
|
||||
try {
|
||||
let res = await getApprovalProcess(props.taskId, props.processId);
|
||||
initProcessData(res);
|
||||
if (res.buttonConfigs) {
|
||||
approvalData.buttonConfigs = res.buttonConfigs;
|
||||
}
|
||||
if (res.relationTasks) {
|
||||
data.predecessorTasks = res.relationTasks;
|
||||
}
|
||||
if (res.isAddOrSubSign) {
|
||||
approvalData.isAddOrSubSign = res.isAddOrSubSign;
|
||||
}
|
||||
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvedType.value = ApproveType.AGREE;
|
||||
approvalData.approvedContent = '';
|
||||
approvalData.rejectNodeActivityId = '';
|
||||
approvalData.rejectNodeActivityIds = [];
|
||||
approvalData.circulateConfigs = [];
|
||||
showLoading.value = false;
|
||||
showVisible.value = true;
|
||||
} catch (error) {
|
||||
showLoading.value = false;
|
||||
emits('close');
|
||||
}
|
||||
} else {
|
||||
// 只能选一个
|
||||
showLoading.value = false;
|
||||
showVisible.value = false;
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('审批'),
|
||||
description: t('请选择一个流程进行审批'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function changeApprovedType() {
|
||||
approvalData.approvedType = approvedType.value;
|
||||
if (approvedType.value == ApproveType.AGREE) {
|
||||
approvalData.approvedResult = ApproveCode.AGREE;
|
||||
} else if (approvedType.value == ApproveType.REJECT) {
|
||||
approvalData.rejectNodeActivityIds = await getRejectNode(props.processId, props.taskId);
|
||||
approvalData.approvedResult = ApproveCode.REJECT;
|
||||
} else if (approvedType.value == ApproveType.DISAGREE) {
|
||||
approvalData.approvedResult = ApproveCode.DISAGREE;
|
||||
} else if (approvedType.value == ApproveType.FINISH) {
|
||||
approvalData.approvedResult = ApproveCode.FINISH;
|
||||
} else {
|
||||
approvalData.approvedResult = '';
|
||||
}
|
||||
}
|
||||
function changeButtonCodeType(v) {
|
||||
approvalData.approvedType = ApproveType.OTHER;
|
||||
approvalData.approvedResult = v.target.value;
|
||||
}
|
||||
function getUploadFileFolderIds(formModels) {
|
||||
let fileFolderIds: Array<string> = [];
|
||||
let uploadComponentIds = formInformation.value.getUploadComponentIds();
|
||||
uploadComponentIds.forEach((ids) => {
|
||||
if (ids.includes(separator)) {
|
||||
let arr = ids.split(separator);
|
||||
if (arr.length == 2 && formModels[arr[0]][arr[1]]) {
|
||||
fileFolderIds.push(formModels[arr[0]][arr[1]]);
|
||||
} else if (
|
||||
arr.length == 3 &&
|
||||
formModels[arr[0]][arr[1]] &&
|
||||
Array.isArray(formModels[arr[0]][arr[1]])
|
||||
) {
|
||||
formModels[arr[0]][arr[1]].forEach((o) => {
|
||||
fileFolderIds.push(o[arr[2]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return fileFolderIds;
|
||||
}
|
||||
const onFinish = async (values: any) => {
|
||||
await submit();
|
||||
try {
|
||||
if (validateSuccess.value) {
|
||||
let formModels = await formInformation.value.getFormModels();
|
||||
let system = formInformation.value.getSystemType();
|
||||
let fileFolderIds: Array<string> = getUploadFileFolderIds(formModels);
|
||||
let params: PostApprovalData = {
|
||||
approvedType: approvalData.approvedType,
|
||||
approvedResult: approvalData.approvedResult, // approvalData.approvedType 审批结果 如果为 4 就需要传buttonCode
|
||||
approvedContent: approvalData.approvedContent,
|
||||
formData: formModels,
|
||||
rejectNodeActivityId: approvalData.rejectNodeActivityId,
|
||||
taskId: props.taskId,
|
||||
fileFolderIds,
|
||||
circulateConfigs: approvalData.circulateConfigs,
|
||||
stampId: values.stampId,
|
||||
stampPassword: values.password,
|
||||
isOldSystem: system,
|
||||
};
|
||||
|
||||
let res = await postApproval(params);
|
||||
// 下一节点审批人
|
||||
let taskList: Array<ApproveTask> = [];
|
||||
if (res && res.length > 0) {
|
||||
taskList = res
|
||||
.filter((ele) => {
|
||||
return ele.isMultiInstance == false && ele.isAppoint == true;
|
||||
})
|
||||
.map((ele) => {
|
||||
return {
|
||||
taskId: ele.taskId,
|
||||
taskName: ele.taskName,
|
||||
provisionalApprover: ele.provisionalApprover,
|
||||
selectIds: [],
|
||||
};
|
||||
});
|
||||
if (taskList.length > 0) {
|
||||
approveUserData.list = taskList;
|
||||
approveUserData.schemaId = props.schemaId;
|
||||
approveUserData.visible = true;
|
||||
data.submitLoading = false;
|
||||
} else {
|
||||
close();
|
||||
data.submitLoading = false;
|
||||
save(true, t('审批流程'));
|
||||
}
|
||||
} else {
|
||||
close();
|
||||
data.submitLoading = false;
|
||||
save(true, t('审批流程'));
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const onFinishFailed = () => {
|
||||
submit();
|
||||
};
|
||||
async function submit() {
|
||||
data.submitLoading = true;
|
||||
validateSuccess.value = false;
|
||||
try {
|
||||
let validateForms = await formInformation.value.validateForm();
|
||||
if (validateForms.length > 0) {
|
||||
let successValidate = validateForms.filter((ele) => {
|
||||
return ele.validate;
|
||||
});
|
||||
if (successValidate.length == validateForms.length) {
|
||||
validateSuccess.value = true;
|
||||
data.submitLoading = false;
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('审批流程'), t('表单校验未通过'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('审批流程'), t('审批流程失败'));
|
||||
}
|
||||
}
|
||||
|
||||
function changeApproveUserData() {
|
||||
approveUserData.visible = false;
|
||||
close();
|
||||
}
|
||||
function save(res: boolean, title: string) {
|
||||
if (res) {
|
||||
notificationSuccess(title);
|
||||
close();
|
||||
} else {
|
||||
notificationError(title);
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
showVisible.value = false;
|
||||
emits('close');
|
||||
printData.value = {
|
||||
borderColor: '#000000',
|
||||
style: '1',
|
||||
underline: '0',
|
||||
submitLoading: false,
|
||||
};
|
||||
}
|
||||
function reset() {
|
||||
approvalData.isAddOrSubSign = false;
|
||||
approvalData.stampInfo = {
|
||||
stampId: '',
|
||||
password: '',
|
||||
};
|
||||
approvalData.buttonConfigs = [];
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvalData.approvedContent = '';
|
||||
approvalData.rejectNodeActivityId = '';
|
||||
approvalData.rejectNodeActivityIds = [];
|
||||
approvalData.circulateConfigs = [];
|
||||
}
|
||||
|
||||
function printForm() {
|
||||
isShowPrint.value = true;
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.description-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(102 102 102 / 99.6%);
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
align-self: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.item-box {
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
|
||||
.text-label {
|
||||
width: 80px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传阅人
|
||||
:deep(.opr-box) {
|
||||
flex-direction: column !important;
|
||||
|
||||
.header-box {
|
||||
flex-basis: 40px !important;
|
||||
}
|
||||
|
||||
.button-box {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
}
|
||||
|
||||
.approve-group {
|
||||
.ant-radio-wrapper {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(span.ant-radio + *) {
|
||||
padding-right: 12px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form) {
|
||||
&.approval-form {
|
||||
.ant-form-item-label label {
|
||||
width: 90px;
|
||||
margin-left: -9px;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.ant-form-item-control-input {
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
76
src/views/workflow/task/components/BatchApprovalInfo.vue
Normal file
76
src/views/workflow/task/components/BatchApprovalInfo.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
width="1000px"
|
||||
@register="registerModal"
|
||||
:title="t('批量审核')"
|
||||
:cancel-text="t('关闭')"
|
||||
@cancel="handlesubmit"
|
||||
:show-ok-btn="false"
|
||||
:closable="false"
|
||||
>
|
||||
<BasicTable @register="registerTable" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { h } from 'vue';
|
||||
const { t } = useI18n();
|
||||
|
||||
const columns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流程任务名称'),
|
||||
dataIndex: 'schemaName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('当前审批节点'),
|
||||
dataIndex: 'currentNodeName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('发起人'),
|
||||
dataIndex: 'startUserName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('审核结果'),
|
||||
dataIndex: 'approveResult',
|
||||
align: 'left',
|
||||
customRender: ({ record }) => {
|
||||
return h(
|
||||
Tag,
|
||||
{
|
||||
color: record.approveResult === '审核成功' ? 'green' : 'red',
|
||||
},
|
||||
() => t(`${record.approveResult}`),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('审核详情'),
|
||||
dataIndex: 'approveDetail',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const [registerTable, { setTableData }] = useTable({
|
||||
title: t('审核明细'),
|
||||
columns,
|
||||
useSearchForm: false,
|
||||
showTableSetting: false,
|
||||
striped: false,
|
||||
pagination: false,
|
||||
});
|
||||
const [registerModal, { closeModal }] = useModalInner((data) => {
|
||||
setTableData(data || []);
|
||||
});
|
||||
function handlesubmit() {
|
||||
closeModal();
|
||||
emit('success');
|
||||
}
|
||||
</script>
|
||||
211
src/views/workflow/task/components/BatchApprovalProcess.vue
Normal file
211
src/views/workflow/task/components/BatchApprovalProcess.vue
Normal file
@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<span @click.stop="show">
|
||||
<slot></slot>
|
||||
<a-modal
|
||||
v-model:visible="data.visible"
|
||||
:width="800"
|
||||
:title="t('批量审批')"
|
||||
@ok="submit"
|
||||
@cancel="cancel"
|
||||
:okText="t('确定')"
|
||||
:cancelText="t('取消')"
|
||||
>
|
||||
<div class="model-box">
|
||||
<NodeHead :nodeName="t('审批流程')" />
|
||||
<a-table
|
||||
class="box"
|
||||
:pagination="false"
|
||||
:dataSource="props.selectedRows"
|
||||
:columns="configColumns"
|
||||
:scroll="{ y: '160px' }"
|
||||
/>
|
||||
<div class="mt-2 mb-2">
|
||||
<NodeHead :nodeName="t('审批信息')" />
|
||||
<div class="text-box">
|
||||
<div class="text-label">{{ t('审批结果:') }}</div>
|
||||
<a-radio-group v-model:value="data.approvedType" name="approvedType" class="flex-1">
|
||||
<a-radio :value="ApproveType.AGREE">{{ t('同意') }}</a-radio>
|
||||
<a-radio :value="ApproveType.DISAGREE">{{ t('拒绝') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="text-box">
|
||||
<div class="text-label">{{ t('审批内容:') }}</div>
|
||||
<a-textarea
|
||||
v-model:value="data.approvedContent"
|
||||
:rows="6"
|
||||
:maxlength="100"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="needStampRef" class="mt-2 mb-2">
|
||||
<NodeHead :nodeName="t('签章')" />
|
||||
<a-form :model="data" :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }" ref="formData">
|
||||
<!-- 电子签章 -->
|
||||
<a-form-item
|
||||
:label="t('电子签章')"
|
||||
name="stampId"
|
||||
:rules="[{ required: true, message: t('请选择电子签章') }]"
|
||||
v-if="data.hasStamp"
|
||||
>
|
||||
<SelectStamp v-if="data.hasStamp" v-model:stampId="data.stampId" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('签章密码')"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: t('请输入签章密码') }]"
|
||||
v-if="data.hasStampPassword"
|
||||
>
|
||||
<a-input-password v-model:value="data.password" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import { TasksModel } from '/@/api/workflow/model';
|
||||
import { ApproveType } from '/@/enums/workflowEnum';
|
||||
import { getBatchApprovalInfo, postBatchApproval } from '/@/api/workflow/task';
|
||||
import SelectStamp from './stamp/SelectStamp.vue';
|
||||
import { GetBatchApprovalInfo, PostBatchApprovalData } from '/@/model/workflow/bpmnConfig';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selectedRows: Array<TasksModel>;
|
||||
}>(),
|
||||
{
|
||||
selectedRows: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
);
|
||||
let emits = defineEmits(['close']);
|
||||
const formData = ref();
|
||||
const configColumns = [
|
||||
{
|
||||
title: '序号',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('流水号'),
|
||||
dataIndex: 'serialNumber',
|
||||
sorter: {
|
||||
multiple: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'processName',
|
||||
sorter: {
|
||||
multiple: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('当前任务名称'),
|
||||
dataIndex: 'taskName',
|
||||
width: 160,
|
||||
sorter: {
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('流程发起人'),
|
||||
dataIndex: 'startUserName',
|
||||
},
|
||||
];
|
||||
|
||||
const data: {
|
||||
visible: boolean;
|
||||
hasStamp: boolean;
|
||||
hasStampPassword: boolean;
|
||||
stampId: string;
|
||||
password: string;
|
||||
approvedType: ApproveType;
|
||||
approvedContent: string;
|
||||
dataSource: Array<TasksModel>;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
hasStamp: true,
|
||||
hasStampPassword: true,
|
||||
stampId: '',
|
||||
password: '',
|
||||
dataSource: [],
|
||||
approvedType: ApproveType.AGREE,
|
||||
approvedContent: '',
|
||||
});
|
||||
|
||||
const needStampRef = ref(true);
|
||||
|
||||
async function show() {
|
||||
data.visible = true;
|
||||
let ids = props.selectedRows
|
||||
.map((ele) => {
|
||||
return ele.taskId;
|
||||
})
|
||||
.join(',');
|
||||
const param: GetBatchApprovalInfo = { taskIds: ids };
|
||||
const info = await getBatchApprovalInfo(param);
|
||||
needStampRef.value = info.needStamp;
|
||||
data.hasStampPassword = info.needPassword ? true : false;
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
let ids: Array<string> = props.selectedRows.map((ele) => {
|
||||
return ele.taskId;
|
||||
});
|
||||
let params: PostBatchApprovalData = {
|
||||
approvedContent: data.approvedContent,
|
||||
approvedType: data.approvedType,
|
||||
stampId: data.stampId,
|
||||
taskIds: ids,
|
||||
};
|
||||
if (data.password) {
|
||||
params.stampPassword = data.password;
|
||||
}
|
||||
try {
|
||||
if (needStampRef.value) {
|
||||
await formData.value.validate();
|
||||
}
|
||||
let res = await postBatchApproval(params);
|
||||
if (res) {
|
||||
message.success(t('批量审批成功'));
|
||||
data.visible = false;
|
||||
emits('close', res);
|
||||
} else {
|
||||
message.error(t('批量审批失败'));
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
function cancel() {
|
||||
data.visible = false;
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-box {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
|
||||
.text-label {
|
||||
width: 80px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
284
src/views/workflow/task/components/DelegateProcess.vue
Normal file
284
src/views/workflow/task/components/DelegateProcess.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="data.visible"
|
||||
:maskClosable="false"
|
||||
:width="900"
|
||||
:title="title"
|
||||
@ok="submit"
|
||||
@cancel="close"
|
||||
>
|
||||
<div class="box" v-if="data.visible">
|
||||
<NodeHead class="mb-3 mt-3" :nodeName="t('基础信息')" />
|
||||
<div class="item">
|
||||
<label><em class="text-red-600">*</em>{{ t('被委托人:') }}</label>
|
||||
<SelectUser
|
||||
:selectedIds="data.delegateUserIds"
|
||||
:multiple="true"
|
||||
@change="
|
||||
(ids) => {
|
||||
data.delegateUserIds = ids;
|
||||
}
|
||||
"
|
||||
@change-names="
|
||||
(names) => {
|
||||
data.delegateUserNames = names;
|
||||
}
|
||||
"
|
||||
>
|
||||
<a-input
|
||||
:value="data.delegateUserNames"
|
||||
:placeholder="t('请选择委托人')"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</SelectUser>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label><em class="text-red-600">*</em>{{ t('时间区间:') }}</label>
|
||||
<a-range-picker v-model:value="data.searchDate" style="width: 100%" />
|
||||
</div>
|
||||
<div class="item">
|
||||
<label>{{ t('委托说明:') }}</label>
|
||||
<a-textarea
|
||||
v-model:value="data.remark"
|
||||
:placeholder="t('请填写委托说明')"
|
||||
:auto-size="{ minRows: 2, maxRows: 5 }"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<NodeHead class="mb-3 mt-3" :nodeName="t('模板列表')" />
|
||||
<SearchBox
|
||||
:searchConfig="{
|
||||
field: 'keyword',
|
||||
label: t('模板名称'),
|
||||
type: 'input',
|
||||
}"
|
||||
@search="
|
||||
(v) => {
|
||||
keyword = v;
|
||||
getList();
|
||||
}
|
||||
"
|
||||
@scroll-height="$emit('scrollHeight')"
|
||||
/>
|
||||
<template v-if="data.list.length > 0">
|
||||
<div class="list-page-box">
|
||||
<TemplateCard
|
||||
v-for="(item, index) in data.list"
|
||||
:class="data.checkSchemaIds.includes(item.id) ? 'picked' : ''"
|
||||
@click="check(item.id)"
|
||||
:key="index"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
<div class="page-box">
|
||||
<a-pagination
|
||||
v-model:current="data.pagination.current"
|
||||
:pageSize="data.pagination.pageSize"
|
||||
:total="data.pagination.total"
|
||||
show-less-items
|
||||
@change="getList"
|
||||
/></div>
|
||||
</template>
|
||||
|
||||
<div v-else>
|
||||
<EmptyBox />
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { SelectUser } from '/@/components/SelectOrganizational/index';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import TemplateCard from '/@bpmn/components/card/TemplateCard.vue';
|
||||
import { EmptyBox } from '/@/components/ModalPanel/index';
|
||||
import { getDesignPage } from '/@/api/workflow/design';
|
||||
import { postDelegate, putDelegate, getDelegateInfo } from '/@/api/workflow/delegate';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { SearchBox } from '/@/components/ModalPanel/index';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
let props = defineProps({
|
||||
id: String,
|
||||
});
|
||||
let emits = defineEmits(['close']);
|
||||
const title = computed(() => {
|
||||
return props.id == '' ? t('新增流程委托') : t('编辑流程委托');
|
||||
});
|
||||
const keyword = ref('');
|
||||
const data: {
|
||||
visible: boolean;
|
||||
delegateUserNames: string;
|
||||
delegateUserIds: Array<string>;
|
||||
schemaIds: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
remark: string;
|
||||
searchDate: [Dayjs | null, Dayjs | null];
|
||||
pagination: { current: number; total: number; pageSize: number };
|
||||
list: Array<any>;
|
||||
checkSchemaIds: Array<string>;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
delegateUserNames: '',
|
||||
delegateUserIds: [],
|
||||
schemaIds: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
remark: '',
|
||||
searchDate: [null, null],
|
||||
list: [],
|
||||
checkSchemaIds: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
pageSize: 6,
|
||||
},
|
||||
});
|
||||
onMounted(() => {
|
||||
open();
|
||||
});
|
||||
async function open() {
|
||||
if (props.id) {
|
||||
try {
|
||||
let res = await getDelegateInfo(props.id);
|
||||
if (res.delegateUserIds) {
|
||||
data.delegateUserIds = res.delegateUserIds.split(',');
|
||||
}
|
||||
if (res.schemaIds) {
|
||||
data.checkSchemaIds = res.schemaIds.split(',');
|
||||
}
|
||||
if (res.remark) {
|
||||
data.remark = res.remark;
|
||||
}
|
||||
if (res.startTime && res.endTime) {
|
||||
data.searchDate = [dayjs(res.startTime), dayjs(res.endTime)];
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
await getList();
|
||||
data.visible = true;
|
||||
}
|
||||
function close() {
|
||||
data.visible = false;
|
||||
emits('close');
|
||||
}
|
||||
function check(id: string) {
|
||||
if (data.checkSchemaIds.includes(id)) {
|
||||
data.checkSchemaIds.splice(
|
||||
data.checkSchemaIds.findIndex((itemId) => itemId === id),
|
||||
1,
|
||||
);
|
||||
} else {
|
||||
data.checkSchemaIds.push(id);
|
||||
}
|
||||
}
|
||||
async function getList() {
|
||||
const searchParams = {
|
||||
limit: data.pagination.current,
|
||||
size: data.pagination.pageSize,
|
||||
keyword: keyword.value,
|
||||
enabledMark: 1,
|
||||
};
|
||||
|
||||
try {
|
||||
let res = await getDesignPage(searchParams);
|
||||
data.pagination.total = res.total;
|
||||
data.list = res.list;
|
||||
} catch (error) {}
|
||||
}
|
||||
async function submit() {
|
||||
if (data.delegateUserIds.length == 0) {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('流程委托'),
|
||||
description: t('请选择被委托人'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (data.checkSchemaIds.length == 0) {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('流程委托'),
|
||||
description: t('请选择模板'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!data.searchDate[0] || !data.searchDate[1]) {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('流程委托'),
|
||||
description: t('请选择时间区间'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
let res = false;
|
||||
let params = {
|
||||
delegateUserIds: data.delegateUserIds.join(','),
|
||||
schemaIds: data.checkSchemaIds.join(','),
|
||||
remark: data.remark,
|
||||
startTime: data.searchDate[0],
|
||||
endTime: data.searchDate[1],
|
||||
};
|
||||
if (props.id) {
|
||||
res = await putDelegate(props.id, params);
|
||||
} else {
|
||||
res = await postDelegate(params);
|
||||
}
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('流程委托'),
|
||||
description: title.value + t('成功'),
|
||||
});
|
||||
close();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('流程委托'),
|
||||
description: title.value + t('失败'),
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
height: 578px;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 8px;
|
||||
|
||||
label {
|
||||
width: 90px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-page-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.page-box {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.picked {
|
||||
border-width: 3px;
|
||||
border-style: dotted;
|
||||
border-color: #5332f5;
|
||||
}
|
||||
</style>
|
||||
336
src/views/workflow/task/components/LaunchProcess.vue
Normal file
336
src/views/workflow/task/components/LaunchProcess.vue
Normal file
@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<LoadingBox v-if="showLoading" />
|
||||
<ProcessLayout class="wrap" v-if="visible">
|
||||
<template #title> {{ t('发起流程') }}【{{ data.item.name }}】 </template>
|
||||
<template #close>
|
||||
<a-button type="primary" class="clean-icon" @click.stop="$emit('close')">{{
|
||||
t('关闭')
|
||||
}}</a-button>
|
||||
</template>
|
||||
<template #left>
|
||||
<FlowPanel
|
||||
:xml="data.xml"
|
||||
:taskRecords="[]"
|
||||
:predecessorTasks="selectedPredecessorTasks"
|
||||
processId=""
|
||||
position="top"
|
||||
>
|
||||
<FormInformation
|
||||
:opinionsComponents="data.opinionsComponents"
|
||||
:opinions="data.opinions"
|
||||
:disabled="false"
|
||||
:formInfos="data.formInfos"
|
||||
:formAssignmentData="data.formAssignmentData"
|
||||
ref="formInformation"
|
||||
/>
|
||||
</FlowPanel>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="launch-box">
|
||||
<div class="description-box">
|
||||
<NodeHead :nodeName="t('发起流程')" class="title" />
|
||||
<ProcessInfo class="item-box" :item="data.item" />
|
||||
</div>
|
||||
<PredecessorTask
|
||||
v-if="data.relationTasks && data.relationTasks.length > 0"
|
||||
@change="changePredecessorTasks"
|
||||
:schemaId="schemaId"
|
||||
:relationTasks="data.predecessorTasks"
|
||||
/>
|
||||
<ApproveUser
|
||||
v-if="approveUserData.visible"
|
||||
:taskList="approveUserData.list"
|
||||
:schemaId="approveUserData.schemaId"
|
||||
@change="changeApproveUserData"
|
||||
/>
|
||||
<div class="button-box">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="mr-2"
|
||||
:disabled="isPublish"
|
||||
:loading="data.submitLoading"
|
||||
@click="saveLaunch"
|
||||
>{{ t('发起') }}</a-button
|
||||
>
|
||||
<a-button class="mr-2" @click="saveDraft">{{ t('保存草稿') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ProcessLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessLayout from './flow/Layout.vue';
|
||||
import FlowPanel from './flow/FlowPanel.vue';
|
||||
import FormInformation from './flow/FormInformation.vue';
|
||||
import { LoadingBox } from '/@/components/ModalPanel/index';
|
||||
import ProcessInfo from './flow/ProcessInfo.vue';
|
||||
import PredecessorTask from './PredecessorTask.vue';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import ApproveUser from './flow/ApproveUser.vue';
|
||||
import { postDraft, putDraft } from '/@/api/workflow/process';
|
||||
import {
|
||||
postLaunch,
|
||||
getStartProcessInfo,
|
||||
getReStartProcessInfo,
|
||||
reLaunch,
|
||||
} from '/@/api/workflow/task';
|
||||
import { computed, onMounted, ref, toRaw, nextTick } from 'vue';
|
||||
import { ApproveTask, SchemaTaskItem } from '/@/model/workflow/bpmnConfig';
|
||||
import userTaskItem from './../hooks/userTaskItem';
|
||||
import { separator } from '../../design/bpmn/config/info';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
schemaId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
draftsJsonStr: {
|
||||
type: String,
|
||||
},
|
||||
draftsId: {
|
||||
type: String,
|
||||
},
|
||||
taskId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
processId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
formData: {
|
||||
type: Object,
|
||||
},
|
||||
formId: {
|
||||
type: String,
|
||||
},
|
||||
rowKeyData: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
let emits = defineEmits(['close']);
|
||||
let formInformation = ref();
|
||||
let visible = ref(false);
|
||||
let showLoading = ref(false);
|
||||
const { data, approveUserData, initProcessData, notificationError, notificationSuccess } =
|
||||
userTaskItem();
|
||||
|
||||
const selectedPredecessorTasks = computed(() => {
|
||||
return data.predecessorTasks.filter((ele) => {
|
||||
return ele.taskId;
|
||||
});
|
||||
});
|
||||
const isPublish = ref(true);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
if (props.processId) {
|
||||
// 重新发起
|
||||
let res = await getReStartProcessInfo(props.taskId, props.processId);
|
||||
res.taskApproveOpinions = [];
|
||||
initProcessData(res);
|
||||
showLoading.value = false;
|
||||
} else if (props.schemaId && props.formId) {
|
||||
let res = await getStartProcessInfo(props.schemaId);
|
||||
|
||||
if (props.formData && props.formId) {
|
||||
res.formInfos.map((m) => {
|
||||
if (m.formConfig.formId === props.formId) {
|
||||
m.formData = toRaw(props.formData);
|
||||
}
|
||||
});
|
||||
}
|
||||
initProcessData(res);
|
||||
showLoading.value = false;
|
||||
} else {
|
||||
// 发起流程
|
||||
let res = await getStartProcessInfo(props.schemaId);
|
||||
initProcessData(res);
|
||||
showLoading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
showLoading.value = false;
|
||||
}
|
||||
|
||||
showLoading.value = false;
|
||||
visible.value = true;
|
||||
await nextTick();
|
||||
initDraftsFormData();
|
||||
});
|
||||
async function initDraftsFormData() {
|
||||
isPublish.value = Object.keys(data.formInfos).length > 0 ? false : true;
|
||||
if (props.draftsJsonStr) {
|
||||
let formDataJson = JSON.parse(props.draftsJsonStr);
|
||||
let formData: Array<any> = [];
|
||||
|
||||
data.formInfos.forEach((item) => {
|
||||
if (
|
||||
formDataJson &&
|
||||
item.formConfig &&
|
||||
item.formConfig.key &&
|
||||
formDataJson[item.formConfig.key]
|
||||
) {
|
||||
formData.push(item.formConfig.key ? formDataJson[item.formConfig.key] : {});
|
||||
}
|
||||
});
|
||||
await formInformation.value.setFormData(formData);
|
||||
}
|
||||
}
|
||||
function getUploadFileFolderIds(formModels) {
|
||||
let fileFolderIds: Array<string> = [];
|
||||
let uploadComponentIds = formInformation.value.getUploadComponentIds();
|
||||
uploadComponentIds.forEach((ids) => {
|
||||
if (ids.includes(separator)) {
|
||||
let arr = ids.split(separator);
|
||||
if (arr.length == 2 && formModels[arr[0]][arr[1]]) {
|
||||
fileFolderIds.push(formModels[arr[0]][arr[1]]);
|
||||
} else if (
|
||||
arr.length == 3 &&
|
||||
formModels[arr[0]][arr[1]] &&
|
||||
Array.isArray(formModels[arr[0]][arr[1]])
|
||||
) {
|
||||
formModels[arr[0]][arr[1]].forEach((o) => {
|
||||
fileFolderIds.push(o[arr[2]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return fileFolderIds;
|
||||
}
|
||||
async function saveLaunch() {
|
||||
data.submitLoading = true;
|
||||
try {
|
||||
let validateForms = await formInformation.value.validateForm();
|
||||
let system = formInformation.value.getSystemType();
|
||||
|
||||
if (validateForms.length > 0) {
|
||||
let successValidate = validateForms.filter((ele) => {
|
||||
return ele.validate;
|
||||
});
|
||||
if (successValidate.length == validateForms.length) {
|
||||
let formModels = await formInformation.value.getFormModels();
|
||||
let relationTasks: Array<{
|
||||
schemaId: string;
|
||||
taskId: string;
|
||||
}> = [];
|
||||
if (data.predecessorTasks && data.predecessorTasks.length > 0) {
|
||||
relationTasks = data.predecessorTasks.map((ele) => {
|
||||
return { taskId: ele.taskId, schemaId: ele.schemaId };
|
||||
});
|
||||
}
|
||||
let fileFolderIds: Array<string> = getUploadFileFolderIds(formModels);
|
||||
//如果传入了processId 代表是重新发起流程
|
||||
let res;
|
||||
if (props.processId) {
|
||||
res = await reLaunch(
|
||||
props.processId,
|
||||
props.schemaId,
|
||||
formModels,
|
||||
relationTasks,
|
||||
fileFolderIds,
|
||||
system,
|
||||
);
|
||||
} else {
|
||||
res = await postLaunch(
|
||||
props.schemaId,
|
||||
formModels,
|
||||
relationTasks,
|
||||
fileFolderIds,
|
||||
system,
|
||||
);
|
||||
}
|
||||
|
||||
// 下一节点审批人
|
||||
let taskList: Array<ApproveTask> = [];
|
||||
if (res && res.length > 0) {
|
||||
taskList = res
|
||||
.filter((ele) => {
|
||||
return ele.isMultiInstance == false && ele.isAppoint == true;
|
||||
})
|
||||
.map((ele) => {
|
||||
return {
|
||||
taskId: ele.taskId,
|
||||
taskName: ele.taskName,
|
||||
provisionalApprover: ele.provisionalApprover,
|
||||
selectIds: [],
|
||||
};
|
||||
});
|
||||
if (taskList.length > 0) {
|
||||
approveUserData.list = taskList;
|
||||
approveUserData.schemaId = props.schemaId;
|
||||
approveUserData.visible = true;
|
||||
data.submitLoading = false;
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
save(true, t('发起流程'));
|
||||
}
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
save(true, t('发起流程'));
|
||||
}
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('发起流程'), t('表单校验未通过'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('发起流程'), t('发起流程失败'));
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDraft() {
|
||||
try {
|
||||
let formModels = await formInformation.value.saveDraftData();
|
||||
if (props.draftsId) {
|
||||
let res = await putDraft(props.schemaId, formModels, props.draftsId, props.rowKeyData);
|
||||
save(res, t('保存草稿'));
|
||||
} else {
|
||||
let res = await postDraft(props.schemaId, formModels, props.rowKeyData);
|
||||
save(res, t('保存草稿'));
|
||||
}
|
||||
} catch (error) {
|
||||
notificationError(t('保存草稿'));
|
||||
}
|
||||
}
|
||||
function changePredecessorTasks(list: Array<SchemaTaskItem>) {
|
||||
data.predecessorTasks = list;
|
||||
}
|
||||
function save(res: boolean, title: string) {
|
||||
if (res) {
|
||||
notificationSuccess(title);
|
||||
emits('close');
|
||||
} else {
|
||||
notificationError(title);
|
||||
}
|
||||
}
|
||||
|
||||
function changeApproveUserData() {
|
||||
approveUserData.visible = false;
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.description-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(102 102 102 / 99.6%);
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
align-self: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.item-box {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
src/views/workflow/task/components/LookProcess.vue
Normal file
62
src/views/workflow/task/components/LookProcess.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<span @click.stop="look"
|
||||
><slot></slot>
|
||||
<LoadingBox v-if="showLoading" />
|
||||
<ProcessLayout class="wrap" v-if="visible" @click.stop="">
|
||||
<template #title> {{ t('查看流程') }} </template>
|
||||
<template #close>
|
||||
<a-button type="primary" class="clean-icon" @click.stop="close">{{ t('关闭') }}</a-button>
|
||||
</template>
|
||||
<template #full>
|
||||
<LookTask v-if="visible" :taskId="props.taskId" :processId="props.processId" />
|
||||
</template>
|
||||
</ProcessLayout>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessLayout from './flow/Layout.vue';
|
||||
import LookTask from './flow/LookTask.vue';
|
||||
import { LoadingBox } from '/@/components/ModalPanel/index';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
processId: string;
|
||||
taskId: string;
|
||||
visible?: boolean;
|
||||
}>(),
|
||||
{
|
||||
processId: '',
|
||||
taskId: '',
|
||||
visible: false,
|
||||
},
|
||||
);
|
||||
let emits = defineEmits(['close']);
|
||||
let visible = ref(false);
|
||||
let showLoading = ref(false);
|
||||
onMounted(() => {
|
||||
if (props.visible) {
|
||||
look();
|
||||
}
|
||||
});
|
||||
async function look() {
|
||||
if (props.processId) {
|
||||
showLoading.value = false;
|
||||
visible.value = true;
|
||||
} else {
|
||||
showLoading.value = false;
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('查看'),
|
||||
description: t('请选择一个流程进行查看'),
|
||||
});
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
visible.value = false;
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
170
src/views/workflow/task/components/PredecessorTask.vue
Normal file
170
src/views/workflow/task/components/PredecessorTask.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<NodeHead nodeName="前置任务" class="title" />
|
||||
<div v-for="(item, index) in props.relationTasks" :key="index" class="task-box">
|
||||
<div class="label-box">
|
||||
<span>任务名称:</span>
|
||||
<span>{{ item.schemaName }}</span>
|
||||
</div>
|
||||
<a-input
|
||||
:value="item.taskName"
|
||||
placeholder="点击选择前置任务"
|
||||
@click="open(index, item.schemaId)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
:width="1000"
|
||||
v-model:visible="visible"
|
||||
title="选择前置任务"
|
||||
:maskClosable="false"
|
||||
@ok="submit"
|
||||
@cancel="close"
|
||||
>
|
||||
<div class="p-5">
|
||||
<a-table
|
||||
v-if="visible"
|
||||
:dataSource="data.dataSource"
|
||||
:columns="columns"
|
||||
rowKey="processId"
|
||||
:row-selection="{
|
||||
selectedRowKeys: data.selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
type: 'radio',
|
||||
}"
|
||||
:pagination="data.pagination"
|
||||
:scroll="{ y: '400px' }"
|
||||
/>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { PredecessorTaskItem, SchemaTaskItem, TaskItem } from '/@/model/workflow/bpmnConfig';
|
||||
import { getRelationTasks } from '/@/api/workflow/task';
|
||||
const emit = defineEmits(['change']);
|
||||
const props = withDefaults(
|
||||
defineProps<{ relationTasks: Array<SchemaTaskItem>; schemaId: string }>(),
|
||||
{
|
||||
relationTasks: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
);
|
||||
let visible = ref(false);
|
||||
let data: {
|
||||
dataSource: Array<PredecessorTaskItem>;
|
||||
selectedRowKeys: string[];
|
||||
selectedTask: TaskItem;
|
||||
pagination: { current: number; total: number; pageSize: number };
|
||||
selectIndex: number;
|
||||
} = reactive({
|
||||
dataSource: [],
|
||||
selectedRowKeys: [],
|
||||
selectedTask: {
|
||||
taskId: '',
|
||||
taskName: '',
|
||||
processId: '',
|
||||
},
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
pageSize: 15,
|
||||
},
|
||||
selectIndex: -1,
|
||||
});
|
||||
|
||||
let columns = [
|
||||
{
|
||||
title: '序号',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 60,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '任务',
|
||||
dataIndex: 'schemaName',
|
||||
key: 'schemaName',
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'taskName',
|
||||
key: 'taskName',
|
||||
ellipsis: true,
|
||||
},
|
||||
// {
|
||||
// title: '等级',
|
||||
// dataIndex: 'level',
|
||||
// key: 'level',
|
||||
// },
|
||||
{
|
||||
title: '发起人',
|
||||
dataIndex: 'originator',
|
||||
key: 'originator',
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
},
|
||||
];
|
||||
async function open(index: number, relationSchemaId: string) {
|
||||
data.selectIndex = index;
|
||||
let res = await getRelationTasks(props.schemaId, relationSchemaId, {
|
||||
limit: data.pagination.current,
|
||||
size: data.pagination.pageSize,
|
||||
});
|
||||
data.dataSource = res.list;
|
||||
data.pagination.total = res.total;
|
||||
visible.value = true;
|
||||
}
|
||||
function close() {
|
||||
data.selectIndex = -1;
|
||||
data.dataSource = [];
|
||||
data.pagination.total = 0;
|
||||
visible.value = false;
|
||||
}
|
||||
function submit() {
|
||||
let list = props.relationTasks;
|
||||
list[data.selectIndex].taskId = data.selectedTask.taskId;
|
||||
list[data.selectIndex].taskName = data.selectedTask.taskName;
|
||||
list[data.selectIndex].processId = data.selectedTask.processId;
|
||||
emit('change', list);
|
||||
close();
|
||||
}
|
||||
const onSelectChange = (selectedRowKeys: string[], selectedRows: Array<PredecessorTaskItem>) => {
|
||||
let { taskId, taskName, processId } = selectedRows[0];
|
||||
data.selectedTask.taskId = taskId;
|
||||
data.selectedTask.taskName = taskName;
|
||||
data.selectedTask.processId = processId;
|
||||
data.selectedRowKeys = selectedRowKeys;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-box {
|
||||
margin: 8px 0;
|
||||
|
||||
.label-box {
|
||||
margin-bottom: 8px;
|
||||
color: rgba(102, 102, 102, 0.996);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
147
src/views/workflow/task/components/RejectProcess.vue
Normal file
147
src/views/workflow/task/components/RejectProcess.vue
Normal file
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<span @click.stop="rejectNode"
|
||||
><slot></slot>
|
||||
<a-modal
|
||||
v-model:visible="data.visible"
|
||||
:title="t('请选择撤回到的节点')"
|
||||
width="700px"
|
||||
@ok="submit"
|
||||
@cancel="close"
|
||||
:okText="t('确认')"
|
||||
:cancelText="t('取消')"
|
||||
@click.stop=""
|
||||
>
|
||||
<div class="box" v-if="data.visible">
|
||||
<div
|
||||
class="item"
|
||||
:class="data.checkedIds.includes(item.activityId) ? 'activity' : ''"
|
||||
v-for="(item, index) in data.list"
|
||||
:key="index"
|
||||
@click="check(item.activityId)"
|
||||
>{{ item.activityName }}</div
|
||||
>
|
||||
</div>
|
||||
<a-alert
|
||||
message="外部流程节点、子流程内部节点、会签节点都不支持撤回;撤回到开始节点需要二次确认!"
|
||||
type="warning"
|
||||
/>
|
||||
</a-modal>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import { getRejectNodeList, withdraw } from '/@/api/workflow/task';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { getStartNodeId } from '/@bpmn/config/property';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(defineProps<{ processId: string; taskId: string }>(), {
|
||||
processId: '',
|
||||
taskId: '',
|
||||
});
|
||||
let emits = defineEmits(['close', 'restart']);
|
||||
const data: {
|
||||
visible: boolean;
|
||||
checkedIds: Array<string>;
|
||||
list: Array<{
|
||||
activityId: string;
|
||||
activityName: string;
|
||||
}>;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
list: [],
|
||||
checkedIds: [],
|
||||
});
|
||||
async function submit() {
|
||||
if (data.checkedIds.length > 0) {
|
||||
if (getStartNodeId == data.checkedIds[0]) {
|
||||
emits('restart');
|
||||
data.visible = false;
|
||||
} else {
|
||||
try {
|
||||
let res = await withdraw(props.processId, data.checkedIds[0]);
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('撤回'),
|
||||
description: t('撤回成功'),
|
||||
});
|
||||
close();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('撤回'),
|
||||
description: t('撤回失败'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('撤回'),
|
||||
description: t('请选择一个节点进行撤回'),
|
||||
});
|
||||
}
|
||||
}
|
||||
async function rejectNode() {
|
||||
if (props.processId) {
|
||||
try {
|
||||
let res = await getRejectNodeList(props.processId, props.taskId);
|
||||
if (res && Array.isArray(res) && res.length > 0) {
|
||||
data.visible = true;
|
||||
data.list = res;
|
||||
}
|
||||
} catch (error) {
|
||||
close();
|
||||
}
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('撤回'),
|
||||
description: t('请选择一个流程进行撤回'),
|
||||
});
|
||||
}
|
||||
}
|
||||
function check(activityId) {
|
||||
if (data.checkedIds.includes(activityId)) {
|
||||
data.checkedIds = [];
|
||||
} else {
|
||||
data.checkedIds = [activityId];
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
data.visible = false;
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px;
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
|
||||
.item {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border: 1px solid rgb(198 226 255 / 100%);
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
.activity {
|
||||
color: rgb(158 203 251);
|
||||
border: 1px solid rgb(198 226 255 / 100%);
|
||||
background: rgb(236 245 255 / 100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
src/views/workflow/task/components/RestartProcess.vue
Normal file
44
src/views/workflow/task/components/RestartProcess.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<span @click.stop="restart"
|
||||
><slot></slot>
|
||||
<LaunchProcess
|
||||
v-if="visible"
|
||||
:schemaId="schemaId"
|
||||
:taskId="taskId"
|
||||
:processId="processId"
|
||||
@close="close"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import LaunchProcess from './LaunchProcess.vue';
|
||||
const props = defineProps({
|
||||
schemaId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
taskId: {
|
||||
type: String,
|
||||
},
|
||||
processId: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
let emits = defineEmits(['close']);
|
||||
let visible = ref(false);
|
||||
function restart() {
|
||||
if (props.taskId) {
|
||||
visible.value = true;
|
||||
} else {
|
||||
// 只能选一个
|
||||
visible.value = false;
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
visible.value = false;
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
408
src/views/workflow/task/components/WorkflowApprovalProcess.vue
Normal file
408
src/views/workflow/task/components/WorkflowApprovalProcess.vue
Normal file
@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<ProcessLayout class="wrap" v-if="showVisible" @click.stop="">
|
||||
<template #title> {{ t('审批流程') }}【{{ data.item.name }}】 </template>
|
||||
<template #close>
|
||||
<a-button type="primary" class="clean-icon" @click.stop="close">{{ t('关闭') }}</a-button>
|
||||
</template>
|
||||
<template #left>
|
||||
<FlowPanel
|
||||
:xml="data.xml"
|
||||
:taskRecords="data.taskRecords"
|
||||
:predecessorTasks="selectedPredecessorTasks"
|
||||
:processId="props.processId"
|
||||
position="top"
|
||||
>
|
||||
<FormInformation
|
||||
:opinionsComponents="data.opinionsComponents"
|
||||
:opinions="data.opinions"
|
||||
:disabled="false"
|
||||
:formInfos="data.formInfos"
|
||||
:formAssignmentData="data.formAssignmentData"
|
||||
ref="formInformation"
|
||||
/>
|
||||
</FlowPanel>
|
||||
</template>
|
||||
<template #right>
|
||||
<a-tabs style="width: 280px">
|
||||
<a-tab-pane key="1" :tab="t('审批信息')">
|
||||
<NodeHead :nodeName="t('基础信息')" />
|
||||
<div class="description-box">
|
||||
<ProcessInfo class="item-box" :item="data.item">
|
||||
<NodeHead :nodeName="t('审批信息')" />
|
||||
<div
|
||||
class="text-box"
|
||||
v-if="approvalData.buttonConfigs && approvalData.buttonConfigs.length > 0"
|
||||
>
|
||||
<div class="text-label">{{ t('审批结果:') }}</div>
|
||||
<span>
|
||||
<a-radio-group
|
||||
v-model:value="approvalData.approvedType"
|
||||
name="approvedType"
|
||||
@change="changeApprovedType"
|
||||
>
|
||||
<span v-for="(item, index) in approvalData.buttonConfigs" :key="index">
|
||||
<a-radio
|
||||
v-if="item.approveType !== ApproveType.OTHER"
|
||||
:value="item.approveType"
|
||||
>{{ item.buttonName }}</a-radio
|
||||
>
|
||||
</span>
|
||||
</a-radio-group>
|
||||
<a-radio-group v-model:value="approvalData.approvedResult" name="buttonCode">
|
||||
<span v-for="(item, index) in approvalData.buttonConfigs" :key="index">
|
||||
<a-radio
|
||||
v-if="item.approveType === ApproveType.OTHER"
|
||||
:value="item.buttonCode"
|
||||
@change="changeButtonCodeType"
|
||||
>{{ item.buttonName }}</a-radio
|
||||
>
|
||||
</span>
|
||||
</a-radio-group></span
|
||||
>
|
||||
</div>
|
||||
<div class="text-box" v-if="approvalData.approvedType === ApproveType.REJECT">
|
||||
<div class="text-label">{{ t('驳回节点:') }}</div>
|
||||
<a-select style="width: 100%" v-model:value="approvalData.rejectNodeActivityId">
|
||||
<a-select-option
|
||||
v-for="(item, index) in approvalData.rejectNodeActivityIds"
|
||||
:key="index"
|
||||
:value="item.activityId"
|
||||
>{{ item.activityName }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="text-box">
|
||||
<div class="text-label">{{ t('审批内容:') }}</div>
|
||||
<a-textarea
|
||||
v-model:value="approvalData.approvedContent"
|
||||
:rows="6"
|
||||
:maxlength="100"
|
||||
/>
|
||||
</div>
|
||||
</ProcessInfo>
|
||||
</div>
|
||||
<!-- 电子签章 -->
|
||||
<a-form-item :label="t('电子签章')" name="password" v-if="data.hasStamp">
|
||||
<SelectStamp v-if="data.hasStamp" v-model:stampId="approvalData.stampInfo.stampId" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('签章密码')" name="password" v-if="data.hasStampPassword">
|
||||
<a-input-password v-model:value="approvalData.stampInfo.password" style="width: 100%" />
|
||||
</a-form-item>
|
||||
|
||||
<ApproveUser
|
||||
v-if="approveUserData.visible"
|
||||
:taskList="approveUserData.list"
|
||||
:schemaId="approveUserData.schemaId"
|
||||
@change="changeApproveUserData" />
|
||||
<div class="button-box">
|
||||
<a-button type="primary" class="mr-2" :loading="data.submitLoading" @click="submit">{{
|
||||
t('审批')
|
||||
}}</a-button>
|
||||
<!-- 转办 -->
|
||||
<TransferUser :taskId="props.taskId" @close="close" />
|
||||
<!-- 加签减签 -->
|
||||
<AddOrSubtract
|
||||
v-if="approvalData.isAddOrSubSign"
|
||||
:schemaId="props.schemaId"
|
||||
:taskId="props.taskId"
|
||||
/> </div
|
||||
></a-tab-pane>
|
||||
<a-tab-pane key="2" :tab="t('传阅信息')" force-render>
|
||||
<MemberTable v-model:memberList="approvalData.circulateConfigs" :isCommonType="true" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
</ProcessLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, defineAsyncComponent, onMounted } from 'vue';
|
||||
import ProcessLayout from './flow/Layout.vue';
|
||||
import FormInformation from './flow/FormInformation.vue';
|
||||
import FlowPanel from './flow/FlowPanel.vue';
|
||||
import ProcessInfo from './flow/ProcessInfo.vue';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import { getApprovalProcess, getRejectNode, postApproval } from '/@/api/workflow/task';
|
||||
import userTaskItem from './../hooks/userTaskItem';
|
||||
import { ApproveTask, PostApprovalData, rejectNodeItem } from '/@/model/workflow/bpmnConfig';
|
||||
import { ButtonConfigItem } from '/@/model/workflow/workflowConfig';
|
||||
import { MemberConfig } from '/@/model/workflow/memberSetting';
|
||||
import { ApproveCode, ApproveType } from '/@/enums/workflowEnum';
|
||||
import { separator } from '../../design/bpmn/config/info';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const ApproveUser = defineAsyncComponent(() => import('./flow/ApproveUser.vue'));
|
||||
const AddOrSubtract = defineAsyncComponent(() => import('./flow/AddOrSubtract.vue'));
|
||||
const TransferUser = defineAsyncComponent(() => import('./flow/TransferUser.vue'));
|
||||
const MemberTable = defineAsyncComponent(
|
||||
() => import('/@bpmn/components/member/MemberTable.vue'),
|
||||
);
|
||||
const SelectStamp = defineAsyncComponent(() => import('./stamp/SelectStamp.vue'));
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
schemaId: string;
|
||||
processId: string;
|
||||
taskId: string;
|
||||
visible: boolean;
|
||||
}>(),
|
||||
{
|
||||
schemaId: '',
|
||||
processId: '',
|
||||
taskId: '',
|
||||
visible: false,
|
||||
},
|
||||
);
|
||||
let emits = defineEmits(['close']);
|
||||
|
||||
let formInformation = ref();
|
||||
let showVisible = ref(false);
|
||||
let showLoading = ref(false);
|
||||
let approvalData: {
|
||||
isAddOrSubSign: Boolean; //是否加签减签
|
||||
stampInfo: {
|
||||
stampId: string;
|
||||
password: string;
|
||||
};
|
||||
buttonConfigs: Array<ButtonConfigItem>;
|
||||
approvedType: ApproveType;
|
||||
approvedContent: string;
|
||||
approvedResult: string;
|
||||
rejectNodeActivityId: string;
|
||||
rejectNodeActivityIds: Array<rejectNodeItem>;
|
||||
circulateConfigs: Array<MemberConfig>;
|
||||
} = reactive({
|
||||
isAddOrSubSign: false,
|
||||
stampInfo: {
|
||||
stampId: '',
|
||||
password: '',
|
||||
},
|
||||
buttonConfigs: [],
|
||||
approvedType: ApproveType.AGREE,
|
||||
approvedResult: ApproveCode.AGREE,
|
||||
approvedContent: '',
|
||||
rejectNodeActivityId: '',
|
||||
rejectNodeActivityIds: [],
|
||||
circulateConfigs: [],
|
||||
});
|
||||
const { data, approveUserData, initProcessData, notificationError, notificationSuccess } =
|
||||
userTaskItem();
|
||||
|
||||
onMounted(() => {
|
||||
if (props.visible) {
|
||||
approval();
|
||||
}
|
||||
});
|
||||
const selectedPredecessorTasks = computed(() => {
|
||||
return data.predecessorTasks.filter((ele) => {
|
||||
return ele.taskId;
|
||||
});
|
||||
});
|
||||
|
||||
// 审批
|
||||
async function approval() {
|
||||
showLoading.value = true;
|
||||
reset();
|
||||
if (props.taskId) {
|
||||
try {
|
||||
let res = await getApprovalProcess(props.taskId, props.processId);
|
||||
initProcessData(res);
|
||||
if (res.buttonConfigs) {
|
||||
approvalData.buttonConfigs = res.buttonConfigs;
|
||||
}
|
||||
if (res.relationTasks) {
|
||||
data.predecessorTasks = res.relationTasks;
|
||||
}
|
||||
if (res.isAddOrSubSign) {
|
||||
approvalData.isAddOrSubSign = res.isAddOrSubSign;
|
||||
}
|
||||
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvalData.approvedContent = '';
|
||||
approvalData.rejectNodeActivityId = '';
|
||||
approvalData.rejectNodeActivityIds = [];
|
||||
approvalData.circulateConfigs = [];
|
||||
showLoading.value = false;
|
||||
showVisible.value = true;
|
||||
} catch (error) {
|
||||
showLoading.value = false;
|
||||
emits('close');
|
||||
}
|
||||
} else {
|
||||
// 只能选一个
|
||||
showLoading.value = false;
|
||||
showVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function changeApprovedType() {
|
||||
if (approvalData.approvedType == ApproveType.AGREE) {
|
||||
approvalData.approvedResult = ApproveCode.AGREE;
|
||||
} else if (approvalData.approvedType == ApproveType.REJECT) {
|
||||
approvalData.rejectNodeActivityIds = await getRejectNode(props.processId, props.taskId);
|
||||
approvalData.approvedResult = ApproveCode.REJECT;
|
||||
} else if (approvalData.approvedType == ApproveType.DISAGREE) {
|
||||
approvalData.approvedResult = ApproveCode.DISAGREE;
|
||||
} else if (approvalData.approvedType == ApproveType.FINISH) {
|
||||
approvalData.approvedResult = ApproveCode.FINISH;
|
||||
} else {
|
||||
approvalData.approvedResult = '';
|
||||
}
|
||||
}
|
||||
function changeButtonCodeType() {
|
||||
approvalData.approvedType = ApproveType.OTHER;
|
||||
}
|
||||
function getUploadFileFolderIds(formModels) {
|
||||
let fileFolderIds: Array<string> = [];
|
||||
let uploadComponentIds = formInformation.value.getUploadComponentIds();
|
||||
uploadComponentIds.forEach((ids) => {
|
||||
if (ids.includes(separator)) {
|
||||
let arr = ids.split(separator);
|
||||
if (arr.length == 2 && formModels[arr[0]][arr[1]]) {
|
||||
fileFolderIds.push(formModels[arr[0]][arr[1]]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return fileFolderIds;
|
||||
}
|
||||
async function submit() {
|
||||
data.submitLoading = true;
|
||||
try {
|
||||
let validateForms = await formInformation.value.validateForm();
|
||||
if (validateForms.length > 0) {
|
||||
let successValidate = validateForms.filter((ele) => {
|
||||
return ele.validate;
|
||||
});
|
||||
if (successValidate.length == validateForms.length) {
|
||||
let formModels = await formInformation.value.getFormModels();
|
||||
let fileFolderIds: Array<string> = getUploadFileFolderIds(formModels);
|
||||
let params: PostApprovalData = {
|
||||
approvedType: approvalData.approvedResult
|
||||
? ApproveType.OTHER
|
||||
: approvalData.approvedType,
|
||||
approvedResult: approvalData.approvedResult, // approvalData.approvedType 审批结果 如果为 4 就需要传buttonCode
|
||||
approvedContent: approvalData.approvedContent,
|
||||
formData: formModels,
|
||||
rejectNodeActivityId: approvalData.rejectNodeActivityId,
|
||||
taskId: props.taskId,
|
||||
fileFolderIds,
|
||||
circulateConfigs: approvalData.circulateConfigs,
|
||||
stampId: approvalData.stampInfo.stampId,
|
||||
stampPassword: approvalData.stampInfo.password,
|
||||
};
|
||||
let res = await postApproval(params);
|
||||
// 下一节点审批人
|
||||
let taskList: Array<ApproveTask> = [];
|
||||
if (res && res.length > 0) {
|
||||
taskList = res
|
||||
.filter((ele) => {
|
||||
return ele.isMultiInstance == false && ele.isAppoint == true;
|
||||
})
|
||||
.map((ele) => {
|
||||
return {
|
||||
taskId: ele.taskId,
|
||||
taskName: ele.taskName,
|
||||
provisionalApprover: ele.provisionalApprover,
|
||||
selectIds: [],
|
||||
};
|
||||
});
|
||||
if (taskList.length > 0) {
|
||||
approveUserData.list = taskList;
|
||||
approveUserData.schemaId = props.schemaId;
|
||||
approveUserData.visible = true;
|
||||
data.submitLoading = false;
|
||||
} else {
|
||||
close();
|
||||
data.submitLoading = false;
|
||||
save(true, t('审批流程'));
|
||||
}
|
||||
} else {
|
||||
close();
|
||||
data.submitLoading = false;
|
||||
save(true, t('审批流程'));
|
||||
}
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('审批流程'), t('表单校验未通过'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('审批流程'), t('审批流程失败'));
|
||||
}
|
||||
}
|
||||
|
||||
function changeApproveUserData() {
|
||||
approveUserData.visible = false;
|
||||
close();
|
||||
}
|
||||
function save(res: boolean, title: string) {
|
||||
if (res) {
|
||||
notificationSuccess(title);
|
||||
close();
|
||||
} else {
|
||||
notificationError(title);
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
showVisible.value = false;
|
||||
emits('close');
|
||||
}
|
||||
function reset() {
|
||||
approvalData.isAddOrSubSign = false;
|
||||
approvalData.stampInfo = {
|
||||
stampId: '',
|
||||
password: '',
|
||||
};
|
||||
approvalData.buttonConfigs = [];
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvalData.approvedContent = '';
|
||||
approvalData.rejectNodeActivityId = '';
|
||||
approvalData.rejectNodeActivityIds = [];
|
||||
approvalData.circulateConfigs = [];
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.description-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(102, 102, 102, 0.996);
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
align-self: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.item-box {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
|
||||
.text-label {
|
||||
width: 80px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传阅人
|
||||
:deep(.opr-box) {
|
||||
flex-direction: column !important;
|
||||
|
||||
.header-box {
|
||||
flex-basis: 40px !important;
|
||||
}
|
||||
|
||||
.button-box {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
26
src/views/workflow/task/components/WorkflowLookProcess.vue
Normal file
26
src/views/workflow/task/components/WorkflowLookProcess.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<ProcessLayout class="wrap" @click.stop="">
|
||||
<template #title> {{ t('查看流程') }} </template>
|
||||
<template #close>
|
||||
<a-button type="primary" class="clean-icon" @click.stop="close">{{ t('关闭') }}</a-button>
|
||||
</template>
|
||||
<template #full>
|
||||
<LookTask :processId="props.processId" />
|
||||
</template>
|
||||
</ProcessLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessLayout from './flow/Layout.vue';
|
||||
import LookTask from './flow/LookTask.vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
let props = defineProps({
|
||||
processId: String,
|
||||
});
|
||||
let emits = defineEmits(['close']);
|
||||
|
||||
function close() {
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
95
src/views/workflow/task/components/flow/AddOrSubtract.vue
Normal file
95
src/views/workflow/task/components/flow/AddOrSubtract.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<a-button class="mr-2" @click="show">{{ t('加签或减签') }}</a-button>
|
||||
<a-modal
|
||||
:width="1000"
|
||||
:visible="data.visible"
|
||||
:title="t('加签或减签')"
|
||||
:maskClosable="false"
|
||||
@ok="submit"
|
||||
@cancel="cancel"
|
||||
>
|
||||
<div class="p-5 box" v-if="data.visible">
|
||||
<SelectApproveUser
|
||||
:schemaId="props.schemaId"
|
||||
:taskId="props.taskId"
|
||||
v-model:select-ids="data.selectedIds"
|
||||
/>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import SelectApproveUser from './SelectApproveUser.vue';
|
||||
import { postSetSign } from '/@/api/workflow/task';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
schemaId: {
|
||||
type: String,
|
||||
// required: true,
|
||||
},
|
||||
processId: {
|
||||
type: String,
|
||||
// required: true,
|
||||
},
|
||||
taskId: {
|
||||
type: String,
|
||||
// required: true,
|
||||
},
|
||||
});
|
||||
let data: {
|
||||
visible: boolean;
|
||||
selectedIds: Array<string>;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
selectedIds: [],
|
||||
});
|
||||
function show() {
|
||||
data.selectedIds = [];
|
||||
data.visible = true;
|
||||
}
|
||||
function cancel() {
|
||||
data.selectedIds = [];
|
||||
data.visible = false;
|
||||
}
|
||||
async function submit() {
|
||||
let msgs: Array<string> = [];
|
||||
if (msgs.length > 0) {
|
||||
msgs.forEach((msg) => {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('加签减签'),
|
||||
description: msg,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
if (props.schemaId && props.taskId) {
|
||||
await postSetSign(props.schemaId, props.taskId, data.selectedIds);
|
||||
cancel();
|
||||
}
|
||||
} catch (_error) {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('加签减签'),
|
||||
description: t('选择加签减签失败'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.list-page-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="800"
|
||||
:visible="true"
|
||||
:title="t('指派审核人')"
|
||||
:maskClosable="false"
|
||||
@ok="submit"
|
||||
@cancel="close"
|
||||
>
|
||||
<div class="p-5">
|
||||
<div class="mt-2"
|
||||
><div>{{ title }}{{ t('【当前】:') }}</div>
|
||||
<a-input :value="data.currentUserNames" disabled />
|
||||
</div>
|
||||
<div class="mt-2"
|
||||
><div>{{ title }}{{ t('【指派给】:') }}</div>
|
||||
|
||||
<SelectUser
|
||||
:selectedIds="selectedIds"
|
||||
:disabledIds="data.currentUserIds"
|
||||
:multiple="true"
|
||||
@change="getUserList"
|
||||
>
|
||||
<a-input :value="data.selectedNames" />
|
||||
</SelectUser>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive } from 'vue';
|
||||
import { getApproveUserList, postSetAssignee } from '/@/api/workflow/task';
|
||||
import { SelectUser } from '/@/components/SelectOrganizational/index';
|
||||
import { getUserMulti } from '/@/api/system/user';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
schemaId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
taskId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
let emits = defineEmits(['close']);
|
||||
let data: {
|
||||
currentUserNames: string;
|
||||
currentUserIds: Array<string>;
|
||||
selectedNames: string;
|
||||
selectedList: Array<{ id: string; name: string }>;
|
||||
} = reactive({
|
||||
selectedList: [],
|
||||
currentUserIds: [],
|
||||
currentUserNames: '',
|
||||
selectedNames: '',
|
||||
});
|
||||
const selectedIds = computed(() => {
|
||||
return data.selectedList.map((ele) => {
|
||||
return ele.id;
|
||||
});
|
||||
});
|
||||
onMounted(async () => {
|
||||
if (props.schemaId && props.taskId) {
|
||||
try {
|
||||
let userList = await getApproveUserList(props.schemaId, props.taskId);
|
||||
data.currentUserNames = userList
|
||||
.map((ele) => {
|
||||
return ele.name;
|
||||
})
|
||||
.join(',');
|
||||
data.currentUserIds = userList.map((ele) => {
|
||||
return ele.id;
|
||||
});
|
||||
} catch (_error) {}
|
||||
}
|
||||
});
|
||||
|
||||
async function getUserList(list: Array<string>) {
|
||||
data.selectedList = await getUserMulti(list.join(','));
|
||||
data.selectedNames = data.selectedList
|
||||
.map((ele) => {
|
||||
return ele.name;
|
||||
})
|
||||
.join(',');
|
||||
}
|
||||
async function submit() {
|
||||
try {
|
||||
let res = await postSetAssignee(props.taskId, selectedIds.value);
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('指派审核人'),
|
||||
description: t('指派审核人成功'),
|
||||
});
|
||||
close();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('指派审核人'),
|
||||
description: t('指派审核人失败'),
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
function close() {
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
129
src/views/workflow/task/components/flow/ApproveUser.vue
Normal file
129
src/views/workflow/task/components/flow/ApproveUser.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="1000"
|
||||
:visible="true"
|
||||
:title="t('下一节点审批人')"
|
||||
:maskClosable="false"
|
||||
:closable="false"
|
||||
:cancel-button-props="{
|
||||
disabled: true,
|
||||
}"
|
||||
:okText="t('确认')"
|
||||
:cancelText="t('取消')"
|
||||
@ok="submit"
|
||||
>
|
||||
<div class="p-5">
|
||||
<NodeHead :nodeName="t('信息')" />
|
||||
<div class="mt-5 mb-5 ml-5">{{
|
||||
t('请在十分钟内指定相关审批人员,时限内未完成指定的话,系统将按照现有默认人员进行处理。')
|
||||
}}</div>
|
||||
<a-tabs v-model:activeKey="data.index">
|
||||
<a-tab-pane v-for="(item, index) in data.tasks" :key="index" :tab="item.taskName">
|
||||
<SelectApproveUser
|
||||
:schemaId="props.schemaId"
|
||||
:taskId="item.taskId"
|
||||
:hasMoreBtn="item.provisionalApprover"
|
||||
v-model:select-ids="data.tasks[index].selectIds"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import SelectApproveUser from './SelectApproveUser.vue';
|
||||
import { batchApproverUsers } from '/@/api/workflow/task';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import { ApproveTask, BatchApproverUsersParams } from '/@/model/workflow/bpmnConfig';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
taskList: Array<ApproveTask>;
|
||||
schemaId: string | undefined;
|
||||
}>(),
|
||||
{
|
||||
taskList: () => {
|
||||
return [];
|
||||
},
|
||||
schemaId: '',
|
||||
},
|
||||
);
|
||||
|
||||
let emits = defineEmits(['change']);
|
||||
let data: {
|
||||
index: number;
|
||||
tasks: Array<ApproveTask>;
|
||||
} = reactive({
|
||||
index: 0,
|
||||
tasks: [],
|
||||
});
|
||||
onMounted(() => {
|
||||
if (props.schemaId && props.taskList.length > 0) {
|
||||
props.taskList.forEach(async (item) => {
|
||||
data.tasks.push({
|
||||
taskId: item.taskId,
|
||||
taskName: item.taskName,
|
||||
provisionalApprover: item.provisionalApprover,
|
||||
selectIds: [],
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
async function submit() {
|
||||
let approveUserList: BatchApproverUsersParams = [];
|
||||
let msgs: Array<string> = [];
|
||||
data.tasks.forEach((element) => {
|
||||
if (element.selectIds.length > 0) {
|
||||
approveUserList.push({
|
||||
taskId: element.taskId,
|
||||
approvedUsers: element.selectIds,
|
||||
});
|
||||
} else {
|
||||
msgs.push(t('任务:') + element.taskName + t('未选择下一节点审批人'));
|
||||
}
|
||||
});
|
||||
if (msgs.length > 0) {
|
||||
msgs.forEach((msg) => {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('下一节点审批人'),
|
||||
description: msg,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
if (props.schemaId) {
|
||||
let res = await batchApproverUsers(props.schemaId, approveUserList);
|
||||
if (res) {
|
||||
emits('change');
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('下一节点审批人'),
|
||||
description: t('选择下一节点审批人失败'),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (_error) {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('下一节点审批人'),
|
||||
description: t('选择下一节点审批人失败'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.list-page-box) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
61
src/views/workflow/task/components/flow/EmptyBox.vue
Normal file
61
src/views/workflow/task/components/flow/EmptyBox.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<div class="empty-box">
|
||||
<IconFontSymbol icon="history" class="empty-icon" />
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="desc">{{ desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const defaultMsg = t('暂无数据');
|
||||
|
||||
export default {};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: defaultMsg,
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
default: defaultMsg,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.empty-box {
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 60px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 700;
|
||||
margin: 6px 0;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
margin: 6px 0;
|
||||
}
|
||||
</style>
|
||||
51
src/views/workflow/task/components/flow/FlowPanel.vue
Normal file
51
src/views/workflow/task/components/flow/FlowPanel.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<a-tabs :tab-position="props.position" v-model:activeKey="activeKey">
|
||||
<a-tab-pane :key="1" :tab="t('表单信息')" force-render><slot></slot></a-tab-pane>
|
||||
<a-tab-pane :key="2" :tab="t('流程信息')">
|
||||
<ProcessInformation :xml="xml" :processId="processId"
|
||||
/></a-tab-pane>
|
||||
<a-tab-pane :key="3" :tab="t('流转记录')" style="overflow: auto"
|
||||
><FlowRecord :list="taskRecords" :processId="processId"
|
||||
/></a-tab-pane>
|
||||
<a-tab-pane :key="4" :tab="t('附件汇总')"
|
||||
><SummaryOfAttachments :processId="processId"
|
||||
/></a-tab-pane>
|
||||
<a-tab-pane :key="5 + index" v-for="(item, index) in predecessorTasks" :tab="item.schemaName">
|
||||
<LookRelationTask
|
||||
v-if="activeKey === 5 + index"
|
||||
:taskId="item.taskId"
|
||||
:processId="item.processId"
|
||||
position="left"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FlowRecord from './FlowRecord.vue';
|
||||
import ProcessInformation from './ProcessInformation.vue';
|
||||
import SummaryOfAttachments from './SummaryOfAttachments.vue';
|
||||
import LookRelationTask from './LookRelationTask.vue';
|
||||
import { ref } from 'vue';
|
||||
import { SchemaTaskItem } from '/@/model/workflow/bpmnConfig';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
let props = withDefaults(
|
||||
defineProps<{
|
||||
position: string;
|
||||
xml: string | undefined;
|
||||
taskRecords: Array<any> | undefined;
|
||||
processId: string | undefined;
|
||||
predecessorTasks: Array<SchemaTaskItem> | undefined;
|
||||
}>(),
|
||||
{
|
||||
xml: '',
|
||||
processId: '',
|
||||
predecessorTasks: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const activeKey = ref(1);
|
||||
</script>
|
||||
208
src/views/workflow/task/components/flow/FlowRecord.vue
Normal file
208
src/views/workflow/task/components/flow/FlowRecord.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="flow-box">
|
||||
<!-- 流转记录 -->
|
||||
<EmptyBox
|
||||
v-if="results.length == 0"
|
||||
:title="t('当前无相关流转记录')"
|
||||
:desc="t('流程需发起后才会有流转记录产生')"
|
||||
/>
|
||||
<div class="relative" v-else>
|
||||
<a-button type="primary" class="!absolute right-0 cursor-pointer z-99" @click="selfHandle">{{
|
||||
btnText ? '仅查看本人' : '查看所有'
|
||||
}}</a-button>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane v-for="(parent, idx) in results" :key="idx" :tab="t(parent.schemaName)">
|
||||
<div class="card-box" v-for="(item, index) in parent.records" :key="index">
|
||||
<div class="card-item" v-if="index % 2 == 0">
|
||||
<div class="card basic">
|
||||
<div class="node-box">
|
||||
<span class="icon blue"></span>
|
||||
<span class="title">{{ t('节点名称') }}</span>
|
||||
<span class="sign">: </span>
|
||||
<span class="text color2">{{ item.nodeName }}</span></div
|
||||
>
|
||||
<div class="node-box">
|
||||
<span class="icon green"></span>
|
||||
<span class="title">{{ t('审批信息') }}</span>
|
||||
<span class="sign">: </span>
|
||||
<span class="text">{{ item.comment }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dot-box"
|
||||
><div class="line"></div><div class="dot"></div><div class="line"></div
|
||||
></div>
|
||||
<div class="date basic">{{ item.startTime }}</div>
|
||||
</div>
|
||||
<div class="card-item" v-else>
|
||||
<div class="date basic date-padding date-right">{{ item.startTime }}</div>
|
||||
<div class="dot-box"
|
||||
><div class="line"></div><div class="dot"></div><div class="line"></div
|
||||
></div>
|
||||
<div class="card basic">
|
||||
<div class="node-box">
|
||||
<span class="icon blue"></span>
|
||||
<span class="title">{{ t('节点名称') }}</span>
|
||||
<span class="sign">: </span>
|
||||
<span class="text color2">{{ item.nodeName }}</span></div
|
||||
>
|
||||
<div class="node-box">
|
||||
<span class="icon green"></span>
|
||||
<span class="title">{{ t('审批信息') }}</span>
|
||||
<span class="sign">: </span>
|
||||
<span class="text">{{ item.comment }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import EmptyBox from './EmptyBox.vue';
|
||||
import { getSelfRecords } from '/@/api/workflow/process';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { TaskRecordList } from '/@/model/workflow/bpmnConfig';
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
list: { type: Array as PropType<TaskRecordList[]> },
|
||||
processId: String,
|
||||
});
|
||||
const results = ref<TaskRecordList[]>(props.list || []);
|
||||
const activeKey = ref(0);
|
||||
const btnText = ref(true);
|
||||
onMounted(() => {
|
||||
if (!props.list) getRecords(0);
|
||||
});
|
||||
function selfHandle() {
|
||||
results.value = [];
|
||||
if (btnText.value) {
|
||||
getRecords(1);
|
||||
} else {
|
||||
if (props.list) {
|
||||
results.value = props.list;
|
||||
} else {
|
||||
getRecords(0);
|
||||
}
|
||||
}
|
||||
|
||||
btnText.value = !btnText.value;
|
||||
}
|
||||
function getRecords(isSelf) {
|
||||
getSelfRecords({ processId: props.processId, onlySelf: isSelf }).then((res) => {
|
||||
if (res.taskRecords) {
|
||||
results.value.push({
|
||||
records: res.taskRecords,
|
||||
schemaName: '当前流程',
|
||||
});
|
||||
}
|
||||
|
||||
if (res.otherProcessApproveRecord) {
|
||||
results.value = results.value.concat(res.otherProcessApproveRecord);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.flow-box {
|
||||
height: 80vh;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.dot-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid;
|
||||
border-color: @primary-color;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.line {
|
||||
flex: 1;
|
||||
width: 2px;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
align-self: center;
|
||||
padding: 10px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.date-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.date-padding {
|
||||
padding-left: 20%;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.basic {
|
||||
flex-basis: 40%;
|
||||
}
|
||||
|
||||
.card {
|
||||
height: 100%;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #ebeef5;
|
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.node-box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 10px 0;
|
||||
|
||||
.icon {
|
||||
width: 6px;
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: #5e95ff;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #95f204;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sign {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 22px;
|
||||
color: #999;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
472
src/views/workflow/task/components/flow/FormInformation.vue
Normal file
472
src/views/workflow/task/components/flow/FormInformation.vue
Normal file
@ -0,0 +1,472 @@
|
||||
<template>
|
||||
<!-- 表单信息 -->
|
||||
<div class="form-container">
|
||||
<div class="box">
|
||||
<div class="form-left relative" :style="{ width: formLeft }" @mousedown="handleLeftDown">
|
||||
<div class="resize-shrink-sidebar" id="approval-form-left" title="收缩侧边栏">
|
||||
<span class="shrink-sidebar-text">⋮</span>
|
||||
</div>
|
||||
<div class="left-title">
|
||||
<NodeHead :nodeName="t('表单信息')" v-show="showPanel" />
|
||||
<div @click="changeShowPanel" class="in-or-out">
|
||||
<component :is="fewerPanelComponent" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-box">
|
||||
<div
|
||||
v-for="(item, index) in forms.configs"
|
||||
:key="index"
|
||||
:class="activeIndex == index ? 'form-name actived' : 'form-name'"
|
||||
>
|
||||
<span :class="item.validate ? 'dot' : 'dot validate'"></span>
|
||||
<div class="icon-box"> <IconFontSymbol icon="formItem" /> </div
|
||||
><span @click="changeActiveIndex(index)" v-show="showPanel">{{ item.formName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-right" :style="{ paddingLeft: formRight }">
|
||||
<div v-for="(item, index) in forms.configs" :key="index" :tab="item.formName">
|
||||
<div v-show="activeIndex == index">
|
||||
<SystemForm
|
||||
class="form-box"
|
||||
v-if="item.formType == FormType.SYSTEM"
|
||||
:systemComponent="item.systemComponent"
|
||||
:isViewProcess="props.disabled"
|
||||
:formModel="item.formModel"
|
||||
:workflowConfig="item"
|
||||
:ref="setItemRef"
|
||||
/>
|
||||
<SimpleForm
|
||||
v-else-if="item.formType == FormType.CUSTOM"
|
||||
class="form-box"
|
||||
:ref="setItemRef"
|
||||
:formProps="item.formProps"
|
||||
:formModel="item.formModel"
|
||||
:isWorkFlow="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
|
||||
import { FewerLeft, FewerRight } from '/@/components/ModalPanel';
|
||||
import { NodeHead } from '/@/components/ModalPanel/index';
|
||||
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
|
||||
import { onBeforeUpdate, nextTick, onMounted, reactive, computed, ref } from 'vue';
|
||||
import { TaskApproveOpinion, ValidateForms } from '/@/model/workflow/bpmnConfig';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
|
||||
import { FormEventColumnConfig } from '/@/model/generator/formEventConfig';
|
||||
import { changeFormJson } from '/@/hooks/web/useWorkFlowForm';
|
||||
import { SystemForm } from '/@/components/SystemForm/index';
|
||||
import { FormType } from '/@/enums/workflowEnum';
|
||||
import { createFormEvent, loadFormEvent, submitFormEvent } from '/@/hooks/web/useFormEvent';
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled: boolean | undefined;
|
||||
formInfos: Array<any>;
|
||||
opinions?: Array<TaskApproveOpinion> | undefined;
|
||||
opinionsComponents?: Array<string> | undefined;
|
||||
formAssignmentData?: null | Recordable;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
formInfos: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const emits = defineEmits(['getFormConfigs']);
|
||||
|
||||
let formLeft = ref('24%');
|
||||
let formRight = ref('calc(24% + 10px)');
|
||||
let showPanel = ref(true);
|
||||
let uploadComponent: { ids: Array<string> } = reactive({ ids: [] });
|
||||
let fewerPanelComponent = computed(() => {
|
||||
return showPanel.value ? FewerLeft : FewerRight;
|
||||
});
|
||||
|
||||
let activeIndex = ref(0);
|
||||
let itemRefs = ref([]) as any;
|
||||
const setItemRef = (el: never) => {
|
||||
itemRefs.value.push(el);
|
||||
};
|
||||
function showRightBox() {
|
||||
formLeft.value = '24%';
|
||||
formRight.value = 'calc(24% + 10px)';
|
||||
}
|
||||
function hideRightBox() {
|
||||
formLeft.value = '58px';
|
||||
formRight.value = '78px';
|
||||
}
|
||||
function changeShowPanel() {
|
||||
showPanel.value = !showPanel.value;
|
||||
if (showPanel.value) {
|
||||
showRightBox();
|
||||
} else {
|
||||
hideRightBox();
|
||||
}
|
||||
}
|
||||
onBeforeUpdate(() => {
|
||||
itemRefs.value = [];
|
||||
});
|
||||
let forms: {
|
||||
formModels: Array<Recordable>;
|
||||
configs: Array<{
|
||||
formName: string;
|
||||
formProps: {};
|
||||
formModel: Recordable;
|
||||
formKey: string;
|
||||
validate: boolean;
|
||||
formType: FormType;
|
||||
workflowPermissions?: Array<any>;
|
||||
opinions?: Array<any>;
|
||||
opinionsComponents?: Array<any>;
|
||||
systemComponent?: {
|
||||
functionalModule: string;
|
||||
functionName: string;
|
||||
functionFormName: string;
|
||||
};
|
||||
formJson?: Array<any>;
|
||||
isOldSystem?: boolean;
|
||||
}>;
|
||||
formEventConfigs: FormEventColumnConfig[];
|
||||
} = reactive({
|
||||
formModels: [],
|
||||
configs: [],
|
||||
formEventConfigs: [],
|
||||
});
|
||||
onMounted(async () => {
|
||||
for await (let element of props.formInfos) {
|
||||
let formModels = {};
|
||||
if (element.formData) {
|
||||
formModels = cloneDeep(element.formData);
|
||||
}
|
||||
// 参数赋值[赋值权限最大]
|
||||
if (props.formAssignmentData) {
|
||||
if (props.formAssignmentData[element.formConfig.formId]) {
|
||||
formModels = { ...formModels, ...props.formAssignmentData[element.formConfig.formId] };
|
||||
}
|
||||
}
|
||||
forms.formModels.push(formModels);
|
||||
// 系统表单
|
||||
if (element.formType == FormType.SYSTEM) {
|
||||
forms.configs.push({
|
||||
formName: element.formConfig.formName,
|
||||
formProps: {},
|
||||
formModel: formModels,
|
||||
formKey: element.formConfig.key,
|
||||
validate: true,
|
||||
formType: element.formType,
|
||||
workflowPermissions: element.formConfig.children,
|
||||
opinions: props.opinions,
|
||||
opinionsComponents: props.opinionsComponents,
|
||||
systemComponent: {
|
||||
functionalModule: element.functionalModule,
|
||||
functionName: element.functionName,
|
||||
functionFormName: 'Form',
|
||||
},
|
||||
formJson: element.formJson,
|
||||
isOldSystem: false,
|
||||
});
|
||||
// 上传组件Id集合
|
||||
setTimeout(() => {
|
||||
getSystemUploadComponentIds();
|
||||
}, 0);
|
||||
} else {
|
||||
const model = JSON.parse(element.formJson) as GeneratorConfig;
|
||||
const { formJson, formEventConfig } = model;
|
||||
if (formEventConfig) {
|
||||
forms.formEventConfigs.push(formEventConfig);
|
||||
|
||||
//初始化表单
|
||||
await createFormEvent(formEventConfig, formModels, true);
|
||||
//加载表单
|
||||
await loadFormEvent(formEventConfig, formModels, true);
|
||||
|
||||
//TODO 暂不放开 工作流没有获取表单数据这个步骤 获取表单数据
|
||||
// getFormDataEvent(formEventConfig, formModels,true);
|
||||
}
|
||||
let formKey = element.formConfig.key;
|
||||
|
||||
let config = {
|
||||
formName: element.formConfig.formName,
|
||||
formProps: {},
|
||||
formModel: {},
|
||||
formKey,
|
||||
validate: true,
|
||||
formType: element.formType,
|
||||
};
|
||||
let isViewProcess = props.disabled;
|
||||
let { buildOptionJson, uploadComponentIds } = changeFormJson(
|
||||
{
|
||||
formJson,
|
||||
formConfigChildren: element.formConfig.children,
|
||||
formConfigKey: element.formConfig.key,
|
||||
opinions: props.opinions,
|
||||
opinionsComponents: props.opinionsComponents,
|
||||
},
|
||||
isViewProcess,
|
||||
uploadComponent.ids,
|
||||
);
|
||||
uploadComponent.ids = uploadComponentIds;
|
||||
if (buildOptionJson.schemas) {
|
||||
config.formProps = buildOptionJson;
|
||||
forms.configs.push(config);
|
||||
}
|
||||
}
|
||||
// });
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
setTimeout(() => {
|
||||
setFormModel();
|
||||
}, 0);
|
||||
emits('getFormConfigs', forms.configs.length ? forms.configs[activeIndex.value] : null);
|
||||
});
|
||||
|
||||
function setFormModel() {
|
||||
for (let index = 0; index < itemRefs.value.length; index++) {
|
||||
if (itemRefs.value[index]) {
|
||||
itemRefs.value[index].setFieldsValue(forms.formModels[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setFormData(formData) {
|
||||
await nextTick();
|
||||
forms.formModels = formData;
|
||||
setFormModel();
|
||||
}
|
||||
|
||||
function changeActiveIndex(index: number) {
|
||||
activeIndex.value = index;
|
||||
emits('getFormConfigs', forms.configs[activeIndex.value]);
|
||||
}
|
||||
function getSystemUploadComponentIds() {
|
||||
for (let index = 0; index < itemRefs.value.length; index++) {
|
||||
if (itemRefs.value[index] && itemRefs.value[index].getUploadComponentIds) {
|
||||
let ids = itemRefs.value[index].getUploadComponentIds();
|
||||
uploadComponent.ids = [...uploadComponent.ids, ...ids];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取上传组件的字段值集合
|
||||
function getUploadComponentIds() {
|
||||
return uploadComponent.ids;
|
||||
}
|
||||
async function validateForm() {
|
||||
let validateForms: ValidateForms = [];
|
||||
for (let index = 0; index < itemRefs.value.length; index++) {
|
||||
if (itemRefs.value[index]) {
|
||||
try {
|
||||
await itemRefs.value[index]?.validate();
|
||||
validateForms.push({
|
||||
validate: true,
|
||||
msgs: [],
|
||||
isOldSystem: forms.configs[index].isOldSystem,
|
||||
});
|
||||
forms.configs[index].validate = true;
|
||||
} catch (error: any | Array<{ errors: Array<string>; name: Array<string> }>) {
|
||||
validateForms.push({
|
||||
validate: false,
|
||||
msgs: error?.errorFields,
|
||||
});
|
||||
forms.configs[index].validate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return validateForms;
|
||||
}
|
||||
|
||||
async function saveDraftData() {
|
||||
let formModes = {};
|
||||
|
||||
for (let index = 0; index < forms.configs.length; index++) {
|
||||
const ele = forms.configs[index];
|
||||
if (ele.formType == FormType.SYSTEM) {
|
||||
let values = await itemRefs.value[index].validate();
|
||||
formModes[ele.formKey] = values;
|
||||
} else {
|
||||
formModes[ele.formKey] = ele.formModel;
|
||||
}
|
||||
}
|
||||
return formModes;
|
||||
}
|
||||
async function getFormModels() {
|
||||
let formModes = {};
|
||||
|
||||
for (let index = 0; index < forms.configs.length; index++) {
|
||||
const ele = forms.configs[index];
|
||||
if (ele.formType == FormType.SYSTEM) {
|
||||
let values = await itemRefs.value[index].workflowSubmit();
|
||||
formModes[ele.formKey] = values;
|
||||
} else {
|
||||
formModes[ele.formKey] = ele.formModel;
|
||||
}
|
||||
}
|
||||
|
||||
// forms.configs.forEach((ele) => {
|
||||
// formModes[ele.formKey] = ele.formModel;
|
||||
// });
|
||||
forms.formEventConfigs.forEach(async (ele, i) => {
|
||||
//此组件 获取数据 就是为了提交表单 所以 表单提交数据 事件 就此处执行
|
||||
await submitFormEvent(ele, forms.configs[i]?.formModel);
|
||||
});
|
||||
return formModes;
|
||||
}
|
||||
function getSystemType() {
|
||||
let system = {};
|
||||
for (let index = 0; index < forms.configs.length; index++) {
|
||||
const ele = forms.configs[index];
|
||||
if (ele.formType == FormType.SYSTEM) {
|
||||
system[ele.formKey] = itemRefs.value[index].getIsOldSystem();
|
||||
}
|
||||
}
|
||||
return system;
|
||||
}
|
||||
function handleLeftDown(e) {
|
||||
let resize = document.getElementById('approval-form-left') as any;
|
||||
let startX = e.clientX;
|
||||
let left = resize?.offsetLeft || 0;
|
||||
|
||||
document.onmousemove = function (e) {
|
||||
let endX = e.clientX;
|
||||
let moveLen = left + (endX - startX);
|
||||
if (moveLen <= 110) {
|
||||
showPanel.value = false;
|
||||
} else {
|
||||
showPanel.value = true;
|
||||
}
|
||||
if (moveLen <= 58) moveLen = 58;
|
||||
formLeft.value = moveLen + 'px';
|
||||
formRight.value = moveLen + 20 + 'px';
|
||||
};
|
||||
document.onmouseup = function () {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
resize.releaseCapture && resize.releaseCapture();
|
||||
};
|
||||
}
|
||||
defineExpose({
|
||||
validateForm,
|
||||
getFormModels,
|
||||
saveDraftData,
|
||||
setFormData,
|
||||
getUploadComponentIds,
|
||||
getSystemType,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 100%;
|
||||
|
||||
.form-left {
|
||||
float: left;
|
||||
height: 100vh;
|
||||
box-shadow: 5px 5px 5px rgb(0 0 0 / 10%);
|
||||
z-index: 9998;
|
||||
|
||||
.resize-shrink-sidebar {
|
||||
cursor: col-resize;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
|
||||
.shrink-sidebar-text {
|
||||
padding: 0 2px;
|
||||
background: #f2f2f2;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.left-box {
|
||||
margin-right: 10px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.form-name {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: rgb(102 102 102 / 99.6%);
|
||||
margin-right: -2px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.actived {
|
||||
border-right: 1px solid #5e95ff;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.validate {
|
||||
background-color: @clear-color;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.left-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 10px 4px 10px 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
.in-or-out {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-box {
|
||||
overflow: auto;
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.form-right {
|
||||
width: 100%;
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
121
src/views/workflow/task/components/flow/Layout.vue
Normal file
121
src/views/workflow/task/components/flow/Layout.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<div class="head"
|
||||
><div class="title"><DesignLogo /><slot name="title"></slot></div
|
||||
><div class="operation"> <slot name="close"></slot></div
|
||||
></div>
|
||||
<div v-if="hasFullSlot" class="full-box"><slot name="full"></slot></div>
|
||||
<div class="box" v-else>
|
||||
<div class="left-box" ref="left">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
<div class="right-box" ref="right">
|
||||
<div class="fewer-panel-box" @click="changeShowPanel">
|
||||
<component :is="fewerPanelComponent" />
|
||||
</div>
|
||||
<div v-show="showPanel" class="right"><slot name="right"></slot></div
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useSlots } from 'vue';
|
||||
import { FewerLeft, FewerRight } from '/@/components/ModalPanel';
|
||||
import { DesignLogo } from '/@/components/ModalPanel/index';
|
||||
let left = ref();
|
||||
let right = ref();
|
||||
let showPanel = ref(true);
|
||||
const hasFullSlot = computed(() => {
|
||||
return !!useSlots().full;
|
||||
});
|
||||
let fewerPanelComponent = computed(() => {
|
||||
return showPanel.value ? FewerRight : FewerLeft;
|
||||
});
|
||||
function showRightBox() {
|
||||
left.value.style.width = 'calc(100% - 350px)';
|
||||
right.value.style.width = '350px';
|
||||
}
|
||||
function hideRightBox() {
|
||||
left.value.style.width = 'calc(100% - 60px)';
|
||||
right.value.style.width = '60px';
|
||||
}
|
||||
function changeShowPanel() {
|
||||
showPanel.value = !showPanel.value;
|
||||
if (showPanel.value) {
|
||||
showRightBox();
|
||||
} else {
|
||||
hideRightBox();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.wrap {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 999;
|
||||
background-color: #fff;
|
||||
|
||||
.head {
|
||||
height: 50px;
|
||||
box-shadow: 5px 5px 5px rgb(0 0 0 / 10%);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.operation {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .wrap {
|
||||
background-color: #151515;
|
||||
}
|
||||
|
||||
.full-box {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
|
||||
.left-box {
|
||||
width: calc(100% - 350px);
|
||||
padding: 0 20px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.right-box {
|
||||
box-shadow: -6px 2px 4px rgb(0 0 0 / 10%);
|
||||
padding: 0 10px;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.fewer-panel-box {
|
||||
width: 20px;
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
right: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.button-box) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.button-box button) {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
:deep(.clean-icon) {
|
||||
background-color: @clear-color;
|
||||
border-color: @clear-color;
|
||||
}
|
||||
</style>
|
||||
37
src/views/workflow/task/components/flow/LookRelationTask.vue
Normal file
37
src/views/workflow/task/components/flow/LookRelationTask.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<FlowPanel
|
||||
v-if="visible"
|
||||
:tab-position="position ? position : 'top'"
|
||||
:xml="data.xml"
|
||||
:taskRecords="data.taskRecords"
|
||||
:predecessorTasks="[]"
|
||||
:processId="props.processId"
|
||||
position="top"
|
||||
>
|
||||
<FormInformation
|
||||
:opinionsComponents="data.opinionsComponents"
|
||||
:opinions="data.opinions"
|
||||
:formInfos="data.formInfos"
|
||||
:disabled="true"
|
||||
/>
|
||||
</FlowPanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FormInformation from './FormInformation.vue';
|
||||
import FlowPanel from './FlowPanel.vue';
|
||||
import { getRelationTaskInfo } from '/@/api/workflow/task';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import userTaskItem from './../../hooks/userTaskItem';
|
||||
let props = defineProps(['taskId', 'position', 'processId']);
|
||||
let visible = ref(false);
|
||||
const { data, initProcessData } = userTaskItem();
|
||||
onMounted(async () => {
|
||||
try {
|
||||
let res = await getRelationTaskInfo(props.taskId);
|
||||
initProcessData(res);
|
||||
visible.value = true;
|
||||
} catch (error) {}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
37
src/views/workflow/task/components/flow/LookTask.vue
Normal file
37
src/views/workflow/task/components/flow/LookTask.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<FlowPanel
|
||||
v-if="visible"
|
||||
:tab-position="position ? position : 'top'"
|
||||
:xml="data.xml"
|
||||
:taskRecords="data.taskRecords"
|
||||
:predecessorTasks="[]"
|
||||
:processId="props.processId"
|
||||
position="top"
|
||||
>
|
||||
<FormInformation
|
||||
:opinionsComponents="data.opinionsComponents"
|
||||
:opinions="data.opinions"
|
||||
:formInfos="data.formInfos"
|
||||
:disabled="true"
|
||||
/>
|
||||
</FlowPanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FormInformation from './FormInformation.vue';
|
||||
import FlowPanel from './FlowPanel.vue';
|
||||
import { getApprovalProcess } from '/@/api/workflow/task';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import userTaskItem from './../../hooks/userTaskItem';
|
||||
let props = defineProps(['position', 'processId', 'taskId']);
|
||||
let visible = ref(false);
|
||||
const { data, initProcessData } = userTaskItem();
|
||||
onMounted(async () => {
|
||||
try {
|
||||
let res = await getApprovalProcess(props.taskId, props.processId);
|
||||
initProcessData(res);
|
||||
visible.value = true;
|
||||
} catch (error) {}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
47
src/views/workflow/task/components/flow/ProcessInfo.vue
Normal file
47
src/views/workflow/task/components/flow/ProcessInfo.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="item"
|
||||
><span class="item-label">{{ t('模板编号:') }}</span
|
||||
><span>{{ item.code }}</span></div
|
||||
>
|
||||
<div class="item"
|
||||
><span class="item-label">{{ t('模板名称:') }}</span
|
||||
><span>{{ item.name }}</span></div
|
||||
>
|
||||
<div class="item"
|
||||
><span class="item-label">{{ t('模板分类:') }}</span
|
||||
><span>{{ item.categoryName }}</span></div
|
||||
>
|
||||
<div class="item"
|
||||
><span class="item-label">{{ t('备注:') }}</span
|
||||
><span>{{ item.remark }}</span></div
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
item: { code: string; name: string; categoryName: string; remark: string };
|
||||
}>(),
|
||||
{
|
||||
item: () => {
|
||||
return { code: '', name: '', categoryName: '', remark: '' };
|
||||
},
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.item {
|
||||
.item-label {
|
||||
width: 80px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
src/views/workflow/task/components/flow/ProcessInformation.vue
Normal file
145
src/views/workflow/task/components/flow/ProcessInformation.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<!-- 流程信息 -->
|
||||
<div class="flow-record-box">
|
||||
<div id="bpmnCanvas" class="canvas" ref="bpmnCanvas"></div>
|
||||
<div class="flow-record-mark"></div>
|
||||
</div>
|
||||
|
||||
<div class="fixed-bottom">
|
||||
<ZoomInOrOut @in="zoomViewport(false)" @out="zoomViewport(true)" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import CustomModeler from '/@bpmn/modeler';
|
||||
import { ZoomInOrOut } from '/@/components/ModalPanel';
|
||||
import { getFinishedTask } from '/@/api/workflow/task';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
xml: string;
|
||||
processId: string;
|
||||
}>(),
|
||||
{
|
||||
xml: '',
|
||||
processId: '',
|
||||
},
|
||||
);
|
||||
const bpmnCanvas = ref();
|
||||
let data: {
|
||||
bpmnViewer: any;
|
||||
zoom: number;
|
||||
xmlString: string;
|
||||
} = reactive({
|
||||
bpmnViewer: null,
|
||||
zoom: 1,
|
||||
xmlString: '',
|
||||
});
|
||||
onMounted(() => {
|
||||
data.xmlString = props.xml;
|
||||
if (data.xmlString) initBpmnModeler();
|
||||
});
|
||||
|
||||
async function initBpmnModeler() {
|
||||
data.bpmnViewer = await new CustomModeler({
|
||||
container: bpmnCanvas.value,
|
||||
additionalModules: [
|
||||
{
|
||||
labelEditingProvider: ['value', ''], //禁用节点编辑
|
||||
paletteProvider: ['value', ''], //禁用/清空左侧工具栏
|
||||
contextPadProvider: ['value', ''], //禁用图形菜单
|
||||
bendpoints: ['value', {}], //禁用连线拖动
|
||||
zoomScroll: ['value', ''], //禁用滚动
|
||||
moveCanvas: ['value', ''], //禁用拖动整个流程图
|
||||
move: ['value', ''], //禁用单个图形拖动
|
||||
},
|
||||
],
|
||||
});
|
||||
await redrawing();
|
||||
if (props.processId) {
|
||||
let res = await getFinishedTask(props.processId);
|
||||
setColors(
|
||||
res.finishedNodes ? res.finishedNodes : [],
|
||||
res.currentNodes ? res.currentNodes : [],
|
||||
);
|
||||
}
|
||||
}
|
||||
async function redrawing() {
|
||||
try {
|
||||
await data.bpmnViewer.importXML(data.xmlString);
|
||||
let canvas = data.bpmnViewer.get('canvas');
|
||||
canvas.zoom('fit-viewport', 'auto');
|
||||
} catch (err) {
|
||||
console.log('err: ', err);
|
||||
}
|
||||
}
|
||||
function setColors(finishedIds: Array<string>, currentIds: Array<string>) {
|
||||
// finishedIds 完成的节点id
|
||||
// currentIds 进行中节点id
|
||||
let modeling = data.bpmnViewer.get('modeling');
|
||||
const elementRegistry = data.bpmnViewer.get('elementRegistry');
|
||||
if (finishedIds.length > 0) {
|
||||
finishedIds.forEach((it) => {
|
||||
let Event = elementRegistry.get(it);
|
||||
|
||||
modeling.setColor(Event, {
|
||||
stroke: 'green',
|
||||
fill: 'white',
|
||||
});
|
||||
});
|
||||
}
|
||||
if (currentIds.length > 0) {
|
||||
currentIds.forEach((it) => {
|
||||
let Event = elementRegistry.get(it);
|
||||
modeling.setColor(Event, {
|
||||
stroke: '#409eff',
|
||||
fill: 'white',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
function zoomViewport(zoomIn = true) {
|
||||
data.zoom = data.bpmnViewer.get('canvas').zoom();
|
||||
data.zoom += zoomIn ? 0.1 : -0.1;
|
||||
data.bpmnViewer.get('canvas').zoom(data.zoom);
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '/@/assets/style/bpmn-js/diagram-js.css';
|
||||
@import '/@/assets/style/bpmn-js/bpmn-font/css/bpmn.css';
|
||||
@import '/@/assets/style/bpmn-js/bpmn-font/css/bpmn-codes.css';
|
||||
@import '/@/assets/style/bpmn-js/bpmn-font/css/bpmn-embedded.css';
|
||||
|
||||
.bjs-powered-by {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.flow-record-box {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
position: relative;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.flow-record-mark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
/* 画布 */
|
||||
.canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 按钮(放大 缩小 清除) */
|
||||
.fixed-bottom {
|
||||
position: absolute;
|
||||
top: 110px;
|
||||
font-size: 30px;
|
||||
left: 40%;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
195
src/views/workflow/task/components/flow/SelectApproveUser.vue
Normal file
195
src/views/workflow/task/components/flow/SelectApproveUser.vue
Normal file
@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div v-if="data.visible">
|
||||
<a-tabs>
|
||||
<a-tab-pane key="1" :tab="t('候选人')">
|
||||
<div class="list-page-box" v-if="data.approvedList.length > 0">
|
||||
<UserCard
|
||||
:class="data.approvedIds.includes(user.id) ? 'picked' : 'not-picked'"
|
||||
v-for="(user, userIndex) in data.approvedList"
|
||||
:key="userIndex"
|
||||
:item="user"
|
||||
@click="checkApprovedId(user)"
|
||||
:disabled="user.canRemove ? false : true"
|
||||
>
|
||||
<template #check>
|
||||
<a-checkbox
|
||||
size="small"
|
||||
:checked="data.approvedIds.includes(user.id)"
|
||||
:disabled="user.canRemove ? false : true"
|
||||
/>
|
||||
</template>
|
||||
</UserCard>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="2" :tab="t('已选人员')">
|
||||
<SelectUser
|
||||
v-if="hasMoreBtn"
|
||||
:selectedIds="data.selectedIds"
|
||||
:disabledIds="data.disabledIds"
|
||||
:multiple="true"
|
||||
@change="changeList"
|
||||
>
|
||||
<a-button type="primary">{{ t('更多人员添加') }}</a-button>
|
||||
</SelectUser>
|
||||
<div class="list-page-box" v-if="data.selectedList.length > 0">
|
||||
<UserCard
|
||||
:class="data.selectedIds.includes(user.id) ? 'picked' : 'not-picked'"
|
||||
v-for="(user, userIndex) in data.selectedList"
|
||||
:key="userIndex"
|
||||
:item="user"
|
||||
@click="checked(user)"
|
||||
:disabled="data.disabledIds.includes(user.id)"
|
||||
>
|
||||
<template #check>
|
||||
<a-checkbox size="small" :checked="data.selectedIds.includes(user.id)" />
|
||||
</template>
|
||||
</UserCard>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { getUserMulti } from '/@/api/system/user';
|
||||
import { getApproveUserList } from '/@/api/workflow/task';
|
||||
import { SelectUser } from '/@/components/SelectOrganizational/index';
|
||||
import { UserCard } from '/@/components/SelectOrganizational/index';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
schemaId: String,
|
||||
taskId: String,
|
||||
hasMoreBtn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['update:selectIds']);
|
||||
// const emits = defineEmits('change');
|
||||
let data: {
|
||||
visible: boolean;
|
||||
approvedList: Array<{
|
||||
[x: string]: any;
|
||||
id: string;
|
||||
name: string;
|
||||
}>;
|
||||
selectedList: Array<{ id: string; name: string }>;
|
||||
approvedIds: Array<string>;
|
||||
disabledIds: Array<string>;
|
||||
selectedIds: Array<string>;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
approvedList: [],
|
||||
selectedList: [],
|
||||
approvedIds: [],
|
||||
disabledIds: [],
|
||||
selectedIds: [],
|
||||
});
|
||||
onMounted(async () => {
|
||||
if (props.schemaId && props.taskId) {
|
||||
try {
|
||||
let userList = await getApproveUserList(props.schemaId, props.taskId);
|
||||
data.approvedList = cloneDeep(userList);
|
||||
data.approvedIds = data.approvedList.map((ele) => {
|
||||
return ele.id;
|
||||
});
|
||||
data.disabledIds = data.approvedList
|
||||
.filter((ele) => {
|
||||
return !ele.canRemove;
|
||||
})
|
||||
.map((ele) => {
|
||||
return ele.id;
|
||||
});
|
||||
|
||||
data.selectedList = cloneDeep(userList);
|
||||
data.selectedIds = data.selectedList.map((ele) => {
|
||||
return ele.id;
|
||||
});
|
||||
changeData();
|
||||
data.visible = true;
|
||||
} catch (_error) {}
|
||||
}
|
||||
});
|
||||
function checkApprovedId(user) {
|
||||
if (data.disabledIds.includes(user.id)) {
|
||||
return false;
|
||||
}
|
||||
if (data.approvedIds.includes(user.id)) {
|
||||
data.approvedIds.splice(
|
||||
data.approvedIds.findIndex((item) => item === user.id),
|
||||
1,
|
||||
);
|
||||
data.selectedIds.splice(
|
||||
data.selectedIds.findIndex((item) => item === user.id),
|
||||
1,
|
||||
);
|
||||
data.selectedList.splice(
|
||||
data.selectedList.findIndex((item) => item.id === user.id),
|
||||
1,
|
||||
);
|
||||
} else {
|
||||
data.approvedIds.push(user.id);
|
||||
data.selectedIds.push(user.id);
|
||||
data.selectedList.push(user);
|
||||
}
|
||||
changeData();
|
||||
}
|
||||
function checked(user) {
|
||||
if (data.disabledIds.includes(user.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.selectedIds.includes(user.id)) {
|
||||
data.selectedList.splice(
|
||||
data.selectedList.findIndex((item) => item.id === user.id),
|
||||
1,
|
||||
);
|
||||
data.selectedIds = data.selectedIds.filter((o) => {
|
||||
return o != user.id;
|
||||
});
|
||||
} else {
|
||||
data.selectedList.push(user);
|
||||
data.selectedIds.push(user.id);
|
||||
}
|
||||
|
||||
if (data.approvedIds.includes(user.id)) {
|
||||
data.approvedIds.splice(
|
||||
data.approvedIds.findIndex((item) => item === user.id),
|
||||
1,
|
||||
);
|
||||
} else {
|
||||
data.approvedIds.push(user.id);
|
||||
}
|
||||
changeData();
|
||||
}
|
||||
async function changeList(userIds: Array<string>) {
|
||||
data.selectedList = await getUserMulti(userIds.join(','));
|
||||
data.selectedIds = userIds;
|
||||
userIds.forEach((id) => {
|
||||
if (!data.approvedIds.includes(id)) {
|
||||
data.approvedIds.push(id);
|
||||
}
|
||||
});
|
||||
changeData();
|
||||
}
|
||||
function changeData() {
|
||||
emits('update:selectIds', data.selectedIds);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.list-page-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
198
src/views/workflow/task/components/flow/SummaryOfAttachments.vue
Normal file
198
src/views/workflow/task/components/flow/SummaryOfAttachments.vue
Normal file
@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 附件汇总 -->
|
||||
<EmptyBox
|
||||
v-if="data.dataSource.length == 0"
|
||||
:title="t('当前无相关附件记录')"
|
||||
:desc="t('流程需发起并在表单中上传附件才会有附件记录产生')"
|
||||
/>
|
||||
<a-table
|
||||
v-else
|
||||
class="p-4"
|
||||
:pagination="false"
|
||||
:dataSource="data.dataSource"
|
||||
:columns="configColumns"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'components'"> {{ t('上传') }} </template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="flex">
|
||||
<a-button size="small" type="primary" class="mr-2" @click="preview(record.fileUrl)">{{
|
||||
t('预览')
|
||||
}}</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="download(record.fileUrl, record.fileName)"
|
||||
>{{ t('下载') }}</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-modal
|
||||
v-model:visible="data.showPreview"
|
||||
:title="t('预览文件')"
|
||||
width="100%"
|
||||
wrap-class-name="full-modal"
|
||||
:footer="null"
|
||||
@ok="data.showPreview = false"
|
||||
>
|
||||
<iframe v-if="data.showPreview" :src="data.fileUrl" class="iframe-box"></iframe>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import EmptyBox from './EmptyBox.vue';
|
||||
import { getFileList } from '/@/api/system/file';
|
||||
import { FilePageListModel } from '/@/api/system/file/model';
|
||||
import { downloadByUrl } from '/@/utils/file/download';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { getAppEnvConfig } from '/@/utils/env';
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(defineProps<{ processId: string }>(), {
|
||||
processId: '',
|
||||
});
|
||||
|
||||
const configColumns = [
|
||||
{
|
||||
title: t('序号'),
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('附件名称'),
|
||||
dataIndex: 'fileName',
|
||||
sorter: {
|
||||
multiple: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('附件格式'),
|
||||
dataIndex: 'fileType',
|
||||
},
|
||||
{
|
||||
title: t('所属组件'),
|
||||
dataIndex: 'components',
|
||||
},
|
||||
{
|
||||
title: t('上传人员'),
|
||||
dataIndex: 'createUserName',
|
||||
},
|
||||
{
|
||||
title: t('上传时间'),
|
||||
dataIndex: 'createDate',
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
dataIndex: 'operation',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
const data: {
|
||||
dataSource: Array<FilePageListModel>;
|
||||
showPreview: boolean;
|
||||
fileUrl: string;
|
||||
} = reactive({
|
||||
dataSource: [],
|
||||
showPreview: false,
|
||||
fileUrl: '',
|
||||
});
|
||||
onMounted(async () => {
|
||||
if (props.processId) {
|
||||
try {
|
||||
let res = await getFileList({ processId: props.processId });
|
||||
data.dataSource = res;
|
||||
} catch (error) {}
|
||||
}
|
||||
});
|
||||
async function preview(fileUrl: string) {
|
||||
data.fileUrl = getAppEnvConfig().VITE_GLOB_UPLOAD_PREVIEW + encodeURIComponent(encode(fileUrl));
|
||||
data.showPreview = true;
|
||||
}
|
||||
async function download(fileUrl: string, fileName: string) {
|
||||
downloadByUrl({
|
||||
url: fileUrl,
|
||||
fileName: fileName,
|
||||
});
|
||||
}
|
||||
|
||||
function encode(input: string) {
|
||||
let _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
let output = '';
|
||||
let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
input = utf8_encode(input);
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
output =
|
||||
output +
|
||||
_keyStr.charAt(enc1) +
|
||||
_keyStr.charAt(enc2) +
|
||||
_keyStr.charAt(enc3) +
|
||||
_keyStr.charAt(enc4);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function utf8_encode(input: string) {
|
||||
input = input.replace(/\r\n/g, '\n');
|
||||
let tufters = '';
|
||||
for (let n = 0; n < input.length; n++) {
|
||||
let c = input.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
tufters += String.fromCharCode(c);
|
||||
} else if (c > 127 && c < 2048) {
|
||||
tufters += String.fromCharCode((c >> 6) | 192);
|
||||
tufters += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
tufters += String.fromCharCode((c >> 12) | 224);
|
||||
tufters += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
tufters += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return tufters;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.iframe-box {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.full-modal {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
top: 0;
|
||||
padding-bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
46
src/views/workflow/task/components/flow/TransferUser.vue
Normal file
46
src/views/workflow/task/components/flow/TransferUser.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<SelectUser :selectedIds="[]" :multiple="false" @change="submit">
|
||||
<a-button type="primary" style="width: 100%" class="mr-2">{{ t('转办') }}</a-button>
|
||||
</SelectUser>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SelectUser } from '/@/components/SelectOrganizational/index';
|
||||
import { postTransfer } from '/@/api/workflow/task';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(defineProps<{ taskId: string | undefined }>(), {
|
||||
taskId: '',
|
||||
});
|
||||
const emits = defineEmits(['close']);
|
||||
async function submit(ids: Array<string>) {
|
||||
if (ids.length === 1) {
|
||||
try {
|
||||
let res = await postTransfer(props.taskId, ids[0]);
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('转办'),
|
||||
description: t('转办成功'),
|
||||
});
|
||||
emits('close');
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('转办'),
|
||||
description: t('转办失败:') + error,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('转办'),
|
||||
description: t('转办失败'),
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
262
src/views/workflow/task/components/print/FormPrint.vue
Normal file
262
src/views/workflow/task/components/print/FormPrint.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="打印预览"
|
||||
width="100%"
|
||||
wrap-class-name="full-modal"
|
||||
:bodyStyle="{ padding: '10px' }"
|
||||
@cancel="emits('update:isShowPrint', false)"
|
||||
>
|
||||
<div class="btn-box">
|
||||
<a-button style="color: #606266; font-size: 13px" @click="handlePrint">
|
||||
<template #icon>
|
||||
<PrinterOutlined />
|
||||
</template>
|
||||
打印
|
||||
</a-button>
|
||||
</div>
|
||||
<div v-if="printData?.style === '1' && !!configs?.formProps">
|
||||
<SimpleForm
|
||||
class="print-style1-box"
|
||||
:formProps="configs?.formProps"
|
||||
:formModel="configs?.formModel"
|
||||
:isWorkFlow="true"
|
||||
/>
|
||||
</div>
|
||||
<div :class="`print-style${props.printData?.style}-box`" v-else>
|
||||
<template v-for="(item, index) in formInfo" :key="index">
|
||||
<TableStyle :item="item" :componentType="item.type" />
|
||||
</template>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, Ref, StyleValue, provide, inject, onMounted } from 'vue';
|
||||
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
|
||||
import TableStyle from './TableStyle.vue';
|
||||
import { getUserMulti } from '/@/api/system/user';
|
||||
import { getDicItemDetail } from '/@/api/system/dic';
|
||||
import { getDepartment } from '/@/api/system/department';
|
||||
import { getAreaMulti } from '/@/api/system/area';
|
||||
import { PrinterOutlined } from '@ant-design/icons-vue';
|
||||
import { buildComponentType } from '/@/utils/helper/designHelper';
|
||||
import html2canvas from 'html2canvas';
|
||||
import printJS from 'print-js';
|
||||
import { cloneDeep, isNil } from 'lodash-es';
|
||||
import domtoimage from 'dom-to-image';
|
||||
const props = defineProps({
|
||||
formConfigs: Object,
|
||||
printData: Object,
|
||||
isShowPrint: Boolean,
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:isShowPrint']);
|
||||
const configs = ref({
|
||||
formProps: {} as any,
|
||||
formModel: {},
|
||||
});
|
||||
const formInfo = ref<any>([]);
|
||||
const activeKey = inject<Ref<number>>('tabActiveKey');
|
||||
const visible = ref<boolean>(props.isShowPrint);
|
||||
const borderColorStyle = computed(() => {
|
||||
return { 'border-color': `${props.printData?.borderColor} !important` };
|
||||
});
|
||||
const underlineStyle = computed(() => {
|
||||
return { 'border-bottom': props.printData?.underline === '1' ? '1px solid' : 0 };
|
||||
});
|
||||
|
||||
provide<Ref<StyleValue>>('borderColorStyle', borderColorStyle);
|
||||
provide<Ref<StyleValue>>('underlineStyle', underlineStyle);
|
||||
onMounted(async () => {
|
||||
configs.value.formProps = cloneDeep(props.formConfigs?.formProps);
|
||||
formInfo.value = [];
|
||||
if (props.printData?.style !== '1') {
|
||||
await changeFormat(configs.value.formProps.schemas, formInfo.value);
|
||||
}
|
||||
});
|
||||
configs.value.formModel = cloneDeep(props.formConfigs?.formModel);
|
||||
|
||||
const handlePrint = async () => {
|
||||
let element: HTMLElement = window.document.querySelector(
|
||||
`.print-style${props.printData?.style || 1}-box`,
|
||||
)!;
|
||||
|
||||
let url;
|
||||
if (props.printData?.style === '1') {
|
||||
url = await domtoimage.toPng(element);
|
||||
} else {
|
||||
let canvas = await html2canvas(element, {
|
||||
backgroundColor: null,
|
||||
useCORS: true,
|
||||
windowHeight: document.body.scrollHeight,
|
||||
});
|
||||
url = canvas.toDataURL();
|
||||
}
|
||||
printJS({
|
||||
printable: url,
|
||||
type: 'image',
|
||||
documentTitle: '打印',
|
||||
});
|
||||
};
|
||||
const changeFormat = async (schemas, info) => {
|
||||
if (!configs.value) return;
|
||||
const keys = Object.keys(configs.value.formModel);
|
||||
for (const item of schemas) {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
const layoutChildren = {};
|
||||
for (let index = 0; index < item.children.length; index++) {
|
||||
const name =
|
||||
item.type === 'grid'
|
||||
? index
|
||||
: item.type === 'card'
|
||||
? item.componentProps?.title
|
||||
: item.children[index].name;
|
||||
layoutChildren[name] = [];
|
||||
if ((index === activeKey?.value && item.type === 'tab') || item.type !== 'tab') {
|
||||
await changeFormat(item.children[index].list, layoutChildren[name]);
|
||||
}
|
||||
}
|
||||
info.push({
|
||||
type: item.type,
|
||||
value: layoutChildren,
|
||||
});
|
||||
} else if (item.type === 'form') {
|
||||
if (keys.includes(item.componentProps.mainKey)) {
|
||||
// const children: object[] = [];
|
||||
// configs.value.formModel[item.componentProps.mainKey].forEach((sub) => {
|
||||
// const subInfo = {};
|
||||
// item.componentProps.columns.forEach((col) => {
|
||||
// if (Object.keys(sub).includes(col.dataIndex) && !!col.show) {
|
||||
// subInfo[col.title] = sub[col.dataIndex];
|
||||
// }
|
||||
// });
|
||||
// children.push(subInfo);
|
||||
// });
|
||||
const columns: any[] = [];
|
||||
const value: any[] = [];
|
||||
configs.value.formModel[item.componentProps.mainKey].forEach((sub) => {
|
||||
let val = {};
|
||||
item.componentProps.columns.forEach(async (col) => {
|
||||
if (Object.keys(sub).includes(col.dataIndex) && !!col.show) {
|
||||
if (!columns.map((x) => x.key).includes(col.dataIndex)) {
|
||||
columns.push({
|
||||
title: col.title,
|
||||
key: col.dataIndex,
|
||||
dataIndex: col.dataIndex,
|
||||
});
|
||||
}
|
||||
|
||||
val[col.dataIndex] = await changeValue(col, sub[col.dataIndex]);
|
||||
}
|
||||
});
|
||||
value.push(val);
|
||||
});
|
||||
info.push({
|
||||
type: item.type,
|
||||
label: item.label,
|
||||
value,
|
||||
columns,
|
||||
});
|
||||
}
|
||||
} else if (keys.includes(item.field) && !!item.show) {
|
||||
let value = await changeValue(item, configs.value.formModel[item.field]);
|
||||
info.push({
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
componentProps: item.componentProps,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changeValue = async (item, fieldValue) => {
|
||||
const type = item.type ? buildComponentType(item.type) : item.componentType;
|
||||
if (isNil(fieldValue)) return fieldValue;
|
||||
if (type === 'User' || (type === 'Info' && item.componentProps?.infoType === 0)) {
|
||||
const res = await getUserMulti(fieldValue);
|
||||
return res?.map((x) => x.name).toString();
|
||||
} else if (type === 'Dept' || (type === 'Info' && item.componentProps?.infoType === 1)) {
|
||||
const res = await getDepartment(fieldValue);
|
||||
return res?.name;
|
||||
} else if (type === 'Area') {
|
||||
const res = await getAreaMulti(fieldValue);
|
||||
return res?.map((x) => x.name).join(' / ');
|
||||
} else if (item.componentProps?.datasourceType === 'dic') {
|
||||
const res = await getDicItemDetail(item.componentProps?.params.itemId, fieldValue);
|
||||
return res?.map((x) => x.name).toString();
|
||||
}
|
||||
return fieldValue;
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.btn-box {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.borderClass {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.print-style2-box,
|
||||
.print-style3-box,
|
||||
.print-style4-box {
|
||||
margin: 10px 20px;
|
||||
|
||||
:deep(.ant-row) {
|
||||
font-size: 16px;
|
||||
border: 1px solid !important;
|
||||
|
||||
.ant-col {
|
||||
padding: 15px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.print-style3-box {
|
||||
:deep(.ant-col:last-child) {
|
||||
border-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.print-style2-box {
|
||||
:deep(.ant-col:first-child) {
|
||||
border-right: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
.print-style4-box {
|
||||
:deep(.ant-row) {
|
||||
border-left: 0 !important;
|
||||
border-right: 0 !important;
|
||||
border-top: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.full-modal {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
top: 0 !important;
|
||||
padding-bottom: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
176
src/views/workflow/task/components/print/TableStyle.vue
Normal file
176
src/views/workflow/task/components/print/TableStyle.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<template v-if="componentType === 'form'">
|
||||
<a-row :style="borderColorStyle">
|
||||
<a-col :span="3" :style="borderColorStyle">
|
||||
{{ item?.label }}
|
||||
</a-col>
|
||||
<a-col :span="21" :style="borderColorStyle">
|
||||
<a-table
|
||||
:dataSource="item?.value"
|
||||
:columns="item?.columns"
|
||||
:pagination="false"
|
||||
tableLayout="fixed"
|
||||
v-if="item?.value.length"
|
||||
/>
|
||||
<!-- <table :cellpadding="10" :cellspacing="10" v-if="item?.value.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th :style="borderColorStyle" v-for="title in Object.keys(item?.value[0])">
|
||||
{{ title }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="value in item?.value">
|
||||
<td :style="borderColorStyle" v-for="subFormInfo in Object.values(value)">
|
||||
{{ subFormInfo }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table> -->
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<table
|
||||
:cellpadding="10"
|
||||
:cellspacing="10"
|
||||
v-else-if="item?.type === 'tab' || item?.type === 'card'"
|
||||
>
|
||||
<template v-if="Object.keys(item?.value)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
:style="borderColorStyle"
|
||||
v-for="(title, index) in Object.keys(item?.value)"
|
||||
:key="index"
|
||||
>
|
||||
{{ title }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(value, index) in item?.value" :key="index">
|
||||
<tr v-if="!!value.length">
|
||||
<td :style="borderColorStyle" :colspan="Object.keys(item?.value).length">
|
||||
<template v-for="(info, idx) in Object.values(value)" :key="idx">
|
||||
<TableStyle :item="info" :componentType="info.type" />
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</template>
|
||||
</table>
|
||||
<div class="grid-box" v-else-if="item?.type === 'grid'">
|
||||
<div class="grid-box-left">
|
||||
<TableStyle
|
||||
v-for="(gridItem, index) in item.value[0]"
|
||||
:key="index"
|
||||
:item="gridItem"
|
||||
:componentType="gridItem.type"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<TableStyle
|
||||
v-for="(gridItem, index) in item.value[1]"
|
||||
:key="index"
|
||||
:item="gridItem"
|
||||
:componentType="gridItem.type"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="noBorderComponent.includes(item?.type)"
|
||||
:style="item?.type === 'title' ? underlineStyle : ''"
|
||||
class="noborder-box"
|
||||
>
|
||||
<a-divider
|
||||
v-if="item?.type === 'divider'"
|
||||
:orientation="item?.componentProps?.orientation"
|
||||
:style="item?.componentProps?.style"
|
||||
>
|
||||
{{ item?.label }}
|
||||
</a-divider>
|
||||
<h2
|
||||
v-else-if="item?.type === 'title'"
|
||||
:align="item.componentProps.align"
|
||||
:style="{
|
||||
fontWeight: 'bold',
|
||||
fontSize: item.componentProps.fontSize + 'px',
|
||||
color: item.componentProps.color,
|
||||
...item.componentProps.style,
|
||||
}"
|
||||
>
|
||||
{{ item?.label }}
|
||||
</h2>
|
||||
</div>
|
||||
<template v-else>
|
||||
<a-row :style="borderColorStyle">
|
||||
<a-col :span="3" :style="borderColorStyle">
|
||||
{{ item?.label }}
|
||||
</a-col>
|
||||
<a-col :span="21">
|
||||
<span>{{ item?.value }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { inject, StyleValue, Ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
item: Object,
|
||||
componentType: String,
|
||||
});
|
||||
const borderColorStyle = inject<Ref<StyleValue>>('borderColorStyle')!;
|
||||
const underlineStyle = inject<Ref<StyleValue>>('underlineStyle')!;
|
||||
const noBorderComponent = ['title', 'divider'];
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead) tr th {
|
||||
border: 1px solid;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody) tr td {
|
||||
border: 1px solid;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
.noborder-box {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-box {
|
||||
display: flex;
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
|
||||
.grid-box-left {
|
||||
border-right: 1px solid;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.ant-row {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.ant-row:not(:last-child) {
|
||||
border-bottom: 1px solid !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
123
src/views/workflow/task/components/processTasks/Drafts.vue
Normal file
123
src/views/workflow/task/components/processTasks/Drafts.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #action="{ record }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
icon: 'clarity:note-edit-line',
|
||||
auth: 'processtasks:edit',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: 'processtasks:delete',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: t('是否确认删除'),
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<LaunchProcess
|
||||
v-if="processData.visible"
|
||||
:draftsId="processData.draftsId"
|
||||
:schemaId="processData.schemaId"
|
||||
:draftsJsonStr="processData.draftsJsonStr"
|
||||
@close="processData.visible = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import userTaskTable from './../../hooks/userTaskTable';
|
||||
|
||||
import LaunchProcess from './../LaunchProcess.vue';
|
||||
import { BasicTable, useTable, TableAction, BasicColumn } from '/@/components/Table';
|
||||
import { deleteDraft, getDraftInfo, getSchemaTask } from '/@/api/workflow/process';
|
||||
import { reactive } from 'vue';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const configColumns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'schemaName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('发起者'),
|
||||
dataIndex: 'originator',
|
||||
sorter: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('发起时间'),
|
||||
dataIndex: 'createDate',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
const processData = reactive({
|
||||
visible: false,
|
||||
schemaId: '',
|
||||
draftsJsonStr: '',
|
||||
draftsId: '',
|
||||
});
|
||||
const { formConfig } = userTaskTable();
|
||||
const [registerTable, { reload }] = useTable({
|
||||
title: t('草稿箱列表'),
|
||||
api: getSchemaTask,
|
||||
rowKey: 'id',
|
||||
columns: configColumns,
|
||||
formConfig: formConfig('Drafts'),
|
||||
beforeFetch: (params) => {
|
||||
return { data: params, taskUrl: TaskTypeUrl.DRAFT };
|
||||
},
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
striped: false,
|
||||
pagination: {
|
||||
pageSize: 18,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 80,
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
async function handleEdit(record: Recordable) {
|
||||
try {
|
||||
let res = await getDraftInfo(record.id);
|
||||
processData.draftsId = record.id;
|
||||
processData.schemaId = res.schemaId;
|
||||
processData.draftsJsonStr = res.formData;
|
||||
processData.visible = true;
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
async function handleDelete(record: Recordable) {
|
||||
try {
|
||||
let res = await deleteDraft([record.id]);
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('删除'),
|
||||
description: t('删除成功'),
|
||||
});
|
||||
reload();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('删除'),
|
||||
description: t('删除失败'),
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" @selection-change="selectionChange">
|
||||
<template #toolbar>
|
||||
<LookProcess :taskId="taskId" :processId="processId" @close="reload"
|
||||
><a-button v-auth="'processtasks:view'">{{ t('查看') }}</a-button></LookProcess
|
||||
>
|
||||
</template>
|
||||
|
||||
<template #currentProgress="{ record }">
|
||||
<a-progress v-if="record.currentProgress" :percent="record.currentProgress" size="small" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import userTaskTable from './../../hooks/userTaskTable';
|
||||
|
||||
import LookProcess from './../LookProcess.vue';
|
||||
|
||||
import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
|
||||
import { getSchemaTask } from '/@/api/workflow/process';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const configColumns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流水号'),
|
||||
dataIndex: 'serialNumber',
|
||||
width: 50,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'processName',
|
||||
width: '32%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('任务名称'),
|
||||
dataIndex: 'taskName',
|
||||
sorter: true,
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('当前进度'),
|
||||
dataIndex: 'currentProgress',
|
||||
sorter: true,
|
||||
width: '17%',
|
||||
slots: { customRender: 'currentProgress' },
|
||||
},
|
||||
{
|
||||
title: t('发起人'),
|
||||
dataIndex: 'originator',
|
||||
align: 'left',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('发起时间'),
|
||||
width: 120,
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
const { formConfig, processId, taskId, selectionChange } = userTaskTable();
|
||||
const [registerTable, { reload }] = useTable({
|
||||
title: t('我的传阅列表'),
|
||||
api: getSchemaTask,
|
||||
rowKey: 'taskId',
|
||||
columns: configColumns,
|
||||
formConfig: formConfig(),
|
||||
beforeFetch: (params) => {
|
||||
return { data: params, taskUrl: TaskTypeUrl.CIRCULATED };
|
||||
},
|
||||
rowSelection: {
|
||||
type: 'radio',
|
||||
},
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
striped: false,
|
||||
pagination: {
|
||||
pageSize: 18,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
181
src/views/workflow/task/components/processTasks/MyProcess.vue
Normal file
181
src/views/workflow/task/components/processTasks/MyProcess.vue
Normal file
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" @selection-change="selectionChange">
|
||||
<template #toolbar>
|
||||
<RejectProcess
|
||||
:processId="processId"
|
||||
:taskId="taskId"
|
||||
@close="reload"
|
||||
@restart="restartProcess"
|
||||
><a-button v-auth="'processtasks:withdraw'">{{ t('撤回') }}</a-button></RejectProcess
|
||||
>
|
||||
<LookProcess :taskId="taskId" :processId="processId" @close="reload"
|
||||
><a-button v-auth="'processtasks:view'">{{ t('查看') }}</a-button></LookProcess
|
||||
>
|
||||
<a-button v-auth="'processtasks:relaunch'" @click="restartProcess">{{
|
||||
t('重新发起')
|
||||
}}</a-button>
|
||||
</template>
|
||||
|
||||
<template #currentProgress="{ record }">
|
||||
<a-progress v-if="record.currentProgress" :percent="record.currentProgress" size="small" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: 'processtasks:delete',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: t('移入回收站'),
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<LaunchProcess
|
||||
v-if="restartProcessVisible"
|
||||
:schemaId="schemaId"
|
||||
:taskId="taskId"
|
||||
:processId="processId"
|
||||
@close="restartProcessClose"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import userTaskTable from './../../hooks/userTaskTable';
|
||||
|
||||
import { ref, unref, watch } from 'vue';
|
||||
|
||||
import LookProcess from './../LookProcess.vue';
|
||||
import LaunchProcess from './../LaunchProcess.vue';
|
||||
import RejectProcess from './../RejectProcess.vue';
|
||||
|
||||
import { BasicTable, useTable, TableAction, BasicColumn } from '/@/components/Table';
|
||||
import { getSchemaTask, moveRecycle } from '/@/api/workflow/process';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
const { t } = useI18n();
|
||||
const restartProcessVisible = ref(false);
|
||||
const configColumns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流水号'),
|
||||
dataIndex: 'serialNumber',
|
||||
width: 50,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'processName',
|
||||
align: 'left',
|
||||
width: '32%',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: t('任务名称'),
|
||||
dataIndex: 'currentTaskName',
|
||||
sorter: true,
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('当前进度'),
|
||||
dataIndex: 'currentProgress',
|
||||
sorter: true,
|
||||
width: '17%',
|
||||
slots: { customRender: 'currentProgress' },
|
||||
},
|
||||
{
|
||||
title: t('发起人'),
|
||||
dataIndex: 'originator',
|
||||
align: 'left',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('发起时间'),
|
||||
align: 'left',
|
||||
width: 120,
|
||||
dataIndex: 'createTime',
|
||||
},
|
||||
];
|
||||
|
||||
const { formConfig, processId, taskId, schemaId, selectionChange } = userTaskTable();
|
||||
|
||||
const [registerTable, { reload }] = useTable({
|
||||
title: t('我的流程列表'),
|
||||
api: getSchemaTask,
|
||||
rowKey: 'id',
|
||||
columns: configColumns,
|
||||
formConfig: formConfig('MyProcess'),
|
||||
rowSelection: {
|
||||
type: 'radio',
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
return { data: params, taskUrl: TaskTypeUrl.MY_PROCESS };
|
||||
},
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
striped: false,
|
||||
pagination: {
|
||||
pageSize: 18,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 60,
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
function restartProcess() {
|
||||
if (processId.value) {
|
||||
restartProcessVisible.value = true;
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('提示'),
|
||||
description: t('请选择一个流程重新发起'),
|
||||
});
|
||||
}
|
||||
}
|
||||
function restartProcessClose() {
|
||||
restartProcessVisible.value = false;
|
||||
reload();
|
||||
}
|
||||
async function handleDelete(record: Recordable) {
|
||||
if (record.processId) {
|
||||
try {
|
||||
let res = await moveRecycle(record.processId);
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('移入回收站'),
|
||||
description: t('移入回收站成功'),
|
||||
});
|
||||
reload();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('移入回收站'),
|
||||
description: t('移入回收站失败'),
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
const { currentRoute } = useRouter();
|
||||
watch(
|
||||
() => unref(currentRoute),
|
||||
(val) => {
|
||||
if (val.name == 'ProcessTasks') reload();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
136
src/views/workflow/task/components/processTasks/RecycleBin.vue
Normal file
136
src/views/workflow/task/components/processTasks/RecycleBin.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" @selection-change="selectionChange">
|
||||
<template #toolbar>
|
||||
<LookProcess :taskId="taskId" :processId="processId" @close="reload">
|
||||
<a-button v-auth="'processtasks:view'">{{ t('查看') }} </a-button>
|
||||
</LookProcess>
|
||||
<RestartProcess :schemaId="schemaId" :taskId="taskId" :processId="processId" @close="reload">
|
||||
<a-button v-auth="'processtasks:relaunch'">{{ t('重新发起') }}</a-button>
|
||||
</RestartProcess>
|
||||
</template>
|
||||
|
||||
<template #currentProgress="{ record }">
|
||||
<a-progress v-if="record.currentProgress" :percent="record.currentProgress" size="small" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: 'processtasks:delete',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: t('是否确认删除'),
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import userTaskTable from './../../hooks/userTaskTable';
|
||||
|
||||
import LookProcess from './../LookProcess.vue';
|
||||
import RestartProcess from './../RestartProcess.vue';
|
||||
import { BasicTable, useTable, TableAction, BasicColumn } from '/@/components/Table';
|
||||
import { deleteRecycle, getSchemaTask } from '/@/api/workflow/process';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
const configColumns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流水号'),
|
||||
dataIndex: 'serialNumber',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'processName',
|
||||
width: '32%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('任务名称'),
|
||||
dataIndex: 'currentTaskName',
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('当前进度'),
|
||||
dataIndex: 'currentProgress',
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
slots: { customRender: 'currentProgress' },
|
||||
},
|
||||
{
|
||||
title: t('发起人'),
|
||||
dataIndex: 'originator',
|
||||
width: 80,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('发起时间'),
|
||||
width: 140,
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
const { formConfig, processId, taskId, schemaId, selectionChange } = userTaskTable();
|
||||
|
||||
const [registerTable, { reload }] = useTable({
|
||||
title: t('回收站列表'),
|
||||
api: getSchemaTask,
|
||||
rowKey: 'taskId',
|
||||
columns: configColumns,
|
||||
formConfig: formConfig(),
|
||||
rowSelection: {
|
||||
type: 'radio',
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
return { data: params, taskUrl: TaskTypeUrl.RECYCLE };
|
||||
},
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
striped: false,
|
||||
pagination: {
|
||||
pageSize: 18,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 60,
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
async function handleDelete(record: Recordable) {
|
||||
if (record.processId) {
|
||||
try {
|
||||
let res = await deleteRecycle([record.processId]);
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('移入回收站'),
|
||||
description: t('移入回收站成功'),
|
||||
});
|
||||
reload();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('移入回收站'),
|
||||
description: t('移入回收站失败'),
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
117
src/views/workflow/task/components/processTasks/TaskDone.vue
Normal file
117
src/views/workflow/task/components/processTasks/TaskDone.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" @selection-change="selectionChange">
|
||||
<template #toolbar>
|
||||
<div class="button-box">
|
||||
<RejectProcess
|
||||
:taskId="taskId"
|
||||
:processId="processId"
|
||||
@close="reload"
|
||||
@restart="restartProcess"
|
||||
class="mr-2"
|
||||
><a-button v-auth="'processtasks:withdraw'">{{ t('撤回') }}</a-button></RejectProcess
|
||||
>
|
||||
<LookProcess :taskId="taskId" :processId="processId" @close="reload"
|
||||
><a-button v-auth="'processtasks:view'">{{ t('查看') }}</a-button></LookProcess
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #currentProgress="{ record }">
|
||||
<a-progress v-if="record.currentProgress" :percent="record.currentProgress" size="small" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<LaunchProcess
|
||||
v-if="restartProcessVisible"
|
||||
:schemaId="schemaId"
|
||||
:taskId="taskId"
|
||||
@close="restartProcessClose"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import userTaskTable from './../../hooks/userTaskTable';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import LookProcess from './../LookProcess.vue';
|
||||
import LaunchProcess from './../LaunchProcess.vue';
|
||||
import RejectProcess from './../RejectProcess.vue';
|
||||
|
||||
import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
|
||||
import { getSchemaTask } from '/@/api/workflow/process';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const restartProcessVisible = ref(false);
|
||||
const configColumns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流水号'),
|
||||
dataIndex: 'serialNumber',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'processName',
|
||||
width: '32%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('任务名称'),
|
||||
dataIndex: 'currentTaskName',
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('当前进度'),
|
||||
dataIndex: 'currentProgress',
|
||||
width: '17%',
|
||||
slots: { customRender: 'currentProgress' },
|
||||
},
|
||||
{
|
||||
title: t('发起人'),
|
||||
dataIndex: 'originator',
|
||||
align: 'left',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('发起时间'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
];
|
||||
|
||||
const { formConfig, processId, taskId, schemaId, selectionChange } = userTaskTable();
|
||||
const [registerTable, { reload }] = useTable({
|
||||
title: t('已办任务列表'),
|
||||
api: getSchemaTask,
|
||||
rowKey: 'id',
|
||||
columns: configColumns,
|
||||
formConfig: formConfig(),
|
||||
beforeFetch: (params) => {
|
||||
return { data: params, taskUrl: TaskTypeUrl.FINISHED_TASKS };
|
||||
},
|
||||
rowSelection: {
|
||||
type: 'radio',
|
||||
},
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
striped: false,
|
||||
pagination: {
|
||||
pageSize: 18,
|
||||
},
|
||||
indexColumnProps: {
|
||||
width: 50,
|
||||
},
|
||||
});
|
||||
|
||||
function restartProcess() {
|
||||
restartProcessVisible.value = true;
|
||||
}
|
||||
function restartProcessClose() {
|
||||
restartProcessVisible.value = false;
|
||||
reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
132
src/views/workflow/task/components/processTasks/ToDoTasks.vue
Normal file
132
src/views/workflow/task/components/processTasks/ToDoTasks.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" @selection-change="selectionChange">
|
||||
<template #toolbar>
|
||||
<BatchApprovalProcess
|
||||
v-if="showBatchApproval"
|
||||
@close="BatchClearHandler"
|
||||
:selectedRows="data.selectedRows"
|
||||
>
|
||||
<a-button v-auth="'processtasks:batchApproval'">{{ t('批量审批') }}</a-button>
|
||||
</BatchApprovalProcess>
|
||||
<ApprovalProcess
|
||||
v-else
|
||||
:taskId="taskId"
|
||||
:processId="processId"
|
||||
:schemaId="schemaId"
|
||||
@close="clearHandler"
|
||||
:visible="false"
|
||||
>
|
||||
<a-button v-auth="'processtasks:approve'">{{ t('审批') }}</a-button>
|
||||
</ApprovalProcess>
|
||||
<LookProcess :taskId="taskId" :processId="processId" @close="clearHandler">
|
||||
<a-button v-auth="'processtasks:view'">{{ t('查看') }}</a-button>
|
||||
</LookProcess>
|
||||
</template>
|
||||
|
||||
<template #currentProgress="{ record }">
|
||||
<a-progress v-if="record.currentProgress" :percent="record.currentProgress" size="small" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<InfoModal @register="registerModal" @success="reload" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import userTaskTable from './../../hooks/userTaskTable';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import LookProcess from './../LookProcess.vue';
|
||||
import ApprovalProcess from './../ApprovalProcess.vue';
|
||||
import BatchApprovalProcess from './../BatchApprovalProcess.vue';
|
||||
import InfoModal from '../BatchApprovalInfo.vue';
|
||||
import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
|
||||
import { getSchemaTask } from '/@/api/workflow/process';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { unref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
const { t } = useI18n();
|
||||
const configColumns: BasicColumn[] = [
|
||||
{
|
||||
title: t('流水号'),
|
||||
dataIndex: 'serialNumber',
|
||||
width: 80,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('流程名称'),
|
||||
dataIndex: 'processName',
|
||||
width: '32%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('任务名称'),
|
||||
dataIndex: 'taskName',
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('当前进度'),
|
||||
dataIndex: 'currentProgress',
|
||||
width: '17%',
|
||||
align: 'left',
|
||||
slots: { customRender: 'currentProgress' },
|
||||
},
|
||||
{
|
||||
title: t('发起人'),
|
||||
dataIndex: 'startUserName',
|
||||
width: 80,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('发起时间'),
|
||||
width: 120,
|
||||
dataIndex: 'startTime',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const { formConfig, data, processId, taskId, schemaId, selectionChange, showBatchApproval } =
|
||||
userTaskTable();
|
||||
function BatchClearHandler(v) {
|
||||
if (v) {
|
||||
openModal(true, v);
|
||||
}
|
||||
clearSelectedRowKeys();
|
||||
}
|
||||
const clearHandler = () => {
|
||||
clearSelectedRowKeys();
|
||||
reload();
|
||||
};
|
||||
const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
|
||||
title: t('待办任务列表'),
|
||||
api: getSchemaTask,
|
||||
rowKey: 'id',
|
||||
columns: configColumns,
|
||||
formConfig: formConfig(),
|
||||
beforeFetch: (params) => {
|
||||
return { data: params, taskUrl: TaskTypeUrl.PENDING_TASKS };
|
||||
},
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
striped: false,
|
||||
pagination: {
|
||||
pageSize: 18,
|
||||
},
|
||||
tableSetting: {
|
||||
size: false,
|
||||
setting: false,
|
||||
},
|
||||
});
|
||||
const { currentRoute } = useRouter();
|
||||
watch(
|
||||
() => unref(currentRoute),
|
||||
(val) => {
|
||||
if (val.name == 'ProcessTasks') reload();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
69
src/views/workflow/task/components/stamp/SelectStamp.vue
Normal file
69
src/views/workflow/task/components/stamp/SelectStamp.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<a-input-group compact class="box" @click.stop="">
|
||||
<a-select
|
||||
:value="props.stampId"
|
||||
:placeholder="t('请选择电子签章')"
|
||||
allowClear
|
||||
style="width: calc(100% - 48px)"
|
||||
@change="changeIds"
|
||||
>
|
||||
<a-select-option :value="bind.id" v-for="bind in data.list" :key="bind.id">
|
||||
{{ bind.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button class="fixed" @click.stop="data.visible = true">
|
||||
<Icon icon="ant-design:plus-outlined"
|
||||
/></a-button>
|
||||
<StampDetail v-if="data.visible" :type="StampType.PRIVATE_SIGNATURE" @close="close" />
|
||||
</a-input-group>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import StampDetail from './StampInfo.vue';
|
||||
import { getStampPage } from '/@/api/workflow/stamp';
|
||||
import { StampInfo } from '/@/api/workflow/model';
|
||||
import { StampType } from '/@/enums/workflowEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
let emits = defineEmits(['update:stampId']);
|
||||
const props = defineProps({
|
||||
stampId: String,
|
||||
});
|
||||
|
||||
let data: { visible: boolean; list: Array<StampInfo> } = reactive({
|
||||
visible: false,
|
||||
list: [],
|
||||
});
|
||||
onMounted(async () => {
|
||||
await getList();
|
||||
});
|
||||
function close() {
|
||||
data.visible = false;
|
||||
getList();
|
||||
}
|
||||
async function getList() {
|
||||
let options = await getStampPage(StampType.PRIVATE_SIGNATURE, {
|
||||
limit: 1,
|
||||
size: 100,
|
||||
});
|
||||
data.list = options.list;
|
||||
data.list.forEach((o) => {
|
||||
if (o.isDefault === 1) {
|
||||
emits('update:stampId', o.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
function changeIds(val) {
|
||||
console.log('val: ', val);
|
||||
emits('update:stampId', val);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fixed {
|
||||
font-weight: 700;
|
||||
width: 49px;
|
||||
}
|
||||
</style>
|
||||
100
src/views/workflow/task/components/stamp/Sign.vue
Normal file
100
src/views/workflow/task/components/stamp/Sign.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div @click.stop="open" class="sign-box">
|
||||
<img v-if="data.imgUrl" class="imgCanvas" :src="data.imgUrl" />
|
||||
<span v-else>{{ t('点击手写签名') }}</span>
|
||||
</div>
|
||||
<a-modal
|
||||
v-model:visible="data.visible"
|
||||
:width="600"
|
||||
:title="t('签名')"
|
||||
@ok="confirm"
|
||||
@cancel="cancel"
|
||||
>
|
||||
<vue-esign
|
||||
v-if="data.visible"
|
||||
ref="canvas"
|
||||
:width="800"
|
||||
:height="300"
|
||||
:isCrop="signInfo.isCrop"
|
||||
:lineWidth="signInfo.lineWidth"
|
||||
:lineColor="signInfo.lineColor"
|
||||
v-model:bgColor="signInfo.bgColor"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import vueEsign from 'vue-esign';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
let emits = defineEmits(['submit']);
|
||||
let props = defineProps(['src']);
|
||||
const canvas = ref();
|
||||
const data: {
|
||||
visible: boolean;
|
||||
imgUrl: string;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
imgUrl: '',
|
||||
});
|
||||
const signInfo = reactive({
|
||||
lineWidth: 6,
|
||||
lineColor: '#000000',
|
||||
bgColor: '',
|
||||
resultImg: '',
|
||||
isCrop: false,
|
||||
});
|
||||
onMounted(() => {
|
||||
if (props.src) {
|
||||
data.imgUrl = props.src;
|
||||
}
|
||||
});
|
||||
function open() {
|
||||
data.visible = true;
|
||||
if (props.src) {
|
||||
data.imgUrl = props.src;
|
||||
}
|
||||
}
|
||||
function cancel() {
|
||||
canvas.value.reset();
|
||||
}
|
||||
function confirm() {
|
||||
canvas.value
|
||||
.generate()
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
emits('submit', res);
|
||||
data.imgUrl = res;
|
||||
data.visible = false;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err: ', err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.sign-box {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
background-color: #fafafa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.3s;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sign {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px solid rgb(0 0 0 / 20%);
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
300
src/views/workflow/task/components/stamp/StampInfo.vue
Normal file
300
src/views/workflow/task/components/stamp/StampInfo.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="data.visible"
|
||||
:maskClosable="false"
|
||||
:width="600"
|
||||
:title="title"
|
||||
@ok="submit"
|
||||
@cancel="close"
|
||||
:okText="t('确认')"
|
||||
:cancelText="t('取消')"
|
||||
>
|
||||
<div class="box" v-if="data.visible">
|
||||
<a-form
|
||||
:model="data.info"
|
||||
ref="formRef"
|
||||
name="basic"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
autocomplete="off"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('签章名称')"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: t('请填写签章名称!') }]"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="t('请填写签章名称')"
|
||||
v-model:value="data.info.name"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('印章分类')"
|
||||
name="stampCategory"
|
||||
:rules="[{ required: true, message: t('请填写印章分类!') }]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="data.info.stampCategory"
|
||||
:placeholder="t('请选择印章分类')"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option v-for="item in data.categoryOptions" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('签章密码')"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: t('请填写签章密码!') }]"
|
||||
>
|
||||
<a-input-password
|
||||
:placeholder="t('请填写签章密码')"
|
||||
v-model:value="data.info.password"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('签章排序')"
|
||||
name="sortCode"
|
||||
:rules="[{ required: true, message: t('请填写签章排序!') }]"
|
||||
>
|
||||
<a-input-number
|
||||
:placeholder="t('请填写签章排序')"
|
||||
v-model:value="data.info.sortCode"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('签章类型')"
|
||||
name="fileType"
|
||||
:rules="[{ required: true, message: t('请填写签章类型!') }]"
|
||||
>
|
||||
<a-radio-group v-model:value="data.info.fileType" name="radioGroup">
|
||||
<a-radio :value="StampFileTypeAttributes.UPLOAD_PICTURES">{{ t('上传照片') }}</a-radio>
|
||||
<a-radio :value="StampFileTypeAttributes.HANDWRITTEN_SIGNATURE">{{
|
||||
t('手写签名')
|
||||
}}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('上传照片')"
|
||||
name="fileUrl"
|
||||
v-if="data.info.fileType === StampFileTypeAttributes.UPLOAD_PICTURES"
|
||||
>
|
||||
<a-upload
|
||||
v-if="data.info.fileType === StampFileTypeAttributes.UPLOAD_PICTURES"
|
||||
name="file"
|
||||
accept="image/*"
|
||||
:headers="data.headers"
|
||||
:max-count="1"
|
||||
:showUploadList="false"
|
||||
:action="data.action"
|
||||
list-type="picture-card"
|
||||
@change="photoChange"
|
||||
>
|
||||
<img v-if="data.photoUrl" :src="data.photoUrl" />
|
||||
<div v-else>{{ t('点击上传照片') }}</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('手写签名')"
|
||||
name="fileUrl"
|
||||
v-if="data.info.fileType === StampFileTypeAttributes.HANDWRITTEN_SIGNATURE"
|
||||
>
|
||||
<Sign
|
||||
v-if="data.info.fileType === StampFileTypeAttributes.HANDWRITTEN_SIGNATURE"
|
||||
:src="data.signUrl"
|
||||
@submit="uploadSign"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('签章备注')" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="data.info.remark"
|
||||
:placeholder="t('请填写签章备注')"
|
||||
:auto-size="{ minRows: 2, maxRows: 5 }"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { StampInfo } from '/@/api/workflow/model';
|
||||
import { TreeItem } from '/@/components/Tree';
|
||||
import Sign from './Sign.vue';
|
||||
import { getDicDetailList } from '/@/api/system/dic';
|
||||
import { uploadSrc, uploadBlobApi } from '/@/api/sys/upload';
|
||||
import { postStamp, putStamp } from '/@/api/workflow/stamp';
|
||||
import { StampCategory } from '/@/enums/workflowEnum';
|
||||
|
||||
import { getAppEnvConfig } from '/@/utils/env';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
import type { UploadChangeParam } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { StampType, StampFileTypeAttributes } from '/@/enums/workflowEnum';
|
||||
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
let props = defineProps(['id', 'type', 'info']);
|
||||
const formRef = ref();
|
||||
let emits = defineEmits(['close']);
|
||||
const title = computed(() => {
|
||||
return props.id == '' ? t('新增签章') : t('编辑签章');
|
||||
});
|
||||
const defaultInfo: StampInfo = {
|
||||
enabledMark: 0,
|
||||
fileType: StampFileTypeAttributes.UPLOAD_PICTURES,
|
||||
fileUrl: '',
|
||||
password: '',
|
||||
stampCategory: undefined,
|
||||
stampType: StampType.PUBLIC_SIGNATURE,
|
||||
name: '',
|
||||
sortCode: 0,
|
||||
remark: '',
|
||||
id: '',
|
||||
maintain: '',
|
||||
};
|
||||
const data: {
|
||||
visible: boolean;
|
||||
categoryOptions: TreeItem[];
|
||||
info: StampInfo;
|
||||
action: string;
|
||||
headers: { Authorization: string };
|
||||
photoUrl: string;
|
||||
signUrl: string;
|
||||
} = reactive({
|
||||
visible: false,
|
||||
categoryOptions: [],
|
||||
info: defaultInfo,
|
||||
action: '',
|
||||
headers: { Authorization: '' },
|
||||
photoUrl: '',
|
||||
signUrl: '',
|
||||
});
|
||||
|
||||
const onCheck = async () => {
|
||||
try {
|
||||
await formRef.value.validateFields();
|
||||
return true;
|
||||
} catch (errorInfo) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
onMounted(async () => {
|
||||
if (props.id) {
|
||||
data.info = { ...defaultInfo, ...cloneDeep(props.info) };
|
||||
if (data.info.fileType === StampFileTypeAttributes.UPLOAD_PICTURES) {
|
||||
data.photoUrl = data.info.fileUrl;
|
||||
} else {
|
||||
data.signUrl = data.info.fileUrl;
|
||||
}
|
||||
} else {
|
||||
data.info = defaultInfo;
|
||||
}
|
||||
data.action = getAppEnvConfig().VITE_GLOB_API_URL + uploadSrc;
|
||||
data.headers.Authorization = `Bearer ${getToken()}`;
|
||||
open();
|
||||
});
|
||||
async function open() {
|
||||
data.categoryOptions = (await getDicDetailList({
|
||||
itemId: StampCategory.ID,
|
||||
})) as unknown as TreeItem[];
|
||||
if (props.id) {
|
||||
}
|
||||
data.info.stampType = props.type;
|
||||
data.visible = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
data.visible = false;
|
||||
emits('close');
|
||||
}
|
||||
|
||||
async function uploadSign(base64) {
|
||||
data.info.fileUrl = base64;
|
||||
const blob = dataURLtoBlob(base64);
|
||||
|
||||
const fileUrl = await uploadBlobApi(blob, t('手写签名.png'));
|
||||
if (fileUrl) {
|
||||
data.signUrl = fileUrl;
|
||||
message.success(t('手写签章上传成功'));
|
||||
} else {
|
||||
message.error(t('手写签章上传失败'));
|
||||
}
|
||||
}
|
||||
async function submit() {
|
||||
let valid = await onCheck();
|
||||
let res = false;
|
||||
if (valid) {
|
||||
if (data.info.fileType === StampFileTypeAttributes.UPLOAD_PICTURES) {
|
||||
if (!data.photoUrl) {
|
||||
message.error(t('照片未上传'));
|
||||
return false;
|
||||
} else {
|
||||
data.info.fileUrl = data.photoUrl;
|
||||
}
|
||||
} else {
|
||||
if (!data.signUrl) {
|
||||
message.error(t('签名未上传'));
|
||||
return false;
|
||||
} else {
|
||||
data.info.fileUrl = data.signUrl;
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (props.id) {
|
||||
res = await putStamp(props.id, props.type, data.info);
|
||||
} else {
|
||||
res = await postStamp(props.type, data.info);
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('签章'),
|
||||
description: title.value + t('成功'),
|
||||
});
|
||||
close();
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('签章'),
|
||||
description: title.value + t('失败'),
|
||||
});
|
||||
}
|
||||
}
|
||||
function photoChange(info: UploadChangeParam) {
|
||||
if (info.file.status !== 'uploading') {
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
if (info.file && info.file.response && info.file.response.code == 0) {
|
||||
message.success(t(`{name}上传成功!`, { name: info.file.name }));
|
||||
console.log(info, t('上传成功'));
|
||||
data.photoUrl = info.file.response.data.fileUrl;
|
||||
} else {
|
||||
message.error(t('上传照片失败'));
|
||||
}
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(t(`{name}上传失败.`, { name: info.file.name }));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user