提交
This commit is contained in:
1 parent
f7d1004e6b
commit
310b2efe8a
91 files changed
+14420
-10840
No files matched your search
@@ -69,8 +69,6 @@
|
||||
filterable
|
||||
clearable
|
||||
:multiple="multiple"
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择人员"
|
||||
@update:model-value="onSelectChange"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
<!-- FormCreate 自定义控件:职位选择(Fresh Mint · PC 下拉完整 tag 换行;窄屏抽屉) -->
|
||||
<template>
|
||||
<div class="oa-post-select">
|
||||
<template v-if="disabled">
|
||||
<span class="oa-post-select__readonly">{{ displayText || '—' }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isNarrow">
|
||||
<button type="button" class="oa-post-select__trigger" @click="openDrawer">
|
||||
<span v-if="displayText" class="oa-post-select__trigger-text">{{ displayText }}</span>
|
||||
<span v-else class="oa-post-select__placeholder">请选择职位</span>
|
||||
<span class="oa-post-select__chev">▾</span>
|
||||
</button>
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
title="选择职位"
|
||||
direction="btt"
|
||||
size="75%"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
class="oa-post-drawer"
|
||||
modal-class="protocol-dialog-mask"
|
||||
>
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="搜索职位名称或编码"
|
||||
class="oa-post-select__search"
|
||||
/>
|
||||
<div class="oa-post-select__list">
|
||||
<label v-for="p in filteredOptions" :key="p.id" class="oa-post-select__row">
|
||||
<el-checkbox
|
||||
v-if="multiple"
|
||||
:model-value="pendingIds.includes(p.id)"
|
||||
@change="(v: boolean | string | number) => onNarrowCheck(p.id, !!v)"
|
||||
/>
|
||||
<el-radio
|
||||
v-else
|
||||
:model-value="pendingIds[0] || ''"
|
||||
:value="p.id"
|
||||
@change="() => onNarrowPick(p.id)"
|
||||
/>
|
||||
<span class="oa-post-select__row-label">{{ p.label }}</span>
|
||||
<span v-if="p.sub" class="oa-post-select__row-sub">{{ p.sub }}</span>
|
||||
</label>
|
||||
<div v-if="!filteredOptions.length" class="oa-post-select__empty">暂无职位</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="confirmDrawer">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<el-select
|
||||
v-else
|
||||
:model-value="selectModel"
|
||||
class="oa-post-select__select"
|
||||
filterable
|
||||
clearable
|
||||
:multiple="multiple"
|
||||
placeholder="请选择职位"
|
||||
@update:model-value="onSelectChange"
|
||||
>
|
||||
<el-option v-for="p in postOptions" :key="p.id" :label="p.label" :value="p.id">
|
||||
<span>{{ p.label }}</span>
|
||||
<span v-if="p.sub" class="oa-post-select__opt-sub">{{ p.sub }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Check, Close } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { fetchPostList } from '@/api/admin'
|
||||
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||
import {
|
||||
formatPostDisplay,
|
||||
normalizePostModelValue,
|
||||
type OaPostValueItem
|
||||
} from '@/utils/oa-post-value'
|
||||
|
||||
defineOptions({ name: 'post' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: unknown
|
||||
disabled?: boolean
|
||||
multiple?: boolean
|
||||
max?: number | null
|
||||
formCreateInject?: Record<string, unknown>
|
||||
}>(),
|
||||
{ multiple: true, max: null }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: OaPostValueItem | OaPostValueItem[] | null): void
|
||||
(e: 'change', v: OaPostValueItem | OaPostValueItem[] | null): void
|
||||
}>()
|
||||
|
||||
type PostOpt = { id: string; label: string; sub: string }
|
||||
|
||||
const nameMap = ref(new Map<string, string>())
|
||||
const postOptions = ref<PostOpt[]>([])
|
||||
const isNarrow = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
const keyword = ref('')
|
||||
const pendingIds = ref<string[]>([])
|
||||
let mql: MediaQueryList | null = null
|
||||
|
||||
const normalized = computed(() => normalizePostModelValue(props.modelValue, !!props.multiple))
|
||||
|
||||
const displayText = computed(() => formatPostDisplay(normalized.value))
|
||||
|
||||
const selectModel = computed(() => {
|
||||
const n = normalized.value
|
||||
if (props.multiple) {
|
||||
return Array.isArray(n) ? n.map((x) => x.id) : []
|
||||
}
|
||||
return n && !Array.isArray(n) ? n.id : undefined
|
||||
})
|
||||
|
||||
const filteredOptions = computed(() => {
|
||||
const q = keyword.value.trim().toLowerCase()
|
||||
if (!q) return postOptions.value
|
||||
return postOptions.value.filter(
|
||||
(p) =>
|
||||
p.label.toLowerCase().includes(q) ||
|
||||
p.sub.toLowerCase().includes(q) ||
|
||||
p.id.toLowerCase().includes(q)
|
||||
)
|
||||
})
|
||||
|
||||
function displayNameOf(id: string): string {
|
||||
return nameMap.value.get(id) || id
|
||||
}
|
||||
|
||||
function emitFromIds(ids: string[]) {
|
||||
const items: OaPostValueItem[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const id of ids) {
|
||||
const tid = String(id || '').trim()
|
||||
if (!tid || seen.has(tid)) continue
|
||||
seen.add(tid)
|
||||
items.push({ id: tid, name: displayNameOf(tid) })
|
||||
}
|
||||
if (props.multiple) {
|
||||
const max = props.max != null && Number(props.max) > 0 ? Number(props.max) : null
|
||||
if (max != null && items.length > max) {
|
||||
ElMessage.warning(`最多可选 ${max} 个职位`)
|
||||
return
|
||||
}
|
||||
emit('update:modelValue', items)
|
||||
emit('change', items)
|
||||
} else {
|
||||
const one = items[0] || null
|
||||
emit('update:modelValue', one)
|
||||
emit('change', one)
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectChange(val: string | string[] | null | undefined) {
|
||||
if (props.multiple) {
|
||||
emitFromIds(Array.isArray(val) ? val.map(String) : [])
|
||||
} else {
|
||||
emitFromIds(val ? [String(val)] : [])
|
||||
}
|
||||
}
|
||||
|
||||
function openDrawer() {
|
||||
const n = normalized.value
|
||||
if (props.multiple) {
|
||||
pendingIds.value = Array.isArray(n) ? n.map((x) => x.id) : []
|
||||
} else {
|
||||
pendingIds.value = n && !Array.isArray(n) ? [n.id] : []
|
||||
}
|
||||
keyword.value = ''
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
function onNarrowCheck(id: string, checked: boolean) {
|
||||
const set = new Set(pendingIds.value)
|
||||
if (checked) set.add(id)
|
||||
else set.delete(id)
|
||||
pendingIds.value = [...set]
|
||||
}
|
||||
|
||||
function onNarrowPick(id: string) {
|
||||
pendingIds.value = [id]
|
||||
}
|
||||
|
||||
function confirmDrawer() {
|
||||
emitFromIds(pendingIds.value)
|
||||
drawerVisible.value = false
|
||||
}
|
||||
|
||||
async function loadPosts() {
|
||||
try {
|
||||
const pageSize = MAX_ADMIN_LIST_PAGE_SIZE
|
||||
const raw: any[] = []
|
||||
let page = 1
|
||||
for (;;) {
|
||||
const res = await fetchPostList({ page, pageSize })
|
||||
const list = res?.list || []
|
||||
raw.push(...list)
|
||||
if (list.length < pageSize) break
|
||||
if (typeof res?.total === 'number' && raw.length >= res.total) break
|
||||
page += 1
|
||||
}
|
||||
const map = new Map<string, string>()
|
||||
const opts: PostOpt[] = []
|
||||
for (const row of raw) {
|
||||
if (String(row.status ?? '1') !== '1') continue
|
||||
const id = String(row.id || '').trim()
|
||||
if (!id) continue
|
||||
const name = String(row.postName || '').trim()
|
||||
const code = String(row.postCode || '').trim()
|
||||
const label = name || code || id
|
||||
map.set(id, label)
|
||||
opts.push({ id, label, sub: name && code && name !== code ? code : '' })
|
||||
}
|
||||
const n = normalized.value
|
||||
const kept = Array.isArray(n) ? n : n ? [n] : []
|
||||
for (const x of kept) {
|
||||
if (!map.has(x.id) && x.name) map.set(x.id, x.name)
|
||||
}
|
||||
nameMap.value = map
|
||||
postOptions.value = opts
|
||||
} catch {
|
||||
nameMap.value = new Map()
|
||||
postOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function syncNarrow(e?: MediaQueryList | MediaQueryListEvent) {
|
||||
const matches = e && 'matches' in e ? e.matches : !!mql?.matches
|
||||
isNarrow.value = matches
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPosts()
|
||||
mql = window.matchMedia('(max-width: 767px)')
|
||||
syncNarrow(mql)
|
||||
mql.addEventListener('change', syncNarrow)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
mql?.removeEventListener('change', syncNarrow)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.oa-post-select {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-post-select__select {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.oa-post-select__select :deep(.el-select),
|
||||
.oa-post-select__select :deep(.el-select__wrapper),
|
||||
.oa-post-select__select :deep(.el-tooltip__trigger) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-post-select__select :deep(.el-select__selection) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.oa-post-select__select :deep(.el-select__wrapper) {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.oa-post-select__trigger {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid var(--mint-border, #cbd5e1);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-post-select__readonly {
|
||||
color: var(--mint-text, #0f172a);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.oa-post-select__placeholder {
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.oa-post-select__trigger-text {
|
||||
min-width: 0;
|
||||
color: var(--mint-text, #0f172a);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.oa-post-select__chev {
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.oa-post-select__search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.oa-post-select__list {
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.oa-post-select__row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: 6px 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-post-select__row-label {
|
||||
min-width: 0;
|
||||
color: var(--mint-text, #0f172a);
|
||||
}
|
||||
|
||||
.oa-post-select__row-sub,
|
||||
.oa-post-select__opt-sub {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.oa-post-select__empty {
|
||||
padding: 16px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.oa-post-select__select :deep(.el-select__wrapper),
|
||||
.oa-post-select__trigger {
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -65,6 +65,23 @@
|
||||
:value="u.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="isPostField(rule.field)"
|
||||
:model-value="personValueIds(rule.value)"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="选择职位(任一命中)"
|
||||
:disabled="disabled"
|
||||
class="cond-rule-editor__post-select"
|
||||
@update:model-value="(v) => onValueChange(idx, Array.isArray(v) ? v.map(String) : [])"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in postOptions"
|
||||
:key="p.id"
|
||||
:label="p.label"
|
||||
:value="p.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="optionsFor(rule.field)?.length"
|
||||
:model-value="rule.value"
|
||||
@@ -115,7 +132,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { Delete, Plus } from '@element-plus/icons-vue'
|
||||
import { fetchUserList } from '@/api/admin'
|
||||
import { fetchPostList, fetchUserList } from '@/api/admin'
|
||||
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||
import {
|
||||
opsForFieldType,
|
||||
@@ -147,6 +164,7 @@
|
||||
|
||||
const localRules = computed(() => props.modelValue || [])
|
||||
const userOptions = ref<Array<{ id: string; label: string }>>([])
|
||||
const postOptions = ref<Array<{ id: string; label: string }>>([])
|
||||
|
||||
function meta(field: string) {
|
||||
return props.candidates.find((c) => c.field === field)
|
||||
@@ -157,6 +175,10 @@
|
||||
return t === 'person' || t === 'initiator'
|
||||
}
|
||||
|
||||
function isPostField(field: string) {
|
||||
return meta(field)?.type === 'post'
|
||||
}
|
||||
|
||||
function personValueIds(v: unknown): string[] {
|
||||
if (Array.isArray(v)) return v.map(String)
|
||||
if (v == null || v === '') return []
|
||||
@@ -164,7 +186,7 @@
|
||||
}
|
||||
|
||||
function defaultValueForType(type: string, opts?: ConditionCandidate['options']) {
|
||||
if (type === 'person' || type === 'initiator') return [] as string[]
|
||||
if (type === 'person' || type === 'post' || type === 'initiator') return [] as string[]
|
||||
return opts?.[0]?.value ?? ''
|
||||
}
|
||||
|
||||
@@ -285,10 +307,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPosts() {
|
||||
try {
|
||||
const pageSize = MAX_ADMIN_LIST_PAGE_SIZE
|
||||
const raw: any[] = []
|
||||
let page = 1
|
||||
for (;;) {
|
||||
const res = await fetchPostList({ page, pageSize })
|
||||
const list = res?.list || []
|
||||
raw.push(...list)
|
||||
if (list.length < pageSize) break
|
||||
if (typeof res?.total === 'number' && raw.length >= res.total) break
|
||||
page += 1
|
||||
}
|
||||
postOptions.value = raw
|
||||
.filter((r) => String(r.status ?? '1') === '1')
|
||||
.map((r) => {
|
||||
const id = String(r.id || '')
|
||||
const name = String(r.postName || '').trim()
|
||||
const code = String(r.postCode || '').trim()
|
||||
return { id, label: name || code || id }
|
||||
})
|
||||
.filter((p) => p.id)
|
||||
} catch {
|
||||
postOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.candidates.some((c) => c.type === 'person' || c.type === 'initiator')) {
|
||||
loadUsers()
|
||||
}
|
||||
if (props.candidates.some((c) => c.type === 'post')) {
|
||||
loadPosts()
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -297,6 +349,13 @@
|
||||
if (need && !userOptions.value.length) loadUsers()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.candidates.some((c) => c.type === 'post'),
|
||||
(need) => {
|
||||
if (need && !postOptions.value.length) loadPosts()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -336,4 +395,8 @@
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cond-rule-editor__post-select :deep(.el-select__selection) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
@@ -156,34 +156,110 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="showRolesPicker" label="工作流角色">
|
||||
<el-select
|
||||
v-model="cfg.oaRoleIds"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="选择角色"
|
||||
style="width: 100%"
|
||||
:loading="roleLoading"
|
||||
>
|
||||
<el-option-group label="预置">
|
||||
<el-option
|
||||
v-for="r in presetRoles"
|
||||
:key="r.id"
|
||||
:label="`${r.roleName}(${r.roleCode})`"
|
||||
:value="r.id"
|
||||
/>
|
||||
</el-option-group>
|
||||
<el-option-group label="自定义">
|
||||
<el-option
|
||||
v-for="r in customRoles"
|
||||
:key="r.id"
|
||||
:label="`${r.roleName}(${r.roleCode})`"
|
||||
:value="r.id"
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<el-form-item v-if="showRolesPicker">
|
||||
<template #label>
|
||||
工作流角色
|
||||
<span class="form-label-tip">可多选,已选角色成员将作为本节点{{ editing.type === 'cc' ? '抄送' : '审批' }}人</span>
|
||||
</template>
|
||||
<div v-loading="roleLoading" class="member-picker">
|
||||
<el-select
|
||||
v-model="rolePickBuffer"
|
||||
filterable
|
||||
clearable
|
||||
:loading="roleLoading"
|
||||
placeholder="搜索角色名称 / 编码,点击添加"
|
||||
style="width: 100%"
|
||||
popper-class="oa-flow-member-select-popper"
|
||||
@change="onRolePick"
|
||||
>
|
||||
<el-option-group v-if="presetRoles.length" label="预置">
|
||||
<el-option
|
||||
v-for="r in presetRoles"
|
||||
:key="r.id"
|
||||
:label="roleOptionLabel(r)"
|
||||
:value="r.id"
|
||||
:disabled="(cfg.oaRoleIds || []).includes(r.id)"
|
||||
>
|
||||
<div class="member-option">
|
||||
<span class="member-avatar" :style="{ background: avatarColor(r.id) }">
|
||||
{{ avatarInitial(r.roleName) }}
|
||||
</span>
|
||||
<span class="member-option__meta">
|
||||
<span class="member-option__name">{{ r.roleName || '未命名' }}</span>
|
||||
<span v-if="r.roleCode" class="member-option__sub">
|
||||
编码 {{ r.roleCode }}
|
||||
</span>
|
||||
</span>
|
||||
<el-tag
|
||||
v-if="(cfg.oaRoleIds || []).includes(r.id)"
|
||||
size="small"
|
||||
type="info"
|
||||
effect="plain"
|
||||
class="member-option__tag"
|
||||
>
|
||||
已选
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="customRoles.length" label="自定义">
|
||||
<el-option
|
||||
v-for="r in customRoles"
|
||||
:key="r.id"
|
||||
:label="roleOptionLabel(r)"
|
||||
:value="r.id"
|
||||
:disabled="(cfg.oaRoleIds || []).includes(r.id)"
|
||||
>
|
||||
<div class="member-option">
|
||||
<span class="member-avatar" :style="{ background: avatarColor(r.id) }">
|
||||
{{ avatarInitial(r.roleName) }}
|
||||
</span>
|
||||
<span class="member-option__meta">
|
||||
<span class="member-option__name">{{ r.roleName || '未命名' }}</span>
|
||||
<span v-if="r.roleCode" class="member-option__sub">
|
||||
编码 {{ r.roleCode }}
|
||||
</span>
|
||||
</span>
|
||||
<el-tag
|
||||
v-if="(cfg.oaRoleIds || []).includes(r.id)"
|
||||
size="small"
|
||||
type="info"
|
||||
effect="plain"
|
||||
class="member-option__tag"
|
||||
>
|
||||
已选
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
|
||||
<div
|
||||
class="member-chips"
|
||||
:class="{ 'is-empty': !(cfg.oaRoleIds && cfg.oaRoleIds.length) }"
|
||||
>
|
||||
<template v-if="cfg.oaRoleIds && cfg.oaRoleIds.length">
|
||||
<span v-for="id in cfg.oaRoleIds" :key="id" class="member-chip">
|
||||
<span class="member-avatar member-avatar--sm" :style="{ background: avatarColor(id) }">
|
||||
{{ avatarInitial(resolveRole(id)?.roleName) }}
|
||||
</span>
|
||||
<span class="member-chip__name" :title="roleChipTitle(id)">
|
||||
{{ resolveRole(id)?.roleName || id }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="member-chip__rm"
|
||||
:aria-label="`移除 ${resolveRole(id)?.roleName || id}`"
|
||||
@click="removeRole(id)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>暂未选择角色,请从上方搜索添加</template>
|
||||
</div>
|
||||
<p class="member-picked-count">已选 {{ cfg.oaRoleIds?.length || 0 }} 个角色</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="editing.type === 'approval'" label="多人审批方式">
|
||||
@@ -364,6 +440,7 @@
|
||||
const roleOptions = ref<any[]>([])
|
||||
const userOptions = ref<UserOption[]>([])
|
||||
const memberPickBuffer = ref('')
|
||||
const rolePickBuffer = ref('')
|
||||
|
||||
const presetRoles = computed(() =>
|
||||
roleOptions.value.filter((r) => r.roleKind === 'preset' || ['GM', 'CHAIRMAN'].includes(r.roleCode))
|
||||
@@ -387,11 +464,27 @@
|
||||
return userOptions.value.find((u) => u.id === id)
|
||||
}
|
||||
|
||||
function resolveRole(id: string) {
|
||||
return roleOptions.value.find((r) => r.id === id)
|
||||
}
|
||||
|
||||
function chipTitle(id: string) {
|
||||
const u = resolveUser(id)
|
||||
return u ? memberOptionLabel(u) : id
|
||||
}
|
||||
|
||||
function roleOptionLabel(r: { roleName?: string; roleCode?: string }) {
|
||||
const name = (r.roleName || '').trim() || '未命名'
|
||||
const code = (r.roleCode || '').trim()
|
||||
if (code && code !== name) return `${name}(${code})`
|
||||
return name
|
||||
}
|
||||
|
||||
function roleChipTitle(id: string) {
|
||||
const r = resolveRole(id)
|
||||
return r ? roleOptionLabel(r) : id
|
||||
}
|
||||
|
||||
function memberOptionLabel(u: UserOption) {
|
||||
const name = (u.nickName || '').trim() || '未命名'
|
||||
const no = (u.userNo || '').trim()
|
||||
@@ -421,6 +514,20 @@
|
||||
cfg.userIds = cfg.userIds.filter((x) => x !== id)
|
||||
}
|
||||
|
||||
function onRolePick(id: string) {
|
||||
if (!id) return
|
||||
if (!cfg.oaRoleIds) cfg.oaRoleIds = []
|
||||
if (!cfg.oaRoleIds.includes(id)) cfg.oaRoleIds.push(id)
|
||||
nextTick(() => {
|
||||
rolePickBuffer.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
function removeRole(id: string) {
|
||||
if (!cfg.oaRoleIds) return
|
||||
cfg.oaRoleIds = cfg.oaRoleIds.filter((x) => x !== id)
|
||||
}
|
||||
|
||||
async function setAssigneeType(type: AssigneeType) {
|
||||
cfg.assigneeType = type
|
||||
await onAssigneeTypeChange()
|
||||
@@ -580,6 +687,7 @@
|
||||
cfg.oaRoleIds = [...(ac.oaRoleIds || [])]
|
||||
cfg.signMode = ac.signMode || 'or'
|
||||
memberPickBuffer.value = ''
|
||||
rolePickBuffer.value = ''
|
||||
if (showUsersPicker.value) await loadUsers()
|
||||
if (showRolesPicker.value) await loadRoles()
|
||||
drawerVisible.value = true
|
||||
|
||||
@@ -5,6 +5,7 @@ export const CONDITION_ELIGIBLE_TYPES = new Set([
|
||||
'number',
|
||||
'money',
|
||||
'person',
|
||||
'post',
|
||||
'radio',
|
||||
'select',
|
||||
'datePicker',
|
||||
@@ -31,7 +32,7 @@ export interface ConditionCandidate {
|
||||
}
|
||||
|
||||
export function opsForFieldType(type: string): Array<{ value: ConditionOp; label: string }> {
|
||||
if (type === 'person' || type === 'initiator') {
|
||||
if (type === 'person' || type === 'post' || type === 'initiator') {
|
||||
return [{ value: 'contains', label: '包含' }]
|
||||
}
|
||||
// 单选/下拉为离散选项,仅等于/不等于;输入框常填数字参与区间比较,与数字/金额/日期同套运算符
|
||||
|
||||
Reference in new issue
Block a user