2025-02-21 11:25:09 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="officer-selector-container">
|
|
|
|
|
|
<!-- 触发下拉框的输入框 -->
|
|
|
|
|
|
<el-popover
|
|
|
|
|
|
v-model:visible="popoverVisible"
|
|
|
|
|
|
placement="top-start"
|
|
|
|
|
|
:width="800"
|
|
|
|
|
|
trigger="click"
|
|
|
|
|
|
popper-class="officer-selector-popover"
|
|
|
|
|
|
:hide-after="0"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 下拉内容:人员选择面板 -->
|
|
|
|
|
|
<template #default>
|
|
|
|
|
|
<el-card class="department-personnel-selector" shadow="never">
|
|
|
|
|
|
<div class="selector-content">
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
|
<!-- 部门树 -->
|
|
|
|
|
|
<el-col :span="8">
|
|
|
|
|
|
<el-card shadow="never" class="department-tree-card">
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="department-header">
|
|
|
|
|
|
<span>部门结构</span>
|
|
|
|
|
|
<el-button v-if="selectedDepartment" type="text" size="small" @click="selectedDepartment = null">
|
|
|
|
|
|
清除
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<el-scrollbar height="300px">
|
|
|
|
|
|
<el-tree
|
|
|
|
|
|
:data="departmentsTree"
|
|
|
|
|
|
node-key="id"
|
|
|
|
|
|
:props="{
|
|
|
|
|
|
label: 'name',
|
|
|
|
|
|
children: 'children',
|
|
|
|
|
|
}"
|
|
|
|
|
|
:highlight-current="true"
|
|
|
|
|
|
@node-click="handleDepartmentSelect"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #default="{ node, data }">
|
|
|
|
|
|
<div class="custom-tree-node">
|
|
|
|
|
|
<span>{{ node.label }}</span>
|
|
|
|
|
|
<el-tag size="small" type="info" effect="plain">
|
|
|
|
|
|
{{ countPeopleInDepartment(data.path) }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-tree>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 人员列表 -->
|
|
|
|
|
|
<el-col :span="16">
|
|
|
|
|
|
<el-card shadow="never" class="personnel-list-card">
|
|
|
|
|
|
<div class="search-section">
|
|
|
|
|
|
<div class="search-row">
|
|
|
|
|
|
<el-input v-model="searchTerm" placeholder="搜索人员、职位或部门..." clearable>
|
|
|
|
|
|
<template #prefix>
|
|
|
|
|
|
<font-awesome-icon icon="fa-search" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 全选和清空按钮 -->
|
|
|
|
|
|
<div class="selection-actions">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:disabled="filteredPeople.length === 0"
|
|
|
|
|
|
@click.stop="selectAll"
|
|
|
|
|
|
plain
|
|
|
|
|
|
>
|
|
|
|
|
|
<font-awesome-icon icon="fa-check-double" class="action-icon" />
|
|
|
|
|
|
全选
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="danger"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:disabled="selectedPeople.length === 0"
|
|
|
|
|
|
@click.stop="clearSelection"
|
|
|
|
|
|
plain
|
|
|
|
|
|
>
|
|
|
|
|
|
<font-awesome-icon icon="fa-times" class="action-icon" />
|
|
|
|
|
|
清空
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="selectedDepartment" class="selected-department">
|
|
|
|
|
|
<span class="label">当前部门:</span>
|
|
|
|
|
|
<el-tag closable @close="selectedDepartment = null">
|
|
|
|
|
|
{{ selectedDepartment.name }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-scrollbar height="300px" class="personnel-scrollbar">
|
|
|
|
|
|
<el-empty v-if="loading" description="加载中..." />
|
|
|
|
|
|
<el-empty v-else-if="filteredPeople.length === 0" description="未找到匹配的人员" />
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else class="personnel-list">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="person in filteredPeople"
|
|
|
|
|
|
:key="person.id"
|
|
|
|
|
|
class="personnel-item"
|
|
|
|
|
|
:class="{ 'is-selected': isPersonSelected(person.id) }"
|
|
|
|
|
|
@click="togglePerson(person)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-avatar :size="40" :src="person.image">
|
|
|
|
|
|
{{ person.name.slice(0, 1) }}
|
|
|
|
|
|
</el-avatar>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="personnel-info">
|
|
|
|
|
|
<div class="personnel-name">{{ person.name }}</div>
|
|
|
|
|
|
<div class="personnel-details">
|
|
|
|
|
|
<el-tag size="small" effect="plain">{{ person.roleName || '未知角色' }}</el-tag>
|
|
|
|
|
|
<el-divider direction="vertical" />
|
|
|
|
|
|
<el-tooltip :content="person.departmentName" placement="top">
|
|
|
|
|
|
<span class="department-path">{{ person.departmentName }}</span>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<font-awesome-icon v-if="isPersonSelected(person.id)" class="check-icon" icon="fa-check" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 已选择人员 -->
|
|
|
|
|
|
<div v-if="selectedPeople.length > 0" class="selected-personnel">
|
|
|
|
|
|
<h3>已选择 ({{ selectedPeople.length }})</h3>
|
|
|
|
|
|
<div class="selected-tags">
|
|
|
|
|
|
<el-tag
|
|
|
|
|
|
v-for="person in selectedPeople"
|
|
|
|
|
|
:key="person.id"
|
|
|
|
|
|
closable
|
|
|
|
|
|
@close="removePerson(person.id)"
|
|
|
|
|
|
class="personnel-tag"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ person.name }}
|
|
|
|
|
|
<span class="department-label">({{ person.departmentName }} · {{ person.roleName || '未知角色' }})</span>
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
class="confirm-button"
|
|
|
|
|
|
@click.stop="confirmSelection"
|
|
|
|
|
|
>
|
|
|
|
|
|
确认选择 ({{ selectedPeople.length }})
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 触发元素 -->
|
|
|
|
|
|
<template #reference>
|
|
|
|
|
|
<div class="reference-input">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
readonly
|
|
|
|
|
|
:placeholder="placeholder"
|
|
|
|
|
|
:value="displayValue"
|
|
|
|
|
|
@click.stop="handleInputClick"
|
|
|
|
|
|
class="selector-input"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #suffix>
|
|
|
|
|
|
<font-awesome-icon icon="fa-chevron-down" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-popover>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, computed, defineProps, defineEmits, onMounted } from 'vue'
|
|
|
|
|
|
import { agencies } from '../api/lawenforcement/Agency'
|
|
|
|
|
|
import { officers } from '../api/lawenforcement/Officer'
|
|
|
|
|
|
|
|
|
|
|
|
// 定义props和emits
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
modelValue: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => [],
|
|
|
|
|
|
},
|
|
|
|
|
|
placeholder: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: '请选择人员',
|
|
|
|
|
|
},
|
|
|
|
|
|
agencyId:{
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: '1'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
|
|
|
|
|
|
|
|
// 弹出框可见性
|
|
|
|
|
|
const popoverVisible = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 处理输入框点击事件
|
|
|
|
|
|
function handleInputClick() {
|
|
|
|
|
|
popoverVisible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 部门树型结构 - 使用API获取的机构数据
|
|
|
|
|
|
const departmentsTree = ref([])
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 人员数据 - 从API获取
|
|
|
|
|
|
const people = ref([])
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式状态
|
|
|
|
|
|
// 使用props.modelValue初始化selectedPeople,确保与v-model同步
|
|
|
|
|
|
const selectedPeople = ref([...props.modelValue])
|
|
|
|
|
|
const selectedDepartment = ref(null)
|
|
|
|
|
|
const searchTerm = ref('')
|
|
|
|
|
|
|
|
|
|
|
|
// 获取机构树和人员数据
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
|
|
|
|
|
|
// 获取机构树
|
|
|
|
|
|
const agencyResponse = await agencies.tree()
|
|
|
|
|
|
if (agencyResponse.data && agencyResponse.data.length > 0) {
|
|
|
|
|
|
// 处理机构树数据,确保字段名称正确
|
|
|
|
|
|
departmentsTree.value = processAgencyTree(agencyResponse.data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载所有人员数据
|
|
|
|
|
|
await loadAllOfficers()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载机构和人员数据失败:', error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 处理机构树数据,确保字段名称与组件期望的一致
|
|
|
|
|
|
function processAgencyTree(agencies) {
|
|
|
|
|
|
return agencies.map(agency => ({
|
|
|
|
|
|
id: agency.agencyId, // 使用 agencyId 作为唯一标识
|
|
|
|
|
|
agencyId: agency.agencyId, // 保留原始 agencyId 字段
|
|
|
|
|
|
name: agency.agencyName, // 使用 agencyName 作为显示名称
|
|
|
|
|
|
path: agency.agencyPath || agency.agencyName, // 使用 agencyPath 或 agencyName 作为路径
|
|
|
|
|
|
code: agency.agencyCode, // 使用 agencyCode 作为代码
|
|
|
|
|
|
level: agency.agencyLevel, // 使用 agencyLevel 作为级别
|
|
|
|
|
|
leaf: agency.leaf, // 使用 leaf 表示是否为叶节点
|
|
|
|
|
|
parentId: agency.parent?.agencyId || '0', // 使用父节点的 agencyId 作为 parentId
|
|
|
|
|
|
// 递归处理子节点
|
|
|
|
|
|
children: agency.children && agency.children.length > 0 ? processAgencyTree(agency.children) : []
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接加载所有执法人员数据
|
|
|
|
|
|
async function loadAllOfficers() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
// 使用officers.querylist接口直接查询全部执法人员信息
|
|
|
|
|
|
const response = await officers.querylist()
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
|
// 处理返回的数据,确保数据格式一致
|
|
|
|
|
|
const officersData = response.data.map(officer => ({
|
|
|
|
|
|
id: officer.officerId,
|
|
|
|
|
|
name: officer.officerName,
|
|
|
|
|
|
role: officer.role || 'zfry',
|
|
|
|
|
|
roleName: officer.roleName || '执法人员',
|
|
|
|
|
|
departmentName: officer.agency?.agencyName || '未知部门',
|
|
|
|
|
|
departmentPath: officer.agency?.agencyPath || '',
|
|
|
|
|
|
certificateNo: officer.certificateNo || '',
|
|
|
|
|
|
avatar: officer.avatar || ''
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
// 更新人员列表,确保不重复
|
|
|
|
|
|
const uniqueOfficers = officersData.filter(newOfficer =>
|
|
|
|
|
|
!people.value.some(existingOfficer => existingOfficer.id === newOfficer.id)
|
|
|
|
|
|
)
|
|
|
|
|
|
people.value = [...people.value, ...uniqueOfficers]
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载所有人员数据失败:', error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性:根据选中的部门和搜索词过滤人员
|
|
|
|
|
|
const filteredPeople = computed(() => {
|
|
|
|
|
|
return people.value.filter((person) => {
|
|
|
|
|
|
const matchesSearch =
|
|
|
|
|
|
searchTerm.value === '' ||
|
|
|
|
|
|
person.name.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
|
|
|
|
|
|
person.role.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
|
|
|
|
|
|
person.departmentName.toLowerCase().includes(searchTerm.value.toLowerCase())
|
|
|
|
|
|
|
|
|
|
|
|
const matchesDepartment = !selectedDepartment.value || person.departmentPath.startsWith(selectedDepartment.value.path)
|
|
|
|
|
|
|
|
|
|
|
|
return matchesSearch && matchesDepartment
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 计算显示值
|
|
|
|
|
|
const displayValue = computed(() => {
|
|
|
|
|
|
if (!props.modelValue || props.modelValue.length === 0) return ''
|
|
|
|
|
|
if (props.modelValue.length === 1) return props.modelValue[0].name
|
|
|
|
|
|
return `已选择 ${props.modelValue.length} 人`
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
|
function handleDepartmentSelect(data) {
|
|
|
|
|
|
// 只设置选中的部门,不再加载数据
|
|
|
|
|
|
selectedDepartment.value = data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确认选择,更新v-model并关闭弹窗
|
|
|
|
|
|
function confirmSelection(e) {
|
|
|
|
|
|
emit('update:modelValue', [...selectedPeople.value])
|
|
|
|
|
|
popoverVisible.value = false
|
|
|
|
|
|
// 阻止事件冒泡
|
|
|
|
|
|
if (e) e.stopPropagation()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function togglePerson(person) {
|
|
|
|
|
|
const index = selectedPeople.value.findIndex((p) => p.id === person.id)
|
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
|
selectedPeople.value.push(person)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
selectedPeople.value.splice(index, 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 不需要在这里emit事件,只在确认选择时更新父组件
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removePerson(personId) {
|
|
|
|
|
|
const index = selectedPeople.value.findIndex((p) => p.id === personId)
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
selectedPeople.value.splice(index, 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 全选当前筛选出的人员
|
|
|
|
|
|
function selectAll() {
|
|
|
|
|
|
// 将所有筛选出的人员添加到已选择列表中(避免重复)
|
|
|
|
|
|
filteredPeople.value.forEach((person) => {
|
|
|
|
|
|
if (!isPersonSelected(person.id)) {
|
|
|
|
|
|
selectedPeople.value.push(person)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
// 不立即更新v-model,等用户点击确认按钮时再更新
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 15:32:58 +08:00
|
|
|
|
|
2025-02-21 11:25:09 +08:00
|
|
|
|
function isPersonSelected(personId) {
|
|
|
|
|
|
return selectedPeople.value.some((p) => p.id === personId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function countPeopleInDepartment(departmentPath) {
|
|
|
|
|
|
return people.value.filter((p) => p.departmentPath.startsWith(departmentPath)).length
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.officer-selector-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selector-input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-tags-preview {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reference-input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.officer-selector-popover) {
|
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.department-personnel-selector {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-width: 1200px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header h2 {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selector-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.department-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-tree-node {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-section {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selection-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-icon {
|
|
|
|
|
|
margin-right: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-department {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-department .label {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-item:hover {
|
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-item.is-selected {
|
|
|
|
|
|
background-color: #ecf5ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-info {
|
|
|
|
|
|
margin-left: 12px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-details {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.department-path {
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
max-width: 200px;
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.check-icon {
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-personnel {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-personnel h3 {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-tags {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.personnel-tag {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.department-label {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-button {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
//
|