857 lines
21 KiB
Vue
857 lines
21 KiB
Vue
<!-- 发起审批 · 表单底部审批流预览(对齐 docs/mockups/oa-approval-start-flow-preview-demo.html) -->
|
||
<template>
|
||
<div v-loading="loading" class="oa-flow-preview">
|
||
<div class="oa-flow-preview__title">
|
||
<span>审批流程</span>
|
||
<span v-if="resolved && hint" class="oa-flow-preview__hint">{{ hint }}</span>
|
||
</div>
|
||
<div v-if="!resolved" class="oa-flow-preview__pending">
|
||
{{ pendingMessage || '必填信息填写后,流程将自动显示' }}
|
||
</div>
|
||
<div v-else class="oa-flow-preview__chain">
|
||
<div v-if="!loading && !steps.length" class="oa-flow-preview__empty">暂无流程配置</div>
|
||
<div v-for="(s, idx) in steps" :key="s.id || idx" class="oa-flow-step">
|
||
<div class="oa-flow-rail">
|
||
<div class="oa-flow-dot" :class="{ end: s.type === 'end' }" />
|
||
<div v-if="idx < steps.length - 1" class="oa-flow-line" />
|
||
</div>
|
||
<div class="oa-flow-card">
|
||
<div class="oa-flow-card__row">
|
||
<span class="oa-flow-card__name">{{ s.name || '-' }}</span>
|
||
<span v-if="signBadge(s)" class="oa-flow-badge" :class="signBadge(s)">
|
||
{{ signBadgeText(s) }}
|
||
</span>
|
||
<template v-if="s.meta">
|
||
<span class="oa-flow-card__sep">·</span>
|
||
<span class="oa-flow-card__meta" :title="s.meta">{{ s.meta }}</span>
|
||
</template>
|
||
<span v-if="s.resolveError" class="oa-flow-card__err" :title="s.resolveError">
|
||
{{ s.resolveError }}
|
||
</span>
|
||
<template v-if="isInitiatorSelect(s)">
|
||
<span class="oa-flow-card__select">
|
||
<span class="oa-flow-card__people">
|
||
<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="移除"
|
||
@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 }}
|
||
</span>
|
||
</span>
|
||
</div>
|
||
</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"
|
||
>
|
||
<div class="member-picker">
|
||
<el-select
|
||
v-model="memberPickBuffer"
|
||
filterable
|
||
clearable
|
||
placeholder="搜索姓名 / 工号,点击添加"
|
||
style="width: 100%"
|
||
popper-class="oa-flow-member-select-popper"
|
||
@change="onMemberPick"
|
||
>
|
||
<el-option
|
||
v-for="u in userOptions"
|
||
:key="u.id"
|
||
:label="memberOptionLabel(u)"
|
||
:value="u.id"
|
||
:disabled="pickerIds.includes(u.id)"
|
||
>
|
||
<div class="member-option">
|
||
<span class="member-avatar" :style="{ background: avatarColor(u.id) }">
|
||
{{ avatarInitial(u.nickName) }}
|
||
</span>
|
||
<span class="member-option__meta">
|
||
<span class="member-option__name">{{ u.nickName || '未命名' }}</span>
|
||
<span v-if="memberOptionSub(u)" class="member-option__sub">
|
||
{{ memberOptionSub(u) }}
|
||
</span>
|
||
</span>
|
||
<el-tag
|
||
v-if="pickerIds.includes(u.id)"
|
||
size="small"
|
||
type="info"
|
||
effect="plain"
|
||
class="member-option__tag"
|
||
>
|
||
已选
|
||
</el-tag>
|
||
</div>
|
||
</el-option>
|
||
</el-select>
|
||
|
||
<div class="member-chips" :class="{ 'is-empty': !pickerIds.length }">
|
||
<template v-if="pickerIds.length">
|
||
<span v-for="id in pickerIds" :key="id" class="member-chip">
|
||
<span
|
||
class="member-avatar member-avatar--sm"
|
||
:style="{ background: avatarColor(id) }"
|
||
>
|
||
{{ avatarInitial(resolveUser(id)?.nickName) }}
|
||
</span>
|
||
<span class="member-chip__name" :title="chipTitle(id)">
|
||
{{ resolveUser(id)?.nickName || id }}
|
||
</span>
|
||
<button
|
||
type="button"
|
||
class="member-chip__rm"
|
||
:aria-label="`移除 ${resolveUser(id)?.nickName || id}`"
|
||
@click="removePickerMember(id)"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
</template>
|
||
<template v-else>暂未选择成员,请从上方搜索添加</template>
|
||
</div>
|
||
<p class="member-picked-count">已选 {{ pickerIds.length }} 人</p>
|
||
</div>
|
||
<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, nextTick, 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 AVATAR_COLORS = [
|
||
'#059669',
|
||
'#0ea5e9',
|
||
'#8b5cf6',
|
||
'#d97706',
|
||
'#ef4444',
|
||
'#14b8a6',
|
||
'#6366f1',
|
||
'#ec4899'
|
||
]
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
steps?: OaFlowPreviewStep[]
|
||
loading?: boolean
|
||
hint?: string
|
||
resolved?: boolean
|
||
pendingMessage?: string
|
||
selectByNode?: Record<string, string[]>
|
||
userOptions?: UserOption[]
|
||
}>(),
|
||
{
|
||
steps: () => [],
|
||
loading: false,
|
||
hint: '按当前发起人解析审批人',
|
||
resolved: true,
|
||
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 memberPickBuffer = ref('')
|
||
|
||
const pickerTitle = computed(() =>
|
||
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) {
|
||
if (!(s.type === 'approval' || s.type === 'cc')) return false
|
||
return stepAssigneeTypes(s).includes('initiator_select')
|
||
}
|
||
|
||
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 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
|
||
return AVATAR_COLORS[hash % AVATAR_COLORS.length]
|
||
}
|
||
|
||
function avatarInitial(name?: string) {
|
||
const t = (name || '?').trim()
|
||
return t ? t.slice(0, 1) : '?'
|
||
}
|
||
|
||
function resolveUser(id: string) {
|
||
return userOptions.value.find((u) => u.id === id)
|
||
}
|
||
|
||
function memberOptionLabel(u: UserOption) {
|
||
const name = (u.nickName || '').trim() || '未命名'
|
||
const no = (u.userNo || '').trim()
|
||
if (no && no !== name) return `${name}(${no})`
|
||
return name
|
||
}
|
||
|
||
function memberOptionSub(u: UserOption) {
|
||
const name = (u.nickName || '').trim()
|
||
const no = (u.userNo || '').trim()
|
||
if (!no || no === name) return ''
|
||
return `账号 ${no}`
|
||
}
|
||
|
||
function chipTitle(id: string) {
|
||
const u = resolveUser(id)
|
||
return u ? memberOptionLabel(u) : id
|
||
}
|
||
|
||
function onMemberPick(id: string) {
|
||
if (!id) return
|
||
if (!pickerIds.value.includes(id)) {
|
||
pickerIds.value = [...pickerIds.value, id]
|
||
}
|
||
nextTick(() => {
|
||
memberPickBuffer.value = ''
|
||
})
|
||
}
|
||
|
||
function removePickerMember(id: string) {
|
||
pickerIds.value = pickerIds.value.filter((x) => x !== id)
|
||
}
|
||
|
||
function updateSelect(nodeId: string, ids: string[]) {
|
||
const next: Record<string, string[]> = { ...(props.selectByNode || {}) }
|
||
const uniq: string[] = []
|
||
const seen = new Set<string>()
|
||
for (const x of ids) {
|
||
if (!x || seen.has(x)) continue
|
||
seen.add(x)
|
||
uniq.push(x)
|
||
}
|
||
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] || [])]
|
||
memberPickBuffer.value = ''
|
||
pickerVisible.value = true
|
||
}
|
||
|
||
function confirmPicker() {
|
||
if (pickerNodeId.value) {
|
||
updateSelect(pickerNodeId.value, [...pickerIds.value])
|
||
}
|
||
pickerVisible.value = false
|
||
}
|
||
|
||
function onPickerClosed() {
|
||
pickerNodeId.value = ''
|
||
pickerNodeType.value = ''
|
||
pickerIds.value = []
|
||
memberPickBuffer.value = ''
|
||
}
|
||
|
||
function signBadge(s: OaFlowPreviewStep) {
|
||
if (s.type === 'cc' || s.signMode === 'cc') return 'cc'
|
||
if (s.signMode === 'and') return 'and'
|
||
if (s.signMode === 'or') return 'or'
|
||
return ''
|
||
}
|
||
|
||
function signBadgeText(s: OaFlowPreviewStep) {
|
||
const k = signBadge(s)
|
||
if (k === 'cc') return '抄送'
|
||
if (k === 'and') return '会签'
|
||
if (k === 'or') return '或签'
|
||
return ''
|
||
}
|
||
|
||
function avatarChar(name: string) {
|
||
const n = (name || '').trim()
|
||
return n ? n.slice(0, 1) : '?'
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.oa-flow-preview {
|
||
margin: 8px 0 12px;
|
||
padding: 12px 12px 8px;
|
||
background: #f8fafc;
|
||
border: 1px solid var(--mint-border, #e2e8f0);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.oa-flow-preview__title {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
color: var(--mint-text, #334155);
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.oa-flow-preview__hint {
|
||
color: var(--mint-text-muted, #94a3b8);
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.oa-flow-preview__pending {
|
||
padding: 16px 8px 20px;
|
||
color: var(--mint-text-muted, #94a3b8);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
text-align: center;
|
||
}
|
||
|
||
.oa-flow-preview__empty {
|
||
padding: 8px 0;
|
||
color: var(--mint-text-muted, #94a3b8);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.oa-flow-preview__chain {
|
||
min-height: 24px;
|
||
}
|
||
|
||
.oa-flow-step {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.oa-flow-rail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
width: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.oa-flow-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
margin-top: 12px;
|
||
background: #94a3b8;
|
||
border-radius: 50%;
|
||
box-shadow: 0 0 0 3px #e2e8f0;
|
||
flex-shrink: 0;
|
||
|
||
&.end {
|
||
background: #cbd5e1;
|
||
box-shadow: 0 0 0 3px #f1f5f9;
|
||
}
|
||
}
|
||
|
||
.oa-flow-line {
|
||
width: 2px;
|
||
flex: 1;
|
||
min-height: 10px;
|
||
margin: 4px 0;
|
||
background: #e2e8f0;
|
||
}
|
||
|
||
.oa-flow-card {
|
||
flex: 1;
|
||
min-width: 0;
|
||
margin-bottom: 6px;
|
||
padding: 9px 12px;
|
||
background: #fff;
|
||
border: 1px solid var(--mint-border, #e2e8f0);
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.oa-flow-card__row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px 8px;
|
||
align-items: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
.oa-flow-card__name {
|
||
flex-shrink: 0;
|
||
color: var(--mint-text-strong, #0f172a);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.oa-flow-card__meta {
|
||
flex-shrink: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
color: var(--mint-text-muted, #94a3b8);
|
||
font-size: 11px;
|
||
line-height: 1.2;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.oa-flow-card__sep {
|
||
flex-shrink: 0;
|
||
color: #cbd5e1;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.oa-flow-card__err {
|
||
flex-shrink: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
color: #d97706;
|
||
font-size: 11px;
|
||
text-overflow: ellipsis;
|
||
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;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
min-width: 0;
|
||
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;
|
||
align-items: center;
|
||
padding: 1px 6px 1px 2px;
|
||
color: var(--mint-text, #334155);
|
||
font-size: 11px;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
background: #f1f5f9;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.oa-person-chip__dot {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
color: #fff;
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
background: var(--primary, #10b981);
|
||
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;
|
||
font-size: 10px;
|
||
font-weight: 500;
|
||
line-height: 16px;
|
||
white-space: nowrap;
|
||
border-radius: 4px;
|
||
|
||
&.or {
|
||
color: #d97706;
|
||
background: #fff7ed;
|
||
}
|
||
|
||
&.and {
|
||
color: #2563eb;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
&.cc {
|
||
color: #0891b2;
|
||
background: #ecfeff;
|
||
}
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
gap: 8px;
|
||
justify-content: flex-end;
|
||
|
||
.el-button .el-icon {
|
||
margin-right: 6px;
|
||
}
|
||
}
|
||
|
||
.member-picker {
|
||
width: 100%;
|
||
}
|
||
|
||
.member-option {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
width: 100%;
|
||
min-height: 40px;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.member-option__meta {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
justify-content: center;
|
||
min-width: 0;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.member-option__name {
|
||
overflow: hidden;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.member-option__sub {
|
||
overflow: hidden;
|
||
color: var(--mint-text-muted, #64748b);
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.member-option__tag {
|
||
flex-shrink: 0;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.member-avatar {
|
||
display: inline-flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 36px;
|
||
height: 36px;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
text-align: center;
|
||
border-radius: 50%;
|
||
|
||
&--sm {
|
||
width: 28px;
|
||
height: 28px;
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
|
||
.member-chips {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
min-height: 44px;
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: #f8fafc;
|
||
border: 1px dashed #cbd5e1;
|
||
border-radius: 8px;
|
||
|
||
&.is-empty {
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--mint-text-muted, #94a3b8);
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
|
||
.member-chip {
|
||
display: inline-flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
max-width: 100%;
|
||
padding: 4px 8px 4px 4px;
|
||
background: #fff;
|
||
border: 1px solid var(--mint-border, #e2e8f0);
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.member-chip__name {
|
||
max-width: 120px;
|
||
overflow: hidden;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.member-chip__rm {
|
||
display: grid;
|
||
flex-shrink: 0;
|
||
place-items: center;
|
||
width: 20px;
|
||
height: 20px;
|
||
padding: 0;
|
||
color: var(--mint-text-muted, #64748b);
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
background: #f1f5f9;
|
||
border: 0;
|
||
border-radius: 50%;
|
||
|
||
&:hover {
|
||
color: #dc2626;
|
||
background: #fee2e2;
|
||
}
|
||
}
|
||
|
||
.member-picked-count {
|
||
margin: 8px 0 0;
|
||
color: var(--mint-text-muted, #94a3b8);
|
||
font-size: 12px;
|
||
}
|
||
</style>
|
||
|
||
<!-- 下拉挂 body,须非 scoped -->
|
||
<style lang="scss">
|
||
.oa-flow-member-select-popper {
|
||
.el-select-dropdown__list {
|
||
padding: 8px;
|
||
}
|
||
|
||
.el-select-dropdown__item {
|
||
height: auto !important;
|
||
min-height: 56px;
|
||
margin-bottom: 8px;
|
||
padding: 10px 12px !important;
|
||
line-height: normal !important;
|
||
border-radius: 8px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
&.is-disabled {
|
||
opacity: 0.55;
|
||
}
|
||
|
||
&.hover,
|
||
&:hover {
|
||
background: var(--primary-bg, #ecfdf5);
|
||
}
|
||
}
|
||
|
||
.member-option {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
width: 100%;
|
||
min-height: 36px;
|
||
}
|
||
|
||
.member-option__meta {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
justify-content: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
.member-option__name {
|
||
overflow: hidden;
|
||
color: var(--mint-text, #0f172a);
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.member-option__sub {
|
||
overflow: hidden;
|
||
color: var(--mint-text-muted, #64748b);
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.member-option__tag {
|
||
flex-shrink: 0;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.member-avatar {
|
||
display: inline-flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 36px;
|
||
height: 36px;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
text-align: center;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
</style>
|