Files
im-uniapp/pages/workbench/task/create.vue
2025-07-14 14:37:49 +08:00

1236 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="create-task-container">
<!-- 表单内容 -->
<view class="form-container">
<form @submit="submitForm">
<!-- 任务主题 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">任务主题</text>
<text class="required">*</text>
</view>
<input
v-model="form.taskTitle"
class="form-input"
placeholder="请输入任务主题"
maxlength="100"
/>
</view>
<!-- 工作类型 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">工作类型</text>
<text class="required">*</text>
</view>
<picker
@change="onTaskTypeChange"
:value="taskTypeIndex"
:range="taskTypeOptions"
range-key="label"
class="form-picker"
>
<view class="picker-text">
{{ taskTypeOptions[taskTypeIndex] ? taskTypeOptions[taskTypeIndex].label : '请选择工作类型' }}
</view>
</picker>
</view>
<!-- 任务模式 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">任务模式</text>
</view>
<view class="radio-group">
<view
class="radio-item"
:class="{ active: form.status === 0 }"
@click="form.status = 0"
>
<view class="radio-circle"></view>
<text class="radio-text">单任务模式</text>
</view>
<view
class="radio-item"
:class="{ active: form.status === 1 }"
@click="form.status = 1"
>
<view class="radio-circle"></view>
<text class="radio-text">报工模式</text>
</view>
</view>
</view>
<!-- 报工周期 -->
<view class="form-item" v-if="form.status === 1">
<view class="form-label">
<text class="label-text">报工周期</text>
</view>
<input
v-model="form.timeGap"
type="number"
class="form-input"
placeholder="请输入报工周期"
min="1"
max="10"
/>
</view>
<!-- 任务时间 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">开始时间</text>
<text class="required">*</text>
</view>
<picker
mode="date"
@change="onBeginTimeChange"
class="form-picker"
>
<view class="picker-text">
{{ form.beginTime || '请选择开始时间' }}
</view>
</picker>
</view>
<view class="form-item" v-if="form.status === 0">
<view class="form-label">
<text class="label-text">结束时间</text>
<text class="required">*</text>
</view>
<picker
mode="date"
@change="onFinishTimeChange"
class="form-picker"
>
<view class="picker-text">
{{ form.finishTime || '请选择结束时间' }}
</view>
</picker>
</view>
<!-- 优先级 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">优先级</text>
</view>
<view class="radio-group">
<view
class="radio-item"
:class="{ active: form.taskGrade === '0' }"
@click="form.taskGrade = '0'"
>
<view class="radio-circle"></view>
<text class="radio-text">一般</text>
</view>
<view
class="radio-item"
:class="{ active: form.taskGrade === '1' }"
@click="form.taskGrade = '1'"
>
<view class="radio-circle"></view>
<text class="radio-text">中等</text>
</view>
<view
class="radio-item"
:class="{ active: form.taskGrade === '2' }"
@click="form.taskGrade = '2'"
>
<view class="radio-circle"></view>
<text class="radio-text">紧急</text>
</view>
</view>
</view>
<!-- 关联项目 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">关联项目</text>
</view>
<picker
@change="onProjectChange"
:value="projectIndex"
:range="projectList"
range-key="projectName"
class="form-picker"
>
<view class="picker-text">
{{ projectIndex === -1 ? '请选择项目' : projectList[projectIndex].projectName }}
</view>
</picker>
<view v-if="projectIndex !== -1" class="selected-project" style="margin-top: 10rpx;">
<text>{{ projectList[projectIndex].projectName }}</text>
<text class="remove-btn" @click="removeProject" style="color:#ff4757;margin-left:20rpx;">移除</text>
</view>
</view>
<!-- 执行人 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">执行人</text>
<text class="required">*</text>
</view>
<!-- 已选用户标签 -->
<view v-if="selectedUsers.length > 0" class="user-tags">
<view v-for="user in selectedUsers" :key="user.userId" class="user-tag">
<text>{{ user.nickName }}</text>
<text class="remove-btn" @click="removeUser(user)">×</text>
</view>
</view>
<button type="primary" size="mini" @click="openUserPopup">点击选择</button>
<!-- 弹窗 -->
<uni-popup ref="userPopup" type="center">
<view class="user-select-popup">
<view class="popup-header">
<text>选择执行人</text>
<text class="close-btn" @click="closeUserPopup">×</text>
</view>
<!-- 新增搜索框 -->
<view style="padding: 16rpx;">
<input
v-model="userSearchKeyword"
placeholder="请输入昵称搜索"
class="form-input"
style="width: 100%;"
/>
</view>
<scroll-view scroll-y class="user-list" style="height: 400rpx;">
<view
v-for="user in filteredUserList"
:key="user.userId"
class="user-row"
@click="toggleUser(user)"
>
<checkbox :checked="isSelected(user)" />
<text>{{ user.nickName }}</text>
<text class="user-dept">{{ user.deptName }}</text>
</view>
<!-- 新增无匹配提示 -->
<view v-if="filteredUserList.length === 0" style="text-align:center;color:#999;padding:20rpx;">
暂无匹配用户
</view>
</scroll-view>
<view class="popup-footer">
<button type="primary" size="mini" @click="confirmUserSelect">确定</button>
</view>
</view>
</uni-popup>
</view>
<!-- 详细描述 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">详细描述</text>
</view>
<textarea
v-model="form.content"
class="form-textarea"
placeholder="请输入详细描述"
maxlength="1000"
auto-height
/>
</view>
<!-- 附件 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">附件</text>
</view>
<view class="file-upload">
<view class="upload-btn" @click="chooseFile">
<text class="upload-icon">+</text>
<text class="upload-text">选择文件</text>
</view>
<view class="file-list" v-if="fileList.length > 0">
<view
class="file-item"
v-for="(file, index) in fileList"
:key="index"
>
<text class="file-name">{{ file.name }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text>
<text class="remove-file" @click="removeFile(index)">×</text>
</view>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-container">
<button
type="primary"
class="submit-btn"
:loading="loading"
@click="submitForm"
>
{{ loading ? '提交中...' : '提交' }}
</button>
</view>
</form>
</view>
</view>
</template>
<script>
import { addTask } from '@/api/oa/task'
import { listProject } from '@/api/oa/project'
import { selectUser, deptTreeSelect } from '@/api/oa/user'
import { uploadFiles } from '@/api/common/upload'
export default {
name: 'CreateTask',
data() {
return {
loading: false,
form: {
taskTitle: '',
taskType: '',
status: 0,
timeGap: 3,
beginTime: '',
finishTime: '',
taskGrade: '0',
projectId: '',
workerIds: '',
content: '',
accessory: ''
},
taskTypeOptions: [
{ label: '技术审查', value: 1 },
{ label: '初步设计', value: 2 },
{ label: '图纸详细设计', value: 3 },
{ label: 'PLC程序设计', value: 4 },
{ label: '画面设计', value: 5 },
{ label: '调试(传动、打点、精度)', value: 6 },
{ label: '验收资料', value: 7 },
{ label: '验收报告/差旅记录', value: 8 },
{ label: '项目结项', value: 9 }
],
taskTypeIndex: 0,
projectList: [],
projectIndex: -1,
userList: [],
selectedUsers: [],
userSearchKeyword: '', // 新增:用户搜索关键字
// userPopupVisible: false, // 不再需要
fileList: [],
uploadedFiles: []
}
},
computed: {
filteredUserList() {
if (!this.userSearchKeyword.trim()) {
return this.userList;
}
return this.userList.filter(user =>
user.nickName && user.nickName.includes(this.userSearchKeyword.trim())
);
}
},
onLoad() {
this.initData()
},
methods: {
// 初始化数据
async initData() {
await Promise.all([
this.getProjectList(),
this.getUserList()
])
},
// 获取项目列表
async getProjectList() {
try {
const res = await listProject({
pageNum: 1,
pageSize: 999
})
this.projectList = res.rows || []
} catch (error) {
console.error('获取项目列表失败:', error)
uni.showToast({
title: '获取项目列表失败',
icon: 'none'
})
}
},
// 获取用户列表
async getUserList() {
try {
const res = await selectUser({ pageNum: 1, pageSize: 999, deptId: 100 })
// const resr = await deptTreeSelect()
this.userList = res.rows || []
} catch (error) {
console.error('获取用户列表失败:', error)
uni.showToast({
title: '获取用户列表失败',
icon: 'none'
})
}
},
// 工作类型选择
onTaskTypeChange(e) {
this.taskTypeIndex = e.detail.value
this.form.taskType = this.taskTypeOptions[this.taskTypeIndex].value
},
// 开始时间选择
onBeginTimeChange(e) {
this.form.beginTime = e.detail.value + ' 00:00:00'
},
// 结束时间选择
onFinishTimeChange(e) {
this.form.finishTime = e.detail.value + ' 23:59:59'
},
// 项目选择
onProjectChange(e) {
this.projectIndex = e.detail.value
this.form.projectId = this.projectList[this.projectIndex].projectId
},
// 移除项目
removeProject() {
this.projectIndex = -1
this.form.projectId = ''
},
// 打开用户选择器
openUserPopup() {
this.$refs.userPopup.open()
},
// 关闭用户选择器
closeUserPopup() {
this.$refs.userPopup.close()
},
// 勾选/取消用户
toggleUser(user) {
const index = this.selectedUsers.findIndex(u => u.userId === user.userId)
if (index > -1) {
this.selectedUsers.splice(index, 1)
} else {
this.selectedUsers.push(user)
}
},
// 判断用户是否已选
isSelected(user) {
return this.selectedUsers.some(u => u.userId === user.userId)
},
// 移除已选用户
removeUser(user) {
const index = this.selectedUsers.findIndex(u => u.userId === user.userId)
if (index > -1) {
this.selectedUsers.splice(index, 1)
}
},
// 确认选择
confirmUserSelect() {
this.form.workerIds = this.selectedUsers.map(u => u.userId).join(',')
this.closeUserPopup()
},
// 选择文件
chooseFile() {
// 安卓平台使用 plus.io.pickFiles
// #ifdef APP-PLUS
if (typeof plus !== 'undefined' && plus.io) {
console.log('使用安卓平台文件选择API')
this.doPickFiles()
return
}
// #endif
// 微信小程序环境
if (typeof wx !== 'undefined' && wx.chooseMessageFile) {
wx.chooseMessageFile({
count: 5,
type: 'all',
success: (res) => {
const processedFiles = res.tempFiles.map(file => ({
name: file.name,
size: file.size,
path: file.path || file.tempFilePath,
type: file.type
}))
// 验证文件
const validFiles = this.validateFiles(processedFiles)
if (validFiles.length > 0) {
this.fileList = [...this.fileList, ...validFiles]
}
},
fail: (err) => {
console.error('选择文件失败:', err)
uni.showToast({
title: '选择文件失败',
icon: 'none'
})
}
})
return
}
// H5平台使用 uni.chooseFile
// #ifdef H5
uni.chooseFile({
count: 5,
type: 'all',
success: (res) => {
const processedFiles = res.tempFiles.map(file => ({
name: file.name,
size: file.size,
path: file.path || file.tempFilePath || file.url,
type: file.type
}))
// 验证文件
const validFiles = this.validateFiles(processedFiles)
if (validFiles.length > 0) {
this.fileList = [...this.fileList, ...validFiles]
}
},
fail: (err) => {
console.error('选择文件失败:', err)
uni.showToast({
title: '选择文件失败',
icon: 'none'
})
}
})
// #endif
// 其他平台提示不支持
// #ifndef APP-PLUS || H5 || MP-WEIXIN
uni.showToast({
title: '当前平台不支持文件选择',
icon: 'none'
})
// #endif
},
// 移除文件
removeFile(index) {
this.fileList.splice(index, 1)
},
// 根据文件名获取文件类型
getFileType(fileName) {
const ext = fileName.split('.').pop().toLowerCase()
const typeMap = {
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'bmp': 'image/bmp',
'webp': 'image/webp',
'pdf': 'application/pdf',
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls': 'application/vnd.ms-excel',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'txt': 'text/plain',
'zip': 'application/zip',
'rar': 'application/x-rar-compressed',
'7z': 'application/x-7z-compressed'
}
return typeMap[ext] || 'application/octet-stream'
},
// 执行文件选择
doPickFiles() {
try {
plus.io.pickFiles({
multiple: true,
maximum: 5,
filter: 'none',
onSuccess: (files) => {
console.log('安卓平台选择文件成功:', files)
const processedFiles = files.map(file => ({
name: file.name,
size: file.size,
path: file.path,
type: file.type || this.getFileType(file.name)
}))
// 验证文件
const validFiles = this.validateFiles(processedFiles)
if (validFiles.length > 0) {
this.fileList = [...this.fileList, ...validFiles]
console.log('处理后的文件列表:', this.fileList)
}
},
onFail: (error) => {
console.error('安卓平台选择文件失败:', error)
// 尝试使用备用方法
this.fallbackPickFiles()
}
})
} catch (error) {
console.error('plus.io.pickFiles 不可用:', error)
// 尝试使用备用方法
this.fallbackPickFiles()
}
},
// 备用文件选择方法
fallbackPickFiles() {
// 使用 uni.chooseImage 作为备用方案
uni.chooseImage({
count: 5,
sizeType: ['original'],
sourceType: ['album'],
success: (res) => {
console.log('备用方法选择文件成功:', res)
const processedFiles = res.tempFilePaths.map((path, index) => ({
name: `image_${Date.now()}_${index}.jpg`,
size: 0,
path: path,
type: 'image/jpeg'
}))
// 验证文件
const validFiles = this.validateFiles(processedFiles)
if (validFiles.length > 0) {
this.fileList = [...this.fileList, ...validFiles]
console.log('处理后的文件列表:', this.fileList)
}
},
fail: (error) => {
console.error('备用方法选择文件失败:', error)
uni.showToast({
title: '选择文件失败',
icon: 'none'
})
}
})
},
// 验证文件
validateFiles(files) {
const allowedTypes = ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg']
const maxSize = 200 * 1024 * 1024 // 200MB
const validFiles = []
const invalidFiles = []
files.forEach(file => {
// 文件类型验证
const ext = file.name.split('.').pop().toLowerCase()
if (!allowedTypes.includes(ext)) {
invalidFiles.push(`${file.name} (格式不支持)`)
return
}
// 文件大小验证
if (file.size > maxSize) {
invalidFiles.push(`${file.name} (超过200MB)`)
return
}
validFiles.push(file)
})
// 显示无效文件提示
if (invalidFiles.length > 0) {
uni.showToast({
title: `以下文件不符合要求:${invalidFiles.join(', ')}`,
icon: 'none',
duration: 3000
})
}
return validFiles
},
// 格式化文件大小
formatFileSize(size) {
if (size < 1024) {
return size + 'B'
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + 'KB'
} else {
return (size / (1024 * 1024)).toFixed(2) + 'MB'
}
},
// 上传文件
async uploadFiles() {
if (this.fileList.length === 0) {
return []
}
try {
const results = await uploadFiles(this.fileList, {
allowedTypes: ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg'],
maxSize: 200,
extraData: 1,
onProgress: (progress) => {
console.log(`上传进度: ${progress.progress.toFixed(1)}% - ${progress.file.name}`)
}
})
return results
} catch (error) {
throw error
}
},
// 表单验证
validateForm() {
if (!this.form.taskTitle.trim()) {
uni.showToast({
title: '请输入任务主题',
icon: 'none'
})
return false
}
if (!this.form.taskType) {
uni.showToast({
title: '请选择工作类型',
icon: 'none'
})
return false
}
if (!this.form.beginTime) {
uni.showToast({
title: '请选择开始时间',
icon: 'none'
})
return false
}
if (this.form.status === 0 && !this.form.finishTime) {
uni.showToast({
title: '请选择结束时间',
icon: 'none'
})
return false
}
if (this.form.workerIds === '') {
uni.showToast({
title: '请选择执行人',
icon: 'none'
})
return false
}
return true
},
// 提交表单
async submitForm() {
if (!this.validateForm()) {
return
}
this.loading = true
try {
// 上传文件
if (this.fileList.length > 0) {
// 显示上传进度提示
uni.showLoading({
title: '正在上传文件...',
mask: true
})
const uploadedFiles = await this.uploadFiles()
// 隐藏上传进度提示
uni.hideLoading()
// 提取ossId并拼接成字符串
const ossIds = uploadedFiles.map(file => file.ossId).filter(Boolean)
this.form.accessory = ossIds.join(',')
console.log('上传完成ossId字符串:', this.form.accessory)
} else {
this.form.accessory = ''
}
// 显示提交提示
uni.showLoading({
title: '正在创建任务...',
mask: true
})
// 提交任务
await addTask(this.form)
// 隐藏提交提示
uni.hideLoading()
uni.showToast({
title: '任务创建成功',
icon: 'success'
})
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('创建任务失败:', error)
// 隐藏所有loading
uni.hideLoading()
// 显示错误信息
const errorMessage = error.message || '创建任务失败'
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
})
} finally {
this.loading = false
}
}
}
}
</script>
<style lang="scss" scoped>
.create-task-container {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.form-container {
max-width: 90vw;
margin: 0 auto;
box-sizing: border-box;
padding: 30rpx 20rpx;
}
.form-item {
border-radius: 12rpx;
box-sizing: border-box;
padding: 30rpx;
margin-bottom: 20rpx;
}
.form-label {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.label-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.required {
color: #ff4757;
margin-left: 8rpx;
}
}
.form-input {
width: 100%;
height: 80rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
background-color: #fff;
}
.form-picker {
width: 100%;
height: 80rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
background-color: #fff;
.picker-text {
font-size: 28rpx;
color: #333;
}
}
.radio-group {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.radio-item {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
background-color: #fff;
&.active {
border-color: #007aff;
background-color: #f0f8ff;
.radio-circle {
background-color: #007aff;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 12rpx;
height: 12rpx;
background-color: #fff;
border-radius: 50%;
}
}
}
.radio-circle {
width: 32rpx;
height: 32rpx;
border: 2rpx solid #ddd;
border-radius: 50%;
margin-right: 16rpx;
position: relative;
}
.radio-text {
font-size: 28rpx;
color: #333;
}
}
.user-selector {
min-height: 80rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 20rpx;
background-color: #fff;
.selected-users {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.user-tag {
display: flex;
align-items: center;
background-color: #f0f8ff;
border: 1rpx solid #007aff;
border-radius: 20rpx;
padding: 8rpx 16rpx;
.user-name {
font-size: 24rpx;
color: #007aff;
margin-right: 8rpx;
}
.remove-btn {
font-size: 24rpx;
color: #007aff;
font-weight: bold;
}
}
.placeholder {
font-size: 28rpx;
color: #999;
}
}
.form-textarea {
width: 100%;
min-height: 200rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
background-color: #fff;
line-height: 1.5;
}
.file-upload {
.upload-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 160rpx;
border: 2rpx dashed #ddd;
border-radius: 8rpx;
background-color: #fafafa;
.upload-icon {
font-size: 48rpx;
color: #999;
margin-bottom: 16rpx;
}
.upload-text {
font-size: 28rpx;
color: #999;
}
}
.file-list {
margin-top: 20rpx;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
margin-bottom: 16rpx;
.file-name {
font-size: 28rpx;
color: #333;
flex: 1;
margin-right: 16rpx;
}
.file-size {
font-size: 24rpx;
color: #999;
margin-right: 16rpx;
}
.remove-file {
font-size: 32rpx;
color: #ff4757;
font-weight: bold;
}
}
}
.submit-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 30rpx;
border-top: 1rpx solid #eee;
.submit-btn {
width: 100%;
height: 88rpx;
background-color: #007aff;
color: #fff;
border-radius: 8rpx;
font-size: 32rpx;
font-weight: 500;
}
}
.user-popup {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
max-height: 80vh;
display: flex;
flex-direction: column;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.close-btn {
font-size: 40rpx;
color: #999;
font-weight: bold;
}
}
.user-list {
flex: 1;
overflow-y: auto;
padding: 0 30rpx;
}
.user-item {
display: flex;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
.user-avatar {
margin-right: 20rpx;
.avatar-img {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
}
.user-info {
flex: 1;
.user-nickname {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
}
.user-dept {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
display: block;
}
}
.user-check {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #007aff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #007aff;
.check-icon {
color: #fff;
font-size: 24rpx;
font-weight: bold;
}
}
}
.popup-footer {
padding: 30rpx;
border-top: 1rpx solid #eee;
.confirm-btn {
width: 100%;
height: 88rpx;
background-color: #007aff;
color: #fff;
border-radius: 8rpx;
font-size: 32rpx;
font-weight: 500;
}
}
}
.user-tags {
display: flex;
flex-wrap: wrap;
margin-bottom: 10rpx;
}
.user-tag {
background: #e6f7ff;
color: #007aff;
border-radius: 20rpx;
padding: 6rpx 16rpx;
margin-right: 10rpx;
margin-bottom: 10rpx;
display: flex;
align-items: center;
}
.remove-btn {
margin-left: 8rpx;
color: #ff4757;
font-weight: bold;
cursor: pointer;
}
.user-select-popup {
width: 90vw;
max-width: 700rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #eee;
font-size: 32rpx;
font-weight: bold;
}
.close-btn {
font-size: 40rpx;
color: #999;
font-weight: bold;
}
.popup-body {
display: flex;
height: 400rpx;
}
.dept-tree {
width: 180rpx;
border-right: 1rpx solid #eee;
background: #f8f8f8;
padding: 10rpx 0;
}
.dept-item {
padding: 16rpx 24rpx;
cursor: pointer;
}
.dept-item.active {
background: #e6f7ff;
color: #007aff;
}
.dept-children {
margin-left: 10rpx;
}
.user-list {
flex: 1;
padding: 10rpx 0 10rpx 10rpx;
overflow-y: auto;
}
.user-row {
display: flex;
align-items: center;
padding: 10rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.user-row .user-dept {
margin-left: 16rpx;
color: #999;
font-size: 24rpx;
}
.popup-footer {
padding: 20rpx;
text-align: right;
}
</style>