| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- <template>
- <view class="addAttendancePersonnelModal">
- <view class="slot-content" @click.stop="openModal">
- <slot></slot>
- </view>
- <u-popup :show="show" mode="center" :round="8">
- <view class="modal-content">
- <view class="title">
- <text>{{ notkezhang ? '当班人员' : '负责区域' }}</text>
- <u-icon name="close" color="#666666" size="20" @click="close" />
- </view>
- <view class="title-cell" v-if="notkezhang">
- <SelectData ref="selectData" @search="searchUsers" @searchViewShowEvent="searchViewShowHandler" />
- </view>
- <view class="title-cell" v-if="!searchViewShow && selectedUser && selectedUser.length">
- <view class="title-text">{{ `班组成员(${selectedUser.length}人)` }}</view>
- <view class="personnel-list">
- <view class="personnel-item" v-for="item of selectedUser" :key="item.userId"
- @click="removeSelcetUser(item)">
- <view class="personnel-img">
- <UserAvatar :userName="item.nickName || item.userName" :avatarLink="item.avatar" />
- </view>
- <view class="personnel-name">{{ item.nickName || item.userName }}</view>
- <view class="personnel-close" v-if="notkezhang">
- <u-icon name="close" color="#666666" size="14" />
- </view>
- </view>
- </view>
- </view>
- <view class="title-cell" v-if="notkezhang && searchViewShow">
- <view style="margin-bottom: 15px;" v-if="addWorkUsers && addWorkUsers.length">
- <view class="title-text">
- {{ `添加人员(${addWorkUsers.length}人)` }}
- </view>
- <view class="personnel-list" v-if="addWorkUsers && addWorkUsers.length">
- <view class="personnel-item" v-for="item of addWorkUsers" :key="item.userId" @click="selectAddUser(item)">
- <view class="personnel-img">
- <UserAvatar :userName="item.nickName" :avatarLink="item.avatar" />
- </view>
- <view class="personnel-name">{{ item.nickName }}</view>
- <view class="personnel-close">
- <u-icon name="close" color="#666666" size="14" />
- </view>
- </view>
- </view>
- </view>
- <view class="title-text">
- {{ `搜索结果(${allUsers.length}人)` }}
- </view>
- <view class="personnel-list" v-if="allUsers && allUsers.length">
- <view class="personnel-item" v-for="item of allUsers" :key="item.userId" @click="selectAddUser(item)">
- <view class="personnel-img">
- <UserAvatar :userName="item.nickName" :avatarLink="item.avatar" />
- </view>
- <view class="personnel-name">{{ item.nickName }}</view>
- </view>
- </view>
- <view v-else class="empty"></view>
- </view>
- <view class="title-cell" v-if="!searchViewShow">
- <view class="title-text">{{ notkezhang ? '选择上通道时间' : '选择上区域时间' }}</view>
- <uni-datetime-picker type="datetime" v-model="currentTime" />
- </view>
- <view class="title-cell" v-if="!searchViewShow">
- <view class="title-text" style="display: flex; align-items: center;">{{ notkezhang ? '选择通道' : '选择区域' }}
- <view v-if="!userInfo.roles.includes('banzuzhang')">
- <SelectArea :notkezhang="notkezhang" @selected="selectedArea">
- <u-icon name="plus-circle" color="#2A70D1" size="25" />
- </SelectArea>
- </view>
- </view>
- <view class="">
- <uniDataPicker v-if="userInfo.roles.includes('banzuzhang')" :localdata="channelList"
- :popup-title="notkezhang ? '请选择上岗通道' : '请选择上岗区域'" v-model="channelOrRegional"
- @change="onLocationChange" />
- <view class="selected-areas" v-if="selectedAreas && selectedAreas.length">
- <view class="area-item" v-for="area in selectedAreas" :key="area.id">
- <view class="area-label">{{ area.label }}<u-icon name="close" color="#666666" size="14"
- @click="removeArea(area)" /></view>
- <text-tag :tags="area.children" @remove="removeSubArea($event, area.id)" />
- </view>
- </view>
- </view>
- </view>
- <view class="footer-btn">
- <view v-if="searchViewShow" class="custom-btn-normal" @click="appendWorkUsers"
- :class="{ disabled: addWorkUsers.length === 0 }">添加人员</view>
- <view v-else class="custom-btn-normal" style="margin-top: 10rpx;" @click="invokerSubmitPostDuty"
- :class="{ disabled: selectedUserIds.length === 0 }">{{ notkezhang ? '确认上通道' : '确认上区域' }}</view>
- <u-modal :show="errModalShow" @confirm="errModalShow = false" width="80vw">
- <view class="modal-slot-content">
- {{ errModalContent }}
- </view>
- </u-modal>
- </view>
- </view>
- </u-popup>
- </view>
- </template>
- <script>
- import SelectData from './SelectData'
- import SelectArea from './SelectArea'
- import TimePicker from './TimePicker'
- import {
- getUserList,
- addPostRecord,
- getPostRecordList,
- dataConfigTree,
- memberList,
- queryLastTime
- } from "@/api/attendance/attendance"
- import moment from 'moment'
- import uniDataPicker from '@/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker'
- import { formatTime, formatName, isAfterTodayStart } from '@/utils/formatUtils'
- import { generateRandomDigits } from '@/utils/handler'
- import UserAvatar from './UserAvatar'
- import { getHandleAreaData } from '@/utils/common'
- export default {
- components: { uniDataPicker, SelectData, TimePicker, UserAvatar, SelectArea },
- props: {
- userInfo: {
- type: Object,
- default: () => ({})
- },
- disabled: {
- type: Boolean,
- default: false
- },
- notkezhang: {
- type: Boolean,
- default: undefined
- },
- selectedMember: {
- type: Array,
- default: () => []
- },
- attendanceInfo: { // 考勤信息
- type: Object,
- default: () => ({})
- },
- },
- data() {
- return {
- show: false,
- searchFocus: false,
- channelOrRegional: '',
- channelOrRegionalName: '',
- channelList: [
- { value: 0, text: "通道A" },
- ],
- selectedUser: [], //选中的人员
- selectedAreas: [], // 选中的区域
- allUsers: [],
- addWorkUsers: [], // 额外添加的人员
- teamUsers: [],
- searchViewShow: false,
- isSubmittingPost: false,
- currentTime: undefined,
- errModalShow: false,
- errModalContent: ''
- }
- },
- computed: {
- selectedUserIds() {
- return (this.selectedUser || []).map(item => item.userId)
- },
- //单选获取的区域
- positions() {
- return this.notkezhang ? {
- regionalCode: '',
- regionalName: '',
- channelCode: this.channelOrRegional,
- channelName: this.channelOrRegionalName
- } : {
- regionalCode: this.channelOrRegional,
- regionalName: this.channelOrRegionalName,
- channelCode: '',
- channelName: ''
- }
- },
- //多选获得的区域
- getSelectArea() {
- let area = []
- this.selectedAreas.forEach(item => {
- item.children.forEach(child => {
- area.push(this.notkezhang ? {
- regionalCode: '',
- regionalName: '',
- channelCode: child.code,
- channelName: child.label,
- terminlCode: item.code,
- terminlName: item.label
- } : {
- regionalCode: child.code,
- regionalName: child.label,
- channelCode: '',
- channelName: '',
- terminlCode: item.code,
- terminlName: item.label
- })
- })
- })
- return area
- }
- },
- watch: {
- show(newValue) {
- if (newValue) {
- if (this.userInfo.roles.includes('kezhang')) {
- this.getQueryLastTime()
- }
- this.currentTime = formatTime(new Date())
- if (this.notkezhang) {
- if (this.userInfo.roles.includes('banzuzhang')) {
- memberList({
- attendanceTeamId: this.userInfo.teamsId,
- attendanceDate: moment().format('YYYY-MM-DD')
- }).then(res => {
- console.log(res.rows, "res.rows")
- this.selectedUser = res.data || []
- })
- return
- }
- this.loadTeamUsers().then(() => {
- // 选中全组人员
- this.teamUsers.forEach((item) => {
- this.selectUserHandler(item, false)
- })
- })
- } else {
- this.loadTeamUsers().then(() => {
- // 查自己
- const curUser = this.teamUsers.find((item) => {
- return item.userId === this.userInfo.userId
- })
- this.selectUserHandler(curUser, false)
- })
- }
- } else {
- this.allUsers = []
- }
- },
- notkezhang: {
- handler(newValue) {
- const level = newValue ? 3 : 2
- this.invokerDataConfigTree(level)
- },
- immediate: true
- }
- },
- methods: {
- //查询最后一次上岗的区域
- async getQueryLastTime() {
- let res = await queryLastTime({
- userId: this.userInfo.userId,
- })
- this.selectedAreas = getHandleAreaData(res.data || [], this.notkezhang)
- console.log(this.selectedAreas, "this.selectedAreas")
- },
- convertTree(list = []) {
- return list.map(node => ({
- text: node.label,
- value: node.code,
- children: node.children ? this.convertTree(node.children) : null
- }))
- },
- invokerDataConfigTree(level) {
- return dataConfigTree(level).then(res => {
- this.channelList = this.convertTree(res.data || [])
- }).catch(() => {
- this.channelList = []
- })
- },
- onLocationChange({ detail }) {
- const { value } = detail
- this.channelOrRegionalName = value.map(item => item.text).join('/')
- },
- openModal() {
- if (this.disabled || (this.selectedMember.length === 0 && this.notkezhang)) return
- if ((!this.attendanceInfo.checkInTime || (this.attendanceInfo.checkInTime && this.attendanceInfo.checkOutTime))) {
- return;
- }
- this.show = true
- },
- close() {
- this.show = false
- },
- //获取多选的区域
- selectedArea(areas) {
- let res = areas.filter(area => {
- return area.children.some((item) => item.checked)
- })
- res = res.map(item => {
- return {
- ...item,
- children: item.children.filter(child => child.checked)
- }
- })
- this.selectedAreas = res
- },
- removeArea(area) {
- this.selectedAreas = this.selectedAreas.filter(a => a.id !== area.id)
- },
- removeSubArea(tags, id) {
- console.log(tags, id)
- // debugger
- const area = this.selectedAreas.find(a => a.id === id)
- area.children = tags
- },
- searchViewShowHandler(show) { // 是否展示搜索用户界面
- this.searchViewShow = show || this.addWorkUsers.length
- },
- selectAddUser(selectItem) { // 移除额外添加人员
- const index = this.addWorkUsers.findIndex(item => item.userId === selectItem.userId)
- if (index >= 0) {
- this.addWorkUsers.splice(index, 1)
- } else {
- this.addWorkUsers.push(selectItem)
- }
- },
- appendWorkUsers() {
- if (this.addWorkUsers.length) {
- this.addWorkUsers.forEach(item => {
- this.selectUserHandler(item, false)
- })
- this.addWorkUsers = []
- this.searchViewShow = false
- this.$refs.selectData.clearInput()
- }
- },
- removeSelcetUser(selectItem, onlyCheck = false) {
- const index = this.selectedUser.findIndex(item => item.userId === selectItem.userId)
- if (index >= 0) {
- !onlyCheck && this.selectedUser.splice(index, 1)
- return undefined
- }
- return selectItem
- },
- checkUserStatus(selectItem) {
- return getPostRecordList({
- userId: selectItem.userId,
- pageNum: 1,
- pageSize: 1
- }).then(res => {
- if (Array(res.rows) && res.rows.length && res.rows[0].checkOutTime === '2000-01-01 00:00:00') {
- if (isAfterTodayStart(res.rows[0].checkInTime)) {
- return `${selectItem.nickName || selectItem.userName} 尚未下通道;`
- }
- } else {
- return ''
- }
- })
- },
- selectUserHandler(selectItem, unique = true) {
- // 🔥 重要:先检查用户是否可以上通道(最新记录的checkOutTime必须不是默认时间)
- const addItem = this.removeSelcetUser(selectItem, !unique)
- if (addItem) {
- this.selectedUser.push(addItem)
- }
- },
- invokerSubmitPostDuty() {
- if (this.selectedUser.length === 0) {
- uni.showToast({
- title: '请选择至少一位人员',
- icon: 'none'
- });
- return;
- }
- if ((!this.notkezhang && this.getSelectArea.length == 0) || (this.notkezhang && !this.channelOrRegional)) {
- uni.showToast({
- title: this.notkezhang ? '请选择上岗通道' : '请选择上岗区域',
- icon: 'none'
- });
- return;
- }
- debugger
- const checkUserStatusALL = this.selectedUser.map(item => {
- return this.checkUserStatus(item)
- })
- uni.showLoading({ title: '正在查询人员状态...' });
- debugger
- Promise.all(checkUserStatusALL).then((res) => {
- const checkResult = res.filter(Boolean)
- if (checkResult.length) {
- this.errModalContent = checkResult.join('\n')
- this.errModalShow = true
- } else {
- this.submitPostDuty()
- }
- }).finally(() => {
- uni.hideLoading()
- })
- },
- // 修改:提交上通道记录到后端,checkOutTime设为2000年1月1日0点
- submitPostDuty() {
- if (this.isSubmittingPost) return;
- this.isSubmittingPost = true;
- uni.showLoading({ title: '正在上通道...' });
- const currentTime = this.currentTime
- // 获取当前用户信息
- const currentUserInfo = this.userInfo;
- // 创建随机工作组id
- const groupId = generateRandomDigits()
- // 为每个选中的用户创建上通道记录
- const promises = this.selectedUser.map((item) => {
- let postRecordList = []
- //如果是科长就是走多选逻辑,如果是班组长就走单选逻辑
- if (!this.notkezhang) {
- postRecordList = this.getSelectArea.map(ele => {
- return {
- userId: item.userId,
- userName: item.nickName || item.userName,
- checkInTime: formatTime(currentTime, 'YYYY-MM-DD hh:mm:ss'),
- // 🔥 重要:上通道时传入默认下岗时间(2000年1月1日0点)
- checkOutTime: '2000-01-01 00:00:00',
- attendanceDate: formatTime(currentTime, 'YYYY-MM-DD'),
- // 🔥 使用从getInfo获取的完整用户信息
- attendanceTeamId: currentUserInfo.teamsId || currentUserInfo.deptId,
- attendanceTeamName: currentUserInfo.teamsName || '未知班组',
- attendanceDepartmentId: currentUserInfo.departmentId,
- attendanceDepartmentName: currentUserInfo.departmentName || '未知部门',
- attendanceStationId: currentUserInfo.stationId,
- attendanceStationName: currentUserInfo.stationName || '机场',
- remark: '手动添加上通道记录',
- terminlCode: '',
- terminlName: '',
- positionCode: '',
- positionName: '',
- shiftCode: groupId,
- shiftName: '',
- ...ele
- }
- })
- } else {
- //如果是班组长走单选逻辑
- postRecordList = [{
- userId: item.userId,
- userName: item.nickName || item.userName,
- checkInTime: formatTime(currentTime, 'YYYY-MM-DD hh:mm:ss'),
- // 🔥 重要:上通道时传入默认下岗时间(2000年1月1日0点)
- checkOutTime: '2000-01-01 00:00:00',
- attendanceDate: formatTime(currentTime, 'YYYY-MM-DD'),
- // 🔥 使用从getInfo获取的完整用户信息
- attendanceTeamId: currentUserInfo.teamsId || currentUserInfo.deptId,
- attendanceTeamName: currentUserInfo.teamsName || '未知班组',
- attendanceDepartmentId: currentUserInfo.departmentId,
- attendanceDepartmentName: currentUserInfo.departmentName || '未知部门',
- attendanceStationId: currentUserInfo.stationId,
- attendanceStationName: currentUserInfo.stationName || '机场',
- remark: '手动添加上通道记录',
- terminlCode: '',
- terminlName: '',
- positionCode: '',
- positionName: '',
- shiftCode: groupId,
- shiftName: '',
- ...this.positions
- }];
- }
- console.log(postRecordList, "postRecordList")
- debugger
- return addPostRecord(postRecordList)
- });
- Promise.all(promises).then((results) => {
- // 检查是否有失败的记录
- const failedCount = results.filter(result => !result || result.code !== 200).length;
- const successCount = this.selectedUser.length - failedCount;
- if (successCount > 0) {
- uni.showToast({
- title: `成功上通道${successCount}人${failedCount > 0 ? `,${failedCount}人失败` : ''}`,
- icon: successCount === this.selectedUser.length ? 'success' : 'none',
- duration: 2000
- });
- // 清空已添加的用户列表
- this.selectedUser = [];
- // 重置通道信息
- this.channelOrRegional = '',
- this.channelOrRegionalName = '',
- this.$emit('updateRecord', () => {
- this.show = false
- })
- }
- }).catch((error) => {
- uni.showToast({
- title: '上通道失败:请稍后重试',
- icon: 'error',
- duration: 2000
- });
- }).finally(() => {
- uni.hideLoading();
- this.isSubmittingPost = false;
- })
- },
- //搜索用户
- async searchUsers(value) {
- const keyword = value.trim();
- if (!keyword) {
- this.allUsers = [];
- return;
- }
- try {
- // 调用用户搜索接口,不限制部门
- const response = await getUserList({
- nickName: keyword, // 按昵称搜索
- status: '0' // 只获取正常状态的用户
- });
- if (response && response.code === 200) {
- this.allUsers = (response.rows || []).map(item => {
- return {
- ...item,
- nickName: formatName(item.nickName)
- }
- });
- } else {
- this.allUsers = [];
- }
- } catch (error) {
- this.allUsers = [];
- uni.showToast({
- title: '搜索失败,请重试',
- icon: 'none',
- duration: 2000
- });
- }
- },
- // 加载同组用户
- async loadTeamUsers() {
- try {
- const currentUserInfo = this.userInfo || {}
- const currentUserDeptId = currentUserInfo.deptId || currentUserInfo.teamsId;
- // 调用用户列表接口,传递deptId获取同组用户
- const response = await getUserList({
- deptId: currentUserDeptId, // 🔥 使用从getInfo获取的deptId
- status: '0' // 只获取正常状态的用户
- });
- if (response && response.code === 200) {
- this.teamUsers = (response.rows || []).map(item => {
- return {
- ...item,
- nickName: formatName(item.nickName)
- }
- })
- } else {
- throw new Error(response.msg || '获取同组用户失败');
- }
- } catch (error) {
- uni.showToast({
- title: '加载同组用户失败',
- icon: 'none',
- duration: 2000
- });
- // 设置空数组,避免页面报错
- this.teamUsers = [];
- }
- },
- },
- }
- </script>
- <style lang="scss" scoped>
- .addAttendancePersonnelModal {
- .slot-content {}
- .empty {
- margin: 0 auto;
- width: 160px;
- height: 155px;
- background: url("../../../static/images/Empty.png") no-repeat;
- background-size: cover;
- }
- .modal-content {
- width: 90vw;
- max-height: 75vh;
- padding: 10px 0;
- .title {
- height: 24px;
- font-weight: 400;
- font-size: 18px;
- color: #333333;
- line-height: 21px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 15px;
- box-sizing: border-box;
- }
- .title-cell {
- padding: 10px 15px;
- box-sizing: border-box;
- .selected-areas {
- .area-item {
- .area-label {
- height: 65rpx;
- line-height: 65rpx;
- font-weight: 400;
- font-size: 18px;
- display: flex;
- align-items: center;
- }
- }
- }
- .title-text {
- height: 16px;
- font-weight: 400;
- font-size: 14px;
- color: #333333;
- line-height: 16px;
- text-align: left;
- margin-bottom: 14px;
- }
- }
- .personnel-list {
- display: flex;
- flex-wrap: wrap;
- row-gap: 8px;
- column-gap: 6px;
- .personnel-item {
- width: fit-content;
- height: 34px;
- background: #F0F0F0;
- border-radius: 6px;
- display: flex;
- align-items: center;
- column-gap: 6px;
- padding: 0 5px;
- .personnel-img {
- width: 24px;
- height: 24px;
- border-radius: 6px;
- overflow: hidden;
- }
- .personnel-name {
- width: fit-content;
- font-weight: 400;
- font-size: 13px;
- color: #3D3D3D;
- text-align: left;
- font-style: normal;
- text-transform: none;
- }
- .personnel-close {
- width: 16px;
- }
- .radio-button {
- width: 14px;
- height: 14px;
- border: 1px solid #2196F3;
- background: #fff;
- border-radius: 50%;
- position: relative;
- transition: border-color 0.2s ease;
- }
- /* 激活状态 - 蓝色边框 */
- .radio-button.active {
- border-color: #496CF4;
- }
- /* 激活状态 - 中间的蓝点 */
- .radio-button.active::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 7px;
- height: 7px;
- background-color: #2196F3;
- border-radius: 50%;
- }
- /* 聚焦状态 */
- .radio-button:focus {
- outline: none;
- box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.3);
- }
- /* 悬停效果 */
- .radio-button:hover .radio-button:not(.active) {
- border-color: #999;
- }
- /* 禁用状态 */
- .radio-button.disabled {
- opacity: 0.5;
- border-color: #999;
- background: #f0f0f0;
- cursor: not-allowed;
- }
- }
- }
- .footer-btn {
- padding: 10px 15px;
- .modal-slot-content {
- white-space: pre;
- }
- }
- }
- }
- </style>
|