feat(oa): CC designer multi-select and preview assigneeTypes support
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
835c1c0836
commit
f7d1004e6b
7 files changed
+166
-25
No files matched your search
@@ -31,11 +31,12 @@
|
||||
<template v-if="isInitiatorSelect(s)">
|
||||
<span class="oa-flow-card__select">
|
||||
<span class="oa-flow-card__people">
|
||||
<template v-if="localPeople(s).length">
|
||||
<span v-for="p in localPeople(s)" :key="p.id" class="oa-person-chip">
|
||||
<template v-if="displayPeople(s).length">
|
||||
<span v-for="p in displayPeople(s)" :key="p.id" class="oa-person-chip">
|
||||
<span class="oa-person-chip__dot">{{ avatarChar(p.nickName) }}</span>
|
||||
{{ p.nickName }}
|
||||
<button
|
||||
v-if="isLocalSelected(s, p.id)"
|
||||
type="button"
|
||||
class="oa-person-chip__x"
|
||||
title="移除"
|
||||
@@ -218,10 +219,14 @@
|
||||
pickerNodeType.value === 'cc' ? '选择抄送人' : '选择审批人'
|
||||
)
|
||||
|
||||
function stepAssigneeTypes(s: OaFlowPreviewStep): string[] {
|
||||
if (Array.isArray(s.assigneeTypes) && s.assigneeTypes.length) return s.assigneeTypes
|
||||
return s.assigneeType ? [s.assigneeType] : []
|
||||
}
|
||||
|
||||
function isInitiatorSelect(s: OaFlowPreviewStep) {
|
||||
return (
|
||||
s.assigneeType === 'initiator_select' && (s.type === 'approval' || s.type === 'cc')
|
||||
)
|
||||
if (!(s.type === 'approval' || s.type === 'cc')) return false
|
||||
return stepAssigneeTypes(s).includes('initiator_select')
|
||||
}
|
||||
|
||||
function localPeople(s: OaFlowPreviewStep) {
|
||||
@@ -233,6 +238,22 @@
|
||||
})
|
||||
}
|
||||
|
||||
function displayPeople(s: OaFlowPreviewStep) {
|
||||
if (!isInitiatorSelect(s)) return s.people || []
|
||||
const fromLocal = localPeople(s)
|
||||
if (s.type !== 'cc') return fromLocal
|
||||
const map = new Map<string, { id: string; nickName: string }>()
|
||||
for (const p of s.people || []) {
|
||||
if (p?.id) map.set(p.id, { id: p.id, nickName: p.nickName || p.id })
|
||||
}
|
||||
for (const p of fromLocal) map.set(p.id, p)
|
||||
return [...map.values()]
|
||||
}
|
||||
|
||||
function isLocalSelected(s: OaFlowPreviewStep, userId: string) {
|
||||
return ((props.selectByNode || {})[s.id] || []).includes(userId)
|
||||
}
|
||||
|
||||
function avatarColor(id: string) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < id.length; i++) hash = (hash * 31 + id.charCodeAt(i)) >>> 0
|
||||
|
||||
@@ -232,7 +232,8 @@
|
||||
dept_leader: '部门负责人',
|
||||
direct_supervisor: '直属主管',
|
||||
supervisor_fallback_dept_leader: '部门负责人(主管回退)',
|
||||
initiator_select: '发起人自选'
|
||||
initiator_select: '发起人自选',
|
||||
multi: '多种选人方式'
|
||||
}
|
||||
return map[src] || src
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
const g = n.conditionGroups?.length || 0
|
||||
return `${g} 个分支`
|
||||
}
|
||||
return summarizeApprover(n.approverConfig)
|
||||
return summarizeApprover(n.approverConfig, n.type)
|
||||
}
|
||||
|
||||
function onAdd(cmd: string) {
|
||||
|
||||
@@ -69,15 +69,20 @@
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="assignee-seg__item"
|
||||
:class="{ 'is-on': cfg.assigneeType === opt.value }"
|
||||
@click="setAssigneeType(opt.value)"
|
||||
:class="{
|
||||
'is-on':
|
||||
editing.type === 'cc'
|
||||
? (cfg.assigneeTypes || []).includes(opt.value)
|
||||
: cfg.assigneeType === opt.value
|
||||
}"
|
||||
@click="onAssigneeOptionClick(opt.value)"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="cfg.assigneeType === 'users'">
|
||||
<el-form-item v-if="showUsersPicker">
|
||||
<template #label>
|
||||
指定成员
|
||||
<span class="form-label-tip">可多选,已选将作为本节点{{ editing.type === 'cc' ? '抄送' : '审批' }}人</span>
|
||||
@@ -151,7 +156,7 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="cfg.assigneeType === 'oa_roles'" label="工作流角色">
|
||||
<el-form-item v-if="showRolesPicker" label="工作流角色">
|
||||
<el-select
|
||||
v-model="cfg.oaRoleIds"
|
||||
multiple
|
||||
@@ -260,6 +265,7 @@
|
||||
createNode,
|
||||
ensureFlowRoot,
|
||||
findParent,
|
||||
normalizeCcAssigneeTypes,
|
||||
type ApproverConfig,
|
||||
type AssigneeType,
|
||||
type ConditionRule,
|
||||
@@ -312,6 +318,7 @@
|
||||
const editing = ref<FlowNode | null>(null)
|
||||
const cfg = reactive<ApproverConfig>({
|
||||
assigneeType: 'dept_leader',
|
||||
assigneeTypes: [],
|
||||
userIds: [],
|
||||
oaRoleIds: [],
|
||||
signMode: 'or'
|
||||
@@ -325,6 +332,15 @@
|
||||
{ label: '发起人自选', value: 'initiator_select' }
|
||||
]
|
||||
|
||||
const showUsersPicker = computed(() => {
|
||||
if (editing.value?.type === 'cc') return (cfg.assigneeTypes || []).includes('users')
|
||||
return cfg.assigneeType === 'users'
|
||||
})
|
||||
const showRolesPicker = computed(() => {
|
||||
if (editing.value?.type === 'cc') return (cfg.assigneeTypes || []).includes('oa_roles')
|
||||
return cfg.assigneeType === 'oa_roles'
|
||||
})
|
||||
|
||||
const drawerTitle = computed(() => {
|
||||
const t = editing.value?.type
|
||||
if (t === 'start') return '发起人'
|
||||
@@ -337,7 +353,7 @@
|
||||
const drawerSubtitle = computed(() => {
|
||||
const t = editing.value?.type
|
||||
if (t === 'approval') return '配置本节点审批人与多人会签规则'
|
||||
if (t === 'cc') return '配置本节点抄送人'
|
||||
if (t === 'cc') return '可同时选择多种方式,抄送时合并去重'
|
||||
if (t === 'condition') return '配置条件分支名称与可视化规则'
|
||||
if (t === 'start') return '流程起点节点'
|
||||
return '配置流程节点'
|
||||
@@ -410,6 +426,21 @@
|
||||
await onAssigneeTypeChange()
|
||||
}
|
||||
|
||||
async function onAssigneeOptionClick(type: AssigneeType) {
|
||||
if (editing.value?.type === 'cc') {
|
||||
const cur = [...(cfg.assigneeTypes || [])]
|
||||
const i = cur.indexOf(type)
|
||||
if (i >= 0) cur.splice(i, 1)
|
||||
else cur.push(type)
|
||||
cfg.assigneeTypes = cur
|
||||
if (cur[0]) cfg.assigneeType = cur[0]
|
||||
if (cur.includes('users')) await loadUsers()
|
||||
if (cur.includes('oa_roles')) await loadRoles()
|
||||
return
|
||||
}
|
||||
await setAssigneeType(type)
|
||||
}
|
||||
|
||||
async function loadRoles() {
|
||||
roleLoading.value = true
|
||||
try {
|
||||
@@ -537,13 +568,20 @@
|
||||
}))
|
||||
}
|
||||
const ac = editing.value.approverConfig || { assigneeType: 'dept_leader' as const }
|
||||
cfg.assigneeType = ac.assigneeType || 'dept_leader'
|
||||
if (editing.value.type === 'cc') {
|
||||
const types = normalizeCcAssigneeTypes(ac)
|
||||
cfg.assigneeTypes = types.length ? types : ['initiator_select']
|
||||
cfg.assigneeType = cfg.assigneeTypes[0]
|
||||
} else {
|
||||
cfg.assigneeTypes = []
|
||||
cfg.assigneeType = ac.assigneeType || 'dept_leader'
|
||||
}
|
||||
cfg.userIds = [...(ac.userIds || [])]
|
||||
cfg.oaRoleIds = [...(ac.oaRoleIds || [])]
|
||||
cfg.signMode = ac.signMode || 'or'
|
||||
memberPickBuffer.value = ''
|
||||
if (cfg.assigneeType === 'users') await loadUsers()
|
||||
if (cfg.assigneeType === 'oa_roles') await loadRoles()
|
||||
if (showUsersPicker.value) await loadUsers()
|
||||
if (showRolesPicker.value) await loadRoles()
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
@@ -594,12 +632,32 @@
|
||||
return
|
||||
}
|
||||
live.name = editing.value.name
|
||||
if (live.type === 'approval' || live.type === 'cc') {
|
||||
if (live.type === 'cc') {
|
||||
const types = [...new Set(cfg.assigneeTypes || [])].filter(Boolean) as AssigneeType[]
|
||||
if (!types.length) {
|
||||
ElMessage.warning('请至少选择一种选人方式')
|
||||
return
|
||||
}
|
||||
if (types.includes('users') && !(cfg.userIds || []).length) {
|
||||
ElMessage.warning('已勾选指定成员,请选择至少一名成员')
|
||||
return
|
||||
}
|
||||
if (types.includes('oa_roles') && !(cfg.oaRoleIds || []).length) {
|
||||
ElMessage.warning('已勾选工作流角色,请选择至少一个角色')
|
||||
return
|
||||
}
|
||||
live.approverConfig = {
|
||||
assigneeTypes: types,
|
||||
assigneeType: types[0],
|
||||
userIds: [...(cfg.userIds || [])],
|
||||
oaRoleIds: [...(cfg.oaRoleIds || [])]
|
||||
}
|
||||
} else if (live.type === 'approval') {
|
||||
live.approverConfig = {
|
||||
assigneeType: cfg.assigneeType,
|
||||
userIds: cfg.assigneeType === 'users' ? [...(cfg.userIds || [])] : undefined,
|
||||
oaRoleIds: cfg.assigneeType === 'oa_roles' ? [...(cfg.oaRoleIds || [])] : undefined,
|
||||
signMode: live.type === 'approval' ? cfg.signMode || 'or' : undefined
|
||||
signMode: cfg.signMode || 'or'
|
||||
}
|
||||
}
|
||||
if (live.type === 'condition' && editing.value.conditionGroups) {
|
||||
|
||||
@@ -8,7 +8,10 @@ export type AssigneeType =
|
||||
| 'initiator_select'
|
||||
|
||||
export interface ApproverConfig {
|
||||
assigneeType: AssigneeType
|
||||
/** 审批人:单选。抄送人:兼容旧数据 / 双写首项 */
|
||||
assigneeType?: AssigneeType
|
||||
/** 抄送人:多选选人方式(优先)。审批人:不使用 */
|
||||
assigneeTypes?: AssigneeType[]
|
||||
userIds?: string[]
|
||||
oaRoleIds?: string[]
|
||||
oaRoleCodes?: string[]
|
||||
@@ -78,7 +81,10 @@ export function createNode(type: 'approval' | 'cc' | 'condition'): FlowNode {
|
||||
id: newId('cc'),
|
||||
type: 'cc',
|
||||
name: '抄送人',
|
||||
approverConfig: { assigneeType: 'initiator_select' },
|
||||
approverConfig: {
|
||||
assigneeTypes: ['initiator_select'],
|
||||
assigneeType: 'initiator_select'
|
||||
},
|
||||
child: null
|
||||
}
|
||||
}
|
||||
@@ -118,6 +124,14 @@ export function ensureFlowRoot(raw: unknown): FlowNode {
|
||||
return createDefaultFlow()
|
||||
}
|
||||
|
||||
const VALID_ASSIGNEE: AssigneeType[] = [
|
||||
'users',
|
||||
'oa_roles',
|
||||
'dept_leader',
|
||||
'direct_supervisor',
|
||||
'initiator_select'
|
||||
]
|
||||
|
||||
const ASSIGNEE_LABEL: Record<AssigneeType, string> = {
|
||||
users: '指定成员',
|
||||
oa_roles: '工作流角色',
|
||||
@@ -126,7 +140,45 @@ const ASSIGNEE_LABEL: Record<AssigneeType, string> = {
|
||||
initiator_select: '发起人自选'
|
||||
}
|
||||
|
||||
export function summarizeApprover(cfg?: ApproverConfig): string {
|
||||
export function normalizeCcAssigneeTypes(cfg?: ApproverConfig | null): AssigneeType[] {
|
||||
if (!cfg) return []
|
||||
const raw = cfg.assigneeTypes
|
||||
if (Array.isArray(raw) && raw.length) {
|
||||
const out: AssigneeType[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const item of raw) {
|
||||
if (VALID_ASSIGNEE.includes(item as AssigneeType) && !seen.has(item)) {
|
||||
seen.add(item)
|
||||
out.push(item as AssigneeType)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
if (cfg.assigneeType && VALID_ASSIGNEE.includes(cfg.assigneeType)) {
|
||||
return [cfg.assigneeType]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function summarizeApprover(cfg?: ApproverConfig, nodeType?: FlowNodeType): string {
|
||||
if (nodeType === 'cc') {
|
||||
const types = normalizeCcAssigneeTypes(cfg)
|
||||
if (!types.length) return '未配置抄送人'
|
||||
const parts = types.map((t) => {
|
||||
const base = ASSIGNEE_LABEL[t] || t
|
||||
if (t === 'users') {
|
||||
const n = cfg?.userIds?.length || 0
|
||||
return n ? `${base}(${n}人)` : `${base}(未选)`
|
||||
}
|
||||
if (t === 'oa_roles') {
|
||||
const n = (cfg?.oaRoleIds?.length || 0) + (cfg?.oaRoleCodes?.length || 0)
|
||||
return n ? `${base}(已选${n})` : `${base}(未选)`
|
||||
}
|
||||
return base
|
||||
})
|
||||
const text = parts.join(' + ')
|
||||
return text.length > 36 ? `${text.slice(0, 34)}…` : text
|
||||
}
|
||||
if (!cfg?.assigneeType) return '未配置审批人'
|
||||
const base = ASSIGNEE_LABEL[cfg.assigneeType] || cfg.assigneeType
|
||||
if (cfg.assigneeType === 'users') {
|
||||
|
||||
Reference in new issue
Block a user