Files
im-uniapp/pages/workbench/task/create.vue

1236 lines
30 KiB
Vue
Raw Normal View History

2025-07-11 18:05:05 +08:00
<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>
2025-07-14 14:37:49 +08:00
<!-- 新增搜索框 -->
<view style="padding: 16rpx;">
<input
v-model="userSearchKeyword"
placeholder="请输入昵称搜索"
class="form-input"
style="width: 100%;"
/>
</view>
2025-07-11 18:05:05 +08:00
<scroll-view scroll-y class="user-list" style="height: 400rpx;">
<view
2025-07-14 14:37:49 +08:00
v-for="user in filteredUserList"
2025-07-11 18:05:05 +08:00
: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>
2025-07-14 14:37:49 +08:00
<!-- 新增无匹配提示 -->
<view v-if="filteredUserList.length === 0" style="text-align:center;color:#999;padding:20rpx;">
暂无匹配用户
</view>
2025-07-11 18:05:05 +08:00
</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>
2025-07-12 10:37:32 +08:00
2025-07-11 18:05:05 +08:00
<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'
2025-07-12 10:37:32 +08:00
import { uploadFiles } from '@/api/common/upload'
2025-07-11 18:05:05 +08:00
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: [],
2025-07-14 14:37:49 +08:00
userSearchKeyword: '', // 新增:用户搜索关键字
2025-07-11 18:05:05 +08:00
// userPopupVisible: false, // 不再需要
fileList: [],
uploadedFiles: []
}
},
2025-07-14 14:37:49 +08:00
computed: {
filteredUserList() {
if (!this.userSearchKeyword.trim()) {
return this.userList;
}
return this.userList.filter(user =>
user.nickName && user.nickName.includes(this.userSearchKeyword.trim())
);
}
},
2025-07-11 18:05:05 +08:00
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 {
2025-07-12 10:37:32 +08:00
const res = await selectUser({ pageNum: 1, pageSize: 999, deptId: 100 })
// const resr = await deptTreeSelect()
this.userList = res.rows || []
2025-07-11 18:05:05 +08:00
} 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()
},
2025-07-12 10:37:32 +08:00
2025-07-11 18:05:05 +08:00
// 选择文件
chooseFile() {
2025-07-12 10:37:32 +08:00
// 安卓平台使用 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
2025-07-11 18:05:05 +08:00
uni.chooseFile({
count: 5,
type: 'all',
success: (res) => {
2025-07-12 10:37:32 +08:00
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'
})
2025-07-11 18:05:05 +08:00
}
})
2025-07-12 10:37:32 +08:00
// #endif
// 其他平台提示不支持
// #ifndef APP-PLUS || H5 || MP-WEIXIN
uni.showToast({
title: '当前平台不支持文件选择',
icon: 'none'
})
// #endif
2025-07-11 18:05:05 +08:00
},
// 移除文件
removeFile(index) {
this.fileList.splice(index, 1)
},
2025-07-12 10:37:32 +08:00
// 根据文件名获取文件类型
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
},
2025-07-11 18:05:05 +08:00
// 格式化文件大小
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() {
2025-07-12 10:37:32 +08:00
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}`)
2025-07-11 18:05:05 +08:00
}
2025-07-12 10:37:32 +08:00
})
return results
} catch (error) {
throw error
}
2025-07-11 18:05:05 +08:00
},
// 表单验证
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) {
2025-07-12 10:37:32 +08:00
// 显示上传进度提示
uni.showLoading({
title: '正在上传文件...',
mask: true
})
2025-07-11 18:05:05 +08:00
const uploadedFiles = await this.uploadFiles()
2025-07-12 10:37:32 +08:00
// 隐藏上传进度提示
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 = ''
2025-07-11 18:05:05 +08:00
}
2025-07-12 10:37:32 +08:00
// 显示提交提示
uni.showLoading({
title: '正在创建任务...',
mask: true
})
2025-07-11 18:05:05 +08:00
// 提交任务
await addTask(this.form)
2025-07-12 10:37:32 +08:00
// 隐藏提交提示
uni.hideLoading()
2025-07-11 18:05:05 +08:00
uni.showToast({
title: '任务创建成功',
icon: 'success'
})
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('创建任务失败:', error)
2025-07-12 10:37:32 +08:00
// 隐藏所有loading
uni.hideLoading()
// 显示错误信息
const errorMessage = error.message || '创建任务失败'
2025-07-11 18:05:05 +08:00
uni.showToast({
2025-07-12 10:37:32 +08:00
title: errorMessage,
icon: 'none',
duration: 3000
2025-07-11 18:05:05 +08:00
})
} 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>