<template>
|
<view class="staff-selector-component">
|
<view class="form-item">
|
<view class="form-label" :class="{ required: required }">{{ label }}</view>
|
<view class="staff-list">
|
<view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId">
|
<view class="staff-info">
|
<text class="staff-name">{{ staff.nickName }}</text>
|
</view>
|
<uni-icons
|
v-if="canRemove(index)"
|
type="closeempty"
|
size="20"
|
color="#ff4d4f"
|
@click="removeStaff(index)"
|
></uni-icons>
|
<uni-icons
|
v-else
|
type="checkmarkempty"
|
size="20"
|
color="#007AFF"
|
></uni-icons>
|
</view>
|
<view class="add-staff" @click="showStaffSelector">
|
<uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons>
|
<text>添加人员</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 人员选择弹窗 -->
|
<uni-popup ref="staffPopup" type="bottom" :safe-area="true">
|
<view class="staff-selector-popup">
|
<view class="popup-header">
|
<view class="popup-title">选择执行人员</view>
|
<view class="popup-close" @click="closeStaffSelector">
|
<uni-icons type="closeempty" size="24" color="#333"></uni-icons>
|
</view>
|
</view>
|
|
<view class="search-box">
|
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
<input
|
class="search-input"
|
placeholder="搜索姓名、手机号"
|
v-model="staffSearchKeyword"
|
@input="onStaffSearch"
|
/>
|
</view>
|
|
<view class="staff-filter">
|
<view
|
class="filter-item"
|
:class="{ active: staffFilterType === 'driver' }"
|
@click="filterStaff('driver')"
|
>司机</view>
|
<view
|
class="filter-item"
|
:class="{ active: staffFilterType === 'doctor' }"
|
@click="filterStaff('doctor')"
|
>医生</view>
|
<view
|
class="filter-item"
|
:class="{ active: staffFilterType === 'nurse' }"
|
@click="filterStaff('nurse')"
|
>护士</view>
|
</view>
|
|
<scroll-view class="staff-list-popup" scroll-y="true">
|
<view
|
class="staff-item-popup"
|
v-for="staff in filteredStaffList"
|
:key="staff.userId"
|
@click="toggleStaffSelection(staff)"
|
>
|
<view class="staff-info">
|
<view class="staff-name-row">
|
<text class="staff-name">{{ staff.nickName }}</text>
|
<text class="staff-phone">{{ staff.phonenumber }}</text>
|
</view>
|
<view class="staff-detail-row">
|
<text class="staff-dept">{{ staff.deptName }}</text>
|
</view>
|
</view>
|
<uni-icons
|
v-if="isStaffSelected(staff.userId)"
|
type="checkmarkempty"
|
size="24"
|
color="#007AFF"
|
></uni-icons>
|
<view v-else class="checkbox-empty"></view>
|
</view>
|
|
<view class="no-data" v-if="filteredStaffList.length === 0">
|
<uni-icons type="info" size="40" color="#ccc"></uni-icons>
|
<text>暂无人员数据</text>
|
</view>
|
</scroll-view>
|
|
<view class="popup-footer">
|
<button class="cancel-btn" @click="closeStaffSelector">取消</button>
|
<button class="confirm-btn" @click="confirmStaffSelection">确定(已选{{ selectedStaff.length }})</button>
|
</view>
|
</view>
|
</uni-popup>
|
</view>
|
</template>
|
|
<script>
|
import { mapState } from 'vuex'
|
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
|
import { listBranchUsers } from "@/api/system/user"
|
|
export default {
|
name: 'StaffSelector',
|
components: {
|
uniPopup
|
},
|
props: {
|
// 已选择的人员列表
|
value: {
|
type: Array,
|
default: () => []
|
},
|
// 标签文本
|
label: {
|
type: String,
|
default: '执行任务人员'
|
},
|
// 是否必填
|
required: {
|
type: Boolean,
|
default: false
|
},
|
// 是否自动添加当前用户
|
autoAddCurrentUser: {
|
type: Boolean,
|
default: true
|
},
|
// 当前用户是否可移除
|
currentUserRemovable: {
|
type: Boolean,
|
default: false
|
}
|
},
|
data() {
|
return {
|
selectedStaff: [],
|
allStaffList: [],
|
filteredStaffList: [],
|
staffSearchKeyword: '',
|
staffFilterType: 'driver' // 默认选中司机
|
}
|
},
|
computed: {
|
...mapState({
|
currentUser: state => ({
|
userId: state.user.userId,
|
nickName: state.user.nickName || '张三',
|
phonenumber: state.user.phonenumber || '',
|
deptId: state.user.deptId || 100,
|
posts: state.user.posts || [],
|
roles: state.user.roles || [],
|
dept: state.user.dept || null
|
})
|
})
|
},
|
watch: {
|
value: {
|
handler(newVal) {
|
if (newVal && Array.isArray(newVal)) {
|
this.selectedStaff = [...newVal]
|
}
|
},
|
immediate: true,
|
deep: true
|
}
|
},
|
mounted() {
|
this.loadStaffList()
|
|
// 如果需要自动添加当前用户且选中人员为空
|
if (this.autoAddCurrentUser && this.selectedStaff.length === 0) {
|
this.initWithCurrentUser()
|
}
|
},
|
methods: {
|
// 初始化选中的人员(默认包含当前用户)
|
initWithCurrentUser() {
|
const currentUserStaff = {
|
userId: this.currentUser.userId,
|
nickName: this.currentUser.nickName,
|
phonenumber: this.currentUser.phonenumber,
|
deptId: this.currentUser.deptId,
|
posts: this.currentUser.posts || [],
|
roles: this.currentUser.roles || [],
|
dept: this.currentUser.dept || null
|
}
|
|
// 获取当前用户的所有角色类型(可能有多个)
|
currentUserStaff.types = this.getUserTypes(currentUserStaff)
|
currentUserStaff.type = currentUserStaff.types[0] || 'driver' // 主要类型
|
|
this.selectedStaff = [currentUserStaff]
|
this.emitChange()
|
},
|
|
// 加载人员列表
|
loadStaffList() {
|
listBranchUsers().then(response => {
|
const userList = response.data || []
|
|
this.allStaffList = userList.map(user => ({
|
userId: user.userId,
|
nickName: user.nickName,
|
phonenumber: user.phonenumber,
|
deptName: user.dept?.deptName || '',
|
postName: user.posts && user.posts.length > 0 ? user.posts[0].postName : '',
|
roleName: user.roles && user.roles.length > 0 ? user.roles[0].roleName : '',
|
posts: user.posts || [],
|
roles: user.roles || [],
|
dept: user.dept || null,
|
// 支持多种类型
|
types: this.getUserTypes(user),
|
type: this.getUserTypes(user)[0] || 'driver' // 主要类型(用于向后兼容)
|
}))
|
|
this.filterStaffList()
|
}).catch(error => {
|
console.error('加载人员列表失败:', error)
|
this.$modal.showToast('加载人员列表失败')
|
})
|
},
|
|
// 根据用户的岗位或角色判断所有类型(支持多种身份)
|
getUserTypes(user) {
|
const types = []
|
const postNames = user.posts ? user.posts.map(p => p.postName).join('') : ''
|
const roleNames = user.roles ? user.roles.map(r => r.roleName).join('') : ''
|
const deptName = user.dept?.deptName || ''
|
|
// 判断是否为司机
|
if (postNames.includes('司机') || roleNames.includes('司机') ||
|
deptName.includes('车队') || deptName.includes('司机')) {
|
types.push('driver')
|
}
|
|
// 判断是否为医生
|
if (postNames.includes('医生') || roleNames.includes('医生') ||
|
deptName.includes('医生') || deptName.includes('医护')) {
|
types.push('doctor')
|
}
|
|
// 判断是否为护士
|
if (postNames.includes('护士') || roleNames.includes('护士') || deptName.includes('护士')) {
|
types.push('nurse')
|
}
|
|
// 如果没有匹配到任何类型,默认为司机
|
if (types.length === 0) {
|
types.push('driver')
|
}
|
|
return types
|
},
|
|
// 获取类型名称
|
getUserTypeName(staffType) {
|
const typeMap = {
|
'driver': '司机',
|
'doctor': '医生',
|
'nurse': '护士'
|
}
|
return typeMap[staffType] || staffType || '司机'
|
},
|
|
// 显示人员选择弹窗
|
showStaffSelector() {
|
this.$refs.staffPopup.open()
|
this.filterStaffList()
|
},
|
|
// 关闭人员选择弹窗
|
closeStaffSelector() {
|
this.$refs.staffPopup.close()
|
this.staffSearchKeyword = ''
|
this.staffFilterType = 'driver' // 重置为默认司机
|
},
|
|
// 人员搜索
|
onStaffSearch(e) {
|
this.staffSearchKeyword = e.detail.value
|
this.filterStaffList()
|
},
|
|
// 筛选人员类型
|
filterStaff(type) {
|
this.staffFilterType = type
|
this.filterStaffList()
|
},
|
|
// 过滤人员列表
|
filterStaffList() {
|
let list = [...this.allStaffList]
|
|
// 按关键词搜索
|
if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') {
|
const keyword = this.staffSearchKeyword.trim().toLowerCase()
|
list = list.filter(staff => {
|
return staff.nickName.toLowerCase().includes(keyword) ||
|
(staff.phonenumber && staff.phonenumber.includes(keyword))
|
})
|
}
|
|
// 根据选中的类型,将有该身份的人排在前面
|
list.sort((a, b) => {
|
const aHasType = a.types.includes(this.staffFilterType)
|
const bHasType = b.types.includes(this.staffFilterType)
|
|
if (aHasType && !bHasType) return -1 // a有该身份,排前面
|
if (!aHasType && bHasType) return 1 // b有该身份,排前面
|
return 0 // 都有或都没有,保持原顺序
|
})
|
|
this.filteredStaffList = list
|
},
|
|
// 切换人员选中状态
|
toggleStaffSelection(staff) {
|
const index = this.selectedStaff.findIndex(s => s.userId === staff.userId)
|
|
if (index > -1) {
|
// 如果是第一个且不可移除(当前用户),不允许移除
|
if (!this.canRemove(index)) {
|
this.$modal.showToast('当前用户不能移除')
|
return
|
}
|
// 已选中,移除
|
this.selectedStaff.splice(index, 1)
|
} else {
|
// 未选中,添加
|
this.selectedStaff.push({ ...staff })
|
}
|
},
|
|
// 判断人员是否已选中
|
isStaffSelected(userId) {
|
return this.selectedStaff.some(staff => staff.userId === userId)
|
},
|
|
// 判断是否可以移除
|
canRemove(index) {
|
// 如果是第一个且当前用户不可移除
|
if (index === 0 && !this.currentUserRemovable) {
|
return false
|
}
|
return true
|
},
|
|
// 确认人员选择
|
confirmStaffSelection() {
|
if (this.selectedStaff.length === 0) {
|
this.$modal.showToast('请至少选择一名人员')
|
return
|
}
|
this.emitChange()
|
this.closeStaffSelector()
|
},
|
|
// 移除人员
|
removeStaff(index) {
|
if (!this.canRemove(index)) {
|
this.$modal.showToast('当前用户不能移除')
|
return
|
}
|
this.selectedStaff.splice(index, 1)
|
this.emitChange()
|
},
|
|
// 触发change事件
|
emitChange() {
|
this.$emit('input', this.selectedStaff)
|
this.$emit('change', this.selectedStaff)
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.staff-selector-component {
|
.form-item {
|
margin-bottom: 40rpx;
|
|
.form-label {
|
font-size: 28rpx;
|
margin-bottom: 15rpx;
|
color: #333;
|
|
&.required::before {
|
content: '*';
|
color: #ff4d4f;
|
margin-right: 4rpx;
|
font-weight: bold;
|
}
|
}
|
|
.staff-list {
|
.staff-item {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 20rpx;
|
background-color: #f9f9f9;
|
border-radius: 10rpx;
|
margin-bottom: 20rpx;
|
|
.staff-info {
|
flex: 1;
|
|
.staff-name {
|
font-size: 28rpx;
|
color: #333;
|
margin-right: 10rpx;
|
display: block;
|
margin-bottom: 8rpx;
|
}
|
|
.staff-roles {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 8rpx;
|
|
.staff-role {
|
font-size: 22rpx;
|
color: white;
|
background-color: #007AFF;
|
padding: 4rpx 12rpx;
|
border-radius: 6rpx;
|
}
|
}
|
}
|
}
|
|
.add-staff {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 20rpx;
|
border: 1rpx dashed #007AFF;
|
border-radius: 10rpx;
|
color: #007AFF;
|
|
text {
|
margin-left: 10rpx;
|
}
|
}
|
}
|
}
|
}
|
|
// 人员选择弹窗样式
|
.staff-selector-popup {
|
background-color: white;
|
border-radius: 20rpx 20rpx 0 0;
|
max-height: 80vh;
|
display: flex;
|
flex-direction: column;
|
|
.popup-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 30rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
flex-shrink: 0;
|
|
.popup-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.popup-close {
|
padding: 10rpx;
|
}
|
}
|
|
.search-box {
|
display: flex;
|
align-items: center;
|
margin: 20rpx 30rpx;
|
padding: 15rpx 20rpx;
|
background-color: #f5f5f5;
|
border-radius: 10rpx;
|
flex-shrink: 0;
|
|
.search-input {
|
flex: 1;
|
margin-left: 10rpx;
|
font-size: 28rpx;
|
}
|
}
|
|
.staff-filter {
|
display: flex;
|
padding: 0 30rpx 20rpx;
|
gap: 15rpx;
|
flex-shrink: 0;
|
|
.filter-item {
|
flex: 1;
|
text-align: center;
|
padding: 15rpx 0;
|
background-color: #f5f5f5;
|
border-radius: 10rpx;
|
font-size: 26rpx;
|
color: #666;
|
|
&.active {
|
background-color: #007AFF;
|
color: white;
|
}
|
}
|
}
|
|
.staff-list-popup {
|
flex: 1;
|
overflow-y: auto;
|
padding: 0 30rpx;
|
|
.staff-item-popup {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 25rpx 20rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
|
&:active {
|
background-color: #f5f5f5;
|
}
|
|
.staff-info {
|
flex: 1;
|
|
.staff-name-row {
|
display: flex;
|
align-items: center;
|
margin-bottom: 10rpx;
|
|
.staff-name {
|
font-size: 30rpx;
|
font-weight: bold;
|
color: #333;
|
margin-right: 20rpx;
|
}
|
|
.staff-phone {
|
font-size: 24rpx;
|
color: #999;
|
}
|
}
|
|
.staff-detail-row {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
|
.staff-dept {
|
font-size: 24rpx;
|
color: #666;
|
margin-right: 15rpx;
|
}
|
|
.staff-types {
|
display: flex;
|
gap: 8rpx;
|
|
.type-tag {
|
font-size: 20rpx;
|
color: white;
|
padding: 4rpx 10rpx;
|
border-radius: 6rpx;
|
|
&.type-driver {
|
background-color: #007AFF;
|
}
|
|
&.type-doctor {
|
background-color: #34C759;
|
}
|
|
&.type-nurse {
|
background-color: #AF52DE;
|
}
|
}
|
}
|
}
|
}
|
|
.checkbox-empty {
|
width: 40rpx;
|
height: 40rpx;
|
border: 2rpx solid #ddd;
|
border-radius: 50%;
|
}
|
}
|
|
.no-data {
|
text-align: center;
|
padding: 100rpx 0;
|
color: #999;
|
|
text {
|
display: block;
|
margin-top: 20rpx;
|
font-size: 28rpx;
|
}
|
}
|
}
|
|
.popup-footer {
|
display: flex;
|
padding: 20rpx 30rpx;
|
border-top: 1rpx solid #f0f0f0;
|
gap: 20rpx;
|
flex-shrink: 0;
|
|
button {
|
flex: 1;
|
height: 80rpx;
|
border-radius: 10rpx;
|
font-size: 30rpx;
|
}
|
|
.cancel-btn {
|
background-color: #f5f5f5;
|
color: #666;
|
}
|
|
.confirm-btn {
|
background-color: #007AFF;
|
color: white;
|
}
|
}
|
}
|
</style>
|