This commit is contained in:
jason
2025-08-10 23:02:20 +08:00
25 changed files with 404 additions and 199 deletions

View File

@ -185,7 +185,7 @@ const { push } = useRouter()
const permissionStore = usePermissionStore()
const loginLoading = ref(false)
const verify = ref()
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)

View File

@ -143,7 +143,7 @@ const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
const { validForm } = useFormValid(formSmsResetPassword)
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
const validatePass2 = (_rule, value, callback) => {
if (value === '') {

View File

@ -47,10 +47,7 @@
/>
</el-form-item>
</el-col>
<el-col
:span="24"
class="px-10px mt-[-20px] mb-[-20px]"
>
<el-col :span="24" class="px-10px mt-[-20px] mb-[-20px]">
<el-form-item>
<el-row justify="space-between" style="width: 100%">
<el-col :span="6">
@ -177,7 +174,7 @@ const permissionStore = usePermissionStore()
const redirect = ref<string>('')
const loginLoading = ref(false)
const verify = ref()
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)

View File

@ -119,7 +119,7 @@ const permissionStore = usePermissionStore()
const redirect = ref<string>('')
const loginLoading = ref(false)
const verify = ref()
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)

View File

@ -11,6 +11,17 @@
</div>
</div>
</el-form-item>
<el-form-item class="mb-20px">
<template #label>
<el-text size="large" tag="b">审批人权限</el-text>
</template>
<div class="flex flex-col">
<el-checkbox v-model="modelData.allowWithdrawTask" label="允许审批人撤回任务" />
<div class="ml-22px">
<el-text type="info"> 审批人可撤回正在审批节点的前一节点 </el-text>
</div>
</div>
</el-form-item>
<el-form-item v-if="modelData.processIdRule" class="mb-20px">
<template #label>
<el-text size="large" tag="b">流程编码</el-text>
@ -422,6 +433,9 @@ const initData = () => {
if (modelData.value.taskAfterTriggerSetting) {
taskAfterTriggerEnable.value = true
}
if (modelData.value.allowWithdrawTask) {
modelData.value.allowWithdrawTask = false
}
}
defineExpose({ initData })

View File

@ -173,7 +173,8 @@ const formData: any = ref({
summarySetting: {
enable: false,
summary: []
}
},
allowWithdrawTask: false
})
// 流程数据

View File

@ -51,8 +51,10 @@
>
<div class="ml-10px -mt-15px -mb-35px">
<ProcessInstanceTimeline
ref="nextAssigneesTimelineRef"
:activity-nodes="nextAssigneesActivityNode"
:show-status-icon="false"
:enable-approve-user-select="true"
@select-user-confirm="selectNextAssigneesConfirm"
/>
</div>
@ -571,6 +573,7 @@ const approveFormRef = ref<FormInstance>()
const signRef = ref()
const approveSignFormRef = ref()
const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 下一个审批节点信息
const nextAssigneesTimelineRef = ref() // 下一个节点审批人时间线组件的引用
const approveReasonForm = reactive({
reason: '',
signPicUrl: '',
@ -717,6 +720,10 @@ const closePopover = (type: string, formRef: FormInstance | undefined) => {
}
popOverVisible.value[type] = false
nextAssigneesActivityNode.value = []
// 清理 Timeline 组件中的自定义审批人数据
if (nextAssigneesTimelineRef.value) {
nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({})
}
}
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
@ -729,6 +736,7 @@ const initNextAssigneesFormField = async () => {
processVariablesStr: JSON.stringify(variables)
})
if (data && data.length > 0) {
const customApproveUsersData: Record<string, any[]> = {} // 用于收集需要设置到 Timeline 组件的自定义审批人数据
data.forEach((node: any) => {
if (
// 情况一:当前节点没有审批人,并且是发起人自选
@ -740,7 +748,18 @@ const initNextAssigneesFormField = async () => {
) {
nextAssigneesActivityNode.value.push(node)
}
// 如果节点有 candidateUsers设置到 customApproveUsers 中
if (node.candidateUsers && node.candidateUsers.length > 0) {
customApproveUsersData[node.id] = node.candidateUsers
}
})
// 将 candidateUsers 设置到 Timeline 组件中
await nextTick() // 等待下一个 tick确保 Timeline 组件已经渲染
if (nextAssigneesTimelineRef.value && Object.keys(customApproveUsersData).length > 0) {
nextAssigneesTimelineRef.value.batchSetCustomApproveUsers(customApproveUsersData)
}
}
}
@ -803,6 +822,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
await TaskApi.approveTask(data)
popOverVisible.value.approve = false
nextAssigneesActivityNode.value = []
// 清理 Timeline 组件中的自定义审批人数据
if (nextAssigneesTimelineRef.value) {
nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({})
}
message.success('审批通过成功')
} else {
// 审批不通过数据

View File

@ -15,7 +15,7 @@
>
<img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
<div
v-if="showStatusIcon"
v-if="props.showStatusIcon"
class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
:style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
>
@ -28,7 +28,8 @@
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}-${index}`">
<!-- 第一行节点名称时间 -->
<div class="flex w-full">
<div class="font-bold"> {{ activity.name }} <span v-if="activity.status === TaskStatusEnum.SKIP">跳过</span>
<div class="font-bold">
{{ activity.name }} <span v-if="activity.status === TaskStatusEnum.SKIP">跳过</span>
</div>
<!-- 信息时间 -->
<div
@ -54,13 +55,13 @@
class="flex flex-wrap gap2 items-center"
v-if="
isEmpty(activity.tasks) &&
isEmpty(activity.candidateUsers) &&
(CandidateStrategy.START_USER_SELECT === activity.candidateStrategy ||
CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy)
((CandidateStrategy.START_USER_SELECT === activity.candidateStrategy &&
isEmpty(activity.candidateUsers)) ||
(props.enableApproveUserSelect &&
CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy))
"
>
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
<el-tooltip content="添加用户" placement="left">
<el-button
class="!px-6px"
@ -118,7 +119,7 @@
</template>
<!-- 信息:任务 ICON -->
<div
v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
v-if="props.showStatusIcon && onlyStatusIconShow.includes(task.status)"
class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
:style="{ backgroundColor: statusIconMap2[task.status]?.color }"
>
@ -164,7 +165,7 @@
<!-- 信息:任务 ICON -->
<div
v-if="showStatusIcon"
v-if="props.showStatusIcon"
class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
:style="{ backgroundColor: statusIconMap2['-1']?.color }"
>
@ -197,13 +198,15 @@ import transactorSvg from '@/assets/svgs/bpm/transactor.svg'
import childProcessSvg from '@/assets/svgs/bpm/child-process.svg'
defineOptions({ name: 'BpmProcessInstanceTimeline' })
withDefaults(
const props = withDefaults(
defineProps<{
activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
showStatusIcon?: boolean // 是否显示头像右下角状态图标
enableApproveUserSelect?: boolean // 是否开启审批人自选功能
}>(),
{
showStatusIcon: true // 默认值为 true
showStatusIcon: true, // 默认值为 true
enableApproveUserSelect: false // 默认值为 false
}
)
const { push } = useRouter() // 路由
@ -234,7 +237,7 @@ const statusIconMap2 = {
const statusIconMap = {
// 跳过
'-2': { color: '#909398', icon: ArrowDown},
'-2': { color: '#909398', icon: ArrowDown },
// 审批未开始
'-1': { color: '#909398', icon: Clock },
'0': { color: '#00b32a', icon: Clock },
@ -340,4 +343,19 @@ const handleChildProcess = (activity: any) => {
}
})
}
/** 设置自定义审批人 */
const setCustomApproveUsers = (activityId: string, users: any[]) => {
customApproveUsers.value[activityId] = users || []
}
/** 批量设置多个节点的自定义审批人 */
const batchSetCustomApproveUsers = (data: Record<string, any[]>) => {
Object.keys(data).forEach((activityId) => {
customApproveUsers.value[activityId] = data[activityId] || []
})
}
// 暴露方法给父组件
defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers })
</script>

View File

@ -184,8 +184,9 @@
:show-overflow-tooltip="true"
/>
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80">
<el-table-column align="center" label="操作" fixed="right" width="130">
<template #default="scope">
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
</template>
</el-table-column>
@ -209,6 +210,7 @@ import * as DefinitionApi from '@/api/bpm/definition'
defineOptions({ name: 'BpmDoneTask' })
const { push } = useRouter() // 路由
const message = useMessage()
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
@ -262,6 +264,14 @@ const handleAudit = (row: any) => {
})
}
/** 测回按钮 */
const handleWithdraw = (row: any) => {
TaskApi.withdrawTask(row.id).then(() => {
message.success('撤回成功')
getList()
})
}
/** 初始化 **/
onMounted(async () => {
await getList()

View File

@ -144,7 +144,7 @@
show-overflow-tooltip
min-width="300"
>
<template #defaul="{ row }">
<template #default="{ row }">
<el-image
:src="row.picUrl"
class="mr-5px h-30px w-30px align-middle"

View File

@ -13,12 +13,34 @@
<el-descriptions-item label="模版发送人名称">
{{ detailData.templateNickname }}
</el-descriptions-item>
<el-descriptions-item label="用户信息">
{{ detailData.toMail }}
<el-descriptions-item label="接收用户">
<span v-if="detailData.userType && detailData.userId">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
({{ detailData.userId }})
</span>
<span v-else></span>
</el-descriptions-item>
<el-descriptions-item label="接收信息">
<div>
<div v-if="detailData.toMails && detailData.toMails.length > 0">
收件
<span v-for="(mail, index) in detailData.toMails" :key="mail">
{{ mail }}<span v-if="index < detailData.toMails.length - 1"></span>
</span>
</div>
<div v-if="detailData.ccMails && detailData.ccMails.length > 0">
抄送
<span v-for="(mail, index) in detailData.ccMails" :key="mail">
{{ mail }}<span v-if="index < detailData.ccMails.length - 1"></span>
</span>
</div>
<div v-if="detailData.bccMails && detailData.bccMails.length > 0">
密送
<span v-for="(mail, index) in detailData.bccMails" :key="mail">
{{ mail }}<span v-if="index < detailData.bccMails.length - 1"></span>
</span>
</div>
</div>
</el-descriptions-item>
<el-descriptions-item label="邮件标题">
{{ detailData.templateTitle }}
@ -58,7 +80,7 @@ defineOptions({ name: 'SystemMailLogDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中
const detailData = ref() // 详情数据
const accountList = ref([]) // 邮箱账号列表
const accountList = ref<MailAccountApi.MailAccountVO[]>([]) // 邮箱账号列表
/** 打开弹窗 */
const open = async (data: MailLogApi.MailLogVO) => {

View File

@ -119,12 +119,36 @@
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="接收邮箱" align="center" prop="toMail" width="200">
<el-table-column label="接收用户" align="center" width="150">
<template #default="scope">
<div>{{ scope.row.toMail }}</div>
<div v-if="scope.row.userType && scope.row.userId">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
{{ '(' + scope.row.userId + ')' }}
<div>{{ '(' + scope.row.userId + ')' }}</div>
</div>
<div v-else>-</div>
</template>
</el-table-column>
<el-table-column label="接收信息" align="center" width="300">
<template #default="scope">
<div class="text-left">
<div v-if="scope.row.toMails && scope.row.toMails.length > 0">
收件
<span v-for="(mail, index) in scope.row.toMails" :key="mail">
{{ mail }}<span v-if="index < scope.row.toMails.length - 1"></span>
</span>
</div>
<div v-if="scope.row.ccMails && scope.row.ccMails.length > 0">
抄送
<span v-for="(mail, index) in scope.row.ccMails" :key="mail">
{{ mail }}<span v-if="index < scope.row.ccMails.length - 1"></span>
</span>
</div>
<div v-if="scope.row.bccMails && scope.row.bccMails.length > 0">
密送
<span v-for="(mail, index) in scope.row.bccMails" :key="mail">
{{ mail }}<span v-if="index < scope.row.bccMails.length - 1"></span>
</span>
</div>
</div>
</template>
</el-table-column>
@ -185,15 +209,15 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
toMail: '',
accountId: null,
templateId: null,
sendStatus: null,
userId: null,
userType: null,
accountId: undefined,
templateId: undefined,
sendStatus: undefined,
userId: undefined,
userType: undefined,
sendTime: []
})
const exportLoading = ref(false) // 导出的加载中
const accountList = ref([]) // 邮箱账号列表
const accountList = ref<MailAccountApi.MailAccountVO[]>([]) // 邮箱账号列表
/** 查询列表 */
const getList = async () => {

View File

@ -10,8 +10,26 @@
<el-form-item label="模板内容" prop="content">
<Editor :model-value="formData.content" height="150px" readonly />
</el-form-item>
<el-form-item label="收件邮箱" prop="mail">
<el-input v-model="formData.mail" placeholder="请输入收件邮箱" />
<el-form-item label="收件邮箱" prop="toMails">
<el-input-tag
v-model="formData.toMails"
placeholder="请输入收件邮箱,多个邮箱用回车分隔"
class="!w-full"
/>
</el-form-item>
<el-form-item label="抄送邮箱" prop="ccMails">
<el-input-tag
v-model="formData.ccMails"
placeholder="请输入抄送邮箱,多个邮箱用回车分隔"
class="!w-full"
/>
</el-form-item>
<el-form-item label="密送邮箱" prop="bccMails">
<el-input-tag
v-model="formData.bccMails"
placeholder="请输入密送邮箱,多个邮箱用回车分隔"
class="!w-full"
/>
</el-form-item>
<el-form-item
v-for="param in formData.params"
@ -43,12 +61,13 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const formData = ref({
content: '',
params: {},
mail: '',
toMails: [],
ccMails: [],
bccMails: [],
templateCode: '',
templateParams: new Map()
})
const formRules = reactive({
mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
templateParams: {}
})
@ -105,7 +124,9 @@ const resetForm = () => {
formData.value = {
content: '',
params: {},
mail: '',
toMails: [],
ccMails: [],
bccMails: [],
templateCode: '',
templateParams: new Map()
}