AddAttendancePersonnelModal.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. <template>
  2. <view class="addAttendancePersonnelModal">
  3. <view class="slot-content" @click.stop="openModal">
  4. <slot></slot>
  5. </view>
  6. <u-popup :show="show" mode="center" :round="8">
  7. <view class="modal-content">
  8. <view class="title">
  9. <text>{{ notkezhang ? '当班人员' : '负责区域' }}</text>
  10. <u-icon name="close" color="#666666" size="20" @click="close" />
  11. </view>
  12. <view class="title-cell" v-if="notkezhang">
  13. <SelectData ref="selectData" @search="searchUsers" @searchViewShowEvent="searchViewShowHandler" />
  14. </view>
  15. <view class="title-cell" v-if="!searchViewShow && selectedUser && selectedUser.length">
  16. <view class="title-text">{{ `班组成员(${selectedUser.length}人)` }}</view>
  17. <view class="personnel-list">
  18. <view class="personnel-item" v-for="item of selectedUser" :key="item.userId"
  19. @click="removeSelcetUser(item)">
  20. <view class="personnel-img">
  21. <UserAvatar :userName="item.nickName || item.userName" :avatarLink="item.avatar" />
  22. </view>
  23. <view class="personnel-name">{{ item.nickName || item.userName }}</view>
  24. <view class="personnel-close" v-if="notkezhang">
  25. <u-icon name="close" color="#666666" size="14" />
  26. </view>
  27. </view>
  28. </view>
  29. </view>
  30. <view class="title-cell" v-if="notkezhang && searchViewShow">
  31. <view style="margin-bottom: 15px;" v-if="addWorkUsers && addWorkUsers.length">
  32. <view class="title-text">
  33. {{ `添加人员(${addWorkUsers.length}人)` }}
  34. </view>
  35. <view class="personnel-list" v-if="addWorkUsers && addWorkUsers.length">
  36. <view class="personnel-item" v-for="item of addWorkUsers" :key="item.userId" @click="selectAddUser(item)">
  37. <view class="personnel-img">
  38. <UserAvatar :userName="item.nickName" :avatarLink="item.avatar" />
  39. </view>
  40. <view class="personnel-name">{{ item.nickName }}</view>
  41. <view class="personnel-close">
  42. <u-icon name="close" color="#666666" size="14" />
  43. </view>
  44. </view>
  45. </view>
  46. </view>
  47. <view class="title-text">
  48. {{ `搜索结果(${allUsers.length}人)` }}
  49. </view>
  50. <view class="personnel-list" v-if="allUsers && allUsers.length">
  51. <view class="personnel-item" v-for="item of allUsers" :key="item.userId" @click="selectAddUser(item)">
  52. <view class="personnel-img">
  53. <UserAvatar :userName="item.nickName" :avatarLink="item.avatar" />
  54. </view>
  55. <view class="personnel-name">{{ item.nickName }}</view>
  56. </view>
  57. </view>
  58. <view v-else class="empty"></view>
  59. </view>
  60. <view class="title-cell" v-if="!searchViewShow">
  61. <view class="title-text">{{ notkezhang ? '选择上通道时间' : '选择上区域时间' }}</view>
  62. <uni-datetime-picker type="datetime" v-model="currentTime" />
  63. </view>
  64. <view class="title-cell" v-if="!searchViewShow">
  65. <view class="title-text" style="display: flex; align-items: center;">{{ notkezhang ? '选择通道' : '选择区域' }}
  66. <view v-if="!userInfo.roles.includes('xiaozuzhang')">
  67. <SelectArea :notkezhang="notkezhang" @selected="selectedArea">
  68. <u-icon name="plus-circle" color="#2A70D1" size="25" />
  69. </SelectArea>
  70. </view>
  71. </view>
  72. <view class="">
  73. <uniDataPicker v-if="userInfo.roles.includes('xiaozuzhang')" :localdata="channelList"
  74. :popup-title="notkezhang ? '请选择上岗通道' : '请选择上岗区域'" v-model="channelOrRegional"
  75. @change="onLocationChange" />
  76. <view class="selected-areas" v-if="selectedAreas && selectedAreas.length">
  77. <view class="area-item" v-for="area in selectedAreas" :key="area.id">
  78. <view class="area-label">{{ area.label }}<u-icon name="close" color="#666666" size="14"
  79. @click="removeArea(area)" /></view>
  80. <text-tag :tags="area.children" @remove="removeSubArea($event, area.id)" />
  81. </view>
  82. </view>
  83. </view>
  84. </view>
  85. <view class="footer-btn">
  86. <view v-if="searchViewShow" class="custom-btn-normal" @click="appendWorkUsers"
  87. :class="{ disabled: addWorkUsers.length === 0 }">添加人员</view>
  88. <view v-else class="custom-btn-normal" style="margin-top: 10rpx;" @click="invokerSubmitPostDuty"
  89. :class="{ disabled: selectedUserIds.length === 0 }">{{ notkezhang ? '确认上通道' : '确认上区域' }}</view>
  90. <u-modal :show="errModalShow" @confirm="errModalShow = false" width="80vw">
  91. <view class="modal-slot-content">
  92. {{ errModalContent }}
  93. </view>
  94. </u-modal>
  95. </view>
  96. </view>
  97. </u-popup>
  98. </view>
  99. </template>
  100. <script>
  101. import SelectData from './SelectData'
  102. import SelectArea from './SelectArea'
  103. import TimePicker from './TimePicker'
  104. import {
  105. getUserList,
  106. addPostRecord,
  107. getPostRecordList,
  108. dataConfigTree,
  109. memberList,
  110. queryLastTime
  111. } from "@/api/attendance/attendance"
  112. import moment from 'moment'
  113. import uniDataPicker from '@/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker'
  114. import { formatTime, formatName, isAfterTodayStart } from '@/utils/formatUtils'
  115. import { generateRandomDigits } from '@/utils/handler'
  116. import UserAvatar from './UserAvatar'
  117. import { getHandleAreaData } from '@/utils/common'
  118. export default {
  119. components: { uniDataPicker, SelectData, TimePicker, UserAvatar, SelectArea },
  120. props: {
  121. userInfo: {
  122. type: Object,
  123. default: () => ({})
  124. },
  125. disabled: {
  126. type: Boolean,
  127. default: false
  128. },
  129. notkezhang: {
  130. type: Boolean,
  131. default: undefined
  132. },
  133. selectedMember: {
  134. type: Array,
  135. default: () => []
  136. },
  137. attendanceInfo: { // 考勤信息
  138. type: Object,
  139. default: () => ({})
  140. },
  141. },
  142. data() {
  143. return {
  144. show: false,
  145. searchFocus: false,
  146. channelOrRegional: '',
  147. channelOrRegionalName: '',
  148. channelList: [
  149. { value: 0, text: "通道A" },
  150. ],
  151. selectedUser: [], //选中的人员
  152. selectedAreas: [], // 选中的区域
  153. allUsers: [],
  154. addWorkUsers: [], // 额外添加的人员
  155. teamUsers: [],
  156. searchViewShow: false,
  157. isSubmittingPost: false,
  158. currentTime: undefined,
  159. errModalShow: false,
  160. errModalContent: '',
  161. selectedLocationPath: [] // 存储完整的三级路径数据
  162. }
  163. },
  164. computed: {
  165. selectedUserIds() {
  166. return (this.selectedUser || []).map(item => item.userId)
  167. },
  168. //单选获取的区域
  169. positions() {
  170. // 解析三级路径数据
  171. const path = this.selectedLocationPath
  172. const result = {
  173. terminlCode: '',
  174. terminlName: '',
  175. regionalCode: '',
  176. regionalName: '',
  177. channelCode: '',
  178. channelName: ''
  179. }
  180. if (path.length >= 1) {
  181. result.terminlCode = path[0].terminlCode || path[0].value
  182. result.terminlName = path[0].terminlName || path[0].text
  183. }
  184. if (path.length >= 2) {
  185. result.regionalCode = path[1].regionalCode || path[1].value
  186. result.regionalName = path[1].regionalName || path[1].text
  187. }
  188. if (path.length >= 3) {
  189. result.channelCode = path[2].channelCode || path[2].value
  190. result.channelName = path[2].channelName || path[2].text
  191. }
  192. return result
  193. },
  194. //多选获得的区域
  195. getSelectArea() {
  196. let area = []
  197. this.selectedAreas.forEach(item => {
  198. item.children.forEach(child => {
  199. area.push({
  200. terminlCode: child.terminlCode || item.code,
  201. terminlName: child.terminlName || item.label,
  202. regionalCode: child.regionalCode || (this.notkezhang ? '' : child.code),
  203. regionalName: child.regionalName || (this.notkezhang ? '' : child.label),
  204. channelCode: child.channelCode || (this.notkezhang ? child.code : ''),
  205. channelName: child.channelName || (this.notkezhang ? child.label : '')
  206. })
  207. })
  208. })
  209. return area
  210. }
  211. },
  212. watch: {
  213. show(newValue) {
  214. if (newValue) {
  215. if (this.userInfo.roles.includes('banzuzhang')) {
  216. this.getQueryLastTime()
  217. }
  218. this.currentTime = formatTime(new Date())
  219. if (this.notkezhang) {
  220. if (this.userInfo.roles.includes('xiaozuzhang')) {
  221. memberList({
  222. attendanceTeamId: this.userInfo.teamsId,
  223. attendanceDate: moment().format('YYYY-MM-DD')
  224. }).then(res => {
  225. console.log(res.rows, "res.rows")
  226. this.selectedUser = res.data || []
  227. })
  228. return
  229. }
  230. this.loadTeamUsers().then(() => {
  231. // 选中全组人员
  232. this.teamUsers.forEach((item) => {
  233. this.selectUserHandler(item, false)
  234. })
  235. })
  236. } else {
  237. this.loadTeamUsers().then(() => {
  238. // 查自己
  239. const curUser = this.teamUsers.find((item) => {
  240. return item.userId === this.userInfo.userId
  241. })
  242. this.selectUserHandler(curUser, false)
  243. })
  244. }
  245. } else {
  246. this.allUsers = []
  247. }
  248. },
  249. notkezhang: {
  250. handler(newValue) {
  251. const level = newValue ? 3 : 2
  252. this.invokerDataConfigTree(level)
  253. },
  254. immediate: true
  255. }
  256. },
  257. methods: {
  258. //查询最后一次上岗的区域
  259. async getQueryLastTime() {
  260. let res = await queryLastTime({
  261. userId: this.userInfo.userId,
  262. })
  263. this.selectedAreas = getHandleAreaData(res.data || [], this.notkezhang)
  264. console.log(this.selectedAreas, "this.selectedAreas")
  265. },
  266. convertTree(list = []) {
  267. return list.map(node => ({
  268. text: node.label,
  269. value: node.code,
  270. children: node.children ? this.convertTree(node.children) : null,
  271. // 保留原始数据字段
  272. terminlName: node.terminlName,
  273. terminlCode: node.terminlCode,
  274. regionalName: node.regionalName,
  275. regionalCode: node.regionalCode,
  276. channelName: node.channelName,
  277. channelCode: node.channelCode
  278. }))
  279. },
  280. invokerDataConfigTree(level) {
  281. return dataConfigTree(level).then(res => {
  282. this.channelList = this.convertTree(res.data || [])
  283. }).catch(() => {
  284. this.channelList = []
  285. })
  286. },
  287. onLocationChange({ detail }) {
  288. const { value } = detail
  289. this.channelOrRegionalName = value.map(item => item.text).join('/')
  290. // 存储完整的三级路径数据
  291. this.selectedLocationPath = value
  292. },
  293. openModal() {
  294. if (this.disabled || (this.selectedMember.length === 0 && this.notkezhang)) return
  295. if ((!this.attendanceInfo.checkInTime || (this.attendanceInfo.checkInTime && this.attendanceInfo.checkOutTime))) {
  296. return;
  297. }
  298. this.show = true
  299. },
  300. close() {
  301. this.show = false
  302. },
  303. //获取多选的区域
  304. selectedArea(areas) {
  305. let res = areas.filter(area => {
  306. return area.children.some((item) => item.checked)
  307. })
  308. res = res.map(item => {
  309. return {
  310. ...item,
  311. children: item.children.filter(child => child.checked)
  312. }
  313. })
  314. this.selectedAreas = res
  315. },
  316. removeArea(area) {
  317. this.selectedAreas = this.selectedAreas.filter(a => a.id !== area.id)
  318. },
  319. removeSubArea(tags, id) {
  320. console.log(tags, id)
  321. // debugger
  322. const area = this.selectedAreas.find(a => a.id === id)
  323. area.children = tags
  324. },
  325. searchViewShowHandler(show) { // 是否展示搜索用户界面
  326. this.searchViewShow = show || this.addWorkUsers.length
  327. },
  328. selectAddUser(selectItem) { // 移除额外添加人员
  329. const index = this.addWorkUsers.findIndex(item => item.userId === selectItem.userId)
  330. if (index >= 0) {
  331. this.addWorkUsers.splice(index, 1)
  332. } else {
  333. this.addWorkUsers.push(selectItem)
  334. }
  335. },
  336. appendWorkUsers() {
  337. if (this.addWorkUsers.length) {
  338. this.addWorkUsers.forEach(item => {
  339. this.selectUserHandler(item, false)
  340. })
  341. this.addWorkUsers = []
  342. this.searchViewShow = false
  343. this.$refs.selectData.clearInput()
  344. }
  345. },
  346. removeSelcetUser(selectItem, onlyCheck = false) {
  347. const index = this.selectedUser.findIndex(item => item.userId === selectItem.userId)
  348. if (index >= 0) {
  349. !onlyCheck && this.selectedUser.splice(index, 1)
  350. return undefined
  351. }
  352. return selectItem
  353. },
  354. checkUserStatus(selectItem) {
  355. return getPostRecordList({
  356. userId: selectItem.userId,
  357. pageNum: 1,
  358. pageSize: 1
  359. }).then(res => {
  360. if (Array(res.rows) && res.rows.length && res.rows[0].checkOutTime === '2000-01-01 00:00:00') {
  361. if (isAfterTodayStart(res.rows[0].checkInTime)) {
  362. return `${selectItem.nickName || selectItem.userName} 尚未下通道;`
  363. }
  364. } else {
  365. return ''
  366. }
  367. })
  368. },
  369. selectUserHandler(selectItem, unique = true) {
  370. // 🔥 重要:先检查用户是否可以上通道(最新记录的checkOutTime必须不是默认时间)
  371. const addItem = this.removeSelcetUser(selectItem, !unique)
  372. if (addItem) {
  373. this.selectedUser.push(addItem)
  374. }
  375. },
  376. invokerSubmitPostDuty() {
  377. if (this.selectedUser.length === 0) {
  378. uni.showToast({
  379. title: '请选择至少一位人员',
  380. icon: 'none'
  381. });
  382. return;
  383. }
  384. if ((!this.notkezhang && this.getSelectArea.length == 0) || (this.notkezhang && !this.channelOrRegional)) {
  385. uni.showToast({
  386. title: this.notkezhang ? '请选择上岗通道' : '请选择上岗区域',
  387. icon: 'none'
  388. });
  389. return;
  390. }
  391. const checkUserStatusALL = this.selectedUser.map(item => {
  392. return this.checkUserStatus(item)
  393. })
  394. uni.showLoading({ title: '正在查询人员状态...' });
  395. Promise.all(checkUserStatusALL).then((res) => {
  396. const checkResult = res.filter(Boolean)
  397. if (checkResult.length) {
  398. this.errModalContent = checkResult.join('\n')
  399. this.errModalShow = true
  400. } else {
  401. this.submitPostDuty()
  402. }
  403. }).finally(() => {
  404. uni.hideLoading()
  405. })
  406. },
  407. // 修改:提交上通道记录到后端,checkOutTime设为2000年1月1日0点
  408. submitPostDuty() {
  409. if (this.isSubmittingPost) return;
  410. this.isSubmittingPost = true;
  411. uni.showLoading({ title: '正在上通道...' });
  412. const currentTime = this.currentTime
  413. // 获取当前用户信息
  414. const currentUserInfo = this.userInfo;
  415. // 创建随机工作组id
  416. const groupId = generateRandomDigits()
  417. // 为每个选中的用户创建上通道记录
  418. const promises = this.selectedUser.map((item) => {
  419. let postRecordList = []
  420. //如果是科长就是走多选逻辑,如果是班组长就走单选逻辑
  421. if (!this.notkezhang) {
  422. postRecordList = this.getSelectArea.map(ele => {
  423. return {
  424. userId: item.userId,
  425. userName: item.nickName || item.userName,
  426. checkInTime: formatTime(currentTime, 'YYYY-MM-DD hh:mm:ss'),
  427. // 🔥 重要:上通道时传入默认下岗时间(2000年1月1日0点)
  428. checkOutTime: '2000-01-01 00:00:00',
  429. attendanceDate: formatTime(currentTime, 'YYYY-MM-DD'),
  430. // 🔥 使用从getInfo获取的完整用户信息
  431. attendanceTeamId: currentUserInfo.teamsId || currentUserInfo.deptId,
  432. attendanceTeamName: currentUserInfo.teamsName || '未知班组',
  433. attendanceDepartmentId: currentUserInfo.departmentId,
  434. attendanceDepartmentName: currentUserInfo.departmentName || '未知部门',
  435. attendanceStationId: currentUserInfo.stationId,
  436. attendanceStationName: currentUserInfo.stationName || '机场',
  437. remark: '手动添加上通道记录',
  438. terminlCode: '',
  439. terminlName: '',
  440. positionCode: '',
  441. positionName: '',
  442. shiftCode: groupId,
  443. shiftName: '',
  444. ...ele
  445. }
  446. })
  447. } else {
  448. //如果是班组长走单选逻辑
  449. postRecordList = [{
  450. userId: item.userId,
  451. userName: item.nickName || item.userName,
  452. checkInTime: formatTime(currentTime, 'YYYY-MM-DD hh:mm:ss'),
  453. // 🔥 重要:上通道时传入默认下岗时间(2000年1月1日0点)
  454. checkOutTime: '2000-01-01 00:00:00',
  455. attendanceDate: formatTime(currentTime, 'YYYY-MM-DD'),
  456. // 🔥 使用从getInfo获取的完整用户信息
  457. attendanceTeamId: currentUserInfo.teamsId || currentUserInfo.deptId,
  458. attendanceTeamName: currentUserInfo.teamsName || '未知班组',
  459. attendanceDepartmentId: currentUserInfo.departmentId,
  460. attendanceDepartmentName: currentUserInfo.departmentName || '未知部门',
  461. attendanceStationId: currentUserInfo.stationId,
  462. attendanceStationName: currentUserInfo.stationName || '机场',
  463. remark: '手动添加上通道记录',
  464. terminlCode: '',
  465. terminlName: '',
  466. positionCode: '',
  467. positionName: '',
  468. shiftCode: groupId,
  469. shiftName: '',
  470. ...this.positions
  471. }];
  472. }
  473. console.log(postRecordList, "postRecordList")
  474. return addPostRecord(postRecordList)
  475. });
  476. Promise.all(promises).then((results) => {
  477. // 检查是否有失败的记录
  478. const failedCount = results.filter(result => !result || result.code !== 200).length;
  479. const successCount = this.selectedUser.length - failedCount;
  480. if (successCount > 0) {
  481. uni.showToast({
  482. title: `成功上通道${successCount}人${failedCount > 0 ? `,${failedCount}人失败` : ''}`,
  483. icon: successCount === this.selectedUser.length ? 'success' : 'none',
  484. duration: 2000
  485. });
  486. // 清空已添加的用户列表
  487. this.selectedUser = [];
  488. // 重置通道信息
  489. this.channelOrRegional = '',
  490. this.channelOrRegionalName = '',
  491. this.$emit('updateRecord', () => {
  492. this.show = false
  493. })
  494. }
  495. }).catch((error) => {
  496. uni.showToast({
  497. title: '上通道失败:请稍后重试',
  498. icon: 'error',
  499. duration: 2000
  500. });
  501. }).finally(() => {
  502. uni.hideLoading();
  503. this.isSubmittingPost = false;
  504. })
  505. },
  506. //搜索用户
  507. async searchUsers(value) {
  508. const keyword = value.trim();
  509. if (!keyword) {
  510. this.allUsers = [];
  511. return;
  512. }
  513. try {
  514. // 调用用户搜索接口,不限制部门
  515. const response = await getUserList({
  516. nickName: keyword, // 按昵称搜索
  517. status: '0' // 只获取正常状态的用户
  518. });
  519. if (response && response.code === 200) {
  520. this.allUsers = (response.rows || []).map(item => {
  521. return {
  522. ...item,
  523. nickName: formatName(item.nickName)
  524. }
  525. });
  526. } else {
  527. this.allUsers = [];
  528. }
  529. } catch (error) {
  530. this.allUsers = [];
  531. uni.showToast({
  532. title: '搜索失败,请重试',
  533. icon: 'none',
  534. duration: 2000
  535. });
  536. }
  537. },
  538. // 加载同组用户
  539. async loadTeamUsers() {
  540. try {
  541. const currentUserInfo = this.userInfo || {}
  542. const currentUserDeptId = currentUserInfo.deptId || currentUserInfo.teamsId;
  543. // 调用用户列表接口,传递deptId获取同组用户
  544. const response = await getUserList({
  545. deptId: currentUserDeptId, // 🔥 使用从getInfo获取的deptId
  546. status: '0' // 只获取正常状态的用户
  547. });
  548. if (response && response.code === 200) {
  549. this.teamUsers = (response.rows || []).map(item => {
  550. return {
  551. ...item,
  552. nickName: formatName(item.nickName)
  553. }
  554. })
  555. } else {
  556. throw new Error(response.msg || '获取同组用户失败');
  557. }
  558. } catch (error) {
  559. uni.showToast({
  560. title: '加载同组用户失败',
  561. icon: 'none',
  562. duration: 2000
  563. });
  564. // 设置空数组,避免页面报错
  565. this.teamUsers = [];
  566. }
  567. },
  568. },
  569. }
  570. </script>
  571. <style lang="scss" scoped>
  572. .addAttendancePersonnelModal {
  573. .slot-content {}
  574. .empty {
  575. margin: 0 auto;
  576. width: 160px;
  577. height: 155px;
  578. background: url("../../../static/images/Empty.png") no-repeat;
  579. background-size: cover;
  580. }
  581. .modal-content {
  582. width: 90vw;
  583. max-height: 75vh;
  584. padding: 10px 0;
  585. .title {
  586. height: 24px;
  587. font-weight: 400;
  588. font-size: 18px;
  589. color: #333333;
  590. line-height: 21px;
  591. display: flex;
  592. justify-content: space-between;
  593. align-items: center;
  594. padding: 15px;
  595. box-sizing: border-box;
  596. }
  597. .title-cell {
  598. padding: 10px 15px;
  599. box-sizing: border-box;
  600. .selected-areas {
  601. .area-item {
  602. .area-label {
  603. height: 65rpx;
  604. line-height: 65rpx;
  605. font-weight: 400;
  606. font-size: 18px;
  607. display: flex;
  608. align-items: center;
  609. }
  610. }
  611. }
  612. .title-text {
  613. height: 16px;
  614. font-weight: 400;
  615. font-size: 14px;
  616. color: #333333;
  617. line-height: 16px;
  618. text-align: left;
  619. margin-bottom: 14px;
  620. }
  621. }
  622. .personnel-list {
  623. display: flex;
  624. flex-wrap: wrap;
  625. row-gap: 8px;
  626. column-gap: 6px;
  627. .personnel-item {
  628. width: fit-content;
  629. height: 34px;
  630. background: #F0F0F0;
  631. border-radius: 6px;
  632. display: flex;
  633. align-items: center;
  634. column-gap: 6px;
  635. padding: 0 5px;
  636. .personnel-img {
  637. width: 24px;
  638. height: 24px;
  639. border-radius: 6px;
  640. overflow: hidden;
  641. }
  642. .personnel-name {
  643. width: fit-content;
  644. font-weight: 400;
  645. font-size: 13px;
  646. color: #3D3D3D;
  647. text-align: left;
  648. font-style: normal;
  649. text-transform: none;
  650. }
  651. .personnel-close {
  652. width: 16px;
  653. }
  654. .radio-button {
  655. width: 14px;
  656. height: 14px;
  657. border: 1px solid #2196F3;
  658. background: #fff;
  659. border-radius: 50%;
  660. position: relative;
  661. transition: border-color 0.2s ease;
  662. }
  663. /* 激活状态 - 蓝色边框 */
  664. .radio-button.active {
  665. border-color: #496CF4;
  666. }
  667. /* 激活状态 - 中间的蓝点 */
  668. .radio-button.active::after {
  669. content: '';
  670. position: absolute;
  671. top: 50%;
  672. left: 50%;
  673. transform: translate(-50%, -50%);
  674. width: 7px;
  675. height: 7px;
  676. background-color: #2196F3;
  677. border-radius: 50%;
  678. }
  679. /* 聚焦状态 */
  680. .radio-button:focus {
  681. outline: none;
  682. box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.3);
  683. }
  684. /* 悬停效果 */
  685. .radio-button:hover .radio-button:not(.active) {
  686. border-color: #999;
  687. }
  688. /* 禁用状态 */
  689. .radio-button.disabled {
  690. opacity: 0.5;
  691. border-color: #999;
  692. background: #f0f0f0;
  693. cursor: not-allowed;
  694. }
  695. }
  696. }
  697. .footer-btn {
  698. padding: 10px 15px;
  699. .modal-slot-content {
  700. white-space: pre;
  701. }
  702. }
  703. }
  704. }
  705. </style>