feat(oa): tree multi-select for dept condition values

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-06 15:40:54 +08:00
1 parent f874447ae2
commit 6beb243604
1 file changed
+66 -2
@@ -82,6 +82,23 @@
:value="p.id"
/>
</el-select>
<el-tree-select
v-else-if="isDeptLikeField(rule.field)"
:model-value="personValueIds(rule.value)"
multiple
filterable
clearable
check-strictly
collapse-tags
collapse-tags-tooltip
default-expand-all
:data="deptTreeData"
:props="{ value: 'value', label: 'label', children: 'children' }"
placeholder="选择部门(任一命中)"
:disabled="disabled"
class="cond-rule-editor__dept-select"
@update:model-value="(v) => onValueChange(idx, Array.isArray(v) ? v.map(String) : [])"
/>
<el-select
v-else-if="optionsFor(rule.field)?.length"
:model-value="rule.value"
@@ -132,7 +149,7 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { Delete, Plus } from '@element-plus/icons-vue'
import { fetchPostList, fetchUserList } from '@/api/admin'
import { fetchDeptTree, fetchPostList, fetchUserList } from '@/api/admin'
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
import {
opsForFieldType,
@@ -166,6 +183,10 @@
const userOptions = ref<Array<{ id: string; label: string }>>([])
const postOptions = ref<Array<{ id: string; label: string }>>([])
type DeptTreeNode = { value: string; label: string; children?: DeptTreeNode[] }
const deptTreeData = ref<DeptTreeNode[]>([])
function meta(field: string) {
return props.candidates.find((c) => c.field === field)
}
@@ -179,6 +200,19 @@
return meta(field)?.type === 'post'
}
function isDeptLikeField(field: string) {
const t = meta(field)?.type
return t === 'dept' || t === 'ownerDept'
}
function mapDeptTree(nodes: Api.Dept.DeptItem[]): DeptTreeNode[] {
return (nodes || []).map((n) => ({
value: String(n.id),
label: String(n.deptName || n.id),
children: n.children?.length ? mapDeptTree(n.children) : undefined
}))
}
function personValueIds(v: unknown): string[] {
if (Array.isArray(v)) return v.map(String)
if (v == null || v === '') return []
@@ -186,7 +220,14 @@
}
function defaultValueForType(type: string, opts?: ConditionCandidate['options']) {
if (type === 'person' || type === 'ownerPerson' || type === 'post' || type === 'initiator') {
if (
type === 'person' ||
type === 'ownerPerson' ||
type === 'dept' ||
type === 'ownerDept' ||
type === 'post' ||
type === 'initiator'
) {
return [] as string[]
}
return opts?.[0]?.value ?? ''
@@ -336,6 +377,15 @@
}
}
async function loadDepts() {
try {
const tree = (await fetchDeptTree()) || []
deptTreeData.value = mapDeptTree(tree as Api.Dept.DeptItem[])
} catch {
deptTreeData.value = []
}
}
onMounted(() => {
if (props.candidates.some((c) => c.type === 'person' || c.type === 'ownerPerson' || c.type === 'initiator')) {
loadUsers()
@@ -343,6 +393,9 @@
if (props.candidates.some((c) => c.type === 'post')) {
loadPosts()
}
if (props.candidates.some((c) => c.type === 'dept' || c.type === 'ownerDept')) {
loadDepts()
}
})
watch(
@@ -361,6 +414,13 @@
if (need && !postOptions.value.length) loadPosts()
}
)
watch(
() => props.candidates.some((c) => c.type === 'dept' || c.type === 'ownerDept'),
(need) => {
if (need && !deptTreeData.value.length) loadDepts()
}
)
</script>
<style scoped lang="scss">
@@ -404,4 +464,8 @@
.cond-rule-editor__post-select :deep(.el-select__selection) {
flex-wrap: wrap;
}
.cond-rule-editor__dept-select :deep(.el-select__selection) {
flex-wrap: wrap;
}
</style>