!360 Merge remote-tracking branch 'yudao/master'
Merge pull request !360 from Jason/masterpull/361/head
commit
97ca9cfa45
|
|
@ -83,6 +83,7 @@ export namespace BpmProcessInstanceApi {
|
||||||
reason: string;
|
reason: string;
|
||||||
signPicUrl: string;
|
signPicUrl: string;
|
||||||
status: number;
|
status: number;
|
||||||
|
attachments?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 抄送流程实例 */
|
/** 抄送流程实例 */
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||||
resultField: '',
|
resultField: '',
|
||||||
returnText: false,
|
returnText: false,
|
||||||
showDescription: false,
|
showDescription: false,
|
||||||
|
showDownloadIcon: true,
|
||||||
});
|
});
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'change',
|
'change',
|
||||||
|
|
@ -295,7 +296,7 @@ function getValue() {
|
||||||
:show-upload-list="{
|
:show-upload-list="{
|
||||||
showPreviewIcon: true,
|
showPreviewIcon: true,
|
||||||
showRemoveIcon: true,
|
showRemoveIcon: true,
|
||||||
showDownloadIcon: true,
|
showDownloadIcon,
|
||||||
}"
|
}"
|
||||||
@remove="handleRemove"
|
@remove="handleRemove"
|
||||||
@preview="handlePreview"
|
@preview="handlePreview"
|
||||||
|
|
@ -361,3 +362,10 @@ function getValue() {
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 文件上传列表显示手型光标样式失效。不知道为啥. 先这里加上 */
|
||||||
|
.ant-upload-list-text .ant-upload-list-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,6 @@ export interface FileUploadProps {
|
||||||
resultField?: string; // support xxx.xxx.xx
|
resultField?: string; // support xxx.xxx.xx
|
||||||
returnText?: boolean; // 是否返回文件文本内容
|
returnText?: boolean; // 是否返回文件文本内容
|
||||||
showDescription?: boolean; // 是否显示下面的描述
|
showDescription?: boolean; // 是否显示下面的描述
|
||||||
|
showDownloadIcon?: boolean; // 是否显示下载按钮
|
||||||
value?: string | string[];
|
value?: string | string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ import {
|
||||||
transferTask,
|
transferTask,
|
||||||
} from '#/api/bpm/task';
|
} from '#/api/bpm/task';
|
||||||
import { setConfAndFields2 } from '#/components/form-create';
|
import { setConfAndFields2 } from '#/components/form-create';
|
||||||
|
import { FileUpload } from '#/components/upload';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import Signature from './signature.vue';
|
import Signature from './signature.vue';
|
||||||
|
|
@ -120,6 +121,7 @@ const approveReasonForm: any = reactive({
|
||||||
reason: '',
|
reason: '',
|
||||||
signPicUrl: '',
|
signPicUrl: '',
|
||||||
nextAssignees: {},
|
nextAssignees: {},
|
||||||
|
attachments: [],
|
||||||
});
|
});
|
||||||
const approveReasonRule: Record<string, any> = computed(() => {
|
const approveReasonRule: Record<string, any> = computed(() => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -142,6 +144,7 @@ const approveReasonRule: Record<string, any> = computed(() => {
|
||||||
const rejectFormRef = ref<FormInstance>();
|
const rejectFormRef = ref<FormInstance>();
|
||||||
const rejectReasonForm = reactive({
|
const rejectReasonForm = reactive({
|
||||||
reason: '',
|
reason: '',
|
||||||
|
attachments: [],
|
||||||
}); // 拒绝表单
|
}); // 拒绝表单
|
||||||
const rejectReasonRule: any = computed(() => {
|
const rejectReasonRule: any = computed(() => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -290,6 +293,14 @@ function closePopover(type: string, formRef: any | FormInstance) {
|
||||||
if (formRef) {
|
if (formRef) {
|
||||||
formRef.resetFields();
|
formRef.resetFields();
|
||||||
}
|
}
|
||||||
|
if (type === 'approve') {
|
||||||
|
approveReasonForm.reason = '';
|
||||||
|
approveReasonForm.attachments = [];
|
||||||
|
approveReasonForm.signPicUrl = '';
|
||||||
|
} else if (type === 'reject') {
|
||||||
|
rejectReasonForm.reason = '';
|
||||||
|
rejectReasonForm.attachments = [];
|
||||||
|
}
|
||||||
if (popOverVisible.value[type]) popOverVisible.value[type] = false;
|
if (popOverVisible.value[type]) popOverVisible.value[type] = false;
|
||||||
nextAssigneesActivityNode.value = [];
|
nextAssigneesActivityNode.value = [];
|
||||||
// 清理 Timeline 组件中的自定义审批人数据
|
// 清理 Timeline 组件中的自定义审批人数据
|
||||||
|
|
@ -401,6 +412,7 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||||
const data = {
|
const data = {
|
||||||
id: runningTask.value.id,
|
id: runningTask.value.id,
|
||||||
reason: approveReasonForm.reason,
|
reason: approveReasonForm.reason,
|
||||||
|
attachments: approveReasonForm.attachments,
|
||||||
variables, // 审批通过, 把修改的字段值赋于流程实例变量
|
variables, // 审批通过, 把修改的字段值赋于流程实例变量
|
||||||
nextAssignees: approveReasonForm.nextAssignees, // 下个自选节点选择的审批人信息
|
nextAssignees: approveReasonForm.nextAssignees, // 下个自选节点选择的审批人信息
|
||||||
} as any;
|
} as any;
|
||||||
|
|
@ -414,6 +426,9 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||||
await formCreateApi.validate();
|
await formCreateApi.validate();
|
||||||
}
|
}
|
||||||
await approveTask(data);
|
await approveTask(data);
|
||||||
|
approveReasonForm.reason = '';
|
||||||
|
approveReasonForm.attachments = [];
|
||||||
|
approveReasonForm.signPicUrl = '';
|
||||||
popOverVisible.value.approve = false;
|
popOverVisible.value.approve = false;
|
||||||
nextAssigneesActivityNode.value = [];
|
nextAssigneesActivityNode.value = [];
|
||||||
// 清理 Timeline 组件中的自定义审批人数据
|
// 清理 Timeline 组件中的自定义审批人数据
|
||||||
|
|
@ -426,8 +441,11 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||||
const data = {
|
const data = {
|
||||||
id: runningTask.value.id,
|
id: runningTask.value.id,
|
||||||
reason: rejectReasonForm.reason,
|
reason: rejectReasonForm.reason,
|
||||||
|
attachments: rejectReasonForm.attachments,
|
||||||
};
|
};
|
||||||
await rejectTask(data);
|
await rejectTask(data);
|
||||||
|
rejectReasonForm.reason = '';
|
||||||
|
rejectReasonForm.attachments = [];
|
||||||
popOverVisible.value.reject = false;
|
popOverVisible.value.reject = false;
|
||||||
message.success('审批不通过成功');
|
message.success('审批不通过成功');
|
||||||
}
|
}
|
||||||
|
|
@ -748,6 +766,34 @@ function handleSignFinish(url: string) {
|
||||||
approveFormRef.value?.validateFields(['signPicUrl']);
|
approveFormRef.value?.validateFields(['signPicUrl']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 附件图片预览 */
|
||||||
|
const imagePreviewVisible = ref(false);
|
||||||
|
const imagePreviewUrl = ref('');
|
||||||
|
|
||||||
|
/** 判断文件是否为图片类型 */
|
||||||
|
function isImageUrl(url: string) {
|
||||||
|
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理文件预览 */
|
||||||
|
function handleFilePreview(file: any) {
|
||||||
|
if (!file?.url && !file?.response) {
|
||||||
|
message.warning('文件地址不存在,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = file.url || file?.response?.url || file?.response;
|
||||||
|
if (!url) {
|
||||||
|
message.warning('文件地址不存在,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isImageUrl(url)) {
|
||||||
|
imagePreviewUrl.value = url;
|
||||||
|
imagePreviewVisible.value = true;
|
||||||
|
} else {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 处理弹窗可见性 */
|
/** 处理弹窗可见性 */
|
||||||
function handlePopoverVisible(visible: boolean) {
|
function handlePopoverVisible(visible: boolean) {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
|
|
@ -843,6 +889,16 @@ defineExpose({ loadTodoTask });
|
||||||
:rows="4"
|
:rows="4"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem label="上传附件/图片" name="attachments">
|
||||||
|
<FileUpload
|
||||||
|
v-model:value="approveReasonForm.attachments"
|
||||||
|
:max-number="10"
|
||||||
|
:multiple="true"
|
||||||
|
:show-download-icon="false"
|
||||||
|
help-text="支持多文件/图片上传"
|
||||||
|
@preview="handleFilePreview"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -900,6 +956,15 @@ defineExpose({ loadTodoTask });
|
||||||
:rows="4"
|
:rows="4"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem label="上传附件/图片" name="attachments">
|
||||||
|
<FileUpload
|
||||||
|
v-model:value="rejectReasonForm.attachments"
|
||||||
|
:max-number="10"
|
||||||
|
:multiple="true"
|
||||||
|
help-text="支持多文件/图片上传"
|
||||||
|
@preview="handleFilePreview"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Button
|
<Button
|
||||||
:disabled="formLoading"
|
:disabled="formLoading"
|
||||||
|
|
@ -1444,4 +1509,17 @@ defineExpose({ loadTodoTask });
|
||||||
|
|
||||||
<!-- 签名弹窗 -->
|
<!-- 签名弹窗 -->
|
||||||
<SignatureModal @success="handleSignFinish" />
|
<SignatureModal @success="handleSignFinish" />
|
||||||
|
|
||||||
|
<!-- 图片预览(隐藏的 Image 组件,仅用于附件预览弹窗) -->
|
||||||
|
<div style="display: none">
|
||||||
|
<Image
|
||||||
|
:preview="{
|
||||||
|
visible: imagePreviewVisible,
|
||||||
|
onVisibleChange: (visible: boolean) => {
|
||||||
|
imagePreviewVisible = visible;
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
:src="imagePreviewUrl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -194,16 +194,25 @@ function shouldShowCustomUserSelect(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否需要显示审批意见 */
|
/** 判断是否需要显示审批意见和附件 */
|
||||||
function shouldShowApprovalReason(task: any, nodeType: BpmNodeTypeEnum) {
|
function shouldShowReasonAndAttachment(task: any, nodeType: BpmNodeTypeEnum) {
|
||||||
return (
|
return (
|
||||||
task.reason &&
|
(task.reason || task.attachments?.length > 0) &&
|
||||||
[BpmNodeTypeEnum.START_USER_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
|
[BpmNodeTypeEnum.START_USER_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
|
||||||
nodeType,
|
nodeType,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAttachmentName(url: string) {
|
||||||
|
return decodeURIComponent(url.slice(url.lastIndexOf('/') + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImageAttachment(url: string) {
|
||||||
|
const ext = url.split('.').pop()?.toLowerCase();
|
||||||
|
return ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp'].includes(ext || '');
|
||||||
|
}
|
||||||
|
|
||||||
/** 用户选择弹窗关闭 */
|
/** 用户选择弹窗关闭 */
|
||||||
function handleUserSelectClosed() {
|
function handleUserSelectClosed() {
|
||||||
selectedUsers.value = [];
|
selectedUsers.value = [];
|
||||||
|
|
@ -406,13 +415,60 @@ defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 审批意见和签名 -->
|
<!-- 审批意见,附件和签名 -->
|
||||||
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
||||||
<div
|
<div
|
||||||
v-if="shouldShowApprovalReason(task, activity.nodeType)"
|
v-if="shouldShowReasonAndAttachment(task, activity.nodeType)"
|
||||||
class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
||||||
>
|
>
|
||||||
审批意见:{{ task.reason }}
|
<div v-if="task.reason">审批意见:{{ task.reason }}</div>
|
||||||
|
<div
|
||||||
|
v-if="(task.attachments?.length || 0) > 0"
|
||||||
|
:class="{
|
||||||
|
'mt-2 border-t border-dashed border-gray-300 pt-2':
|
||||||
|
task.reason,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="mb-1 text-xs font-semibold text-gray-400">
|
||||||
|
附件列表:
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<template
|
||||||
|
v-for="(
|
||||||
|
attachment, attachmentIndex
|
||||||
|
) in task.attachments"
|
||||||
|
:key="attachmentIndex"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<IconifyIcon
|
||||||
|
:icon="
|
||||||
|
isImageAttachment(attachment)
|
||||||
|
? 'lucide:image'
|
||||||
|
: 'lucide:file-text'
|
||||||
|
"
|
||||||
|
class="text-gray-400"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
v-if="isImageAttachment(attachment)"
|
||||||
|
:width="32"
|
||||||
|
:height="32"
|
||||||
|
class="rounded border border-solid border-gray-200 object-cover"
|
||||||
|
:src="attachment"
|
||||||
|
:preview="{ src: attachment }"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="attachment"
|
||||||
|
target="_blank"
|
||||||
|
class="max-w-[240px] truncate text-blue-500 hover:text-blue-600 hover:underline"
|
||||||
|
:title="getAttachmentName(attachment)"
|
||||||
|
>
|
||||||
|
{{ getAttachmentName(attachment) }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ export namespace BpmProcessInstanceApi {
|
||||||
reason: string;
|
reason: string;
|
||||||
signPicUrl: string;
|
signPicUrl: string;
|
||||||
status: number;
|
status: number;
|
||||||
|
attachments?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 抄送流程实例 */
|
/** 抄送流程实例 */
|
||||||
|
|
|
||||||
|
|
@ -139,9 +139,6 @@ function handleRemove(file: UploadFile) {
|
||||||
/** 处理文件预览 */
|
/** 处理文件预览 */
|
||||||
function handlePreview(file: UploadFile) {
|
function handlePreview(file: UploadFile) {
|
||||||
emit('preview', file);
|
emit('preview', file);
|
||||||
if (file.url) {
|
|
||||||
window.open(file.url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理文件数量超限 */
|
/** 处理文件数量超限 */
|
||||||
|
|
@ -307,7 +304,7 @@ function getValue() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="w-full">
|
||||||
<ElUpload
|
<ElUpload
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
ElForm,
|
ElForm,
|
||||||
ElFormItem,
|
ElFormItem,
|
||||||
ElImage,
|
ElImage,
|
||||||
|
ElImageViewer,
|
||||||
ElInput,
|
ElInput,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
ElOption,
|
ElOption,
|
||||||
|
|
@ -55,6 +56,7 @@ import {
|
||||||
transferTask,
|
transferTask,
|
||||||
} from '#/api/bpm/task';
|
} from '#/api/bpm/task';
|
||||||
import { setConfAndFields2 } from '#/components/form-create';
|
import { setConfAndFields2 } from '#/components/form-create';
|
||||||
|
import { FileUpload } from '#/components/upload';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import Signature from './signature.vue';
|
import Signature from './signature.vue';
|
||||||
|
|
@ -119,6 +121,7 @@ const approveReasonForm: any = reactive({
|
||||||
reason: '',
|
reason: '',
|
||||||
signPicUrl: '',
|
signPicUrl: '',
|
||||||
nextAssignees: {},
|
nextAssignees: {},
|
||||||
|
attachments: [],
|
||||||
});
|
});
|
||||||
const approveReasonRule: Record<string, any> = computed(() => {
|
const approveReasonRule: Record<string, any> = computed(() => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -141,6 +144,7 @@ const approveReasonRule: Record<string, any> = computed(() => {
|
||||||
const rejectFormRef = ref<FormInstance>();
|
const rejectFormRef = ref<FormInstance>();
|
||||||
const rejectReasonForm = reactive({
|
const rejectReasonForm = reactive({
|
||||||
reason: '',
|
reason: '',
|
||||||
|
attachments: [],
|
||||||
}); // 拒绝表单
|
}); // 拒绝表单
|
||||||
const rejectReasonRule: any = computed(() => {
|
const rejectReasonRule: any = computed(() => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -299,6 +303,14 @@ function closePopover(type: string, formRef: any | FormInstance) {
|
||||||
if (formRef) {
|
if (formRef) {
|
||||||
formRef.resetFields();
|
formRef.resetFields();
|
||||||
}
|
}
|
||||||
|
if (type === 'approve') {
|
||||||
|
approveReasonForm.reason = '';
|
||||||
|
approveReasonForm.attachments = [];
|
||||||
|
approveReasonForm.signPicUrl = '';
|
||||||
|
} else if (type === 'reject') {
|
||||||
|
rejectReasonForm.reason = '';
|
||||||
|
rejectReasonForm.attachments = [];
|
||||||
|
}
|
||||||
if (popOverVisible.value[type]) popOverVisible.value[type] = false;
|
if (popOverVisible.value[type]) popOverVisible.value[type] = false;
|
||||||
nextAssigneesActivityNode.value = [];
|
nextAssigneesActivityNode.value = [];
|
||||||
// 清理 Timeline 组件中的自定义审批人数据
|
// 清理 Timeline 组件中的自定义审批人数据
|
||||||
|
|
@ -410,6 +422,7 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||||
const data = {
|
const data = {
|
||||||
id: runningTask.value.id,
|
id: runningTask.value.id,
|
||||||
reason: approveReasonForm.reason,
|
reason: approveReasonForm.reason,
|
||||||
|
attachments: approveReasonForm.attachments,
|
||||||
variables, // 审批通过, 把修改的字段值赋于流程实例变量
|
variables, // 审批通过, 把修改的字段值赋于流程实例变量
|
||||||
nextAssignees: approveReasonForm.nextAssignees, // 下个自选节点选择的审批人信息
|
nextAssignees: approveReasonForm.nextAssignees, // 下个自选节点选择的审批人信息
|
||||||
} as any;
|
} as any;
|
||||||
|
|
@ -423,6 +436,9 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||||
await formCreateApi.validate();
|
await formCreateApi.validate();
|
||||||
}
|
}
|
||||||
await approveTask(data);
|
await approveTask(data);
|
||||||
|
approveReasonForm.reason = '';
|
||||||
|
approveReasonForm.attachments = [];
|
||||||
|
approveReasonForm.signPicUrl = '';
|
||||||
popOverVisible.value.approve = false;
|
popOverVisible.value.approve = false;
|
||||||
nextAssigneesActivityNode.value = [];
|
nextAssigneesActivityNode.value = [];
|
||||||
// 清理 Timeline 组件中的自定义审批人数据
|
// 清理 Timeline 组件中的自定义审批人数据
|
||||||
|
|
@ -435,8 +451,11 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||||
const data = {
|
const data = {
|
||||||
id: runningTask.value.id,
|
id: runningTask.value.id,
|
||||||
reason: rejectReasonForm.reason,
|
reason: rejectReasonForm.reason,
|
||||||
|
attachments: rejectReasonForm.attachments,
|
||||||
};
|
};
|
||||||
await rejectTask(data);
|
await rejectTask(data);
|
||||||
|
rejectReasonForm.reason = '';
|
||||||
|
rejectReasonForm.attachments = [];
|
||||||
popOverVisible.value.reject = false;
|
popOverVisible.value.reject = false;
|
||||||
ElMessage.success('审批不通过成功');
|
ElMessage.success('审批不通过成功');
|
||||||
}
|
}
|
||||||
|
|
@ -757,6 +776,34 @@ function handleSignFinish(url: string) {
|
||||||
approveFormRef.value?.validateField('signPicUrl');
|
approveFormRef.value?.validateField('signPicUrl');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断文件是否为图片类型 */
|
||||||
|
function isImageUrl(url: string) {
|
||||||
|
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 附件图片预览 */
|
||||||
|
const imagePreviewVisible = ref(false);
|
||||||
|
const imagePreviewUrl = ref('');
|
||||||
|
|
||||||
|
/** 处理文件预览 */
|
||||||
|
function handleFilePreview(file: any) {
|
||||||
|
if (!file?.url && !file?.response) {
|
||||||
|
ElMessage.warning('文件地址不存在,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = file.url || file?.response?.url || file?.response;
|
||||||
|
if (!url) {
|
||||||
|
ElMessage.warning('文件地址不存在,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isImageUrl(url)) {
|
||||||
|
imagePreviewUrl.value = url;
|
||||||
|
imagePreviewVisible.value = true;
|
||||||
|
} else {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO @jason:handlePopoverVisible 需要要有么?
|
// TODO @jason:handlePopoverVisible 需要要有么?
|
||||||
|
|
||||||
defineExpose({ loadTodoTask });
|
defineExpose({ loadTodoTask });
|
||||||
|
|
@ -849,6 +896,15 @@ defineExpose({ loadTodoTask });
|
||||||
:rows="4"
|
:rows="4"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem label="上传附件/图片" prop="attachments">
|
||||||
|
<FileUpload
|
||||||
|
v-model:value="approveReasonForm.attachments"
|
||||||
|
:max-number="10"
|
||||||
|
:multiple="true"
|
||||||
|
help-text="支持多文件/图片上传"
|
||||||
|
@preview="handleFilePreview"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElSpace>
|
<ElSpace>
|
||||||
<ElButton
|
<ElButton
|
||||||
|
|
@ -873,7 +929,7 @@ defineExpose({ loadTodoTask });
|
||||||
<ElPopover
|
<ElPopover
|
||||||
:visible="popOverVisible.reject"
|
:visible="popOverVisible.reject"
|
||||||
placement="top"
|
placement="top"
|
||||||
:popper-style="{ minWidth: '400px' }"
|
:popper-style="{ minWidth: '500px' }"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
v-if="
|
v-if="
|
||||||
runningTask &&
|
runningTask &&
|
||||||
|
|
@ -907,6 +963,15 @@ defineExpose({ loadTodoTask });
|
||||||
:rows="4"
|
:rows="4"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem label="上传附件/图片" prop="attachments">
|
||||||
|
<FileUpload
|
||||||
|
v-model:value="rejectReasonForm.attachments"
|
||||||
|
:max-number="10"
|
||||||
|
:multiple="true"
|
||||||
|
help-text="支持多文件/图片上传"
|
||||||
|
@preview="handleFilePreview"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElButton
|
<ElButton
|
||||||
:disabled="formLoading"
|
:disabled="formLoading"
|
||||||
|
|
@ -1472,4 +1537,12 @@ defineExpose({ loadTodoTask });
|
||||||
|
|
||||||
<!-- 签名弹窗 -->
|
<!-- 签名弹窗 -->
|
||||||
<SignatureModal @success="handleSignFinish" />
|
<SignatureModal @success="handleSignFinish" />
|
||||||
|
|
||||||
|
<!-- 图片预览弹窗 -->
|
||||||
|
<ElImageViewer
|
||||||
|
v-if="imagePreviewVisible"
|
||||||
|
:url-list="[imagePreviewUrl]"
|
||||||
|
@close="imagePreviewVisible = false"
|
||||||
|
:z-index="9999"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -201,16 +201,25 @@ function shouldShowCustomUserSelect(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否需要显示审批意见 */
|
/** 判断是否需要显示审批意见和附件 */
|
||||||
function shouldShowApprovalReason(task: any, nodeType: BpmNodeTypeEnum) {
|
function shouldShowReasonAndAttachment(task: any, nodeType: BpmNodeTypeEnum) {
|
||||||
return (
|
return (
|
||||||
task.reason &&
|
(task.reason || task.attachments?.length > 0) &&
|
||||||
[BpmNodeTypeEnum.START_USER_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
|
[BpmNodeTypeEnum.START_USER_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
|
||||||
nodeType,
|
nodeType,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAttachmentName(url: string) {
|
||||||
|
return decodeURIComponent(url.slice(url.lastIndexOf('/') + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImageAttachment(url: string) {
|
||||||
|
const ext = url.split('.').pop()?.toLowerCase();
|
||||||
|
return ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp'].includes(ext || '');
|
||||||
|
}
|
||||||
|
|
||||||
/** 用户选择弹窗关闭 */
|
/** 用户选择弹窗关闭 */
|
||||||
function handleUserSelectClosed() {
|
function handleUserSelectClosed() {
|
||||||
selectedUsers.value = [];
|
selectedUsers.value = [];
|
||||||
|
|
@ -411,13 +420,60 @@ defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 审批意见和签名 -->
|
<!-- 审批意见,附件和签名 -->
|
||||||
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
||||||
<div
|
<div
|
||||||
v-if="shouldShowApprovalReason(task, activity.nodeType)"
|
v-if="shouldShowReasonAndAttachment(task, activity.nodeType)"
|
||||||
class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
||||||
>
|
>
|
||||||
审批意见:{{ task.reason }}
|
<div v-if="task.reason">审批意见:{{ task.reason }}</div>
|
||||||
|
<div
|
||||||
|
v-if="(task.attachments?.length || 0) > 0"
|
||||||
|
:class="{
|
||||||
|
'mt-2 border-t border-dashed border-gray-300 pt-2':
|
||||||
|
task.reason,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="mb-1 text-xs font-semibold text-gray-400">
|
||||||
|
附件列表:
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<template
|
||||||
|
v-for="(
|
||||||
|
attachment, attachmentIndex
|
||||||
|
) in task.attachments"
|
||||||
|
:key="attachmentIndex"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<IconifyIcon
|
||||||
|
:icon="
|
||||||
|
isImageAttachment(attachment)
|
||||||
|
? 'lucide:image'
|
||||||
|
: 'lucide:file-text'
|
||||||
|
"
|
||||||
|
class="text-gray-400"
|
||||||
|
/>
|
||||||
|
<ElImage
|
||||||
|
v-if="isImageAttachment(attachment)"
|
||||||
|
style="width: 32px; height: 32px"
|
||||||
|
class="rounded border border-solid border-gray-200 object-cover"
|
||||||
|
:src="attachment"
|
||||||
|
:preview-src-list="[attachment]"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="attachment"
|
||||||
|
target="_blank"
|
||||||
|
class="max-w-[240px] truncate text-blue-500 hover:text-blue-600 hover:underline"
|
||||||
|
:title="getAttachmentName(attachment)"
|
||||||
|
>
|
||||||
|
{{ getAttachmentName(attachment) }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue