| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="class-page"> |
| | | <div class="search-container"> |
| | | <div class="search-form"> |
| | | <div class="search-row"> |
| | | <div class="search-item"> |
| | | <label class="search-label">éæ©æ¶é´ï¼</label> |
| | | <div class="search-input-group"> |
| | | <el-date-picker v-model="query.year" |
| | | type="year" |
| | | size="small" |
| | | format="YYYY" |
| | | placeholder="鿩年" |
| | | @change="refreshTable()" |
| | | style="width: 90px" |
| | | :clearable="false" /> |
| | | <el-select v-model="query.month" |
| | | clearable |
| | | placeholder="éæ©æ" |
| | | style="width: 70px; margin-left: 8px" |
| | | size="small" |
| | | @change="refreshTable()"> |
| | | <el-option v-for="item in monthOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <div class="search-item"> |
| | | <el-input v-model="query.userName" |
| | | placeholder="请è¾å
¥äººååç§°" |
| | | size="small" |
| | | style="width: 120px" |
| | | clearable |
| | | @keyup.enter="refreshTable()" /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <el-tree-select v-model="query.sysDeptId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | size="small" |
| | | clearable |
| | | @change="refreshTable()" |
| | | style="width: 140px" /> |
| | | </div> |
| | | <div class="search-actions"> |
| | | <el-button size="small" |
| | | type="primary" |
| | | @click="refreshTable()" |
| | | :icon="Search"> |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | <el-button size="small" |
| | | @click="refresh()" |
| | | :icon="Refresh" |
| | | style="margin-left: 8px"> |
| | | éç½® |
| | | </el-button> |
| | | </div> |
| | | <div class="search-buttons"> |
| | | <el-button size="small" |
| | | type="primary" |
| | | @click="configTime" |
| | | :icon="Setting"> |
| | | çæ¬¡é
ç½® |
| | | </el-button> |
| | | <el-button size="small" |
| | | type="success" |
| | | @click="handleDown" |
| | | :loading="downLoading" |
| | | :icon="Download" |
| | | style="margin-left: 8px"> |
| | | å¯¼åº |
| | | </el-button> |
| | | <el-button size="small" |
| | | type="warning" |
| | | @click="schedulingVisible = true" |
| | | :icon="Calendar" |
| | | style="margin-left: 8px"> |
| | | æç |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="scheduling-container" |
| | | v-loading="pageLoading"> |
| | | <!-- æåº¦æç --> |
| | | <div class="scheduling-table" |
| | | v-show="query.month"> |
| | | <div class="scheduling-left"> |
| | | <div class="scheduling-header"> |
| | | 人ååç§° |
| | | </div> |
| | | <div class="scheduling-user" |
| | | :class="{ 'scheduling-user-hover': currentUserIndex == index }" |
| | | v-for="(item, index) in listForm" |
| | | :key="'e' + index" |
| | | @mouseenter="onMouseEnter(index)" |
| | | @mouseleave="currentUserIndex = null"> |
| | | <div class="user-avatar"> |
| | | {{ item.name ? item.name.charAt(0) : "" }} |
| | | </div> |
| | | <div class="user-details"> |
| | | <h4 class="user-name">{{ item.name }}</h4> |
| | | <!-- <div class="user-stats"> |
| | | <span class="stat-item">æ©:{{ item.day0 }}</span> |
| | | <span class="stat-item">ä¸:{{ item.day1 }}</span> |
| | | <span class="stat-item">å¤:{{ item.day2 }}</span> |
| | | <span class="stat-item">ä¼:{{ item.day3 }}</span> |
| | | <span class="stat-item">å:{{ item.day4 }}</span> |
| | | <span class="stat-item">å·®:{{ item.day6 }}</span> |
| | | </div> --> |
| | | <div class="user-total"> |
| | | <span class="total-label">å计åºå¤:</span> |
| | | <span class="total-value">{{ item.monthlyAttendance.totalAttendance }}天</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="scheduling-right"> |
| | | <div class="scheduling-calendar"> |
| | | <div class="calendar-header"> |
| | | <div class="calendar-header-item" |
| | | v-for="(item, index) in weeks" |
| | | :key="'b' + index"> |
| | | <span class="week-number" |
| | | v-if="item.week == '卿¥'">{{ item.weekNum }}å¨</span> |
| | | <div class="day-info"> |
| | | <span class="day-number">{{ item.day }}</span> |
| | | <span class="day-week">{{ item.week.charAt(1) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="calendar-body"> |
| | | <div class="calendar-row" |
| | | v-for="(item, index) in listForm" |
| | | :key="'c' + index" |
| | | :class="{ 'calendar-row-hover': currentUserIndex == index }" |
| | | @mouseenter="onMouseEnter(index)" |
| | | @mouseleave="currentUserIndex = null"> |
| | | <div class="calendar-cell" |
| | | v-for="(m, i) in item.list" |
| | | :key="'d' + i"> |
| | | <el-dropdown trigger="click" |
| | | placement="bottom" |
| | | @command="(e) => handleCommand(e, m)" |
| | | class="shift-dropdown"> |
| | | <div class="shift-box" |
| | | :class="{ |
| | | 'shift-box-early': m.shift === 'æ©ç', |
| | | 'shift-box-mid': m.shift === 'ä¸ç', |
| | | 'shift-box-night': m.shift === 'å¤ç', |
| | | 'shift-box-rest': m.shift === '伿¯', |
| | | 'shift-box-leave': m.shift === '请å', |
| | | 'shift-box-other': m.shift === 'å¤11', |
| | | 'shift-box-business': m.shift === 'å¤12', |
| | | }"> |
| | | <span class="shift-text">{{ getShiftNameByValue(m.shift) || 'â' }}</span> |
| | | </div> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item v-for="(n, j) in classType" |
| | | :key="'h' + j" |
| | | :command="n.id">{{ n.shift || 'â' |
| | | }}</el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 年度æç --> |
| | | <div class="yearly-table" |
| | | v-show="!query.month"> |
| | | <div class="scheduling-left"> |
| | | <div class="scheduling-header"> |
| | | 人ååç§° |
| | | </div> |
| | | <div class="scheduling-user" |
| | | :class="{ 'scheduling-user-hover': currentUserIndex == index }" |
| | | v-for="(item, index) in yearList" |
| | | :key="'e' + index" |
| | | @mouseenter="onMouseEnter(index)" |
| | | @mouseleave="currentUserIndex = null"> |
| | | <div class="user-avatar"> |
| | | {{ item.name ? item.name.charAt(0) : "" }} |
| | | </div> |
| | | <div class="user-details"> |
| | | <h4 class="user-name">{{ item.name }}</h4> |
| | | <!-- <div class="user-stats"> |
| | | <span class="stat-item">æ©:{{ item.day0 }}</span> |
| | | <span class="stat-item">ä¸:{{ item.day1 }}</span> |
| | | <span class="stat-item">å¤:{{ item.day2 }}</span> |
| | | <span class="stat-item">ä¼:{{ item.day3 }}</span> |
| | | <span class="stat-item">å:{{ item.day4 }}</span> |
| | | <span class="stat-item">å·®:{{ item.day6 }}</span> |
| | | </div> --> |
| | | <div class="user-total"> |
| | | <span class="total-label">å计åºå¤:</span> |
| | | <span class="total-value">{{ item.work_time }}天</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="scheduling-right"> |
| | | <div class="yearly-calendar"> |
| | | <div class="yearly-header"> |
| | | <div class="yearly-header-item" |
| | | v-for="(item, index) in monthList" |
| | | :key="'b' + index"> |
| | | <span class="month-name">{{ item }}æ</span> |
| | | </div> |
| | | </div> |
| | | <div class="yearly-body"> |
| | | <div class="yearly-row" |
| | | v-for="(item, index) in yearList" |
| | | :key="'c' + index" |
| | | :class="{ 'calendar-row-hover': currentUserIndex == index }" |
| | | @mouseenter="onMouseEnter(index)" |
| | | @mouseleave="currentUserIndex = null"> |
| | | <div class="yearly-cell" |
| | | v-for="(m, i) in item.monthList" |
| | | :key="'d' + i"> |
| | | <div class="monthly-attendance"> |
| | | <span class="attendance-label">å计åºå¤ï¼</span> |
| | | <span class="attendance-value">{{ m.totalMonthAttendance }}</span> |
| | | </div> |
| | | <!-- <div class="monthly-stats"> |
| | | <span class="stat-item">æ©:{{ m.day0 }}</span> |
| | | <span class="stat-item">ä¸:{{ m.day1 }}</span> |
| | | <span class="stat-item">å¤:{{ m.day2 }}</span> |
| | | <span class="stat-item">ä¼:{{ m.day3 }}</span> |
| | | <span class="stat-item">å:{{ m.day4 }}</span> |
| | | <span class="stat-item">å·®:{{ m.day6 }}</span> |
| | | </div> --> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div style="display: flex; justify-content: flex-end; margin-top: 10px; margin-right: 30px"> |
| | | <el-pagination background |
| | | @current-change="currentChange" |
| | | :page-size="pageSize" |
| | | :current-page="currentPage" |
| | | layout="total, prev, pager, next, jumper" |
| | | :total="total"> |
| | | </el-pagination> |
| | | </div> |
| | | <el-dialog title="æç" |
| | | v-model="schedulingVisible" |
| | | width="400px"> |
| | | <div class="search_thing"> |
| | | <div class="search_label" |
| | | style="width: 90px"> |
| | | <span style="color: red; margin-right: 4px">*</span>卿¬¡ï¼ |
| | | </div> |
| | | <div class="search_input"> |
| | | <el-date-picker v-model="schedulingQuery.week" |
| | | type="week" |
| | | format="YYYY 第 ww å¨" |
| | | placeholder="鿩卿¬¡" |
| | | style="width: 100%"> |
| | | </el-date-picker> |
| | | </div> |
| | | </div> |
| | | <div class="search_thing"> |
| | | <div class="search_label" |
| | | style="width: 90px"> |
| | | <span style="color: red; margin-right: 4px">*</span>人ååç§°ï¼ |
| | | </div> |
| | | <div class="search_input"> |
| | | <el-select v-model="schedulingQuery.userId" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | multiple |
| | | clearable |
| | | collapse-tags> |
| | | <el-option v-for="item in personList" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <div class="search_thing"> |
| | | <div class="search_label" |
| | | style="width: 90px"> |
| | | <span style="color: red; margin-right: 4px">*</span>çæ¬¡ï¼ |
| | | </div> |
| | | <div class="search_input"> |
| | | <el-select v-model="schedulingQuery.shift" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in classType" |
| | | :key="item.id" |
| | | :label="getShiftNameByValue(item.shift)" |
| | | :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="schedulingVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="confirmScheduling" |
| | | :loading="loading">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { |
| | | page, |
| | | pageYear, |
| | | add, |
| | | exportFile, |
| | | update, |
| | | staffOnJobListPage, |
| | | } from "@/api/personnelManagement/class"; |
| | | import { deptTreeSelect } from "@/api/system/user.js"; |
| | | import { getAttendanceRules } from "@/api/personnelManagement/attendanceRules.js"; |
| | | import { useDict } from "@/utils/dict"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const router = useRouter(); |
| | | |
| | | // æ¥è¯¢æ¡ä»¶ |
| | | const query = reactive({ |
| | | userName: "", |
| | | sysDeptId: "", |
| | | year: new Date(), |
| | | month: new Date().getMonth() + 1, |
| | | }); |
| | | // è·åçæ¬¡åå
¸å¼ |
| | | const { shifts_list } = useDict("shifts_list"); |
| | | // æä»½é项 |
| | | const monthOptions = [ |
| | | { value: 1, label: "1æ" }, |
| | | { value: 2, label: "2æ" }, |
| | | { value: 3, label: "3æ" }, |
| | | { value: 4, label: "4æ" }, |
| | | { value: 5, label: "5æ" }, |
| | | { value: 6, label: "6æ" }, |
| | | { value: 7, label: "7æ" }, |
| | | { value: 8, label: "8æ" }, |
| | | { value: 9, label: "9æ" }, |
| | | { value: 10, label: "10æ" }, |
| | | { value: 11, label: "11æ" }, |
| | | { value: 12, label: "12æ" }, |
| | | ]; |
| | | |
| | | // é¨é¨å表 |
| | | const deptOptions = ref([]); |
| | | |
| | | // å¨å表 |
| | | const weeks = ref([]); |
| | | |
| | | // çæ¬¡ç±»å |
| | | const classType = ref([]); |
| | | |
| | | // å½åç¨æ·ç´¢å¼ |
| | | const currentUserIndex = ref(null); |
| | | |
| | | // æçå¼¹çªæ¾ç¤ºç¶æ |
| | | const schedulingVisible = ref(false); |
| | | |
| | | // 人åå表 |
| | | const personList = ref([]); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | |
| | | // æçæ¥è¯¢æ¡ä»¶ |
| | | const schedulingQuery = reactive({ |
| | | week: "", |
| | | userId: null, |
| | | shift: "", |
| | | }); |
| | | |
| | | // åè¡¨æ°æ® |
| | | const listForm = ref([]); |
| | | |
| | | // å½å页 |
| | | const currentPage = ref(1); |
| | | |
| | | // æ¯é¡µæ¡æ° |
| | | const pageSize = ref(6); |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(3); |
| | | |
| | | // 页é¢å è½½ç¶æ |
| | | const pageLoading = ref(false); |
| | | |
| | | // æä»½å表 |
| | | const monthList = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); |
| | | |
| | | // 年度å表 |
| | | const yearList = ref([]); |
| | | |
| | | // 导åºå è½½ç¶æ |
| | | const downLoading = ref(false); |
| | | |
| | | // è·åé¨é¨å表 |
| | | const fetchDeptOptions = () => { |
| | | deptTreeSelect().then(response => { |
| | | deptOptions.value = filterDisabledDept( |
| | | JSON.parse(JSON.stringify(response.data)) |
| | | ); |
| | | }); |
| | | }; |
| | | |
| | | // æ ¹æ®ç次å¼è·åçæ¬¡åç§° |
| | | const getShiftNameByValue = value => { |
| | | if (!value) return ""; |
| | | const shift = shifts_list.value.find(item => item.value === value); |
| | | return shift ? shift.label : value; |
| | | }; |
| | | |
| | | // è¿æ»¤ç¦ç¨çé¨é¨ |
| | | const filterDisabledDept = deptList => { |
| | | return deptList.filter(dept => { |
| | | if (dept.disabled) { |
| | | return false; |
| | | } |
| | | if (dept.children && dept.children.length) { |
| | | dept.children = filterDisabledDept(dept.children); |
| | | } |
| | | return true; |
| | | }); |
| | | }; |
| | | |
| | | // å·æ° |
| | | const refresh = () => { |
| | | listForm.value = []; |
| | | yearList.value = []; |
| | | currentPage.value = 1; |
| | | query.userName = ""; |
| | | query.sysDeptId = ""; |
| | | query.year = new Date(); |
| | | query.month = new Date().getMonth() + 1; |
| | | if (query.month) { |
| | | init(); |
| | | } else { |
| | | initYear(); |
| | | } |
| | | }; |
| | | |
| | | // å·æ°è¡¨æ ¼ |
| | | const refreshTable = () => { |
| | | currentPage.value = 1; |
| | | if (query.month) { |
| | | listForm.value = []; |
| | | init(); |
| | | } else { |
| | | yearList.value = []; |
| | | initYear(); |
| | | } |
| | | }; |
| | | |
| | | // é¡µç æ¹å |
| | | const currentChange = num => { |
| | | currentPage.value = num; |
| | | if (query.month) { |
| | | init(); |
| | | } else { |
| | | initYear(); |
| | | } |
| | | }; |
| | | |
| | | // æ°åè½¬ä¸æ |
| | | const transFromNumber = num => { |
| | | let changeNum = ["é¶", "ä¸", "äº", "ä¸", "å", "äº", "å
", "ä¸", "å
«", "ä¹"]; |
| | | let unit = ["", "å", "ç¾", "å", "ä¸"]; |
| | | num = parseInt(num); |
| | | let getWan = temp => { |
| | | let strArr = temp.toString().split("").reverse(); |
| | | let newNum = ""; |
| | | for (var i = 0; i < strArr.length; i++) { |
| | | newNum = |
| | | (i == 0 && strArr[i] == 0 |
| | | ? "" |
| | | : i > 0 && strArr[i] == 0 && strArr[i - 1] == 0 |
| | | ? "" |
| | | : changeNum[strArr[i]] + (strArr[i] == 0 ? unit[0] : unit[i])) + |
| | | newNum; |
| | | } |
| | | return newNum; |
| | | }; |
| | | let overWan = Math.floor(num / 10000); |
| | | let noWan = num % 10000; |
| | | if (noWan.toString().length < 4) noWan = "0" + noWan; |
| | | return overWan ? getWan(overWan) + "ä¸" + getWan(noWan) : getWan(num); |
| | | }; |
| | | |
| | | // åå§åæåº¦æ°æ® |
| | | const init = () => { |
| | | pageLoading.value = true; |
| | | console.log(query.year, "query.year"); |
| | | let year = query.year.getFullYear(); |
| | | let month0 = query.month ? query.month : new Date().getMonth() + 1; |
| | | let month = month0 > 9 ? month0 : "0" + month0; |
| | | page({ |
| | | size: pageSize.value, |
| | | current: currentPage.value, |
| | | time: year + "-" + month + "-01 00:00:00", |
| | | userName: query.userName, |
| | | sysDeptId: query.sysDeptId, |
| | | }) |
| | | .then(res => { |
| | | pageLoading.value = false; |
| | | total.value = res.data.page.total; |
| | | listForm.value = res.data.page.records.map(item => { |
| | | for (let key in item.monthlyAttendance) { |
| | | let type = getDayByDic(key); |
| | | if (type != undefined || type != null) { |
| | | item[`day${type}`] = item.monthlyAttendance[key]; |
| | | } |
| | | } |
| | | return item; |
| | | }); |
| | | let headerList = res.data.headerList; |
| | | weeks.value = []; |
| | | headerList.forEach(item => { |
| | | let obj = { |
| | | weekNum: item.weekly, |
| | | week: item.headerTime.split(" ")[1], |
| | | day: item.headerTime.split(" ")[0], |
| | | }; |
| | | weeks.value.push(obj); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | pageLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // åå§åå¹´åº¦æ°æ® |
| | | const initYear = () => { |
| | | pageLoading.value = true; |
| | | let year = query.year.getFullYear(); |
| | | pageYear({ |
| | | size: pageSize.value, |
| | | current: currentPage.value, |
| | | time: year + "-01-01 00:00:00", |
| | | userName: query.userName, |
| | | sysDeptId: query.sysDeptId, |
| | | }).then(res => { |
| | | pageLoading.value = false; |
| | | total.value = res.data.total; |
| | | yearList.value = res.data.records.map(item => { |
| | | for (let key in item.year) { |
| | | let type = getDayByDic(key); |
| | | if (type != undefined || type != null) { |
| | | item[`day${type}`] = item.year[key]; |
| | | } |
| | | } |
| | | item.monthList = []; |
| | | for (let m in item.month) { |
| | | let obj = {}; |
| | | for (let key in item.month[m]) { |
| | | let type = getDayByDic(key); |
| | | if (type != undefined || type != null) { |
| | | obj[`day${type}`] = item.month[m][key]; |
| | | } |
| | | } |
| | | obj.totalMonthAttendance = item.month[m].totalMonthAttendance; |
| | | item.monthList.push(obj); |
| | | } |
| | | return item; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // é¼ æ è¿å
¥ |
| | | const onMouseEnter = index => { |
| | | currentUserIndex.value = index; |
| | | }; |
| | | |
| | | // 确认æç |
| | | const confirmScheduling = () => { |
| | | if (!schedulingQuery.week) { |
| | | proxy.$modal.msgError("è¯·éæ©å¨æ¬¡"); |
| | | return; |
| | | } |
| | | let time = schedulingQuery.week.getTime(); |
| | | |
| | | // æ ¼å¼åæ¥æä¸º YYYY-MM-DD æ ¼å¼ |
| | | const formatDate = date => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | }; |
| | | |
| | | let startWeek = |
| | | formatDate(new Date(time - 24 * 60 * 60 * 1000)) + " 00:00:00"; |
| | | let endWeek = |
| | | formatDate(new Date(time + 24 * 60 * 60 * 1000 * 5)) + " 00:00:00"; |
| | | |
| | | if (!schedulingQuery.userId || schedulingQuery.userId.length == 0) { |
| | | proxy.$modal.msgError("è¯·éæ©äººå"); |
| | | return; |
| | | } |
| | | if (!schedulingQuery.shift) { |
| | | proxy.$modal.msgError("è¯·éæ©çæ¬¡"); |
| | | return; |
| | | } |
| | | loading.value = true; |
| | | add({ |
| | | startWeek, |
| | | endWeek, |
| | | staffOnJobId: schedulingQuery.userId.join(","), |
| | | personalAttendanceLocationConfigId: schedulingQuery.shift, |
| | | }) |
| | | .then(res => { |
| | | loading.value = false; |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | | schedulingVisible.value = false; |
| | | schedulingQuery.week = ""; |
| | | schedulingQuery.userId = null; |
| | | schedulingQuery.shift = ""; |
| | | refresh(); |
| | | }) |
| | | .catch(err => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // æ¶é´é
ç½® |
| | | const configTime = () => { |
| | | // 跳转å°è夿å¡é¡µé¢ |
| | | router.push({ |
| | | path: "/personnelManagement/checkinRules", |
| | | }); |
| | | }; |
| | | |
| | | // 夿æ¯å¦ä¸ºç©ºå¯¹è±¡ |
| | | const isObjectEmpty = obj => { |
| | | return Object.keys(obj).some(key => !obj[key]); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleDown = () => { |
| | | let year = query.year.getFullYear(); |
| | | let time = ""; |
| | | if (query.month) { |
| | | let month = query.month > 9 ? query.month : "0" + query.month; |
| | | time = year + "-" + month + "-01 00:00:00"; |
| | | } else { |
| | | time = year + "-01-01 00:00:00"; |
| | | } |
| | | downLoading.value = true; |
| | | exportFile({ |
| | | time, |
| | | userName: query.userName, |
| | | sysDeptId: query.sysDeptId, |
| | | isMonth: query.month ? true : false, |
| | | }) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | downLoading.value = false; |
| | | const blob = new Blob([res], { |
| | | type: "application/force-download", |
| | | }); |
| | | let fileName = ""; |
| | | if (query.month) { |
| | | fileName = year + "-" + query.month + " çæ¬¡ä¿¡æ¯"; |
| | | } else { |
| | | fileName = year + " çæ¬¡æ±æ»"; |
| | | } |
| | | proxy.$download.saveAs(blob, fileName + ".xlsx"); |
| | | }) |
| | | .catch(err => { |
| | | downLoading.value = false; |
| | | }); |
| | | }; |
| | | // å¤çå½ä»¤ |
| | | const handleCommand = (e, m) => { |
| | | // if (e != m.shift) { |
| | | update({ |
| | | id: m.id, |
| | | personalAttendanceLocationConfigId: e, |
| | | }).then(res => { |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | | // m.shift = e; |
| | | if (query.month) { |
| | | init(); |
| | | } else { |
| | | initYear(); |
| | | } |
| | | }); |
| | | // } |
| | | }; |
| | | // æ¥è¯¢è§åå表 |
| | | const fetchData = () => { |
| | | getAttendanceRules({ current: -1, size: -1 }).then(res => { |
| | | classType.value = res.data.records; |
| | | }); |
| | | }; |
| | | // è·åç¨æ· |
| | | const getUsers = () => { |
| | | // selectUserCondition({ type: 1 }).then(res => { |
| | | // let arr = res.data; |
| | | // personList.value = arr; |
| | | // }); |
| | | staffOnJobListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | staffState: 1, |
| | | }).then(res => { |
| | | let arr = res.data.records; |
| | | personList.value = arr; |
| | | }); |
| | | }; |
| | | |
| | | // æ ¹æ®åå
¸è·åæ¥æ |
| | | const getDayByDic = e => { |
| | | let obj = classType.value.find(m => m.shift == e); |
| | | if (obj) { |
| | | return obj.id; |
| | | } |
| | | }; |
| | | |
| | | // æ ¹æ®åå
¸è·åçæ¬¡ |
| | | const getShiftByDic = e => { |
| | | let obj = classType.value.find(m => m.shift == e); |
| | | if (obj) { |
| | | return obj.shift; |
| | | } |
| | | return "æ "; |
| | | }; |
| | | |
| | | // åå§å |
| | | onMounted(() => { |
| | | fetchData(); |
| | | getUsers(); |
| | | fetchDeptOptions(); |
| | | if (query.month) { |
| | | init(); |
| | | } else { |
| | | initYear(); |
| | | } |
| | | monthList.value = []; |
| | | for (let i = 12; i > 0; i--) { |
| | | monthList.value.push(i); |
| | | } |
| | | monthList.value.reverse(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .class-page { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .form_title { |
| | | height: 36px; |
| | | display: flex; |
| | | flex-direction: row; |
| | | justify-content: space-between; |
| | | font-weight: 800; |
| | | } |
| | | |
| | | /* æç´¢åºåæ ·å¼ */ |
| | | .search-container { |
| | | background: #f9fafb; |
| | | border-radius: 8px; |
| | | padding: 20px; |
| | | margin-bottom: 20px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .search-form { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .search-row { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | flex-wrap: nowrap; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .search-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .search-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | min-width: 65px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .search-input-group { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .search-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | .search-buttons { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | flex: 1; |
| | | } |
| | | |
| | | /* ååºå¼è°æ´ */ |
| | | @media (max-width: 1200px) { |
| | | .search-row { |
| | | gap: 12px; |
| | | } |
| | | |
| | | .search-item { |
| | | gap: 4px; |
| | | } |
| | | |
| | | .search-label { |
| | | min-width: 60px; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .search-actions { |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | .search-buttons { |
| | | margin-left: 12px; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 992px) { |
| | | .search-row { |
| | | flex-wrap: wrap; |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .search-buttons { |
| | | margin-left: 0; |
| | | margin-top: 12px; |
| | | width: 100%; |
| | | justify-content: flex-start; |
| | | } |
| | | } |
| | | |
| | | /* æçå®¹å¨ */ |
| | | .scheduling-container { |
| | | width: 100%; |
| | | min-height: calc(100vh - 280px); |
| | | background-color: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | overflow: hidden; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | /* æçè¡¨æ ¼ */ |
| | | .scheduling-table { |
| | | display: flex; |
| | | width: 100%; |
| | | height: calc(100vh - 280px); |
| | | } |
| | | |
| | | /* 左侧人åä¿¡æ¯ */ |
| | | .scheduling-left { |
| | | width: 240px; |
| | | min-width: 240px; |
| | | background-color: #f9fafb; |
| | | border-right: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | /* å³ä¾§æçå
容 */ |
| | | .scheduling-right { |
| | | flex: 1; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | /* 表头 */ |
| | | .scheduling-header { |
| | | height: 48px; |
| | | line-height: 48px; |
| | | padding: 0 20px; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | background-color: #f3f4f6; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | /* 人åä¿¡æ¯è¡ */ |
| | | .scheduling-user { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10px 10px; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | transition: all 0.3s ease; |
| | | height: 65px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .scheduling-user:hover, |
| | | .scheduling-user-hover { |
| | | background-color: rgba(59, 130, 246, 0.05); |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | /* ç¨æ·å¤´å */ |
| | | .user-avatar { |
| | | width: 42px; |
| | | height: 42px; |
| | | border-radius: 50%; |
| | | background: linear-gradient(135deg, #3b82f6, #60a5fa); |
| | | color: #fff; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | text-align: center; |
| | | line-height: 42px; |
| | | margin-right: 16px; |
| | | box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .scheduling-user:hover .user-avatar { |
| | | transform: scale(1.05); |
| | | box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3); |
| | | } |
| | | |
| | | /* ç¨æ·è¯¦æ
*/ |
| | | .user-details { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | /* ç¨æ·å */ |
| | | .user-name { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin: 0 0 6px 0; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | /* ç¨æ·ç»è®¡ */ |
| | | .user-stats { |
| | | /* display: flex; */ |
| | | /* flex-wrap: wrap; |
| | | gap: 10px; */ |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .stat-item { |
| | | font-size: 12px; |
| | | color: #666; |
| | | /* background-color: #f9fafb; */ |
| | | /* padding: 2px 8px; */ |
| | | padding-right: 4px; |
| | | /* border-radius: 10px; */ |
| | | /* border: 1px solid #e5e7eb; */ |
| | | /* transition: all 0.3s ease; */ |
| | | } |
| | | |
| | | .scheduling-user:hover .stat-item { |
| | | background-color: rgba(59, 130, 246, 0.1); |
| | | border-color: rgba(59, 130, 246, 0.3); |
| | | } |
| | | |
| | | /* å计åºå¤ */ |
| | | .user-total { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .total-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin-right: 6px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .total-value { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #3b82f6; |
| | | background-color: rgba(59, 130, 246, 0.1); |
| | | padding: 2px 10px; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(59, 130, 246, 0.2); |
| | | } |
| | | |
| | | /* æ¥åå¤´é¨ */ |
| | | .calendar-header { |
| | | display: flex; |
| | | |
| | | border-bottom: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .calendar-header-item { |
| | | width: 50px; |
| | | min-width: 50px; |
| | | height: 48px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-right: 1px solid #e5e7eb; |
| | | background-color: #f3f4f6; |
| | | position: relative; |
| | | } |
| | | |
| | | .week-number { |
| | | position: absolute; |
| | | top: 6px; |
| | | font-size: 10px; |
| | | font-weight: 600; |
| | | color: #3b82f6; |
| | | background-color: #dbeafe; |
| | | padding: 3px 6px; |
| | | border-radius: 12px; |
| | | box-shadow: 0 1px 3px rgba(59, 130, 246, 0.2); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .week-number:hover { |
| | | background-color: #3b82f6; |
| | | color: #fff; |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .day-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | .day-number { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | } |
| | | |
| | | .day-week { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* æ¥åä¸»ä½ */ |
| | | .calendar-body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | /* æ¥åè¡ */ |
| | | .calendar-row { |
| | | display: flex; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .calendar-row:hover, |
| | | .calendar-row-hover { |
| | | background-color: rgba(59, 130, 246, 0.03); |
| | | } |
| | | |
| | | /* æ¥ååå
æ ¼ */ |
| | | .calendar-cell { |
| | | width: 50px; |
| | | min-width: 50px; |
| | | height: 65px; |
| | | border-right: 1px solid #e5e7eb; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* çæ¬¡ä¸ææ¡ */ |
| | | .shift-dropdown { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* çæ¬¡æ¡ */ |
| | | .shift-box { |
| | | width: 90%; |
| | | height: 80%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 6px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .shift-box:hover { |
| | | transform: scale(1.05); |
| | | } |
| | | |
| | | /* çæ¬¡ç±»åæ ·å¼ */ |
| | | .shift-box-early { |
| | | background: rgba(59, 130, 246, 0.15); |
| | | color: #3b82f6; |
| | | } |
| | | |
| | | .shift-box-mid { |
| | | background: rgba(139, 92, 246, 0.15); |
| | | color: #8b5cf6; |
| | | } |
| | | |
| | | .shift-box-night { |
| | | background: rgba(245, 158, 11, 0.15); |
| | | color: #f59e0b; |
| | | } |
| | | |
| | | .shift-box-rest { |
| | | background: rgba(16, 185, 129, 0.15); |
| | | color: #10b981; |
| | | } |
| | | |
| | | .shift-box-leave { |
| | | background: rgba(239, 68, 68, 0.15); |
| | | color: #ef4444; |
| | | } |
| | | |
| | | .shift-box-other { |
| | | background: rgba(236, 72, 153, 0.15); |
| | | color: #ec4899; |
| | | } |
| | | |
| | | .shift-box-business { |
| | | background: rgba(17, 24, 39, 0.15); |
| | | color: #111827; |
| | | } |
| | | |
| | | /* çæ¬¡ææ¬ */ |
| | | .shift-text { |
| | | text-align: center; |
| | | } |
| | | |
| | | /* å¹´åº¦è¡¨æ ¼ */ |
| | | .yearly-table { |
| | | display: flex; |
| | | width: 100%; |
| | | height: calc(100vh - 280px); |
| | | } |
| | | |
| | | /* 年度æ¥å */ |
| | | .yearly-calendar { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 年度表头 */ |
| | | .yearly-header { |
| | | display: grid; |
| | | grid-template-columns: repeat(12, 1fr); |
| | | background-color: #f3f4f6; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .yearly-header-item { |
| | | height: 48px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-right: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .month-name { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | } |
| | | |
| | | /* å¹´åº¦ä¸»ä½ */ |
| | | .yearly-body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | /* å¹´åº¦è¡ */ |
| | | .yearly-row { |
| | | display: grid; |
| | | grid-template-columns: repeat(12, 1fr); |
| | | border-bottom: 1px solid #e5e7eb; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | /* 年度åå
æ ¼ */ |
| | | .yearly-cell { |
| | | padding: 12px; |
| | | border-right: 1px solid #e5e7eb; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | /* æåº¦åºå¤ */ |
| | | .monthly-attendance { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .attendance-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | .attendance-value { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | /* æåº¦ç»è®¡ */ |
| | | .monthly-stats { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .monthly-stats .stat-item { |
| | | font-size: 11px; |
| | | } |
| | | |
| | | /* æ»å¨æ¡æ ·å¼ */ |
| | | .scheduling-right::-webkit-scrollbar { |
| | | height: 8px; |
| | | } |
| | | |
| | | .scheduling-right::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | } |
| | | |
| | | .scheduling-right::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .scheduling-right::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | |
| | | .search_label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | margin-top: 12px; |
| | | } |
| | | </style> |