feat(oa): allow picking initiator-select users inside flow preview
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
624ac87476
commit
eafd3c7d9e
1 file changed
+231
-3
@@ -28,7 +28,33 @@
|
||||
<span v-if="s.resolveError" class="oa-flow-card__err" :title="s.resolveError">
|
||||
{{ s.resolveError }}
|
||||
</span>
|
||||
<span v-if="(s.people || []).length" class="oa-flow-card__people">
|
||||
<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">
|
||||
<span class="oa-person-chip__dot">{{ avatarChar(p.nickName) }}</span>
|
||||
{{ p.nickName }}
|
||||
<button
|
||||
type="button"
|
||||
class="oa-person-chip__x"
|
||||
title="移除"
|
||||
@click="removeUser(s.id, p.id)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else class="oa-flow-card__placeholder">
|
||||
{{ s.type === 'cc' ? '请选择抄送人' : '请选择审批人' }}
|
||||
</span>
|
||||
</span>
|
||||
<button type="button" class="oa-flow-add-btn" title="添加" @click="openPicker(s)">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else-if="(s.people || []).length" class="oa-flow-card__people">
|
||||
<span v-for="p in s.people" :key="p.id" class="oa-person-chip">
|
||||
<span class="oa-person-chip__dot">{{ avatarChar(p.nickName) }}</span>
|
||||
{{ p.nickName || p.id }}
|
||||
@@ -38,15 +64,60 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="pickerVisible"
|
||||
:title="pickerTitle"
|
||||
width="520px"
|
||||
align-center
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="dialog-form-layout"
|
||||
modal-class="protocol-dialog-mask"
|
||||
@closed="onPickerClosed"
|
||||
>
|
||||
<el-select
|
||||
v-model="pickerIds"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择人员"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userOptions"
|
||||
:key="u.id"
|
||||
:label="userOptionLabel(u)"
|
||||
:value="u.id"
|
||||
/>
|
||||
</el-select>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="pickerVisible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
<span>取消</span>
|
||||
</el-button>
|
||||
<el-button type="primary" @click="confirmPicker">
|
||||
<el-icon><Check /></el-icon>
|
||||
<span>确定</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Check, Close, Plus } from '@element-plus/icons-vue'
|
||||
import type { OaFlowPreviewStep } from '@/api/oaApproval'
|
||||
|
||||
defineOptions({ name: 'OaFlowPreview' })
|
||||
|
||||
type UserOption = { id: string; nickName: string; userNo?: string }
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
steps?: OaFlowPreviewStep[]
|
||||
@@ -54,18 +125,100 @@
|
||||
hint?: string
|
||||
resolved?: boolean
|
||||
pendingMessage?: string
|
||||
selectByNode?: Record<string, string[]>
|
||||
userOptions?: UserOption[]
|
||||
}>(),
|
||||
{
|
||||
steps: () => [],
|
||||
loading: false,
|
||||
hint: '按当前发起人解析审批人',
|
||||
resolved: true,
|
||||
pendingMessage: '必填信息填写后,流程将自动显示'
|
||||
pendingMessage: '必填信息填写后,流程将自动显示',
|
||||
selectByNode: () => ({}),
|
||||
userOptions: () => []
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:selectByNode': [Record<string, string[]>]
|
||||
'select-change': []
|
||||
}>()
|
||||
|
||||
const steps = computed(() => props.steps || [])
|
||||
const resolved = computed(() => props.resolved !== false)
|
||||
const userOptions = computed(() => props.userOptions || [])
|
||||
|
||||
const pickerVisible = ref(false)
|
||||
const pickerNodeId = ref('')
|
||||
const pickerNodeType = ref('')
|
||||
const pickerIds = ref<string[]>([])
|
||||
|
||||
const pickerTitle = computed(() =>
|
||||
pickerNodeType.value === 'cc' ? '选择抄送人' : '选择审批人'
|
||||
)
|
||||
|
||||
function isInitiatorSelect(s: OaFlowPreviewStep) {
|
||||
return (
|
||||
s.assigneeType === 'initiator_select' && (s.type === 'approval' || s.type === 'cc')
|
||||
)
|
||||
}
|
||||
|
||||
function localPeople(s: OaFlowPreviewStep) {
|
||||
const ids = (props.selectByNode || {})[s.id] || []
|
||||
const byId = new Map(userOptions.value.map((u) => [u.id, u]))
|
||||
return ids.map((id) => {
|
||||
const u = byId.get(id)
|
||||
return { id, nickName: u?.nickName || id }
|
||||
})
|
||||
}
|
||||
|
||||
function userOptionLabel(u: UserOption) {
|
||||
const name = u.nickName || u.id
|
||||
return u.userNo ? `${name}(${u.userNo})` : name
|
||||
}
|
||||
|
||||
function updateSelect(nodeId: string, ids: string[]) {
|
||||
const next: Record<string, string[]> = { ...(props.selectByNode || {}) }
|
||||
const uniq: string[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const id of ids) {
|
||||
if (!id || seen.has(id)) continue
|
||||
seen.add(id)
|
||||
uniq.push(id)
|
||||
}
|
||||
if (uniq.length) next[nodeId] = uniq
|
||||
else delete next[nodeId]
|
||||
emit('update:selectByNode', next)
|
||||
emit('select-change')
|
||||
}
|
||||
|
||||
function removeUser(nodeId: string, userId: string) {
|
||||
const cur = (props.selectByNode || {})[nodeId] || []
|
||||
updateSelect(
|
||||
nodeId,
|
||||
cur.filter((id) => id !== userId)
|
||||
)
|
||||
}
|
||||
|
||||
function openPicker(s: OaFlowPreviewStep) {
|
||||
pickerNodeId.value = s.id
|
||||
pickerNodeType.value = s.type || ''
|
||||
pickerIds.value = [...((props.selectByNode || {})[s.id] || [])]
|
||||
pickerVisible.value = true
|
||||
}
|
||||
|
||||
function confirmPicker() {
|
||||
if (pickerNodeId.value) {
|
||||
updateSelect(pickerNodeId.value, [...pickerIds.value])
|
||||
}
|
||||
pickerVisible.value = false
|
||||
}
|
||||
|
||||
function onPickerClosed() {
|
||||
pickerNodeId.value = ''
|
||||
pickerNodeType.value = ''
|
||||
pickerIds.value = []
|
||||
}
|
||||
|
||||
function signBadge(s: OaFlowPreviewStep) {
|
||||
if (s.type === 'cc' || s.signMode === 'cc') return 'cc'
|
||||
@@ -222,6 +375,17 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.oa-flow-card__select {
|
||||
display: inline-flex;
|
||||
flex: 1 1 140px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.oa-flow-card__people {
|
||||
display: inline-flex;
|
||||
flex: 1 1 140px;
|
||||
@@ -233,6 +397,45 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.oa-flow-card__select .oa-flow-card__people {
|
||||
flex: 0 1 auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.oa-flow-card__placeholder {
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.oa-flow-add-btn {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
color: var(--mint-text, #334155);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary, #10b981);
|
||||
border-color: #a7f3d0;
|
||||
background: var(--primary-bg, #ecfdf5);
|
||||
}
|
||||
}
|
||||
|
||||
.oa-flow-card__select .oa-flow-add-btn {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.oa-person-chip {
|
||||
display: inline-flex;
|
||||
gap: 3px;
|
||||
@@ -258,6 +461,21 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.oa-person-chip__x {
|
||||
margin: 0;
|
||||
padding: 0 2px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
.oa-flow-badge {
|
||||
flex-shrink: 0;
|
||||
padding: 0 5px;
|
||||
@@ -282,4 +500,14 @@
|
||||
background: #ecfeff;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
|
||||
.el-button .el-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user