AddAttendancePersonnelModal.vue 23 KB

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