Commit d4a6b78f23d995424428e0f576def1096d4026eb
1 parent
809d9ffa
OA 中考勤功能开发完成
Showing
41 changed files
with
4083 additions
and
309 deletions
src/api/oa/addAttendanceClassesStaffApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 保存考勤班次员工信息 | ||
| 5 | + * @param {Object} data 员工信息 | ||
| 6 | + * @returns {Promise} | ||
| 7 | + */ | ||
| 8 | +export function saveAttendanceClassesStaff(data) { | ||
| 9 | + return new Promise((resolve, reject) => { | ||
| 10 | + request({ | ||
| 11 | + url: '/attendanceClasses.saveAttendanceClassesStaff', | ||
| 12 | + method: 'post', | ||
| 13 | + data | ||
| 14 | + }).then(response => { | ||
| 15 | + const res = response.data | ||
| 16 | + resolve(res) | ||
| 17 | + }).catch(error => { | ||
| 18 | + reject(error) | ||
| 19 | + }) | ||
| 20 | + }) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * 上传图片 | ||
| 25 | + * @param {FormData} formData 包含图片文件的FormData | ||
| 26 | + * @returns {Promise} | ||
| 27 | + */ | ||
| 28 | +export function uploadFile(formData) { | ||
| 29 | + return new Promise((resolve, reject) => { | ||
| 30 | + request({ | ||
| 31 | + url: '/uploadImage', | ||
| 32 | + method: 'post', | ||
| 33 | + data: formData, | ||
| 34 | + headers: { | ||
| 35 | + 'Content-Type': 'multipart/form-data' | ||
| 36 | + } | ||
| 37 | + }).then(response => { | ||
| 38 | + const res = response.data | ||
| 39 | + resolve(res) | ||
| 40 | + }).catch(error => { | ||
| 41 | + reject(error) | ||
| 42 | + }) | ||
| 43 | + }) | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +/** | ||
| 47 | + * 查询员工信息 | ||
| 48 | + * @param {Object} params 查询参数 | ||
| 49 | + * @returns {Promise} | ||
| 50 | + */ | ||
| 51 | +export function queryStaffInfos(params) { | ||
| 52 | + return new Promise((resolve, reject) => { | ||
| 53 | + request({ | ||
| 54 | + url: '/query.staff.infos', | ||
| 55 | + method: 'get', | ||
| 56 | + params | ||
| 57 | + }).then(response => { | ||
| 58 | + const res = response.data | ||
| 59 | + resolve(res) | ||
| 60 | + }).catch(error => { | ||
| 61 | + reject(error) | ||
| 62 | + }) | ||
| 63 | + }) | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +/** | ||
| 67 | + * 获取组织树 | ||
| 68 | + * @param {Object} params 查询参数 | ||
| 69 | + * @returns {Promise} | ||
| 70 | + */ | ||
| 71 | +export function listOrgTree(params) { | ||
| 72 | + return new Promise((resolve, reject) => { | ||
| 73 | + request({ | ||
| 74 | + url: '/org.listOrgTree', | ||
| 75 | + method: 'get', | ||
| 76 | + params | ||
| 77 | + }).then(response => { | ||
| 78 | + const res = response.data | ||
| 79 | + resolve(res) | ||
| 80 | + }).catch(error => { | ||
| 81 | + reject(error) | ||
| 82 | + }) | ||
| 83 | + }) | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +/** | ||
| 87 | + * 下载文件 | ||
| 88 | + * @param {Object} params 文件参数 | ||
| 89 | + * @returns {Promise} | ||
| 90 | + */ | ||
| 91 | +export function downloadFile(params) { | ||
| 92 | + return new Promise((resolve, reject) => { | ||
| 93 | + request({ | ||
| 94 | + url: '/callComponent/download/getFile/file', | ||
| 95 | + method: 'get', | ||
| 96 | + params | ||
| 97 | + }).then(response => { | ||
| 98 | + const res = response.data | ||
| 99 | + resolve(res) | ||
| 100 | + }).catch(error => { | ||
| 101 | + reject(error) | ||
| 102 | + }) | ||
| 103 | + }) | ||
| 104 | +} | ||
| 0 | \ No newline at end of file | 105 | \ No newline at end of file |
src/api/oa/attendanceClassesManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 3 | + | ||
| 4 | +// 获取考勤班组列表 | ||
| 5 | +export function listAttendanceClassess(params) { | ||
| 6 | + return new Promise((resolve, reject) => { | ||
| 7 | + request({ | ||
| 8 | + url: '/attendanceClasses.listAttendanceClassess', | ||
| 9 | + method: 'get', | ||
| 10 | + params: { | ||
| 11 | + ...params, | ||
| 12 | + communityId: getCommunityId() | ||
| 13 | + } | ||
| 14 | + }).then(response => { | ||
| 15 | + const res = response.data | ||
| 16 | + resolve(res) | ||
| 17 | + }).catch(error => { | ||
| 18 | + reject(error) | ||
| 19 | + }) | ||
| 20 | + }) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +// 添加考勤班组 | ||
| 24 | +export function saveAttendanceClasses(data) { | ||
| 25 | + return new Promise((resolve, reject) => { | ||
| 26 | + request({ | ||
| 27 | + url: '/attendanceClasses.saveAttendanceClasses', | ||
| 28 | + method: 'post', | ||
| 29 | + data: { | ||
| 30 | + ...data, | ||
| 31 | + communityId: getCommunityId() | ||
| 32 | + } | ||
| 33 | + }).then(response => { | ||
| 34 | + const res = response.data | ||
| 35 | + resolve(res) | ||
| 36 | + }).catch(error => { | ||
| 37 | + reject(error) | ||
| 38 | + }) | ||
| 39 | + }) | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +// 修改考勤班组 | ||
| 43 | +export function updateAttendanceClasses(data) { | ||
| 44 | + return new Promise((resolve, reject) => { | ||
| 45 | + request({ | ||
| 46 | + url: '/attendanceClasses.updateAttendanceClasses', | ||
| 47 | + method: 'post', | ||
| 48 | + data: { | ||
| 49 | + ...data, | ||
| 50 | + communityId: getCommunityId() | ||
| 51 | + } | ||
| 52 | + }).then(response => { | ||
| 53 | + const res = response.data | ||
| 54 | + resolve(res) | ||
| 55 | + }).catch(error => { | ||
| 56 | + reject(error) | ||
| 57 | + }) | ||
| 58 | + }) | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +// 删除考勤班组 | ||
| 62 | +export function deleteAttendanceClasses(data) { | ||
| 63 | + return new Promise((resolve, reject) => { | ||
| 64 | + request({ | ||
| 65 | + url: '/attendanceClasses.deleteAttendanceClasses', | ||
| 66 | + method: 'post', | ||
| 67 | + data: { | ||
| 68 | + ...data, | ||
| 69 | + communityId: getCommunityId() | ||
| 70 | + } | ||
| 71 | + }).then(response => { | ||
| 72 | + const res = response.data | ||
| 73 | + resolve(res) | ||
| 74 | + }).catch(error => { | ||
| 75 | + reject(error) | ||
| 76 | + }) | ||
| 77 | + }) | ||
| 78 | +} | ||
| 0 | \ No newline at end of file | 79 | \ No newline at end of file |
src/api/oa/attendanceClassesStaffManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +// 获取考勤班次列表 | ||
| 4 | +export function listAttendanceClassess(params) { | ||
| 5 | + return new Promise((resolve, reject) => { | ||
| 6 | + request({ | ||
| 7 | + url: '/attendanceClasses.listAttendanceClassess', | ||
| 8 | + method: 'get', | ||
| 9 | + params | ||
| 10 | + }).then(response => { | ||
| 11 | + const res = response.data | ||
| 12 | + resolve(res) | ||
| 13 | + }).catch(error => { | ||
| 14 | + reject(error) | ||
| 15 | + }) | ||
| 16 | + }) | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +// 获取考勤班次员工列表 | ||
| 20 | +export function listAttendanceClassesStaff(params) { | ||
| 21 | + return new Promise((resolve, reject) => { | ||
| 22 | + request({ | ||
| 23 | + url: '/attendanceClasses.listAttendanceClassesStaff', | ||
| 24 | + method: 'get', | ||
| 25 | + params | ||
| 26 | + }).then(response => { | ||
| 27 | + const res = response.data | ||
| 28 | + resolve(res) | ||
| 29 | + }).catch(error => { | ||
| 30 | + reject(error) | ||
| 31 | + }) | ||
| 32 | + }) | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +// 删除考勤班次员工 | ||
| 36 | +export function deleteAttendanceClassesStaff(data) { | ||
| 37 | + return new Promise((resolve, reject) => { | ||
| 38 | + request({ | ||
| 39 | + url: '/attendanceClasses.deleteAttendanceClassesStaff', | ||
| 40 | + method: 'post', | ||
| 41 | + data | ||
| 42 | + }).then(response => { | ||
| 43 | + const res = response.data | ||
| 44 | + resolve(res) | ||
| 45 | + }).catch(error => { | ||
| 46 | + reject(error) | ||
| 47 | + }) | ||
| 48 | + }) | ||
| 49 | +} | ||
| 0 | \ No newline at end of file | 50 | \ No newline at end of file |
src/api/oa/attendanceLogManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 查询考勤记录列表 | ||
| 5 | + * @param {Object} params 查询参数 | ||
| 6 | + * @param {string} params.classesName 班组名称 | ||
| 7 | + * @param {string} params.departmentName 部门名称 | ||
| 8 | + * @param {string} params.date 打卡日期 | ||
| 9 | + * @param {string} params.staffName 员工名称 | ||
| 10 | + * @param {number} params.page 当前页码 | ||
| 11 | + * @param {number} params.row 每页条数 | ||
| 12 | + * @returns {Promise} 考勤记录列表 | ||
| 13 | + */ | ||
| 14 | +export function queryAttendanceLog(params) { | ||
| 15 | + return new Promise((resolve, reject) => { | ||
| 16 | + request({ | ||
| 17 | + url: '/attendanceClass/queryAttendanceLog', | ||
| 18 | + method: 'get', | ||
| 19 | + params: { | ||
| 20 | + ...params, | ||
| 21 | + classesName: params.classesName.trim() || '', | ||
| 22 | + departmentName: params.departmentName.trim() || '' | ||
| 23 | + } | ||
| 24 | + }).then(response => { | ||
| 25 | + const res = response.data | ||
| 26 | + resolve(res) | ||
| 27 | + }).catch(error => { | ||
| 28 | + reject(error) | ||
| 29 | + }) | ||
| 30 | + }) | ||
| 31 | +} | ||
| 0 | \ No newline at end of file | 32 | \ No newline at end of file |
src/api/oa/monthAttendanceManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +// 获取月考勤列表 | ||
| 4 | +export function getMonthAttendance(params) { | ||
| 5 | + return new Promise((resolve, reject) => { | ||
| 6 | + request({ | ||
| 7 | + url: '/attendanceClass/getMonthAttendance', | ||
| 8 | + method: 'get', | ||
| 9 | + params | ||
| 10 | + }).then(response => { | ||
| 11 | + const res = response.data | ||
| 12 | + resolve(res) | ||
| 13 | + }).catch(error => { | ||
| 14 | + reject(error) | ||
| 15 | + }) | ||
| 16 | + }) | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +// 获取考勤班次列表 | ||
| 20 | +export function getAttendanceClasses(params) { | ||
| 21 | + return new Promise((resolve, reject) => { | ||
| 22 | + request({ | ||
| 23 | + url: '/attendanceClasses.listAttendanceClassess', | ||
| 24 | + method: 'get', | ||
| 25 | + params | ||
| 26 | + }).then(response => { | ||
| 27 | + const res = response.data | ||
| 28 | + resolve(res) | ||
| 29 | + }).catch(error => { | ||
| 30 | + reject(error) | ||
| 31 | + }) | ||
| 32 | + }) | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +// 导出月考勤数据 | ||
| 36 | +export function exportMonthAttendance(params) { | ||
| 37 | + return new Promise((resolve, reject) => { | ||
| 38 | + request({ | ||
| 39 | + url: '/export.exportData', | ||
| 40 | + method: 'get', | ||
| 41 | + params | ||
| 42 | + }).then(response => { | ||
| 43 | + const res = response.data | ||
| 44 | + resolve(res) | ||
| 45 | + }).catch(error => { | ||
| 46 | + reject(error) | ||
| 47 | + }) | ||
| 48 | + }) | ||
| 49 | +} | ||
| 0 | \ No newline at end of file | 50 | \ No newline at end of file |
src/api/oa/newOaWorkflowDoingApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +// 获取待办信息 | ||
| 4 | +export function getUndoInfo(params) { | ||
| 5 | + return new Promise((resolve, reject) => { | ||
| 6 | + request({ | ||
| 7 | + url: '/callComponent/undo/list', | ||
| 8 | + method: 'get', | ||
| 9 | + params | ||
| 10 | + }).then(response => { | ||
| 11 | + const res = response.data | ||
| 12 | + resolve(res) | ||
| 13 | + }).catch(error => { | ||
| 14 | + reject(error) | ||
| 15 | + }) | ||
| 16 | + }) | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +// 查询OA工作流列表 | ||
| 20 | +export function listNewOaWorkflows(params) { | ||
| 21 | + return new Promise((resolve, reject) => { | ||
| 22 | + request({ | ||
| 23 | + url: '/oaWorkflow/queryOaWorkflow', | ||
| 24 | + method: 'get', | ||
| 25 | + params | ||
| 26 | + }).then(response => { | ||
| 27 | + const res = response.data | ||
| 28 | + resolve(res) | ||
| 29 | + }).catch(error => { | ||
| 30 | + reject(error) | ||
| 31 | + }) | ||
| 32 | + }) | ||
| 33 | +} | ||
| 0 | \ No newline at end of file | 34 | \ No newline at end of file |
src/api/oa/newOaWorkflowManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 查询OA工作流列表 | ||
| 5 | + * @param {Object} params 查询参数 | ||
| 6 | + * @returns {Promise} Promise对象 | ||
| 7 | + */ | ||
| 8 | +export function queryOaWorkflow(params) { | ||
| 9 | + return new Promise((resolve, reject) => { | ||
| 10 | + request({ | ||
| 11 | + url: '/oaWorkflow/queryOaWorkflow', | ||
| 12 | + method: 'get', | ||
| 13 | + params | ||
| 14 | + }).then(response => { | ||
| 15 | + const res = response.data | ||
| 16 | + resolve(res) | ||
| 17 | + }).catch(error => { | ||
| 18 | + reject(error) | ||
| 19 | + }) | ||
| 20 | + }) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * 检查工作流是否部署 | ||
| 25 | + * @param {Object} params 查询参数 | ||
| 26 | + * @returns {Promise} Promise对象 | ||
| 27 | + */ | ||
| 28 | +export function checkWorkflowDeployment(params) { | ||
| 29 | + return queryOaWorkflow(params) | ||
| 30 | +} | ||
| 0 | \ No newline at end of file | 31 | \ No newline at end of file |
src/api/oa/staffAttendanceManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 3 | + | ||
| 4 | +// 查询员工信息列表 | ||
| 5 | +export function queryStaffInfos(params) { | ||
| 6 | + return new Promise((resolve, reject) => { | ||
| 7 | + request({ | ||
| 8 | + url: '/query.staff.infos', | ||
| 9 | + method: 'get', | ||
| 10 | + params: { | ||
| 11 | + ...params, | ||
| 12 | + communityId: getCommunityId() | ||
| 13 | + } | ||
| 14 | + }).then(response => { | ||
| 15 | + resolve(response.data) | ||
| 16 | + }).catch(error => { | ||
| 17 | + reject(error) | ||
| 18 | + }) | ||
| 19 | + }) | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +// 查询考勤任务 | ||
| 23 | +export function queryAttendanceClassesTask(params) { | ||
| 24 | + return new Promise((resolve, reject) => { | ||
| 25 | + request({ | ||
| 26 | + url: '/attendanceClass/queryAttendanceClassesTask', | ||
| 27 | + method: 'get', | ||
| 28 | + params: { | ||
| 29 | + ...params, | ||
| 30 | + communityId: getCommunityId() | ||
| 31 | + } | ||
| 32 | + }).then(response => { | ||
| 33 | + resolve(response.data) | ||
| 34 | + }).catch(error => { | ||
| 35 | + reject(error) | ||
| 36 | + }) | ||
| 37 | + }) | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +// 查询考勤日志 | ||
| 41 | +export function queryAttendanceLog(params) { | ||
| 42 | + return new Promise((resolve, reject) => { | ||
| 43 | + request({ | ||
| 44 | + url: '/attendanceClass/queryAttendanceLog', | ||
| 45 | + method: 'get', | ||
| 46 | + params: { | ||
| 47 | + ...params, | ||
| 48 | + communityId: getCommunityId() | ||
| 49 | + } | ||
| 50 | + }).then(response => { | ||
| 51 | + resolve(response.data) | ||
| 52 | + }).catch(error => { | ||
| 53 | + reject(error) | ||
| 54 | + }) | ||
| 55 | + }) | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +// 补考勤 | ||
| 59 | +export function attendanceReplenishCheckIn(data) { | ||
| 60 | + return new Promise((resolve, reject) => { | ||
| 61 | + request({ | ||
| 62 | + url: '/attendanceClasses.attendanceReplenishCheckIn', | ||
| 63 | + method: 'post', | ||
| 64 | + data, | ||
| 65 | + headers: { | ||
| 66 | + 'Content-Type': 'application/json' | ||
| 67 | + } | ||
| 68 | + }).then(response => { | ||
| 69 | + resolve(response.data) | ||
| 70 | + }).catch(error => { | ||
| 71 | + reject(error) | ||
| 72 | + }) | ||
| 73 | + }) | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +// 查询组织树 | ||
| 77 | +export function listOrgTree(params) { | ||
| 78 | + return new Promise((resolve, reject) => { | ||
| 79 | + request({ | ||
| 80 | + url: '/org.listOrgTree', | ||
| 81 | + method: 'get', | ||
| 82 | + params: { | ||
| 83 | + ...params, | ||
| 84 | + communityId: getCommunityId() | ||
| 85 | + } | ||
| 86 | + }).then(response => { | ||
| 87 | + resolve(response.data) | ||
| 88 | + }).catch(error => { | ||
| 89 | + reject(error) | ||
| 90 | + }) | ||
| 91 | + }) | ||
| 92 | +} | ||
| 0 | \ No newline at end of file | 93 | \ No newline at end of file |
src/api/oa/todayAttendanceManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 查询今日考勤列表 | ||
| 5 | + * @param {Object} params 查询参数 | ||
| 6 | + * @returns {Promise} Promise对象 | ||
| 7 | + */ | ||
| 8 | +export function queryAttendanceClassesTask(params) { | ||
| 9 | + return new Promise((resolve, reject) => { | ||
| 10 | + request({ | ||
| 11 | + url: '/attendanceClass/queryAttendanceClassesTask', | ||
| 12 | + method: 'get', | ||
| 13 | + params | ||
| 14 | + }).then(response => { | ||
| 15 | + const res = response.data | ||
| 16 | + resolve({ | ||
| 17 | + data: res.data, | ||
| 18 | + total: res.total, | ||
| 19 | + records: res.records | ||
| 20 | + }) | ||
| 21 | + }).catch(error => { | ||
| 22 | + reject(error) | ||
| 23 | + }) | ||
| 24 | + }) | ||
| 25 | +} | ||
| 0 | \ No newline at end of file | 26 | \ No newline at end of file |
src/components/oa/addAttendanceClasses.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog :title="$t('addAttendanceClasses.title')" :visible.sync="visible" width="60%" @close="closeDialog"> | ||
| 3 | + <el-form ref="form" :model="formData" :rules="rules" label-width="150px" label-position="right"> | ||
| 4 | + <el-row> | ||
| 5 | + <el-col :span="12"> | ||
| 6 | + <el-form-item :label="$t('addAttendanceClasses.classesName')" prop="classesName"> | ||
| 7 | + <el-input v-model="formData.classesName" :placeholder="$t('addAttendanceClasses.classesNamePlaceholder')" /> | ||
| 8 | + </el-form-item> | ||
| 9 | + </el-col> | ||
| 10 | + <el-col :span="12"> | ||
| 11 | + <el-form-item :label="$t('addAttendanceClasses.timeOffset')" prop="timeOffset"> | ||
| 12 | + <el-input v-model="formData.timeOffset" type="number" | ||
| 13 | + :placeholder="$t('addAttendanceClasses.timeOffsetPlaceholder')"> | ||
| 14 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 15 | + </el-input> | ||
| 16 | + </el-form-item> | ||
| 17 | + </el-col> | ||
| 18 | + </el-row> | ||
| 19 | + <el-row> | ||
| 20 | + <el-col :span="12"> | ||
| 21 | + <el-form-item :label="$t('addAttendanceClasses.lateOffset')" prop="lateOffset"> | ||
| 22 | + <el-input v-model="formData.lateOffset" type="number" | ||
| 23 | + :placeholder="$t('addAttendanceClasses.lateOffsetPlaceholder')"> | ||
| 24 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 25 | + </el-input> | ||
| 26 | + </el-form-item> | ||
| 27 | + </el-col> | ||
| 28 | + <el-col :span="12"> | ||
| 29 | + <el-form-item :label="$t('addAttendanceClasses.leaveOffset')" prop="leaveOffset"> | ||
| 30 | + <el-input v-model="formData.leaveOffset" type="number" | ||
| 31 | + :placeholder="$t('addAttendanceClasses.leaveOffsetPlaceholder')"> | ||
| 32 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 33 | + </el-input> | ||
| 34 | + </el-form-item> | ||
| 35 | + </el-col> | ||
| 36 | + </el-row> | ||
| 37 | + <el-row> | ||
| 38 | + <el-col :span="12"> | ||
| 39 | + <el-form-item :label="$t('addAttendanceClasses.maxLastOffset')" prop="maxLastOffset"> | ||
| 40 | + <el-input v-model="formData.maxLastOffset" type="number" | ||
| 41 | + :placeholder="$t('addAttendanceClasses.maxLastOffsetPlaceholder')"> | ||
| 42 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 43 | + </el-input> | ||
| 44 | + </el-form-item> | ||
| 45 | + </el-col> | ||
| 46 | + </el-row> | ||
| 47 | + </el-form> | ||
| 48 | + <div slot="footer" class="dialog-footer"> | ||
| 49 | + <el-button @click="closeDialog">{{ $t('common.cancel') }}</el-button> | ||
| 50 | + <el-button type="primary" @click="saveAttendanceClassesInfo">{{ $t('common.save') }}</el-button> | ||
| 51 | + </div> | ||
| 52 | + </el-dialog> | ||
| 53 | +</template> | ||
| 54 | + | ||
| 55 | +<script> | ||
| 56 | +import { saveAttendanceClasses } from '@/api/oa/attendanceClassesManageApi' | ||
| 57 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 58 | + | ||
| 59 | +export default { | ||
| 60 | + name: 'AddAttendanceClasses', | ||
| 61 | + data() { | ||
| 62 | + return { | ||
| 63 | + visible: false, | ||
| 64 | + formData: { | ||
| 65 | + classesId: '', | ||
| 66 | + classesName: '', | ||
| 67 | + timeOffset: '', | ||
| 68 | + leaveOffset: '', | ||
| 69 | + lateOffset: '', | ||
| 70 | + classesObjType: '', | ||
| 71 | + classesObjId: '', | ||
| 72 | + classesObjName: '', | ||
| 73 | + maxLastOffset: '', | ||
| 74 | + communityId: '' | ||
| 75 | + }, | ||
| 76 | + rules: { | ||
| 77 | + classesName: [ | ||
| 78 | + { required: true, message: this.$t('addAttendanceClasses.classesNameRequired'), trigger: 'blur' }, | ||
| 79 | + { max: 64, message: this.$t('addAttendanceClasses.classesNameMaxLength'), trigger: 'blur' } | ||
| 80 | + ], | ||
| 81 | + timeOffset: [ | ||
| 82 | + { required: true, message: this.$t('addAttendanceClasses.timeOffsetRequired'), trigger: 'blur' }, | ||
| 83 | + ], | ||
| 84 | + lateOffset: [ | ||
| 85 | + { required: true, message: this.$t('addAttendanceClasses.lateOffsetRequired'), trigger: 'blur' }, | ||
| 86 | + { max: 20, message: this.$t('addAttendanceClasses.lateOffsetMaxLength'), trigger: 'blur' } | ||
| 87 | + ], | ||
| 88 | + leaveOffset: [ | ||
| 89 | + { required: true, message: this.$t('addAttendanceClasses.leaveOffsetRequired'), trigger: 'blur' }, | ||
| 90 | + { max: 20, message: this.$t('addAttendanceClasses.leaveOffsetMaxLength'), trigger: 'blur' } | ||
| 91 | + ], | ||
| 92 | + maxLastOffset: [ | ||
| 93 | + { required: true, message: this.$t('addAttendanceClasses.maxLastOffsetRequired'), trigger: 'blur' } | ||
| 94 | + ] | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + }, | ||
| 98 | + methods: { | ||
| 99 | + open(params) { | ||
| 100 | + this.formData = { | ||
| 101 | + ...this.formData, | ||
| 102 | + ...params, | ||
| 103 | + communityId: getCommunityId() | ||
| 104 | + } | ||
| 105 | + this.visible = true | ||
| 106 | + }, | ||
| 107 | + closeDialog() { | ||
| 108 | + this.visible = false | ||
| 109 | + this.$refs.form.resetFields() | ||
| 110 | + }, | ||
| 111 | + async saveAttendanceClassesInfo() { | ||
| 112 | + try { | ||
| 113 | + await this.$refs.form.validate() | ||
| 114 | + const response = await saveAttendanceClasses(this.formData) | ||
| 115 | + if (response.code === 0) { | ||
| 116 | + this.$message.success(this.$t('addAttendanceClasses.saveSuccess')) | ||
| 117 | + this.$emit('success') | ||
| 118 | + this.closeDialog() | ||
| 119 | + } else { | ||
| 120 | + this.$message.error(response.msg) | ||
| 121 | + } | ||
| 122 | + } catch (error) { | ||
| 123 | + console.error('保存失败:', error) | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | +} | ||
| 128 | +</script> | ||
| 0 | \ No newline at end of file | 129 | \ No newline at end of file |
src/components/oa/chooseOrgTree.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('staffAttendance.chooseOrg')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | + width="60%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <el-tree | ||
| 9 | + ref="orgTree" | ||
| 10 | + :data="orgs" | ||
| 11 | + node-key="id" | ||
| 12 | + :props="defaultProps" | ||
| 13 | + :highlight-current="true" | ||
| 14 | + @node-click="handleNodeClick" | ||
| 15 | + ></el-tree> | ||
| 16 | + | ||
| 17 | + <span slot="footer" class="dialog-footer"> | ||
| 18 | + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button> | ||
| 19 | + <el-button type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</el-button> | ||
| 20 | + </span> | ||
| 21 | + </el-dialog> | ||
| 22 | +</template> | ||
| 23 | + | ||
| 24 | +<script> | ||
| 25 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 26 | +import { listOrgTree } from '@/api/oa/staffAttendanceManageApi' | ||
| 27 | + | ||
| 28 | +export default { | ||
| 29 | + name: 'ChooseOrgTree', | ||
| 30 | + data() { | ||
| 31 | + return { | ||
| 32 | + visible: false, | ||
| 33 | + orgs: [], | ||
| 34 | + currentOrg: {}, | ||
| 35 | + defaultProps: { | ||
| 36 | + children: 'children', | ||
| 37 | + label: 'name' | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + }, | ||
| 41 | + methods: { | ||
| 42 | + open() { | ||
| 43 | + this.visible = true | ||
| 44 | + this.loadOrgs() | ||
| 45 | + }, | ||
| 46 | + | ||
| 47 | + async loadOrgs() { | ||
| 48 | + try { | ||
| 49 | + const params = { | ||
| 50 | + communityId: getCommunityId() | ||
| 51 | + } | ||
| 52 | + const { data } = await listOrgTree(params) | ||
| 53 | + this.orgs = data || [] | ||
| 54 | + } catch (error) { | ||
| 55 | + console.error('Failed to load orgs:', error) | ||
| 56 | + this.$message.error(this.$t('staffAttendance.loadOrgFailed')) | ||
| 57 | + } | ||
| 58 | + }, | ||
| 59 | + | ||
| 60 | + handleNodeClick(data) { | ||
| 61 | + this.currentOrg = data | ||
| 62 | + }, | ||
| 63 | + | ||
| 64 | + handleConfirm() { | ||
| 65 | + if (!this.currentOrg || !this.currentOrg.id) { | ||
| 66 | + this.$message.warning(this.$t('staffAttendance.selectOrgFirst')) | ||
| 67 | + return | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + this.$emit('switchOrg', { | ||
| 71 | + orgId: this.currentOrg.id, | ||
| 72 | + allOrgName: this.getOrgFullName(this.currentOrg) | ||
| 73 | + }) | ||
| 74 | + this.visible = false | ||
| 75 | + }, | ||
| 76 | + | ||
| 77 | + getOrgFullName(node) { | ||
| 78 | + if (!node) return '' | ||
| 79 | + | ||
| 80 | + let names = [] | ||
| 81 | + let currentNode = node | ||
| 82 | + | ||
| 83 | + while (currentNode) { | ||
| 84 | + names.unshift(currentNode.name) | ||
| 85 | + currentNode = this.$parent ? this.$parent.getNode(currentNode.parentId) : null | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + return names.join('/') | ||
| 89 | + }, | ||
| 90 | + | ||
| 91 | + handleClose() { | ||
| 92 | + this.currentOrg = {} | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | +} | ||
| 96 | +</script> | ||
| 97 | + | ||
| 98 | +<style lang="scss" scoped> | ||
| 99 | +.el-tree { | ||
| 100 | + max-height: 500px; | ||
| 101 | + overflow-y: auto; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +.dialog-footer { | ||
| 105 | + text-align: right; | ||
| 106 | +} | ||
| 107 | +</style> | ||
| 0 | \ No newline at end of file | 108 | \ No newline at end of file |
src/components/oa/deleteAttendanceClasses.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('deleteAttendanceClasses.title')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | + width="30%" | ||
| 6 | + @close="closeDialog" | ||
| 7 | + > | ||
| 8 | + <div class="confirm-content"> | ||
| 9 | + <p>{{ $t('deleteAttendanceClasses.confirmText') }}</p> | ||
| 10 | + </div> | ||
| 11 | + <div slot="footer" class="dialog-footer"> | ||
| 12 | + <el-button @click="closeDialog">{{ $t('deleteAttendanceClasses.cancel') }}</el-button> | ||
| 13 | + <el-button type="primary" @click="deleteAttendanceClasses">{{ $t('deleteAttendanceClasses.confirm') }}</el-button> | ||
| 14 | + </div> | ||
| 15 | + </el-dialog> | ||
| 16 | +</template> | ||
| 17 | + | ||
| 18 | +<script> | ||
| 19 | +import { deleteAttendanceClasses } from '@/api/oa/attendanceClassesManageApi' | ||
| 20 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 21 | + | ||
| 22 | +export default { | ||
| 23 | + name: 'DeleteAttendanceClasses', | ||
| 24 | + data() { | ||
| 25 | + return { | ||
| 26 | + visible: false, | ||
| 27 | + formData: { | ||
| 28 | + classesId: '', | ||
| 29 | + communityId: '' | ||
| 30 | + } | ||
| 31 | + } | ||
| 32 | + }, | ||
| 33 | + methods: { | ||
| 34 | + open(params) { | ||
| 35 | + this.formData = { | ||
| 36 | + classesId: params.classesId, | ||
| 37 | + communityId: getCommunityId() | ||
| 38 | + } | ||
| 39 | + this.visible = true | ||
| 40 | + }, | ||
| 41 | + closeDialog() { | ||
| 42 | + this.visible = false | ||
| 43 | + }, | ||
| 44 | + async deleteAttendanceClasses() { | ||
| 45 | + try { | ||
| 46 | + const response = await deleteAttendanceClasses(this.formData) | ||
| 47 | + if (response.code === 0) { | ||
| 48 | + this.$message.success(this.$t('deleteAttendanceClasses.deleteSuccess')) | ||
| 49 | + this.$emit('success') | ||
| 50 | + this.closeDialog() | ||
| 51 | + } else { | ||
| 52 | + this.$message.error(response.msg) | ||
| 53 | + } | ||
| 54 | + } catch (error) { | ||
| 55 | + console.error('删除失败:', error) | ||
| 56 | + this.$message.error(this.$t('deleteAttendanceClasses.deleteError')) | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | +} | ||
| 61 | +</script> | ||
| 62 | + | ||
| 63 | +<style scoped> | ||
| 64 | +.confirm-content { | ||
| 65 | + text-align: center; | ||
| 66 | + font-size: 16px; | ||
| 67 | + padding: 20px 0; | ||
| 68 | +} | ||
| 69 | +</style> | ||
| 0 | \ No newline at end of file | 70 | \ No newline at end of file |
src/components/oa/deleteAttendanceClassesStaff.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('attendanceClassesStaffManage.delete.title')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | + width="30%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <div style="text-align: center;"> | ||
| 9 | + <p>{{ $t('attendanceClassesStaffManage.delete.confirmText') }}</p> | ||
| 10 | + </div> | ||
| 11 | + <span slot="footer" class="dialog-footer"> | ||
| 12 | + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button> | ||
| 13 | + <el-button type="primary" @click="deleteAttendanceClassesStaff" :loading="loading"> | ||
| 14 | + {{ $t('common.confirm') }} | ||
| 15 | + </el-button> | ||
| 16 | + </span> | ||
| 17 | + </el-dialog> | ||
| 18 | +</template> | ||
| 19 | + | ||
| 20 | +<script> | ||
| 21 | +import { deleteAttendanceClassesStaff } from '@/api/oa/attendanceClassesStaffManageApi' | ||
| 22 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 23 | + | ||
| 24 | +export default { | ||
| 25 | + name: 'DeleteAttendanceClassesStaff', | ||
| 26 | + data() { | ||
| 27 | + return { | ||
| 28 | + visible: false, | ||
| 29 | + loading: false, | ||
| 30 | + currentRow: {} | ||
| 31 | + } | ||
| 32 | + }, | ||
| 33 | + methods: { | ||
| 34 | + open(row) { | ||
| 35 | + this.currentRow = row | ||
| 36 | + this.visible = true | ||
| 37 | + }, | ||
| 38 | + async deleteAttendanceClassesStaff() { | ||
| 39 | + try { | ||
| 40 | + this.loading = true | ||
| 41 | + const params = { | ||
| 42 | + ...this.currentRow, | ||
| 43 | + communityId: getCommunityId() | ||
| 44 | + } | ||
| 45 | + await deleteAttendanceClassesStaff(params) | ||
| 46 | + this.$message.success(this.$t('attendanceClassesStaffManage.delete.success')) | ||
| 47 | + this.$emit('success') | ||
| 48 | + this.visible = false | ||
| 49 | + } catch (error) { | ||
| 50 | + console.error('删除考勤员工失败:', error) | ||
| 51 | + this.$message.error(this.$t('attendanceClassesStaffManage.delete.error')) | ||
| 52 | + } finally { | ||
| 53 | + this.loading = false | ||
| 54 | + } | ||
| 55 | + }, | ||
| 56 | + handleClose() { | ||
| 57 | + this.currentRow = {} | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | +} | ||
| 61 | +</script> | ||
| 0 | \ No newline at end of file | 62 | \ No newline at end of file |
src/components/oa/editAttendanceClasses.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog :title="$t('editAttendanceClasses.title')" :visible.sync="visible" width="60%" @close="closeDialog"> | ||
| 3 | + <el-form ref="form" :model="formData" :rules="rules" label-width="150px" label-position="right"> | ||
| 4 | + <el-row> | ||
| 5 | + <el-col :span="12"> | ||
| 6 | + <el-form-item :label="$t('editAttendanceClasses.classesName')" prop="classesName"> | ||
| 7 | + <el-input v-model="formData.classesName" | ||
| 8 | + :placeholder="$t('editAttendanceClasses.classesNamePlaceholder')" /> | ||
| 9 | + </el-form-item> | ||
| 10 | + </el-col> | ||
| 11 | + <el-col :span="12"> | ||
| 12 | + <el-form-item :label="$t('editAttendanceClasses.timeOffset')" prop="timeOffset"> | ||
| 13 | + <el-input v-model="formData.timeOffset" type="number" | ||
| 14 | + :placeholder="$t('editAttendanceClasses.timeOffsetPlaceholder')"> | ||
| 15 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 16 | + </el-input> | ||
| 17 | + </el-form-item> | ||
| 18 | + </el-col> | ||
| 19 | + </el-row> | ||
| 20 | + <el-row> | ||
| 21 | + <el-col :span="12"> | ||
| 22 | + <el-form-item :label="$t('editAttendanceClasses.lateOffset')" prop="lateOffset"> | ||
| 23 | + <el-input v-model="formData.lateOffset" type="number" | ||
| 24 | + :placeholder="$t('editAttendanceClasses.lateOffsetPlaceholder')"> | ||
| 25 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 26 | + </el-input> | ||
| 27 | + </el-form-item> | ||
| 28 | + </el-col> | ||
| 29 | + <el-col :span="12"> | ||
| 30 | + <el-form-item :label="$t('editAttendanceClasses.leaveOffset')" prop="leaveOffset"> | ||
| 31 | + <el-input v-model="formData.leaveOffset" type="number" | ||
| 32 | + :placeholder="$t('editAttendanceClasses.leaveOffsetPlaceholder')"> | ||
| 33 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 34 | + </el-input> | ||
| 35 | + </el-form-item> | ||
| 36 | + </el-col> | ||
| 37 | + </el-row> | ||
| 38 | + <el-row> | ||
| 39 | + <el-col :span="12"> | ||
| 40 | + <el-form-item :label="$t('editAttendanceClasses.maxLastOffset')" prop="maxLastOffset"> | ||
| 41 | + <el-input v-model="formData.maxLastOffset" type="number" | ||
| 42 | + :placeholder="$t('editAttendanceClasses.maxLastOffsetPlaceholder')"> | ||
| 43 | + <template slot="append">{{ $t('attendanceClassesManage.minute') }}</template> | ||
| 44 | + </el-input> | ||
| 45 | + </el-form-item> | ||
| 46 | + </el-col> | ||
| 47 | + </el-row> | ||
| 48 | + </el-form> | ||
| 49 | + <div slot="footer" class="dialog-footer"> | ||
| 50 | + <el-button @click="closeDialog">{{ $t('common.cancel') }}</el-button> | ||
| 51 | + <el-button type="primary" @click="editAttendanceClasses">{{ $t('common.save') }}</el-button> | ||
| 52 | + </div> | ||
| 53 | + </el-dialog> | ||
| 54 | +</template> | ||
| 55 | + | ||
| 56 | +<script> | ||
| 57 | +import { updateAttendanceClasses } from '@/api/oa/attendanceClassesManageApi' | ||
| 58 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 59 | +import { getDict } from '@/api/community/communityApi' | ||
| 60 | + | ||
| 61 | +export default { | ||
| 62 | + name: 'EditAttendanceClasses', | ||
| 63 | + data() { | ||
| 64 | + return { | ||
| 65 | + visible: false, | ||
| 66 | + formData: { | ||
| 67 | + classesId: '', | ||
| 68 | + classesName: '', | ||
| 69 | + timeOffset: '', | ||
| 70 | + clockCount: '', | ||
| 71 | + clockTypes: [], | ||
| 72 | + clockType: '', | ||
| 73 | + clockTypeValue: '', | ||
| 74 | + leaveOffset: '', | ||
| 75 | + lateOffset: '', | ||
| 76 | + maxLastOffset: '', | ||
| 77 | + attrs: [], | ||
| 78 | + clockTypeValues: [], | ||
| 79 | + communityId: '' | ||
| 80 | + }, | ||
| 81 | + rules: { | ||
| 82 | + classesName: [ | ||
| 83 | + { required: true, message: this.$t('editAttendanceClasses.classesNameRequired'), trigger: 'blur' }, | ||
| 84 | + { max: 64, message: this.$t('editAttendanceClasses.classesNameMaxLength'), trigger: 'blur' } | ||
| 85 | + ], | ||
| 86 | + timeOffset: [ | ||
| 87 | + { required: true, message: this.$t('editAttendanceClasses.timeOffsetRequired'), trigger: 'blur' }, | ||
| 88 | + ], | ||
| 89 | + lateOffset: [ | ||
| 90 | + { required: true, message: this.$t('editAttendanceClasses.lateOffsetRequired'), trigger: 'blur' }, | ||
| 91 | + { max: 20, message: this.$t('editAttendanceClasses.lateOffsetMaxLength'), trigger: 'blur' } | ||
| 92 | + ], | ||
| 93 | + leaveOffset: [ | ||
| 94 | + { required: true, message: this.$t('editAttendanceClasses.leaveOffsetRequired'), trigger: 'blur' }, | ||
| 95 | + { max: 20, message: this.$t('editAttendanceClasses.leaveOffsetMaxLength'), trigger: 'blur' } | ||
| 96 | + ], | ||
| 97 | + maxLastOffset: [ | ||
| 98 | + { required: true, message: this.$t('editAttendanceClasses.maxLastOffsetRequired'), trigger: 'blur' } | ||
| 99 | + ], | ||
| 100 | + classesId: [ | ||
| 101 | + { required: true, message: this.$t('editAttendanceClasses.classesIdRequired'), trigger: 'blur' } | ||
| 102 | + ] | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + }, | ||
| 106 | + methods: { | ||
| 107 | + open(params) { | ||
| 108 | + this.formData = { | ||
| 109 | + ...this.formData, | ||
| 110 | + ...params, | ||
| 111 | + communityId: getCommunityId() | ||
| 112 | + } | ||
| 113 | + this.getClockTypes() | ||
| 114 | + this.visible = true | ||
| 115 | + }, | ||
| 116 | + closeDialog() { | ||
| 117 | + this.visible = false | ||
| 118 | + this.$refs.form.resetFields() | ||
| 119 | + }, | ||
| 120 | + async getClockTypes() { | ||
| 121 | + try { | ||
| 122 | + const data = await getDict('attendance_classes', 'clock_type') | ||
| 123 | + this.formData.clockTypes = data | ||
| 124 | + } catch (error) { | ||
| 125 | + console.error('获取字典数据失败:', error) | ||
| 126 | + } | ||
| 127 | + }, | ||
| 128 | + async editAttendanceClasses() { | ||
| 129 | + try { | ||
| 130 | + await this.$refs.form.validate() | ||
| 131 | + const response = await updateAttendanceClasses(this.formData) | ||
| 132 | + if (response.code === 0) { | ||
| 133 | + this.$message.success(this.$t('editAttendanceClasses.editSuccess')) | ||
| 134 | + this.$emit('success') | ||
| 135 | + this.closeDialog() | ||
| 136 | + } else { | ||
| 137 | + this.$message.error(response.msg) | ||
| 138 | + } | ||
| 139 | + } catch (error) { | ||
| 140 | + console.error('修改失败:', error) | ||
| 141 | + } | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | +} | ||
| 145 | +</script> | ||
| 0 | \ No newline at end of file | 146 | \ No newline at end of file |
src/components/oa/orgTreeShow.vue
| 1 | <template> | 1 | <template> |
| 2 | - <div class="org-tree-show-container"> | 2 | + <div class="org-tree-container"> |
| 3 | <el-tree | 3 | <el-tree |
| 4 | ref="orgTree" | 4 | ref="orgTree" |
| 5 | - :data="orgTreeShowInfo.orgs" | 5 | + :data="orgData" |
| 6 | :props="defaultProps" | 6 | :props="defaultProps" |
| 7 | node-key="id" | 7 | node-key="id" |
| 8 | default-expand-all | 8 | default-expand-all |
| 9 | highlight-current | 9 | highlight-current |
| 10 | - @node-click="handleNodeClick"> | ||
| 11 | - </el-tree> | 10 | + :expand-on-click-node="false" |
| 11 | + @node-click="handleNodeClick" | ||
| 12 | + ></el-tree> | ||
| 12 | </div> | 13 | </div> |
| 13 | </template> | 14 | </template> |
| 14 | 15 | ||
| 15 | <script> | 16 | <script> |
| 16 | -import { listOrgTree } from '@/api/oa/addExamineStaffApi' | 17 | +import { listOrgTree } from '@/api/oa/addAttendanceClassesStaffApi' |
| 17 | import { getCommunityId } from '@/api/community/communityApi' | 18 | import { getCommunityId } from '@/api/community/communityApi' |
| 18 | 19 | ||
| 19 | export default { | 20 | export default { |
| 20 | name: 'OrgTreeShow', | 21 | name: 'OrgTreeShow', |
| 21 | - props: { | ||
| 22 | - callBackListener: { | ||
| 23 | - type: String, | ||
| 24 | - default: '' | ||
| 25 | - } | ||
| 26 | - }, | ||
| 27 | data() { | 22 | data() { |
| 28 | return { | 23 | return { |
| 29 | - orgTreeShowInfo: { | ||
| 30 | - orgs: [], | ||
| 31 | - orgId: '', | ||
| 32 | - curOrg: {} | ||
| 33 | - }, | 24 | + orgData: [], |
| 34 | defaultProps: { | 25 | defaultProps: { |
| 35 | children: 'children', | 26 | children: 'children', |
| 36 | label: 'text' | 27 | label: 'text' |
| 37 | - } | 28 | + }, |
| 29 | + communityId: getCommunityId() | ||
| 38 | } | 30 | } |
| 39 | }, | 31 | }, |
| 40 | - mounted() { | ||
| 41 | - this._loadOrgsShow() | ||
| 42 | - }, | ||
| 43 | methods: { | 32 | methods: { |
| 44 | - refreshTree() { | ||
| 45 | - this._loadOrgsShow() | ||
| 46 | - }, | ||
| 47 | - async _loadOrgsShow() { | 33 | + async refreshTree() { |
| 48 | try { | 34 | try { |
| 49 | const params = { | 35 | const params = { |
| 50 | - communityId: getCommunityId() | 36 | + communityId: this.communityId |
| 51 | } | 37 | } |
| 52 | const res = await listOrgTree(params) | 38 | const res = await listOrgTree(params) |
| 53 | - this.orgTreeShowInfo.orgs = res.data | 39 | + this.orgData = res.data |
| 54 | } catch (error) { | 40 | } catch (error) { |
| 55 | - console.error('加载组织树失败:', error) | 41 | + this.$message.error(this.$t('orgTree.loadFailed')) |
| 56 | } | 42 | } |
| 57 | }, | 43 | }, |
| 58 | handleNodeClick(data) { | 44 | handleNodeClick(data) { |
| 59 | - this.orgTreeShowInfo.curOrg = data | ||
| 60 | - this.orgTreeShowInfo.curOrg.orgId = data.id | ||
| 61 | - this.$emit(this.callBackListener, 'switchOrg', { | 45 | + this.$emit('switchOrg', { |
| 62 | orgId: data.id, | 46 | orgId: data.id, |
| 63 | orgName: data.text | 47 | orgName: data.text |
| 64 | }) | 48 | }) |
| 65 | } | 49 | } |
| 50 | + }, | ||
| 51 | + mounted() { | ||
| 52 | + this.refreshTree() | ||
| 66 | } | 53 | } |
| 67 | } | 54 | } |
| 68 | </script> | 55 | </script> |
| 69 | 56 | ||
| 70 | <style lang="scss" scoped> | 57 | <style lang="scss" scoped> |
| 71 | -.org-tree-show-container { | ||
| 72 | - height: 100%; | ||
| 73 | - | ||
| 74 | - /deep/ .el-tree { | ||
| 75 | - height: 100%; | ||
| 76 | - overflow-y: auto; | 58 | +.org-tree-container { |
| 59 | + height: 500px; | ||
| 60 | + overflow-y: auto; | ||
| 61 | + padding: 10px; | ||
| 62 | + | ||
| 63 | + ::v-deep .el-tree { | ||
| 64 | + background-color: transparent; | ||
| 77 | 65 | ||
| 78 | .el-tree-node__content { | 66 | .el-tree-node__content { |
| 79 | height: 36px; | 67 | height: 36px; |
| 80 | - line-height: 36px; | 68 | + |
| 69 | + &:hover { | ||
| 70 | + background-color: #f5f7fa; | ||
| 71 | + } | ||
| 81 | } | 72 | } |
| 82 | 73 | ||
| 83 | - .el-tree-node.is-current > .el-tree-node__content { | ||
| 84 | - background-color: #f0f7ff; | 74 | + .is-current > .el-tree-node__content { |
| 75 | + background-color: #ecf5ff; | ||
| 85 | } | 76 | } |
| 86 | } | 77 | } |
| 87 | } | 78 | } |
src/components/oa/selectStaff.vue
| 1 | <template> | 1 | <template> |
| 2 | - <el-dialog | ||
| 3 | - :title="$t('selectStaff.title')" | ||
| 4 | - :visible.sync="dialogVisible" | 2 | + <el-dialog |
| 3 | + :title="$t('selectStaff.title')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | width="70%" | 5 | width="70%" |
| 6 | - :close-on-click-modal="false"> | ||
| 7 | - | 6 | + :close-on-click-modal="false" |
| 7 | + > | ||
| 8 | <el-row :gutter="20"> | 8 | <el-row :gutter="20"> |
| 9 | <el-col :span="12" class="border-right"> | 9 | <el-col :span="12" class="border-right"> |
| 10 | - <div class="text-center section-title"> | ||
| 11 | - {{ $t('selectStaff.orgInfo') }} | ||
| 12 | - </div> | ||
| 13 | - <div class="org-tree-container"> | ||
| 14 | - <org-tree-show | ||
| 15 | - ref="orgTree" | ||
| 16 | - :call-back-listener="'selectStaff'"> | ||
| 17 | - </org-tree-show> | 10 | + <div class="text-center mb-20"> |
| 11 | + <h4>{{ $t('selectStaff.orgInfo') }}</h4> | ||
| 18 | </div> | 12 | </div> |
| 13 | + <org-tree-show | ||
| 14 | + ref="orgTree" | ||
| 15 | + @switchOrg="handleSwitchOrg" | ||
| 16 | + ></org-tree-show> | ||
| 19 | </el-col> | 17 | </el-col> |
| 20 | - | 18 | + |
| 21 | <el-col :span="12"> | 19 | <el-col :span="12"> |
| 22 | - <div class="text-center section-title"> | ||
| 23 | - {{ $t('selectStaff.staffInfo') }} | 20 | + <div class="text-center mb-20"> |
| 21 | + <h4>{{ $t('selectStaff.staffInfo') }}</h4> | ||
| 24 | </div> | 22 | </div> |
| 25 | - <div class="staff-list-container"> | 23 | + <div class="staff-list"> |
| 26 | <div | 24 | <div |
| 27 | - v-for="(item, index) in selectStaffInfo.staffs" | 25 | + v-for="(staff, index) in staffList" |
| 28 | :key="index" | 26 | :key="index" |
| 29 | class="staff-item" | 27 | class="staff-item" |
| 30 | - :class="{ 'selected': selectStaffInfo.curStaffId === item.staffId }" | ||
| 31 | - @click="_changeStaff(item)"> | 28 | + :class="{ 'selected': currentStaffId === staff.staffId }" |
| 29 | + @click="handleSelectStaff(staff)" | ||
| 30 | + > | ||
| 32 | <div> | 31 | <div> |
| 33 | - <i class="el-icon-user margin-right-xs"></i> | ||
| 34 | - {{ item.name }} | 32 | + <i class="el-icon-user"></i> |
| 33 | + {{ staff.name }} | ||
| 35 | </div> | 34 | </div> |
| 36 | - <div>{{ item.tel }}</div> | 35 | + <div>{{ staff.tel }}</div> |
| 37 | </div> | 36 | </div> |
| 38 | </div> | 37 | </div> |
| 39 | </el-col> | 38 | </el-col> |
| 40 | </el-row> | 39 | </el-row> |
| 41 | - | 40 | + |
| 42 | <div | 41 | <div |
| 43 | - v-if="selectStaffInfo.staff.from === 'bpmn' || | ||
| 44 | - selectStaffInfo.staff.from === 'purchase' || | ||
| 45 | - selectStaffInfo.staff.from === 'contract'" | ||
| 46 | - class="dialog-footer text-right"> | ||
| 47 | - <el-button size="small" @click="_firstUser"> | ||
| 48 | - {{ $t('selectStaff.submitter') }} | ||
| 49 | - </el-button> | ||
| 50 | - <el-button size="small" @click="_customUser"> | ||
| 51 | - {{ $t('selectStaff.dynamicAssign') }} | ||
| 52 | - </el-button> | 42 | + v-if="staffData.from === 'bpmn' || staffData.from === 'purchase' || staffData.from === 'contract'" |
| 43 | + slot="footer" | ||
| 44 | + class="dialog-footer" | ||
| 45 | + > | ||
| 46 | + <el-button @click="handleFirstUser">{{ $t('selectStaff.submitter') }}</el-button> | ||
| 47 | + <el-button @click="handleCustomUser">{{ $t('selectStaff.dynamicAssign') }}</el-button> | ||
| 53 | </div> | 48 | </div> |
| 54 | </el-dialog> | 49 | </el-dialog> |
| 55 | </template> | 50 | </template> |
| 56 | 51 | ||
| 57 | <script> | 52 | <script> |
| 58 | -import { queryStaffInfos } from '@/api/oa/addExamineStaffApi' | ||
| 59 | -import OrgTreeShow from '@/components/oa/OrgTreeShow' | 53 | +import OrgTreeShow from './OrgTreeShow' |
| 54 | +import { queryStaffInfos } from '@/api/oa/addAttendanceClassesStaffApi' | ||
| 60 | 55 | ||
| 61 | export default { | 56 | export default { |
| 62 | name: 'SelectStaff', | 57 | name: 'SelectStaff', |
| @@ -65,25 +60,23 @@ export default { | @@ -65,25 +60,23 @@ export default { | ||
| 65 | }, | 60 | }, |
| 66 | data() { | 61 | data() { |
| 67 | return { | 62 | return { |
| 68 | - dialogVisible: false, | ||
| 69 | - selectStaffInfo: { | ||
| 70 | - staffs: [], | ||
| 71 | - curStaffId: '', | ||
| 72 | - curStaffName: '', | ||
| 73 | - staff: {} | ||
| 74 | - } | 63 | + visible: false, |
| 64 | + staffData: {}, | ||
| 65 | + staffList: [], | ||
| 66 | + currentStaffId: '', | ||
| 67 | + currentOrgId: '' | ||
| 75 | } | 68 | } |
| 76 | }, | 69 | }, |
| 77 | - mounted() { | ||
| 78 | - this.$on('selectStaff', 'switchOrg', this.loadStaff) | ||
| 79 | - }, | ||
| 80 | methods: { | 70 | methods: { |
| 81 | open(staff) { | 71 | open(staff) { |
| 82 | - this.selectStaffInfo.staff = staff | ||
| 83 | - this.dialogVisible = true | ||
| 84 | - this.$refs.orgTree.refreshTree() | 72 | + this.staffData = staff |
| 73 | + this.visible = true | ||
| 74 | + this.$nextTick(() => { | ||
| 75 | + this.$refs.orgTree.refreshTree() | ||
| 76 | + }) | ||
| 85 | }, | 77 | }, |
| 86 | - async loadStaff(org) { | 78 | + async handleSwitchOrg(org) { |
| 79 | + this.currentOrgId = org.orgId | ||
| 87 | try { | 80 | try { |
| 88 | const params = { | 81 | const params = { |
| 89 | page: 1, | 82 | page: 1, |
| @@ -91,41 +84,35 @@ export default { | @@ -91,41 +84,35 @@ export default { | ||
| 91 | orgId: org.orgId | 84 | orgId: org.orgId |
| 92 | } | 85 | } |
| 93 | const res = await queryStaffInfos(params) | 86 | const res = await queryStaffInfos(params) |
| 94 | - this.selectStaffInfo.staffs = res.staffs | ||
| 95 | - if (res.staffs.length > 0) { | ||
| 96 | - this.selectStaffInfo.curStaffId = res.staffs[0].orgId | 87 | + this.staffList = res.data.staffs |
| 88 | + if (this.staffList.length > 0) { | ||
| 89 | + this.currentStaffId = this.staffList[0].staffId | ||
| 97 | } | 90 | } |
| 98 | } catch (error) { | 91 | } catch (error) { |
| 99 | - console.error('加载员工失败:', error) | 92 | + this.$message.error(this.$t('selectStaff.loadStaffFailed')) |
| 100 | } | 93 | } |
| 101 | }, | 94 | }, |
| 102 | - _changeStaff(item) { | ||
| 103 | - this.selectStaffInfo.staff.staffId = item.userId | ||
| 104 | - this.selectStaffInfo.staff.staffName = item.userName | ||
| 105 | - this.selectStaffInfo.staff.staffTel = item.tel | ||
| 106 | - this.dialogVisible = false | ||
| 107 | - | ||
| 108 | - if (typeof this.selectStaffInfo.staff.call === 'function') { | ||
| 109 | - this.selectStaffInfo.staff.call(this.selectStaffInfo.staff) | ||
| 110 | - } | 95 | + handleSelectStaff(staff) { |
| 96 | + this.$emit('change', { | ||
| 97 | + staffId: staff.userId, | ||
| 98 | + staffName: staff.userName, | ||
| 99 | + staffTel: staff.tel | ||
| 100 | + }) | ||
| 101 | + this.visible = false | ||
| 111 | }, | 102 | }, |
| 112 | - _firstUser() { | ||
| 113 | - this.selectStaffInfo.staff.staffId = '${startUserId}' | ||
| 114 | - this.selectStaffInfo.staff.staffName = this.$t('selectStaff.submitter') | ||
| 115 | - this.dialogVisible = false | ||
| 116 | - | ||
| 117 | - if (typeof this.selectStaffInfo.staff.call === 'function') { | ||
| 118 | - this.selectStaffInfo.staff.call(this.selectStaffInfo.staff) | ||
| 119 | - } | 103 | + handleFirstUser() { |
| 104 | + this.$emit('change', { | ||
| 105 | + staffId: '${startUserId}', | ||
| 106 | + staffName: this.$t('selectStaff.submitter') | ||
| 107 | + }) | ||
| 108 | + this.visible = false | ||
| 120 | }, | 109 | }, |
| 121 | - _customUser() { | ||
| 122 | - this.selectStaffInfo.staff.staffId = '${nextUserId}' | ||
| 123 | - this.selectStaffInfo.staff.staffName = this.$t('selectStaff.dynamicAssign') | ||
| 124 | - this.dialogVisible = false | ||
| 125 | - | ||
| 126 | - if (typeof this.selectStaffInfo.staff.call === 'function') { | ||
| 127 | - this.selectStaffInfo.staff.call(this.selectStaffInfo.staff) | ||
| 128 | - } | 110 | + handleCustomUser() { |
| 111 | + this.$emit('change', { | ||
| 112 | + staffId: '${nextUserId}', | ||
| 113 | + staffName: this.$t('selectStaff.dynamicAssign') | ||
| 114 | + }) | ||
| 115 | + this.visible = false | ||
| 129 | } | 116 | } |
| 130 | } | 117 | } |
| 131 | } | 118 | } |
| @@ -136,22 +123,17 @@ export default { | @@ -136,22 +123,17 @@ export default { | ||
| 136 | border-right: 1px solid #ebeef5; | 123 | border-right: 1px solid #ebeef5; |
| 137 | } | 124 | } |
| 138 | 125 | ||
| 139 | -.section-title { | ||
| 140 | - font-weight: bold; | ||
| 141 | - margin-bottom: 15px; | ||
| 142 | - font-size: 16px; | 126 | +.text-center { |
| 127 | + text-align: center; | ||
| 143 | } | 128 | } |
| 144 | 129 | ||
| 145 | -.org-tree-container { | ||
| 146 | - height: 400px; | ||
| 147 | - overflow-y: auto; | ||
| 148 | - padding: 10px; | 130 | +.mb-20 { |
| 131 | + margin-bottom: 20px; | ||
| 149 | } | 132 | } |
| 150 | 133 | ||
| 151 | -.staff-list-container { | ||
| 152 | - height: 400px; | 134 | +.staff-list { |
| 135 | + max-height: 500px; | ||
| 153 | overflow-y: auto; | 136 | overflow-y: auto; |
| 154 | - padding: 10px; | ||
| 155 | } | 137 | } |
| 156 | 138 | ||
| 157 | .staff-item { | 139 | .staff-item { |
| @@ -160,30 +142,14 @@ export default { | @@ -160,30 +142,14 @@ export default { | ||
| 160 | border: 1px solid #ebeef5; | 142 | border: 1px solid #ebeef5; |
| 161 | border-radius: 4px; | 143 | border-radius: 4px; |
| 162 | cursor: pointer; | 144 | cursor: pointer; |
| 163 | - | 145 | + |
| 164 | &:hover { | 146 | &:hover { |
| 165 | background-color: #f5f7fa; | 147 | background-color: #f5f7fa; |
| 166 | } | 148 | } |
| 167 | - | 149 | + |
| 168 | &.selected { | 150 | &.selected { |
| 169 | background-color: #ecf5ff; | 151 | background-color: #ecf5ff; |
| 170 | border-color: #d9ecff; | 152 | border-color: #d9ecff; |
| 171 | } | 153 | } |
| 172 | } | 154 | } |
| 173 | - | ||
| 174 | -.dialog-footer { | ||
| 175 | - margin-top: 20px; | ||
| 176 | -} | ||
| 177 | - | ||
| 178 | -.margin-right-xs { | ||
| 179 | - margin-right: 5px; | ||
| 180 | -} | ||
| 181 | - | ||
| 182 | -.text-center { | ||
| 183 | - text-align: center; | ||
| 184 | -} | ||
| 185 | - | ||
| 186 | -.text-right { | ||
| 187 | - text-align: right; | ||
| 188 | -} | ||
| 189 | </style> | 155 | </style> |
| 190 | \ No newline at end of file | 156 | \ No newline at end of file |
src/components/oa/staffAttendanceDetail.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('staffAttendance.attendanceDetail')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | + width="70%" | ||
| 6 | + > | ||
| 7 | + <el-table | ||
| 8 | + :data="details" | ||
| 9 | + border | ||
| 10 | + style="width: 100%" | ||
| 11 | + v-loading="loading" | ||
| 12 | + > | ||
| 13 | + <el-table-column | ||
| 14 | + prop="staffName" | ||
| 15 | + :label="$t('staffAttendance.staffName')" | ||
| 16 | + align="center" | ||
| 17 | + ></el-table-column> | ||
| 18 | + | ||
| 19 | + <el-table-column | ||
| 20 | + :label="$t('staffAttendance.face')" | ||
| 21 | + align="center" | ||
| 22 | + > | ||
| 23 | + <template slot-scope="scope"> | ||
| 24 | + <el-image | ||
| 25 | + style="width: 60px; height: 60px; border-radius: 4px;" | ||
| 26 | + :src="scope.row.facePath || '/img/noPhoto.jpg'" | ||
| 27 | + fit="cover" | ||
| 28 | + ></el-image> | ||
| 29 | + </template> | ||
| 30 | + </el-table-column> | ||
| 31 | + | ||
| 32 | + <el-table-column | ||
| 33 | + prop="clockTime" | ||
| 34 | + :label="$t('staffAttendance.clockTime')" | ||
| 35 | + align="center" | ||
| 36 | + ></el-table-column> | ||
| 37 | + </el-table> | ||
| 38 | + | ||
| 39 | + <span slot="footer" class="dialog-footer"> | ||
| 40 | + <el-button @click="visible = false">{{ $t('common.close') }}</el-button> | ||
| 41 | + </span> | ||
| 42 | + </el-dialog> | ||
| 43 | +</template> | ||
| 44 | + | ||
| 45 | +<script> | ||
| 46 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 47 | +import { queryAttendanceLog } from '@/api/oa/staffAttendanceManageApi' | ||
| 48 | + | ||
| 49 | +export default { | ||
| 50 | + name: 'StaffAttendanceDetail', | ||
| 51 | + data() { | ||
| 52 | + return { | ||
| 53 | + visible: false, | ||
| 54 | + loading: false, | ||
| 55 | + details: [], | ||
| 56 | + params: {} | ||
| 57 | + } | ||
| 58 | + }, | ||
| 59 | + methods: { | ||
| 60 | + open(params) { | ||
| 61 | + this.params = params || {} | ||
| 62 | + this.visible = true | ||
| 63 | + this.loadDetails() | ||
| 64 | + }, | ||
| 65 | + | ||
| 66 | + async loadDetails() { | ||
| 67 | + try { | ||
| 68 | + this.loading = true | ||
| 69 | + const { data } = await queryAttendanceLog({ | ||
| 70 | + ...this.params, | ||
| 71 | + page: 1, | ||
| 72 | + row: 30, | ||
| 73 | + communityId: getCommunityId() | ||
| 74 | + }) | ||
| 75 | + this.details = data || [] | ||
| 76 | + } catch (error) { | ||
| 77 | + console.error('Failed to load attendance details:', error) | ||
| 78 | + this.$message.error(this.$t('staffAttendance.loadDetailFailed')) | ||
| 79 | + } finally { | ||
| 80 | + this.loading = false | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | +} | ||
| 85 | +</script> | ||
| 86 | + | ||
| 87 | +<style lang="scss" scoped> | ||
| 88 | +.dialog-footer { | ||
| 89 | + text-align: right; | ||
| 90 | +} | ||
| 91 | +</style> | ||
| 0 | \ No newline at end of file | 92 | \ No newline at end of file |
src/components/oa/staffAttendanceReplenishCheckIn.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('staffAttendance.replenishCheckIn')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | + width="50%" | ||
| 6 | + > | ||
| 7 | + <el-form label-position="left" label-width="120px"> | ||
| 8 | + <el-form-item :label="$t('staffAttendance.attendanceTask')" required> | ||
| 9 | + <el-select | ||
| 10 | + v-model="form.detailId" | ||
| 11 | + :placeholder="$t('staffAttendance.selectTask')" | ||
| 12 | + style="width:100%" | ||
| 13 | + > | ||
| 14 | + <el-option | ||
| 15 | + v-for="(item,index) in details" | ||
| 16 | + :key="index" | ||
| 17 | + :label="`${item.specName}(${item.value})`" | ||
| 18 | + :value="item.detailId" | ||
| 19 | + ></el-option> | ||
| 20 | + </el-select> | ||
| 21 | + </el-form-item> | ||
| 22 | + | ||
| 23 | + <el-form-item :label="$t('staffAttendance.reason')" required> | ||
| 24 | + <el-input | ||
| 25 | + type="textarea" | ||
| 26 | + :rows="4" | ||
| 27 | + v-model="form.remark" | ||
| 28 | + :placeholder="$t('staffAttendance.inputReason')" | ||
| 29 | + ></el-input> | ||
| 30 | + </el-form-item> | ||
| 31 | + </el-form> | ||
| 32 | + | ||
| 33 | + <span slot="footer" class="dialog-footer"> | ||
| 34 | + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button> | ||
| 35 | + <el-button type="primary" @click="handleSubmit">{{ $t('staffAttendance.submitReplenish') }}</el-button> | ||
| 36 | + </span> | ||
| 37 | + </el-dialog> | ||
| 38 | +</template> | ||
| 39 | + | ||
| 40 | +<script> | ||
| 41 | +import { attendanceReplenishCheckIn } from '@/api/oa/staffAttendanceManageApi' | ||
| 42 | + | ||
| 43 | +export default { | ||
| 44 | + name: 'StaffAttendanceReplenishCheckIn', | ||
| 45 | + data() { | ||
| 46 | + return { | ||
| 47 | + visible: false, | ||
| 48 | + details: [], | ||
| 49 | + form: { | ||
| 50 | + detailId: '', | ||
| 51 | + remark: '' | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | + }, | ||
| 55 | + methods: { | ||
| 56 | + open(details) { | ||
| 57 | + this.details = details || [] | ||
| 58 | + this.form = { | ||
| 59 | + detailId: '', | ||
| 60 | + remark: '' | ||
| 61 | + } | ||
| 62 | + this.visible = true | ||
| 63 | + }, | ||
| 64 | + | ||
| 65 | + async handleSubmit() { | ||
| 66 | + if (!this.form.detailId) { | ||
| 67 | + this.$message.warning(this.$t('staffAttendance.selectTaskFirst')) | ||
| 68 | + return | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + if (!this.form.remark) { | ||
| 72 | + this.$message.warning(this.$t('staffAttendance.inputReasonFirst')) | ||
| 73 | + return | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + try { | ||
| 77 | + await attendanceReplenishCheckIn(this.form) | ||
| 78 | + this.$message.success(this.$t('staffAttendance.replenishSuccess')) | ||
| 79 | + this.$emit('success') | ||
| 80 | + this.visible = false | ||
| 81 | + } catch (error) { | ||
| 82 | + console.error('Failed to submit replenish:', error) | ||
| 83 | + this.$message.error(this.$t('staffAttendance.replenishFailed')) | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | +} | ||
| 88 | +</script> | ||
| 89 | + | ||
| 90 | +<style lang="scss" scoped> | ||
| 91 | +.dialog-footer { | ||
| 92 | + text-align: right; | ||
| 93 | +} | ||
| 94 | +</style> | ||
| 0 | \ No newline at end of file | 95 | \ No newline at end of file |
src/components/oa/todayAttendanceDetail.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('todayAttendanceDetail.title')" | ||
| 4 | + :visible.sync="visible" | ||
| 5 | + width="80%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <el-table | ||
| 9 | + :data="tableData" | ||
| 10 | + border | ||
| 11 | + style="width: 100%" | ||
| 12 | + > | ||
| 13 | + <el-table-column | ||
| 14 | + prop="specCd" | ||
| 15 | + :label="$t('todayAttendanceDetail.table.name')" | ||
| 16 | + align="center" | ||
| 17 | + > | ||
| 18 | + <template slot-scope="scope"> | ||
| 19 | + {{ scope.row.specCd === '1001' ? $t('todayAttendanceDetail.table.work') : $t('todayAttendanceDetail.table.offWork') }} | ||
| 20 | + </template> | ||
| 21 | + </el-table-column> | ||
| 22 | + <el-table-column | ||
| 23 | + :label="$t('todayAttendanceDetail.table.attendanceRange')" | ||
| 24 | + align="center" | ||
| 25 | + > | ||
| 26 | + <template slot-scope="scope"> | ||
| 27 | + {{ scope.row.specCd === '1001' | ||
| 28 | + ? `${timeMinFormat(scope.row.leaveValue)}~${timeMinFormat(scope.row.value)}` | ||
| 29 | + : `${timeMinFormat(scope.row.value)}~${timeMinFormat(scope.row.lateValue)}` }} | ||
| 30 | + </template> | ||
| 31 | + </el-table-column> | ||
| 32 | + <el-table-column | ||
| 33 | + :label="$t('todayAttendanceDetail.table.lateEarly')" | ||
| 34 | + align="center" | ||
| 35 | + > | ||
| 36 | + <template slot-scope="scope"> | ||
| 37 | + {{ scope.row.specCd === '1001' | ||
| 38 | + ? `${timeMinFormat(scope.row.value)}~${timeMinFormat(scope.row.lateValue)}` | ||
| 39 | + : `${timeMinFormat(scope.row.leaveValue)}~${timeMinFormat(scope.row.value)}` }} | ||
| 40 | + </template> | ||
| 41 | + </el-table-column> | ||
| 42 | + <el-table-column | ||
| 43 | + prop="stateName" | ||
| 44 | + :label="$t('todayAttendanceDetail.table.status')" | ||
| 45 | + align="center" | ||
| 46 | + /> | ||
| 47 | + <el-table-column | ||
| 48 | + :label="$t('todayAttendanceDetail.table.attendanceTime')" | ||
| 49 | + align="center" | ||
| 50 | + > | ||
| 51 | + <template slot-scope="scope"> | ||
| 52 | + {{ scope.row.checkTime || $t('todayAttendanceDetail.table.notCheckIn') }} | ||
| 53 | + </template> | ||
| 54 | + </el-table-column> | ||
| 55 | + <el-table-column | ||
| 56 | + :label="$t('todayAttendanceDetail.table.snapshot')" | ||
| 57 | + align="center" | ||
| 58 | + > | ||
| 59 | + <template slot-scope="scope"> | ||
| 60 | + <el-image | ||
| 61 | + v-if="scope.row.state !== '10000'" | ||
| 62 | + style="width: 60px; height: 60px;" | ||
| 63 | + :src="scope.row.facePath" | ||
| 64 | + :preview-src-list="[scope.row.facePath]" | ||
| 65 | + fit="cover" | ||
| 66 | + /> | ||
| 67 | + </template> | ||
| 68 | + </el-table-column> | ||
| 69 | + </el-table> | ||
| 70 | + </el-dialog> | ||
| 71 | +</template> | ||
| 72 | + | ||
| 73 | +<script> | ||
| 74 | +export default { | ||
| 75 | + name: 'TodayAttendanceDetail', | ||
| 76 | + data() { | ||
| 77 | + return { | ||
| 78 | + visible: false, | ||
| 79 | + tableData: [] | ||
| 80 | + } | ||
| 81 | + }, | ||
| 82 | + methods: { | ||
| 83 | + open(data) { | ||
| 84 | + this.tableData = data.attendanceClassesTaskDetails || [] | ||
| 85 | + this.visible = true | ||
| 86 | + }, | ||
| 87 | + handleClose() { | ||
| 88 | + this.tableData = [] | ||
| 89 | + this.visible = false | ||
| 90 | + }, | ||
| 91 | + timeMinFormat(time) { | ||
| 92 | + if (!time) return '' | ||
| 93 | + const hours = Math.floor(time / 60) | ||
| 94 | + const minutes = time % 60 | ||
| 95 | + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}` | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | +} | ||
| 99 | +</script> | ||
| 100 | + | ||
| 101 | +<style scoped> | ||
| 102 | +.el-image { | ||
| 103 | + cursor: pointer; | ||
| 104 | + transition: transform 0.3s; | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +.el-image:hover { | ||
| 108 | + transform: scale(1.1); | ||
| 109 | +} | ||
| 110 | +</style> | ||
| 0 | \ No newline at end of file | 111 | \ No newline at end of file |
src/components/oa/uploadImageUrl.vue
| @@ -3,34 +3,36 @@ | @@ -3,34 +3,36 @@ | ||
| 3 | <div v-for="(image, index) in photos" :key="index" class="image-item"> | 3 | <div v-for="(image, index) in photos" :key="index" class="image-item"> |
| 4 | <el-image | 4 | <el-image |
| 5 | :src="image" | 5 | :src="image" |
| 6 | - fit="cover" | ||
| 7 | - style="width: 100px; height: 100px"> | ||
| 8 | - </el-image> | 6 | + fit="cover" |
| 7 | + style="width: 100px; height: 100px;" | ||
| 8 | + :preview-src-list="[image]" | ||
| 9 | + ></el-image> | ||
| 9 | <i | 10 | <i |
| 10 | - class="el-icon-close remove-icon" | ||
| 11 | - @click="_removeImage(image)"> | ||
| 12 | - </i> | 11 | + class="el-icon-delete remove-icon" |
| 12 | + @click="handleRemoveImage(image)" | ||
| 13 | + ></i> | ||
| 13 | </div> | 14 | </div> |
| 14 | - | 15 | + |
| 15 | <div | 16 | <div |
| 16 | - v-if="photos.length < imageCount" | ||
| 17 | - class="upload-button" | ||
| 18 | - @click="_uploadPhoto"> | 17 | + v-if="photos.length < imageCount" |
| 18 | + class="upload-button" | ||
| 19 | + @click="triggerUpload" | ||
| 20 | + > | ||
| 19 | <i class="el-icon-plus"></i> | 21 | <i class="el-icon-plus"></i> |
| 20 | </div> | 22 | </div> |
| 21 | - | 23 | + |
| 22 | <input | 24 | <input |
| 23 | type="file" | 25 | type="file" |
| 24 | - class="file-input" | 26 | + ref="fileInput" |
| 25 | accept="image/*" | 27 | accept="image/*" |
| 26 | - ref="fileInput" | ||
| 27 | - hidden | ||
| 28 | - @change="_choosePhoto"> | 28 | + hidden |
| 29 | + @change="handleFileChange" | ||
| 30 | + > | ||
| 29 | </div> | 31 | </div> |
| 30 | </template> | 32 | </template> |
| 31 | 33 | ||
| 32 | <script> | 34 | <script> |
| 33 | -import { uploadImage } from '@/api/oa/editExamineStaffApi' | 35 | +import { uploadFile } from '@/api/oa/addAttendanceClassesStaffApi' |
| 34 | import { getCommunityId } from '@/api/community/communityApi' | 36 | import { getCommunityId } from '@/api/community/communityApi' |
| 35 | 37 | ||
| 36 | export default { | 38 | export default { |
| @@ -38,121 +40,74 @@ export default { | @@ -38,121 +40,74 @@ export default { | ||
| 38 | props: { | 40 | props: { |
| 39 | imageCount: { | 41 | imageCount: { |
| 40 | type: Number, | 42 | type: Number, |
| 41 | - default: 99 | 43 | + default: 1 |
| 42 | } | 44 | } |
| 43 | }, | 45 | }, |
| 44 | data() { | 46 | data() { |
| 45 | return { | 47 | return { |
| 46 | - photos: [], // 显示图片数组(base64格式) | ||
| 47 | - photosUrl: [], // 向父组件传递的数组({fileId, url}) | ||
| 48 | - communityId: '' | 48 | + photos: [], |
| 49 | + photosUrl: [], | ||
| 50 | + communityId: getCommunityId() | ||
| 49 | } | 51 | } |
| 50 | }, | 52 | }, |
| 51 | - created() { | ||
| 52 | - this.communityId = getCommunityId() | ||
| 53 | - }, | ||
| 54 | methods: { | 53 | methods: { |
| 55 | - open(photos) { | ||
| 56 | - this.photos = [] | ||
| 57 | - this.photosUrl = [] | ||
| 58 | - | ||
| 59 | - if (!photos || photos.length === 0) return | ||
| 60 | - | ||
| 61 | - photos.forEach(photo => { | ||
| 62 | - if (photo.indexOf('base64,') > -1) { | ||
| 63 | - this.photos.push(photo) | ||
| 64 | - return | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - if (photo.indexOf("https") > -1 || photo.indexOf("http") > -1) { | ||
| 68 | - this.urlToBase64(photo) | ||
| 69 | - const urlParams = this._getUrlParams(photo) | ||
| 70 | - if (urlParams.fileId) { | ||
| 71 | - this.photosUrl.push({fileId: urlParams.fileId, url: photo}) | ||
| 72 | - } | ||
| 73 | - return | ||
| 74 | - } | ||
| 75 | - | ||
| 76 | - const url = `/callComponent/download/getFile/file?fileId=${photo}&communityId=-1&time=${new Date()}` | ||
| 77 | - this.urlToBase64(url) | ||
| 78 | - this.photosUrl.push({fileId: photo, url}) | ||
| 79 | - }) | ||
| 80 | - }, | ||
| 81 | - clear() { | ||
| 82 | - this.photos = [] | ||
| 83 | - this.photosUrl = [] | ||
| 84 | - }, | ||
| 85 | - _uploadPhoto() { | 54 | + triggerUpload() { |
| 86 | this.$refs.fileInput.click() | 55 | this.$refs.fileInput.click() |
| 87 | }, | 56 | }, |
| 88 | - _choosePhoto(event) { | ||
| 89 | - const files = event.target.files | ||
| 90 | - if (!files || files.length === 0) return | ||
| 91 | - | ||
| 92 | - const file = files[0] | 57 | + async handleFileChange(event) { |
| 58 | + const file = event.target.files[0] | ||
| 59 | + if (!file) return | ||
| 60 | + | ||
| 93 | if (file.size > 2 * 1024 * 1024) { | 61 | if (file.size > 2 * 1024 * 1024) { |
| 94 | this.$message.error(this.$t('uploadImage.imageSizeLimit')) | 62 | this.$message.error(this.$t('uploadImage.imageSizeLimit')) |
| 95 | return | 63 | return |
| 96 | } | 64 | } |
| 97 | - | 65 | + |
| 66 | + // 预览图片 | ||
| 98 | const reader = new FileReader() | 67 | const reader = new FileReader() |
| 99 | reader.onload = (e) => { | 68 | reader.onload = (e) => { |
| 100 | this.photos.push(e.target.result) | 69 | this.photos.push(e.target.result) |
| 101 | - this._doUploadImageUrl(file) | ||
| 102 | } | 70 | } |
| 103 | reader.readAsDataURL(file) | 71 | reader.readAsDataURL(file) |
| 104 | - | ||
| 105 | - event.target.value = null | ||
| 106 | - }, | ||
| 107 | - _removeImage(image) { | ||
| 108 | - const index = this.photos.indexOf(image) | ||
| 109 | - this.photos.splice(index, 1) | ||
| 110 | - this.photosUrl.splice(index, 1) | ||
| 111 | - this.$emit('notifyUploadImage', this.photosUrl) | ||
| 112 | - }, | ||
| 113 | - async _doUploadImageUrl(file) { | 72 | + |
| 73 | + // 上传图片 | ||
| 114 | try { | 74 | try { |
| 115 | const formData = new FormData() | 75 | const formData = new FormData() |
| 116 | - formData.append("uploadFile", file) | 76 | + formData.append('uploadFile', file) |
| 117 | formData.append('communityId', this.communityId) | 77 | formData.append('communityId', this.communityId) |
| 118 | - | ||
| 119 | - const data = await uploadImage(formData) | ||
| 120 | - this.photosUrl.push(data) | 78 | + |
| 79 | + const res = await uploadFile(formData) | ||
| 80 | + this.photosUrl.push(res.data) | ||
| 121 | this.$emit('notifyUploadImage', this.photosUrl) | 81 | this.$emit('notifyUploadImage', this.photosUrl) |
| 122 | } catch (error) { | 82 | } catch (error) { |
| 123 | - this.$message.error(this.$t('uploadImage.uploadError')) | 83 | + this.$message.error(this.$t('uploadImage.uploadFailed')) |
| 124 | } | 84 | } |
| 85 | + | ||
| 86 | + event.target.value = null | ||
| 125 | }, | 87 | }, |
| 126 | - _getUrlParams(url) { | ||
| 127 | - if (url.indexOf('?') < 0) { | ||
| 128 | - return { fileId: url } | ||
| 129 | - } | ||
| 130 | - | ||
| 131 | - const urlStr = url.split('?')[1] | ||
| 132 | - const obj = {} | ||
| 133 | - const paramsArr = urlStr.split('&') | ||
| 134 | - | ||
| 135 | - for (let i = 0; i < paramsArr.length; i++) { | ||
| 136 | - const arr = paramsArr[i].split('=') | ||
| 137 | - obj[arr[0]] = arr[1] | 88 | + handleRemoveImage(image) { |
| 89 | + const index = this.photos.indexOf(image) | ||
| 90 | + if (index > -1) { | ||
| 91 | + this.photos.splice(index, 1) | ||
| 92 | + this.photosUrl.splice(index, 1) | ||
| 93 | + this.$emit('notifyUploadImage', this.photosUrl) | ||
| 138 | } | 94 | } |
| 139 | - | ||
| 140 | - return obj | ||
| 141 | }, | 95 | }, |
| 142 | - urlToBase64(url) { | ||
| 143 | - const image = new Image() | ||
| 144 | - image.setAttribute('crossOrigin', 'anonymous') | ||
| 145 | - image.src = url | ||
| 146 | - | ||
| 147 | - image.onload = () => { | ||
| 148 | - const canvas = document.createElement('canvas') | ||
| 149 | - canvas.width = image.width | ||
| 150 | - canvas.height = image.height | ||
| 151 | - const ctx = canvas.getContext('2d') | ||
| 152 | - ctx.drawImage(image, 0, 0, image.width, image.height) | ||
| 153 | - const base64 = canvas.toDataURL('image/png') | ||
| 154 | - this.photos.push(base64) | ||
| 155 | - } | 96 | + clearImages() { |
| 97 | + this.photos = [] | ||
| 98 | + this.photosUrl = [] | ||
| 99 | + }, | ||
| 100 | + setImages(images) { | ||
| 101 | + this.clearImages() | ||
| 102 | + images.forEach(img => { | ||
| 103 | + if (img.indexOf('base64,') > -1) { | ||
| 104 | + this.photos.push(img) | ||
| 105 | + return | ||
| 106 | + } | ||
| 107 | + // 处理其他类型的图片URL | ||
| 108 | + this.photos.push(img) | ||
| 109 | + this.photosUrl.push({ url: img }) | ||
| 110 | + }) | ||
| 156 | } | 111 | } |
| 157 | } | 112 | } |
| 158 | } | 113 | } |
| @@ -163,41 +118,39 @@ export default { | @@ -163,41 +118,39 @@ export default { | ||
| 163 | display: flex; | 118 | display: flex; |
| 164 | flex-wrap: wrap; | 119 | flex-wrap: wrap; |
| 165 | gap: 10px; | 120 | gap: 10px; |
| 166 | - | 121 | + |
| 167 | .image-item { | 122 | .image-item { |
| 168 | position: relative; | 123 | position: relative; |
| 169 | - | ||
| 170 | - .remove-icon { | ||
| 171 | - position: absolute; | ||
| 172 | - top: 5px; | ||
| 173 | - right: 5px; | ||
| 174 | - color: #f56c6c; | ||
| 175 | - font-size: 16px; | ||
| 176 | - cursor: pointer; | ||
| 177 | - background: #fff; | ||
| 178 | - border-radius: 50%; | ||
| 179 | - padding: 2px; | ||
| 180 | - | ||
| 181 | - &:hover { | ||
| 182 | - color: #f00; | ||
| 183 | - } | ||
| 184 | - } | 124 | + margin-right: 10px; |
| 185 | } | 125 | } |
| 186 | - | 126 | + |
| 127 | + .remove-icon { | ||
| 128 | + position: absolute; | ||
| 129 | + top: -10px; | ||
| 130 | + right: -10px; | ||
| 131 | + color: #f56c6c; | ||
| 132 | + cursor: pointer; | ||
| 133 | + font-size: 16px; | ||
| 134 | + background: white; | ||
| 135 | + border-radius: 50%; | ||
| 136 | + padding: 2px; | ||
| 137 | + } | ||
| 138 | + | ||
| 187 | .upload-button { | 139 | .upload-button { |
| 188 | width: 100px; | 140 | width: 100px; |
| 189 | height: 100px; | 141 | height: 100px; |
| 190 | - border: 1px dashed #dcdfe6; | 142 | + border: 1px dashed #d9d9d9; |
| 143 | + border-radius: 6px; | ||
| 191 | display: flex; | 144 | display: flex; |
| 192 | justify-content: center; | 145 | justify-content: center; |
| 193 | align-items: center; | 146 | align-items: center; |
| 194 | cursor: pointer; | 147 | cursor: pointer; |
| 195 | - color: #909399; | ||
| 196 | - font-size: 24px; | ||
| 197 | - | 148 | + color: #8c939d; |
| 149 | + font-size: 28px; | ||
| 150 | + background-color: #fbfdff; | ||
| 151 | + | ||
| 198 | &:hover { | 152 | &:hover { |
| 199 | border-color: #409eff; | 153 | border-color: #409eff; |
| 200 | - color: #409eff; | ||
| 201 | } | 154 | } |
| 202 | } | 155 | } |
| 203 | } | 156 | } |
src/components/oa/viewImage.vue
| 1 | <template> | 1 | <template> |
| 2 | - <el-dialog | ||
| 3 | - :visible.sync="visible" | ||
| 4 | - fullscreen | ||
| 5 | - :show-close="false" | ||
| 6 | - @close="handleClose" | ||
| 7 | - > | ||
| 8 | - <div style="position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);"> | ||
| 9 | - <el-image | 2 | + <div class="view-image-wrapper" v-show="visible"> |
| 3 | + <div class="image-container"> | ||
| 4 | + <img | ||
| 10 | :src="imageUrl" | 5 | :src="imageUrl" |
| 11 | - :style="imageStyle" | ||
| 12 | - fit="contain" | ||
| 13 | - /> | ||
| 14 | - <i | ||
| 15 | - class="el-icon-close" | ||
| 16 | - style="position: absolute; right: 20px; top: 20px; font-size: 24px; color: red; cursor: pointer;" | ||
| 17 | - @click="visible = false" | 6 | + :style="{ |
| 7 | + width: imgWidth + 'px', | ||
| 8 | + height: imgHeight + 'px' | ||
| 9 | + }" | ||
| 10 | + @error="handleImageError" | ||
| 18 | /> | 11 | /> |
| 12 | + <i class="el-icon-close close-icon" @click="close"></i> | ||
| 19 | </div> | 13 | </div> |
| 20 | - </el-dialog> | 14 | + </div> |
| 21 | </template> | 15 | </template> |
| 22 | 16 | ||
| 23 | <script> | 17 | <script> |
| @@ -27,35 +21,103 @@ export default { | @@ -27,35 +21,103 @@ export default { | ||
| 27 | return { | 21 | return { |
| 28 | visible: false, | 22 | visible: false, |
| 29 | imageUrl: '', | 23 | imageUrl: '', |
| 30 | - imageStyle: { | ||
| 31 | - width: '800px', | ||
| 32 | - height: '800px' | ||
| 33 | - } | 24 | + imgWidth: 800, |
| 25 | + imgHeight: 800 | ||
| 34 | } | 26 | } |
| 35 | }, | 27 | }, |
| 36 | methods: { | 28 | methods: { |
| 37 | open(url) { | 29 | open(url) { |
| 38 | this.imageUrl = url | 30 | this.imageUrl = url |
| 39 | this.visible = true | 31 | this.visible = true |
| 40 | - this.adjustImageSize(url) | 32 | + this.calculateImageSize(url) |
| 33 | + this.enterFullscreen() | ||
| 34 | + }, | ||
| 35 | + close() { | ||
| 36 | + this.visible = false | ||
| 37 | + this.exitFullscreen() | ||
| 41 | }, | 38 | }, |
| 42 | - handleClose() { | ||
| 43 | - this.imageUrl = '' | 39 | + handleImageError() { |
| 40 | + this.imageUrl = '/img/noPhoto.jpg' | ||
| 44 | }, | 41 | }, |
| 45 | - adjustImageSize(url) { | 42 | + calculateImageSize(url) { |
| 46 | const img = new Image() | 43 | const img = new Image() |
| 47 | img.src = url | 44 | img.src = url |
| 48 | img.onload = () => { | 45 | img.onload = () => { |
| 49 | - const ratio = img.width / img.height | ||
| 50 | - if (ratio > 1) { | ||
| 51 | - this.imageStyle.height = `${800 / ratio}px` | ||
| 52 | - this.imageStyle.width = '800px' | ||
| 53 | - } else { | ||
| 54 | - this.imageStyle.width = `${800 * ratio}px` | ||
| 55 | - this.imageStyle.height = '800px' | ||
| 56 | - } | 46 | + const imgScale = img.width / img.height |
| 47 | + this.imgWidth = 800 | ||
| 48 | + this.imgHeight = 800 / imgScale | ||
| 49 | + } | ||
| 50 | + }, | ||
| 51 | + enterFullscreen() { | ||
| 52 | + const element = document.querySelector('.view-image-wrapper') | ||
| 53 | + if (element.requestFullscreen) { | ||
| 54 | + element.requestFullscreen() | ||
| 55 | + } else if (element.mozRequestFullScreen) { | ||
| 56 | + element.mozRequestFullScreen() | ||
| 57 | + } else if (element.webkitRequestFullscreen) { | ||
| 58 | + element.webkitRequestFullscreen() | ||
| 59 | + } else if (element.msRequestFullscreen) { | ||
| 60 | + element.msRequestFullscreen() | ||
| 61 | + } | ||
| 62 | + }, | ||
| 63 | + exitFullscreen() { | ||
| 64 | + if (document.exitFullscreen) { | ||
| 65 | + document.exitFullscreen() | ||
| 66 | + } else if (document.mozCancelFullScreen) { | ||
| 67 | + document.mozCancelFullScreen() | ||
| 68 | + } else if (document.webkitExitFullscreen) { | ||
| 69 | + document.webkitExitFullscreen() | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + }, | ||
| 73 | + mounted() { | ||
| 74 | + document.addEventListener('fullscreenchange', () => { | ||
| 75 | + if (!document.fullscreenElement) { | ||
| 76 | + this.close() | ||
| 77 | + } | ||
| 78 | + }) | ||
| 79 | + } | ||
| 80 | +} | ||
| 81 | +</script> | ||
| 82 | + | ||
| 83 | +<style lang="scss" scoped> | ||
| 84 | +.view-image-wrapper { | ||
| 85 | + position: fixed; | ||
| 86 | + top: 0; | ||
| 87 | + left: 0; | ||
| 88 | + width: 100%; | ||
| 89 | + height: 100%; | ||
| 90 | + background-color: rgba(0, 0, 0, 0.8); | ||
| 91 | + z-index: 9999; | ||
| 92 | + display: flex; | ||
| 93 | + justify-content: center; | ||
| 94 | + align-items: center; | ||
| 95 | + | ||
| 96 | + .image-container { | ||
| 97 | + position: relative; | ||
| 98 | + background-color: #fff; | ||
| 99 | + padding: 20px; | ||
| 100 | + border-radius: 4px; | ||
| 101 | + | ||
| 102 | + img { | ||
| 103 | + max-width: 90vw; | ||
| 104 | + max-height: 90vh; | ||
| 105 | + object-fit: contain; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + .close-icon { | ||
| 109 | + position: absolute; | ||
| 110 | + top: 10px; | ||
| 111 | + right: 10px; | ||
| 112 | + font-size: 24px; | ||
| 113 | + color: #f56c6c; | ||
| 114 | + cursor: pointer; | ||
| 115 | + z-index: 1; | ||
| 116 | + | ||
| 117 | + &:hover { | ||
| 118 | + color: #f78989; | ||
| 57 | } | 119 | } |
| 58 | } | 120 | } |
| 59 | } | 121 | } |
| 60 | } | 122 | } |
| 61 | -</script> | ||
| 62 | \ No newline at end of file | 123 | \ No newline at end of file |
| 124 | +</style> | ||
| 63 | \ No newline at end of file | 125 | \ No newline at end of file |
src/i18n/oaI18n.js
| @@ -27,6 +27,16 @@ import { messages as addExamineStaffMessages } from '../views/oa/addExamineStaff | @@ -27,6 +27,16 @@ import { messages as addExamineStaffMessages } from '../views/oa/addExamineStaff | ||
| 27 | import { messages as editExamineStaffMessages } from '../views/oa/editExamineStaffLang' | 27 | import { messages as editExamineStaffMessages } from '../views/oa/editExamineStaffLang' |
| 28 | import { messages as examineStaffValueMessages } from '../views/oa/examineStaffValueLang' | 28 | import { messages as examineStaffValueMessages } from '../views/oa/examineStaffValueLang' |
| 29 | import { messages as oaWorkflowManageMessages } from '../views/oa/oaWorkflowManageLang' | 29 | import { messages as oaWorkflowManageMessages } from '../views/oa/oaWorkflowManageLang' |
| 30 | +import { messages as newOaWorkflowManageMessages } from '../views/oa/newOaWorkflowManageLang' | ||
| 31 | +import { messages as newOaWorkflowDoingMessages } from '../views/oa/newOaWorkflowDoingLang' | ||
| 32 | +import { messages as attendanceClassesManageMessages } from '../views/oa/attendanceClassesManageLang' | ||
| 33 | +import { messages as attendanceClassesStaffManageMessages } from '../views/oa/attendanceClassesStaffManageLang' | ||
| 34 | +import { messages as todayAttendanceManageMessages } from '../views/oa/todayAttendanceManageLang' | ||
| 35 | +import { messages as addAttendanceClassesStaffMessages } from '../views/oa/addAttendanceClassesStaffLang' | ||
| 36 | + | ||
| 37 | +import { messages as monthAttendanceManageMessages } from '../views/oa/monthAttendanceManageLang' | ||
| 38 | +import { messages as staffAttendanceManageMessages } from '../views/oa/staffAttendanceManageLang' | ||
| 39 | +import { messages as attendanceLogManageMessages } from '../views/oa/attendanceLogManageLang' | ||
| 30 | 40 | ||
| 31 | export const messages ={ | 41 | export const messages ={ |
| 32 | en:{ | 42 | en:{ |
| @@ -59,6 +69,15 @@ export const messages ={ | @@ -59,6 +69,15 @@ export const messages ={ | ||
| 59 | ...editExamineStaffMessages.en, | 69 | ...editExamineStaffMessages.en, |
| 60 | ...examineStaffValueMessages.en, | 70 | ...examineStaffValueMessages.en, |
| 61 | ...oaWorkflowManageMessages.en, | 71 | ...oaWorkflowManageMessages.en, |
| 72 | + ...newOaWorkflowManageMessages.en, | ||
| 73 | + ...newOaWorkflowDoingMessages.en, | ||
| 74 | + ...attendanceClassesManageMessages.en, | ||
| 75 | + ...attendanceClassesStaffManageMessages.en, | ||
| 76 | + ...todayAttendanceManageMessages.en, | ||
| 77 | + ...addAttendanceClassesStaffMessages.en, | ||
| 78 | + ...monthAttendanceManageMessages.en, | ||
| 79 | + ...staffAttendanceManageMessages.en, | ||
| 80 | + ...attendanceLogManageMessages.en, | ||
| 62 | }, | 81 | }, |
| 63 | zh:{ | 82 | zh:{ |
| 64 | ...activitiesTypeManageMessages.zh, | 83 | ...activitiesTypeManageMessages.zh, |
| @@ -90,5 +109,14 @@ export const messages ={ | @@ -90,5 +109,14 @@ export const messages ={ | ||
| 90 | ...editExamineStaffMessages.zh, | 109 | ...editExamineStaffMessages.zh, |
| 91 | ...examineStaffValueMessages.zh, | 110 | ...examineStaffValueMessages.zh, |
| 92 | ...oaWorkflowManageMessages.zh, | 111 | ...oaWorkflowManageMessages.zh, |
| 112 | + ...newOaWorkflowManageMessages.zh, | ||
| 113 | + ...newOaWorkflowDoingMessages.zh, | ||
| 114 | + ...attendanceClassesManageMessages.zh, | ||
| 115 | + ...attendanceClassesStaffManageMessages.zh, | ||
| 116 | + ...todayAttendanceManageMessages.zh, | ||
| 117 | + ...addAttendanceClassesStaffMessages.zh, | ||
| 118 | + ...monthAttendanceManageMessages.zh, | ||
| 119 | + ...staffAttendanceManageMessages.zh, | ||
| 120 | + ...attendanceLogManageMessages.zh, | ||
| 93 | } | 121 | } |
| 94 | } | 122 | } |
| 95 | \ No newline at end of file | 123 | \ No newline at end of file |
src/router/oaRouter.js
| @@ -125,8 +125,53 @@ export default [ | @@ -125,8 +125,53 @@ export default [ | ||
| 125 | component: () => import('@/views/oa/examineStaffValueList.vue') | 125 | component: () => import('@/views/oa/examineStaffValueList.vue') |
| 126 | }, | 126 | }, |
| 127 | { | 127 | { |
| 128 | - path:'/pages/property/oaWorkflowManage', | ||
| 129 | - name:'/pages/property/oaWorkflowManage', | 128 | + path: '/pages/property/oaWorkflowManage', |
| 129 | + name: '/pages/property/oaWorkflowManage', | ||
| 130 | component: () => import('@/views/oa/oaWorkflowManageList.vue') | 130 | component: () => import('@/views/oa/oaWorkflowManageList.vue') |
| 131 | + }, | ||
| 132 | + { | ||
| 133 | + path: '/pages/property/newOaWorkflowManage', | ||
| 134 | + name: '/pages/property/newOaWorkflowManage', | ||
| 135 | + component: () => import('@/views/oa/newOaWorkflowManageList.vue') | ||
| 136 | + }, | ||
| 137 | + { | ||
| 138 | + path: '/pages/property/newOaWorkflowDoing', | ||
| 139 | + name: '/pages/property/newOaWorkflowDoing', | ||
| 140 | + component: () => import('@/views/oa/newOaWorkflowDoingList.vue') | ||
| 141 | + }, | ||
| 142 | + { | ||
| 143 | + path: '/pages/property/attendanceClassesManage', | ||
| 144 | + name: '/pages/property/attendanceClassesManage', | ||
| 145 | + component: () => import('@/views/oa/attendanceClassesManageList.vue') | ||
| 146 | + }, | ||
| 147 | + { | ||
| 148 | + path: '/pages/attendance/attendanceClassesStaffManage', | ||
| 149 | + name: '/pages/attendance/attendanceClassesStaffManage', | ||
| 150 | + component: () => import('@/views/oa/attendanceClassesStaffManageList.vue') | ||
| 151 | + }, | ||
| 152 | + { | ||
| 153 | + path: '/pages/property/todayAttendanceManage', | ||
| 154 | + name: '/pages/property/todayAttendanceManage', | ||
| 155 | + component: () => import('@/views/oa/todayAttendanceManageList.vue') | ||
| 156 | + }, | ||
| 157 | + { | ||
| 158 | + path: '/views/oa/addAttendanceClassesStaff', | ||
| 159 | + name: '/views/oa/addAttendanceClassesStaff', | ||
| 160 | + component: () => import('@/views/oa/addAttendanceClassesStaffList.vue') | ||
| 161 | + }, | ||
| 162 | + { | ||
| 163 | + path: '/pages/property/monthAttendanceManage', | ||
| 164 | + name: '/pages/property/monthAttendanceManage', | ||
| 165 | + component: () => import('@/views/oa/monthAttendanceManageList.vue') | ||
| 166 | + }, | ||
| 167 | + { | ||
| 168 | + path:'/pages/property/staffAttendanceManage', | ||
| 169 | + name:'/pages/property/staffAttendanceManage', | ||
| 170 | + component: () => import('@/views/oa/staffAttendanceManageList.vue') | ||
| 131 | }, | 171 | }, |
| 172 | + { | ||
| 173 | + path:'/pages/property/attendanceLogManage', | ||
| 174 | + name:'/pages/property/attendanceLogManage', | ||
| 175 | + component: () => import('@/views/oa/attendanceLogManageList.vue') | ||
| 176 | + }, | ||
| 132 | ] | 177 | ] |
| 133 | \ No newline at end of file | 178 | \ No newline at end of file |
src/views/oa/addAttendanceClassesStaffLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + addAttendanceClassesStaff: { | ||
| 4 | + title: 'Add Attendance Classes Staff', | ||
| 5 | + staffName: 'Staff Name', | ||
| 6 | + staffNamePlaceholder: 'Required, please select staff', | ||
| 7 | + staffNameRequired: 'Staff name is required', | ||
| 8 | + attendanceFace: 'Attendance Face', | ||
| 9 | + submitSuccess: 'Add success', | ||
| 10 | + submitFailed: 'Add failed' | ||
| 11 | + }, | ||
| 12 | + uploadImage: { | ||
| 13 | + imageSizeLimit: 'Image size cannot exceed 2MB', | ||
| 14 | + uploadFailed: 'Upload failed' | ||
| 15 | + }, | ||
| 16 | + selectStaff: { | ||
| 17 | + title: 'Select Staff', | ||
| 18 | + orgInfo: 'Organization Information', | ||
| 19 | + staffInfo: 'Staff Information', | ||
| 20 | + submitter: 'Submitter', | ||
| 21 | + dynamicAssign: 'Dynamic Assign', | ||
| 22 | + loadStaffFailed: 'Failed to load staff list' | ||
| 23 | + }, | ||
| 24 | + orgTree: { | ||
| 25 | + loadFailed: 'Failed to load organization tree' | ||
| 26 | + } | ||
| 27 | + }, | ||
| 28 | + zh: { | ||
| 29 | + addAttendanceClassesStaff: { | ||
| 30 | + title: '添加考勤班次员工', | ||
| 31 | + staffName: '员工名称', | ||
| 32 | + staffNamePlaceholder: '必填,请选择员工', | ||
| 33 | + staffNameRequired: '员工名称不能为空', | ||
| 34 | + attendanceFace: '考勤人脸', | ||
| 35 | + submitSuccess: '添加成功', | ||
| 36 | + submitFailed: '添加失败' | ||
| 37 | + }, | ||
| 38 | + uploadImage: { | ||
| 39 | + imageSizeLimit: '图片大小不能超过2MB', | ||
| 40 | + uploadFailed: '上传失败' | ||
| 41 | + }, | ||
| 42 | + selectStaff: { | ||
| 43 | + title: '选择员工', | ||
| 44 | + orgInfo: '组织信息', | ||
| 45 | + staffInfo: '员工信息', | ||
| 46 | + submitter: '提交者', | ||
| 47 | + dynamicAssign: '动态指定', | ||
| 48 | + loadStaffFailed: '加载员工列表失败' | ||
| 49 | + }, | ||
| 50 | + orgTree: { | ||
| 51 | + loadFailed: '加载组织树失败' | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | +} | ||
| 0 | \ No newline at end of file | 55 | \ No newline at end of file |
src/views/oa/addAttendanceClassesStaffList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="add-attendance-classes-staff-container"> | ||
| 3 | + <el-card> | ||
| 4 | + <div slot="header" class="clearfix"> | ||
| 5 | + <span>{{ $t('addAttendanceClassesStaff.title') }}</span> | ||
| 6 | + <el-button type="primary" size="small" class="float-right" @click="handleGoBack"> | ||
| 7 | + <i class="el-icon-close"></i> | ||
| 8 | + {{ $t('common.back') }} | ||
| 9 | + </el-button> | ||
| 10 | + </div> | ||
| 11 | + | ||
| 12 | + <el-row> | ||
| 13 | + <el-col :span="24"> | ||
| 14 | + <el-form label-width="120px"> | ||
| 15 | + <el-form-item :label="$t('addAttendanceClassesStaff.staffName')" class="text-left"> | ||
| 16 | + <el-input v-model="form.staffName" style="width: 80%;" :placeholder="$t('addAttendanceClassesStaff.staffNamePlaceholder')" | ||
| 17 | + disabled></el-input> | ||
| 18 | + <el-button type="primary" @click="handleChooseStaff" class="ml-10"> | ||
| 19 | + <i class="el-icon-search"></i> | ||
| 20 | + {{ $t('common.select') }} | ||
| 21 | + </el-button> | ||
| 22 | + </el-form-item> | ||
| 23 | + | ||
| 24 | + <el-form-item :label="$t('addAttendanceClassesStaff.attendanceFace')"> | ||
| 25 | + <upload-image-url ref="uploadImage" :image-count="1" | ||
| 26 | + @notifyUploadCoverImage="handleImageUpload"></upload-image-url> | ||
| 27 | + </el-form-item> | ||
| 28 | + </el-form> | ||
| 29 | + </el-col> | ||
| 30 | + </el-row> | ||
| 31 | + | ||
| 32 | + <div class="text-right mt-20"> | ||
| 33 | + <el-button type="warning" @click="handleGoBack" class="mr-20"> | ||
| 34 | + {{ $t('common.back') }} | ||
| 35 | + </el-button> | ||
| 36 | + <el-button type="primary" @click="handleSubmit"> | ||
| 37 | + <i class="el-icon-check"></i> | ||
| 38 | + {{ $t('common.submit') }} | ||
| 39 | + </el-button> | ||
| 40 | + </div> | ||
| 41 | + </el-card> | ||
| 42 | + | ||
| 43 | + <select-staff ref="selectStaff" @selectStaff="handleStaffChange"></select-staff> | ||
| 44 | + </div> | ||
| 45 | +</template> | ||
| 46 | + | ||
| 47 | +<script> | ||
| 48 | +import UploadImageUrl from '@/components/upload/UploadImageUrl' | ||
| 49 | +import SelectStaff from '@/components/staff/SelectStaff' | ||
| 50 | +import { saveAttendanceClassesStaff } from '@/api/oa/addAttendanceClassesStaffApi' | ||
| 51 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 52 | + | ||
| 53 | +export default { | ||
| 54 | + name: 'AddAttendanceClassesStaffList', | ||
| 55 | + components: { | ||
| 56 | + UploadImageUrl, | ||
| 57 | + SelectStaff | ||
| 58 | + }, | ||
| 59 | + data() { | ||
| 60 | + return { | ||
| 61 | + form: { | ||
| 62 | + csId: '', | ||
| 63 | + classesId: '', | ||
| 64 | + staffId: '', | ||
| 65 | + staffName: '', | ||
| 66 | + photo: '', | ||
| 67 | + communityId: '' | ||
| 68 | + } | ||
| 69 | + } | ||
| 70 | + }, | ||
| 71 | + created() { | ||
| 72 | + this.form.classesId = this.$route.query.classesId | ||
| 73 | + this.form.communityId = getCommunityId() | ||
| 74 | + }, | ||
| 75 | + methods: { | ||
| 76 | + handleChooseStaff() { | ||
| 77 | + this.$refs.selectStaff.open(this.form) | ||
| 78 | + }, | ||
| 79 | + handleStaffChange(staff) { | ||
| 80 | + this.form.staffId = staff.staffId | ||
| 81 | + this.form.staffName = staff.staffName | ||
| 82 | + }, | ||
| 83 | + handleImageUpload(photos) { | ||
| 84 | + if (photos.length > 0) { | ||
| 85 | + this.form.photo = photos[0] | ||
| 86 | + } | ||
| 87 | + }, | ||
| 88 | + async handleSubmit() { | ||
| 89 | + if (!this.validateForm()) { | ||
| 90 | + return | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + try { | ||
| 94 | + const res = await saveAttendanceClassesStaff(this.form) | ||
| 95 | + if (res.code === 0) { | ||
| 96 | + this.$message.success(this.$t('common.addSuccess')) | ||
| 97 | + this.handleGoBack() | ||
| 98 | + } else { | ||
| 99 | + this.$message.error(res.msg) | ||
| 100 | + } | ||
| 101 | + } catch (error) { | ||
| 102 | + this.$message.error(this.$t('common.addFailed')) | ||
| 103 | + } | ||
| 104 | + }, | ||
| 105 | + validateForm() { | ||
| 106 | + if (!this.form.staffName) { | ||
| 107 | + this.$message.error(this.$t('addAttendanceClassesStaff.staffNameRequired')) | ||
| 108 | + return false | ||
| 109 | + } | ||
| 110 | + return true | ||
| 111 | + }, | ||
| 112 | + handleGoBack() { | ||
| 113 | + this.$router.go(-1) | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | +} | ||
| 117 | +</script> | ||
| 118 | + | ||
| 119 | +<style lang="scss" scoped> | ||
| 120 | +.add-attendance-classes-staff-container { | ||
| 121 | + padding: 20px; | ||
| 122 | + | ||
| 123 | + .el-form-item { | ||
| 124 | + margin-bottom: 22px; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + .ml-10 { | ||
| 128 | + margin-left: 10px; | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + .mt-20 { | ||
| 132 | + margin-top: 20px; | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + .mr-20 { | ||
| 136 | + margin-right: 20px; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + .text-right { | ||
| 140 | + text-align: right; | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + .float-right { | ||
| 144 | + float: right; | ||
| 145 | + } | ||
| 146 | +} | ||
| 147 | +</style> | ||
| 0 | \ No newline at end of file | 148 | \ No newline at end of file |
src/views/oa/attendanceClassesManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + attendanceClassesManage: { | ||
| 4 | + title: 'Attendance Settings', | ||
| 5 | + document: 'Document', | ||
| 6 | + add: 'Add', | ||
| 7 | + classesId: 'Classes ID', | ||
| 8 | + classesName: 'Classes Name', | ||
| 9 | + timeOffset: 'Clock-in Range', | ||
| 10 | + lateOffset: 'Late Range', | ||
| 11 | + leaveOffset: 'Early Leave Range', | ||
| 12 | + maxLastOffset: 'Last Clock-out Range', | ||
| 13 | + operation: 'Operation', | ||
| 14 | + minute: 'minute', | ||
| 15 | + fetchError: 'Failed to fetch attendance classes data' | ||
| 16 | + }, | ||
| 17 | + addAttendanceClasses: { | ||
| 18 | + title: 'Add Attendance Classes', | ||
| 19 | + classesName: 'Classes Name', | ||
| 20 | + classesNamePlaceholder: 'Required, please enter classes name', | ||
| 21 | + classesNameRequired: 'Classes name is required', | ||
| 22 | + classesNameMaxLength: 'Classes name format error', | ||
| 23 | + timeOffset: 'Clock-in Range', | ||
| 24 | + timeOffsetPlaceholder: 'Required, please enter clock-in range (minutes)', | ||
| 25 | + timeOffsetRequired: 'Clock-in range is required', | ||
| 26 | + timeOffsetFormat: 'Clock-in range format error', | ||
| 27 | + lateOffset: 'Late Range', | ||
| 28 | + lateOffsetPlaceholder: 'Required, please enter late range (minutes)', | ||
| 29 | + lateOffsetRequired: 'Late range is required', | ||
| 30 | + lateOffsetMaxLength: 'Late range error', | ||
| 31 | + leaveOffset: 'Early Leave Range', | ||
| 32 | + leaveOffsetPlaceholder: 'Required, please enter early leave range (minutes)', | ||
| 33 | + leaveOffsetRequired: 'Early leave range is required', | ||
| 34 | + leaveOffsetMaxLength: 'Early leave range error', | ||
| 35 | + maxLastOffset: 'Last Clock-out Range', | ||
| 36 | + maxLastOffsetPlaceholder: 'Required, please enter last clock-out range (minutes)', | ||
| 37 | + maxLastOffsetRequired: 'Last clock-out range is required', | ||
| 38 | + saveSuccess: 'Added successfully' | ||
| 39 | + }, | ||
| 40 | + editAttendanceClasses: { | ||
| 41 | + title: 'Edit Attendance Classes', | ||
| 42 | + classesName: 'Classes Name', | ||
| 43 | + classesNamePlaceholder: 'Required, please enter classes name', | ||
| 44 | + classesNameRequired: 'Classes name is required', | ||
| 45 | + classesNameMaxLength: 'Classes name format error', | ||
| 46 | + timeOffset: 'Clock-in Range', | ||
| 47 | + timeOffsetPlaceholder: 'Required, please enter clock-in range (minutes)', | ||
| 48 | + timeOffsetRequired: 'Clock-in range is required', | ||
| 49 | + timeOffsetFormat: 'Clock-in range format error', | ||
| 50 | + lateOffset: 'Late Range', | ||
| 51 | + lateOffsetPlaceholder: 'Required, please enter late range (minutes)', | ||
| 52 | + lateOffsetRequired: 'Late range is required', | ||
| 53 | + lateOffsetMaxLength: 'Late range error', | ||
| 54 | + leaveOffset: 'Early Leave Range', | ||
| 55 | + leaveOffsetPlaceholder: 'Required, please enter early leave range (minutes)', | ||
| 56 | + leaveOffsetRequired: 'Early leave range is required', | ||
| 57 | + leaveOffsetMaxLength: 'Early leave range error', | ||
| 58 | + maxLastOffset: 'Last Clock-out Range', | ||
| 59 | + maxLastOffsetPlaceholder: 'Required, please enter last clock-out range (minutes)', | ||
| 60 | + maxLastOffsetRequired: 'Last clock-out range is required', | ||
| 61 | + classesIdRequired: 'Classes ID is required', | ||
| 62 | + editSuccess: 'Modified successfully' | ||
| 63 | + }, | ||
| 64 | + deleteAttendanceClasses: { | ||
| 65 | + title: 'Confirm Operation', | ||
| 66 | + confirmText: 'Are you sure to delete this attendance classes?', | ||
| 67 | + cancel: 'Cancel', | ||
| 68 | + confirm: 'Confirm Delete', | ||
| 69 | + deleteSuccess: 'Deleted successfully', | ||
| 70 | + deleteError: 'Failed to delete attendance classes' | ||
| 71 | + } | ||
| 72 | + }, | ||
| 73 | + zh: { | ||
| 74 | + attendanceClassesManage: { | ||
| 75 | + title: '考勤设置', | ||
| 76 | + document: '文档', | ||
| 77 | + add: '添加', | ||
| 78 | + classesId: '班组ID', | ||
| 79 | + classesName: '班组名称', | ||
| 80 | + timeOffset: '打卡范围', | ||
| 81 | + lateOffset: '迟到范围', | ||
| 82 | + leaveOffset: '早退范围', | ||
| 83 | + maxLastOffset: '当日下班范围', | ||
| 84 | + operation: '操作', | ||
| 85 | + minute: '分钟', | ||
| 86 | + fetchError: '获取考勤班组数据失败' | ||
| 87 | + }, | ||
| 88 | + addAttendanceClasses: { | ||
| 89 | + title: '添加考勤班组', | ||
| 90 | + classesName: '班次名称', | ||
| 91 | + classesNamePlaceholder: '必填,请填写班次名称', | ||
| 92 | + classesNameRequired: '班次名称不能为空', | ||
| 93 | + classesNameMaxLength: '班次名称格式错误', | ||
| 94 | + timeOffset: '打卡范围', | ||
| 95 | + timeOffsetPlaceholder: '必填,请填写打卡范围(分钟)', | ||
| 96 | + timeOffsetRequired: '打卡范围不能为空', | ||
| 97 | + timeOffsetFormat: '打卡范围格式错误', | ||
| 98 | + lateOffset: '迟到范围', | ||
| 99 | + lateOffsetPlaceholder: '必填,请填写迟到范围(分钟)', | ||
| 100 | + lateOffsetRequired: '迟到范围不能为空', | ||
| 101 | + lateOffsetMaxLength: '迟到范围错误', | ||
| 102 | + leaveOffset: '早退范围', | ||
| 103 | + leaveOffsetPlaceholder: '必填,请填写早退范围(分钟)', | ||
| 104 | + leaveOffsetRequired: '早退范围不能为空', | ||
| 105 | + leaveOffsetMaxLength: '早退范围错误', | ||
| 106 | + maxLastOffset: '当日下班范围', | ||
| 107 | + maxLastOffsetPlaceholder: '必填,请填写当日最后一次下班打卡范围(分钟)', | ||
| 108 | + maxLastOffsetRequired: '当日下班范围不能为空', | ||
| 109 | + saveSuccess: '添加成功' | ||
| 110 | + }, | ||
| 111 | + editAttendanceClasses: { | ||
| 112 | + title: '修改考勤班组', | ||
| 113 | + classesName: '班次名称', | ||
| 114 | + classesNamePlaceholder: '必填,请填写班次名称', | ||
| 115 | + classesNameRequired: '班次名称不能为空', | ||
| 116 | + classesNameMaxLength: '班次名称格式错误', | ||
| 117 | + timeOffset: '打卡范围', | ||
| 118 | + timeOffsetPlaceholder: '必填,请填写打卡范围(分钟)', | ||
| 119 | + timeOffsetRequired: '打卡范围不能为空', | ||
| 120 | + timeOffsetFormat: '打卡范围格式错误', | ||
| 121 | + lateOffset: '迟到范围', | ||
| 122 | + lateOffsetPlaceholder: '必填,请填写迟到范围(分钟)', | ||
| 123 | + lateOffsetRequired: '迟到范围不能为空', | ||
| 124 | + lateOffsetMaxLength: '迟到范围错误', | ||
| 125 | + leaveOffset: '早退范围', | ||
| 126 | + leaveOffsetPlaceholder: '必填,请填写早退范围(分钟)', | ||
| 127 | + leaveOffsetRequired: '早退范围不能为空', | ||
| 128 | + leaveOffsetMaxLength: '早退范围错误', | ||
| 129 | + maxLastOffset: '当日下班范围', | ||
| 130 | + maxLastOffsetPlaceholder: '必填,请填写当日最后一次下班打卡范围(分钟)', | ||
| 131 | + maxLastOffsetRequired: '当日下班范围不能为空', | ||
| 132 | + classesIdRequired: '班组ID不能为空', | ||
| 133 | + editSuccess: '修改成功' | ||
| 134 | + }, | ||
| 135 | + deleteAttendanceClasses: { | ||
| 136 | + title: '请确认您的操作', | ||
| 137 | + confirmText: '确定删除考勤班组吗?', | ||
| 138 | + cancel: '点错了', | ||
| 139 | + confirm: '确认删除', | ||
| 140 | + deleteSuccess: '删除成功', | ||
| 141 | + deleteError: '删除考勤班组失败' | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | +} | ||
| 0 | \ No newline at end of file | 145 | \ No newline at end of file |
src/views/oa/attendanceClassesManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="attendance-classes-manage-container"> | ||
| 3 | + <el-row> | ||
| 4 | + <el-col :span="24"> | ||
| 5 | + <el-card> | ||
| 6 | + <div slot="header" class="flex justify-between"> | ||
| 7 | + <span>{{ $t('attendanceClassesManage.title') }}</span> | ||
| 8 | + <div class="header-tools"> | ||
| 9 | + <el-button type="primary" size="small" @click="showMarkdown"> | ||
| 10 | + <i class="el-icon-document"></i> | ||
| 11 | + {{ $t('attendanceClassesManage.document') }} | ||
| 12 | + </el-button> | ||
| 13 | + <el-button type="primary" size="small" @click="openAddAttendanceClassesModal"> | ||
| 14 | + <i class="el-icon-plus"></i> | ||
| 15 | + {{ $t('attendanceClassesManage.add') }} | ||
| 16 | + </el-button> | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | + | ||
| 20 | + <el-table :data="attendanceClassesManageInfo.attendanceClassess" border style="width: 100%" | ||
| 21 | + v-loading="loading"> | ||
| 22 | + <el-table-column prop="classesId" :label="$t('attendanceClassesManage.classesId')" align="center" /> | ||
| 23 | + <el-table-column prop="classesName" :label="$t('attendanceClassesManage.classesName')" align="center" /> | ||
| 24 | + <el-table-column :label="$t('attendanceClassesManage.timeOffset')" align="center"> | ||
| 25 | + <template slot-scope="scope"> | ||
| 26 | + {{ scope.row.timeOffset }} {{ $t('attendanceClassesManage.minute') }} | ||
| 27 | + </template> | ||
| 28 | + </el-table-column> | ||
| 29 | + <el-table-column :label="$t('attendanceClassesManage.lateOffset')" align="center"> | ||
| 30 | + <template slot-scope="scope"> | ||
| 31 | + {{ scope.row.lateOffset }} {{ $t('attendanceClassesManage.minute') }} | ||
| 32 | + </template> | ||
| 33 | + </el-table-column> | ||
| 34 | + <el-table-column :label="$t('attendanceClassesManage.leaveOffset')" align="center"> | ||
| 35 | + <template slot-scope="scope"> | ||
| 36 | + {{ scope.row.leaveOffset }} {{ $t('attendanceClassesManage.minute') }} | ||
| 37 | + </template> | ||
| 38 | + </el-table-column> | ||
| 39 | + <el-table-column :label="$t('attendanceClassesManage.maxLastOffset')" align="center"> | ||
| 40 | + <template slot-scope="scope"> | ||
| 41 | + {{ scope.row.maxLastOffset }} {{ $t('attendanceClassesManage.minute') }} | ||
| 42 | + </template> | ||
| 43 | + </el-table-column> | ||
| 44 | + <el-table-column :label="$t('common.operation')" align="center" width="200"> | ||
| 45 | + <template slot-scope="scope"> | ||
| 46 | + <el-button size="mini" @click="openEditAttendanceClassesModel(scope.row)"> | ||
| 47 | + {{ $t('common.edit') }} | ||
| 48 | + </el-button> | ||
| 49 | + <el-button size="mini" type="danger" @click="openDeleteAttendanceClassesModel(scope.row)"> | ||
| 50 | + {{ $t('common.delete') }} | ||
| 51 | + </el-button> | ||
| 52 | + </template> | ||
| 53 | + </el-table-column> | ||
| 54 | + </el-table> | ||
| 55 | + | ||
| 56 | + <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" | ||
| 57 | + :current-page="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size" | ||
| 58 | + layout="total, sizes, prev, pager, next, jumper" :total="page.total" class="pagination" /> | ||
| 59 | + </el-card> | ||
| 60 | + </el-col> | ||
| 61 | + </el-row> | ||
| 62 | + | ||
| 63 | + <add-attendance-classes ref="addAttendanceClasses" @success="listAttendanceClasses" /> | ||
| 64 | + <edit-attendance-classes ref="editAttendanceClasses" @success="listAttendanceClasses" /> | ||
| 65 | + <delete-attendance-classes ref="deleteAttendanceClasses" @success="listAttendanceClasses" /> | ||
| 66 | + </div> | ||
| 67 | +</template> | ||
| 68 | + | ||
| 69 | +<script> | ||
| 70 | +import { listAttendanceClassess } from '@/api/oa/attendanceClassesManageApi' | ||
| 71 | +import AddAttendanceClasses from '@/components/oa/addAttendanceClasses' | ||
| 72 | +import EditAttendanceClasses from '@/components/oa/editAttendanceClasses' | ||
| 73 | +import DeleteAttendanceClasses from '@/components/oa/deleteAttendanceClasses' | ||
| 74 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 75 | + | ||
| 76 | +export default { | ||
| 77 | + name: 'AttendanceClassesManageList', | ||
| 78 | + components: { | ||
| 79 | + AddAttendanceClasses, | ||
| 80 | + EditAttendanceClasses, | ||
| 81 | + DeleteAttendanceClasses | ||
| 82 | + }, | ||
| 83 | + data() { | ||
| 84 | + return { | ||
| 85 | + loading: false, | ||
| 86 | + attendanceClassesManageInfo: { | ||
| 87 | + attendanceClassess: [], | ||
| 88 | + classesObjId: '', | ||
| 89 | + classesObjName: '' | ||
| 90 | + }, | ||
| 91 | + page: { | ||
| 92 | + current: 1, | ||
| 93 | + size: 10, | ||
| 94 | + total: 0 | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + }, | ||
| 98 | + created() { | ||
| 99 | + this.communityId = getCommunityId() | ||
| 100 | + this.listAttendanceClasses() | ||
| 101 | + }, | ||
| 102 | + methods: { | ||
| 103 | + async listAttendanceClasses() { | ||
| 104 | + try { | ||
| 105 | + this.loading = true | ||
| 106 | + const params = { | ||
| 107 | + page: this.page.current, | ||
| 108 | + row: this.page.size, | ||
| 109 | + communityId: this.communityId | ||
| 110 | + } | ||
| 111 | + const { data, total } = await listAttendanceClassess(params) | ||
| 112 | + this.attendanceClassesManageInfo.attendanceClassess = data | ||
| 113 | + this.page.total = total | ||
| 114 | + } catch (error) { | ||
| 115 | + this.$message.error(this.$t('attendanceClassesManage.fetchError')) | ||
| 116 | + } finally { | ||
| 117 | + this.loading = false | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + showMarkdown() { | ||
| 121 | + // TODO: 实现文档显示逻辑 | ||
| 122 | + }, | ||
| 123 | + openAddAttendanceClassesModal() { | ||
| 124 | + this.$refs.addAttendanceClasses.open({ | ||
| 125 | + classesObjId: this.attendanceClassesManageInfo.classesObjId, | ||
| 126 | + classesObjName: this.attendanceClassesManageInfo.classesObjName | ||
| 127 | + }) | ||
| 128 | + }, | ||
| 129 | + openEditAttendanceClassesModel(row) { | ||
| 130 | + this.$refs.editAttendanceClasses.open(row) | ||
| 131 | + }, | ||
| 132 | + openDeleteAttendanceClassesModel(row) { | ||
| 133 | + this.$refs.deleteAttendanceClasses.open(row) | ||
| 134 | + }, | ||
| 135 | + handleSizeChange(val) { | ||
| 136 | + this.page.size = val | ||
| 137 | + this.listAttendanceClasses() | ||
| 138 | + }, | ||
| 139 | + handleCurrentChange(val) { | ||
| 140 | + this.page.current = val | ||
| 141 | + this.listAttendanceClasses() | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | +} | ||
| 145 | +</script> | ||
| 146 | + | ||
| 147 | +<style lang="scss" scoped> | ||
| 148 | +.attendance-classes-manage-container { | ||
| 149 | + padding: 20px; | ||
| 150 | + | ||
| 151 | + .header-tools { | ||
| 152 | + float: right; | ||
| 153 | + margin-top: -5px; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + .pagination { | ||
| 157 | + margin-top: 20px; | ||
| 158 | + text-align: right; | ||
| 159 | + } | ||
| 160 | +} | ||
| 161 | +</style> | ||
| 0 | \ No newline at end of file | 162 | \ No newline at end of file |
src/views/oa/attendanceClassesStaffManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + attendanceClassesStaffManage: { | ||
| 4 | + search: { | ||
| 5 | + title: 'Search Conditions', | ||
| 6 | + staffName: 'Please enter staff name' | ||
| 7 | + }, | ||
| 8 | + list: { | ||
| 9 | + title: 'Attendance Staff List' | ||
| 10 | + }, | ||
| 11 | + table: { | ||
| 12 | + face: 'Face', | ||
| 13 | + staffId: 'Staff ID', | ||
| 14 | + staffName: 'Staff Name', | ||
| 15 | + createTime: 'Create Time' | ||
| 16 | + }, | ||
| 17 | + delete: { | ||
| 18 | + title: 'Confirm Operation', | ||
| 19 | + confirmText: 'Are you sure to delete this attendance staff?', | ||
| 20 | + success: 'Delete successfully', | ||
| 21 | + error: 'Delete failed' | ||
| 22 | + }, | ||
| 23 | + fetchError: 'Failed to fetch attendance staff data', | ||
| 24 | + fetchClassError: 'Failed to fetch attendance classes data' | ||
| 25 | + } | ||
| 26 | + }, | ||
| 27 | + zh: { | ||
| 28 | + attendanceClassesStaffManage: { | ||
| 29 | + search: { | ||
| 30 | + title: '查询条件', | ||
| 31 | + staffName: '请输入员工姓名' | ||
| 32 | + }, | ||
| 33 | + list: { | ||
| 34 | + title: '考勤员工列表' | ||
| 35 | + }, | ||
| 36 | + table: { | ||
| 37 | + face: '人脸', | ||
| 38 | + staffId: '员工编号', | ||
| 39 | + staffName: '员工名称', | ||
| 40 | + createTime: '创建时间' | ||
| 41 | + }, | ||
| 42 | + delete: { | ||
| 43 | + title: '确认操作', | ||
| 44 | + confirmText: '确定删除该考勤员工吗?', | ||
| 45 | + success: '删除成功', | ||
| 46 | + error: '删除失败' | ||
| 47 | + }, | ||
| 48 | + fetchError: '获取考勤员工数据失败', | ||
| 49 | + fetchClassError: '获取考勤班次数据失败' | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | +} | ||
| 0 | \ No newline at end of file | 53 | \ No newline at end of file |
src/views/oa/attendanceClassesStaffManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="attendance-classes-staff-manage-container"> | ||
| 3 | + <el-row :gutter="20"> | ||
| 4 | + <el-col :span="4"> | ||
| 5 | + <el-card class="tree-card"> | ||
| 6 | + <div class="treeview attendance-staff"> | ||
| 7 | + <ul class="list-group"> | ||
| 8 | + <li v-for="(item, index) in attendanceClassesStaffManageInfo.attendanceClassess" :key="index" | ||
| 9 | + class="list-group-item node-orgTree" | ||
| 10 | + :class="{ 'vc-node-selected': attendanceClassesStaffManageInfo.conditions.classesId == item.classesId }" | ||
| 11 | + @click="swatchClass(item)"> | ||
| 12 | + {{ item.classesName }} | ||
| 13 | + </li> | ||
| 14 | + </ul> | ||
| 15 | + </div> | ||
| 16 | + </el-card> | ||
| 17 | + </el-col> | ||
| 18 | + <el-col :span="20"> | ||
| 19 | + <el-card> | ||
| 20 | + <div slot="header" class="flex justify-between"> | ||
| 21 | + <span>{{ $t('attendanceClassesStaffManage.search.title') }}</span> | ||
| 22 | + </div> | ||
| 23 | + <el-row :gutter="20"> | ||
| 24 | + <el-col :span="8"> | ||
| 25 | + <el-input v-model="attendanceClassesStaffManageInfo.conditions.staffNameLike" | ||
| 26 | + :placeholder="$t('attendanceClassesStaffManage.search.staffName')" clearable /> | ||
| 27 | + </el-col> | ||
| 28 | + <el-col :span="16"> | ||
| 29 | + <el-button type="primary" @click="_queryAttendanceClassesStaffMethod"> | ||
| 30 | + <i class="el-icon-search"></i> | ||
| 31 | + {{ $t('common.search') }} | ||
| 32 | + </el-button> | ||
| 33 | + <el-button @click="_resetAttendanceClassesStaffMethod"> | ||
| 34 | + <i class="el-icon-refresh"></i> | ||
| 35 | + {{ $t('common.reset') }} | ||
| 36 | + </el-button> | ||
| 37 | + </el-col> | ||
| 38 | + </el-row> | ||
| 39 | + </el-card> | ||
| 40 | + | ||
| 41 | + <el-card style="margin-top: 20px;"> | ||
| 42 | + <div slot="header" class="flex justify-between"> | ||
| 43 | + <span>{{ $t('attendanceClassesStaffManage.list.title') }}</span> | ||
| 44 | + <el-button type="primary" size="small" style="float: right;" @click="_openAddAttendanceClassesStaffModal"> | ||
| 45 | + <i class="el-icon-plus"></i> | ||
| 46 | + {{ $t('common.add') }} | ||
| 47 | + </el-button> | ||
| 48 | + </div> | ||
| 49 | + <el-table :data="attendanceClassesStaffManageInfo.attendanceClassesStaffs" border style="width: 100%" | ||
| 50 | + v-loading="loading"> | ||
| 51 | + <el-table-column prop="personFace" :label="$t('attendanceClassesStaffManage.table.face')" align="center" | ||
| 52 | + width="100"> | ||
| 53 | + <template slot-scope="scope"> | ||
| 54 | + <div style="position: relative; display: inline-block;" | ||
| 55 | + @click="showImg(scope.row.personFace || '/img/noPhoto.jpg')"> | ||
| 56 | + <el-image style="width: 50px; height: 50px" :src="scope.row.personFace || '/img/noPhoto.jpg'" | ||
| 57 | + fit="cover"> | ||
| 58 | + <div slot="error" class="image-slot"> | ||
| 59 | + <img src="/img/noPhoto.jpg" style="width: 50px; height: 50px"> | ||
| 60 | + </div> | ||
| 61 | + </el-image> | ||
| 62 | + <img src="/img/icon-bigimg.png" style="position: absolute;right: 0;bottom: 0;" width="50" height="50" | ||
| 63 | + alt=""> | ||
| 64 | + </div> | ||
| 65 | + </template> | ||
| 66 | + </el-table-column> | ||
| 67 | + <el-table-column prop="staffId" :label="$t('attendanceClassesStaffManage.table.staffId')" align="center" /> | ||
| 68 | + <el-table-column prop="staffName" :label="$t('attendanceClassesStaffManage.table.staffName')" | ||
| 69 | + align="center" /> | ||
| 70 | + <el-table-column prop="createTime" :label="$t('attendanceClassesStaffManage.table.createTime')" | ||
| 71 | + align="center" /> | ||
| 72 | + <el-table-column :label="$t('common.operation')" align="center" width="200"> | ||
| 73 | + <template slot-scope="scope"> | ||
| 74 | + <el-button size="mini" type="danger" @click="_openDeleteAttendanceClassesStaffModel(scope.row)"> | ||
| 75 | + {{ $t('common.delete') }} | ||
| 76 | + </el-button> | ||
| 77 | + <el-button size="mini" type="primary" @click="openStaffDetail(scope.row)"> | ||
| 78 | + {{ $t('common.detail') }} | ||
| 79 | + </el-button> | ||
| 80 | + </template> | ||
| 81 | + </el-table-column> | ||
| 82 | + </el-table> | ||
| 83 | + | ||
| 84 | + <el-pagination :current-page.sync="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size" | ||
| 85 | + :total="page.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" | ||
| 86 | + @current-change="handleCurrentChange" style="margin-top: 20px;" /> | ||
| 87 | + </el-card> | ||
| 88 | + </el-col> | ||
| 89 | + </el-row> | ||
| 90 | + | ||
| 91 | + <delete-attendance-classes-staff ref="deleteDialog" @success="handleSuccess" /> | ||
| 92 | + <view-image ref="viewImage" /> | ||
| 93 | + </div> | ||
| 94 | +</template> | ||
| 95 | + | ||
| 96 | +<script> | ||
| 97 | +import { listAttendanceClassesStaff, listAttendanceClassess } from '@/api/oa/attendanceClassesStaffManageApi' | ||
| 98 | +import DeleteAttendanceClassesStaff from '@/components/oa/deleteAttendanceClassesStaff' | ||
| 99 | +import ViewImage from '@/components/system/viewImage' | ||
| 100 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 101 | + | ||
| 102 | +export default { | ||
| 103 | + name: 'AttendanceClassesStaffManageList', | ||
| 104 | + components: { | ||
| 105 | + DeleteAttendanceClassesStaff, | ||
| 106 | + ViewImage | ||
| 107 | + }, | ||
| 108 | + data() { | ||
| 109 | + return { | ||
| 110 | + loading: false, | ||
| 111 | + attendanceClassesStaffManageInfo: { | ||
| 112 | + attendanceClassesStaffs: [], | ||
| 113 | + total: 0, | ||
| 114 | + records: 1, | ||
| 115 | + moreCondition: false, | ||
| 116 | + csId: '', | ||
| 117 | + attendanceClassess: [], | ||
| 118 | + conditions: { | ||
| 119 | + classesId: '', | ||
| 120 | + staffId: '', | ||
| 121 | + staffNameLike: '', | ||
| 122 | + page: 1, | ||
| 123 | + row: 10 | ||
| 124 | + } | ||
| 125 | + }, | ||
| 126 | + page: { | ||
| 127 | + current: 1, | ||
| 128 | + size: 10, | ||
| 129 | + total: 0 | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + }, | ||
| 133 | + created() { | ||
| 134 | + this.communityId = getCommunityId() | ||
| 135 | + this._loadAttendanceClass() | ||
| 136 | + }, | ||
| 137 | + methods: { | ||
| 138 | + async _listAttendanceClassesStaffs(page = 1, rows = 10) { | ||
| 139 | + try { | ||
| 140 | + this.loading = true | ||
| 141 | + this.attendanceClassesStaffManageInfo.conditions.page = page | ||
| 142 | + this.attendanceClassesStaffManageInfo.conditions.row = rows | ||
| 143 | + | ||
| 144 | + const { data, total } = await listAttendanceClassesStaff(this.attendanceClassesStaffManageInfo.conditions) | ||
| 145 | + this.attendanceClassesStaffManageInfo.attendanceClassesStaffs = data | ||
| 146 | + this.page.total = total | ||
| 147 | + } catch (error) { | ||
| 148 | + console.error('获取考勤员工列表失败:', error) | ||
| 149 | + this.$message.error(this.$t('attendanceClassesStaffManage.fetchError')) | ||
| 150 | + } finally { | ||
| 151 | + this.loading = false | ||
| 152 | + } | ||
| 153 | + }, | ||
| 154 | + _openAddAttendanceClassesStaffModal() { | ||
| 155 | + this.$router.push(`/views/oa/addAttendanceClassesStaff?classesId=${this.attendanceClassesStaffManageInfo.conditions.classesId}`) | ||
| 156 | + }, | ||
| 157 | + _openDeleteAttendanceClassesStaffModel(row) { | ||
| 158 | + this.$refs.deleteDialog.open(row) | ||
| 159 | + }, | ||
| 160 | + _queryAttendanceClassesStaffMethod() { | ||
| 161 | + this.page.current = 1 | ||
| 162 | + this._listAttendanceClassesStaffs() | ||
| 163 | + }, | ||
| 164 | + _resetAttendanceClassesStaffMethod() { | ||
| 165 | + this.attendanceClassesStaffManageInfo.conditions.staffNameLike = '' | ||
| 166 | + this._queryAttendanceClassesStaffMethod() | ||
| 167 | + }, | ||
| 168 | + swatchClass(item) { | ||
| 169 | + this.attendanceClassesStaffManageInfo.conditions.classesId = item.classesId | ||
| 170 | + this._listAttendanceClassesStaffs() | ||
| 171 | + }, | ||
| 172 | + async _loadAttendanceClass() { | ||
| 173 | + try { | ||
| 174 | + const { data } = await listAttendanceClassess({ page: 1, row: 100 }) | ||
| 175 | + this.attendanceClassesStaffManageInfo.attendanceClassess = data | ||
| 176 | + if (data && data.length > 0) { | ||
| 177 | + this.swatchClass(data[0]) | ||
| 178 | + } | ||
| 179 | + } catch (error) { | ||
| 180 | + console.error('获取考勤班次失败:', error) | ||
| 181 | + this.$message.error(this.$t('attendanceClassesStaffManage.fetchClassError')) | ||
| 182 | + } | ||
| 183 | + }, | ||
| 184 | + showImg(url) { | ||
| 185 | + this.$refs.viewImage.open(url) | ||
| 186 | + }, | ||
| 187 | + openStaffDetail(staff) { | ||
| 188 | + this.$router.push(`/staff/staffDetail?staffId=${staff.staffId}`) | ||
| 189 | + }, | ||
| 190 | + handleSizeChange(val) { | ||
| 191 | + this.page.size = val | ||
| 192 | + this._listAttendanceClassesStaffs(this.page.current, val) | ||
| 193 | + }, | ||
| 194 | + handleCurrentChange(val) { | ||
| 195 | + this._listAttendanceClassesStaffs(val, this.page.size) | ||
| 196 | + }, | ||
| 197 | + handleSuccess() { | ||
| 198 | + this._listAttendanceClassesStaffs(this.page.current, this.page.size) | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | +} | ||
| 202 | +</script> | ||
| 203 | + | ||
| 204 | +<style lang="scss" scoped> | ||
| 205 | +.attendance-classes-staff-manage-container { | ||
| 206 | + padding: 20px; | ||
| 207 | + | ||
| 208 | + .tree-card { | ||
| 209 | + height: 100%; | ||
| 210 | + | ||
| 211 | + .treeview { | ||
| 212 | + height: 100%; | ||
| 213 | + | ||
| 214 | + .list-group { | ||
| 215 | + list-style: none; | ||
| 216 | + padding: 0; | ||
| 217 | + margin: 0; | ||
| 218 | + | ||
| 219 | + .list-group-item { | ||
| 220 | + padding: 10px 15px; | ||
| 221 | + margin-bottom: 5px; | ||
| 222 | + border: 1px solid #ddd; | ||
| 223 | + border-radius: 4px; | ||
| 224 | + cursor: pointer; | ||
| 225 | + text-align: center; | ||
| 226 | + | ||
| 227 | + &:hover { | ||
| 228 | + background-color: #f5f5f5; | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + &.vc-node-selected { | ||
| 232 | + background-color: #409EFF; | ||
| 233 | + color: white; | ||
| 234 | + border-color: #409EFF; | ||
| 235 | + } | ||
| 236 | + } | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | +} | ||
| 241 | +</style> | ||
| 0 | \ No newline at end of file | 242 | \ No newline at end of file |
src/views/oa/attendanceLogManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + attendanceLogManage: { | ||
| 4 | + search: { | ||
| 5 | + title: 'Search Conditions', | ||
| 6 | + classesName: 'Please enter class name', | ||
| 7 | + departmentName: 'Please enter department name', | ||
| 8 | + date: 'Please select clock time', | ||
| 9 | + staffName: 'Please enter staff name' | ||
| 10 | + }, | ||
| 11 | + list: { | ||
| 12 | + title: 'Attendance Records' | ||
| 13 | + }, | ||
| 14 | + table: { | ||
| 15 | + face: 'Face', | ||
| 16 | + departmentName: 'Department Name', | ||
| 17 | + staffName: 'Staff Name', | ||
| 18 | + staffId: 'Staff ID', | ||
| 19 | + clockTime: 'Clock Time', | ||
| 20 | + remark: 'Remark' | ||
| 21 | + }, | ||
| 22 | + fetchError: 'Failed to fetch attendance records' | ||
| 23 | + } | ||
| 24 | + }, | ||
| 25 | + zh: { | ||
| 26 | + attendanceLogManage: { | ||
| 27 | + search: { | ||
| 28 | + title: '查询条件', | ||
| 29 | + classesName: '请输入班组名称', | ||
| 30 | + departmentName: '请输入部门名称', | ||
| 31 | + date: '请选择打卡时间', | ||
| 32 | + staffName: '请输入员工名称' | ||
| 33 | + }, | ||
| 34 | + list: { | ||
| 35 | + title: '考勤记录' | ||
| 36 | + }, | ||
| 37 | + table: { | ||
| 38 | + face: '人脸', | ||
| 39 | + departmentName: '部门名称', | ||
| 40 | + staffName: '员工名称', | ||
| 41 | + staffId: '员工ID', | ||
| 42 | + clockTime: '考勤时间', | ||
| 43 | + remark: '说明' | ||
| 44 | + }, | ||
| 45 | + fetchError: '获取考勤记录失败' | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | +} | ||
| 0 | \ No newline at end of file | 49 | \ No newline at end of file |
src/views/oa/attendanceLogManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="attendance-log-manage-container"> | ||
| 3 | + <!-- 查询条件 --> | ||
| 4 | + <el-card class="search-wrapper"> | ||
| 5 | + <div slot="header" class="flex justify-between"> | ||
| 6 | + <span>{{ $t('attendanceLogManage.search.title') }}</span> | ||
| 7 | + </div> | ||
| 8 | + <el-row :gutter="20"> | ||
| 9 | + <el-col :span="4"> | ||
| 10 | + <el-input v-model="searchForm.classesName" :placeholder="$t('attendanceLogManage.search.classesName')" | ||
| 11 | + clearable /> | ||
| 12 | + </el-col> | ||
| 13 | + <el-col :span="4"> | ||
| 14 | + <el-input v-model="searchForm.departmentName" :placeholder="$t('attendanceLogManage.search.departmentName')" | ||
| 15 | + clearable /> | ||
| 16 | + </el-col> | ||
| 17 | + <el-col :span="4"> | ||
| 18 | + <el-date-picker v-model="searchForm.date" type="date" :placeholder="$t('attendanceLogManage.search.date')" | ||
| 19 | + value-format="yyyy-MM-dd" style="width: 100%" /> | ||
| 20 | + </el-col> | ||
| 21 | + <el-col :span="4"> | ||
| 22 | + <el-input v-model="searchForm.staffName" :placeholder="$t('attendanceLogManage.search.staffName')" | ||
| 23 | + clearable /> | ||
| 24 | + </el-col> | ||
| 25 | + <el-col :span="4"> | ||
| 26 | + <el-button type="primary" @click="handleSearch">{{ $t('common.search') }}</el-button> | ||
| 27 | + <el-button @click="handleReset">{{ $t('common.reset') }}</el-button> | ||
| 28 | + </el-col> | ||
| 29 | + </el-row> | ||
| 30 | + </el-card> | ||
| 31 | + | ||
| 32 | + <!-- 考勤记录列表 --> | ||
| 33 | + <el-card class="list-wrapper"> | ||
| 34 | + <div slot="header" class="flex justify-between"> | ||
| 35 | + <span>{{ $t('attendanceLogManage.list.title') }}</span> | ||
| 36 | + </div> | ||
| 37 | + <el-table v-loading="loading" :data="tableData" border style="width: 100%"> | ||
| 38 | + <el-table-column :label="$t('attendanceLogManage.table.face')" align="center" width="100"> | ||
| 39 | + <template slot-scope="scope"> | ||
| 40 | + <el-image style="width: 60px; height: 60px; cursor: pointer;" | ||
| 41 | + :src="scope.row.facePath || '/img/noPhoto.jpg'" fit="cover" | ||
| 42 | + @click="handleViewImage(scope.row.facePath)" /> | ||
| 43 | + </template> | ||
| 44 | + </el-table-column> | ||
| 45 | + <el-table-column prop="departmentName" :label="$t('attendanceLogManage.table.departmentName')" align="center" /> | ||
| 46 | + <el-table-column prop="staffName" :label="$t('attendanceLogManage.table.staffName')" align="center" /> | ||
| 47 | + <el-table-column prop="staffId" :label="$t('attendanceLogManage.table.staffId')" align="center" /> | ||
| 48 | + <el-table-column prop="clockTime" :label="$t('attendanceLogManage.table.clockTime')" align="center" /> | ||
| 49 | + <el-table-column prop="remark" :label="$t('attendanceLogManage.table.remark')" align="center" /> | ||
| 50 | + </el-table> | ||
| 51 | + | ||
| 52 | + <el-pagination :current-page.sync="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size" | ||
| 53 | + :total="page.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" | ||
| 54 | + @current-change="handleCurrentChange" /> | ||
| 55 | + </el-card> | ||
| 56 | + | ||
| 57 | + <!-- 图片查看组件 --> | ||
| 58 | + <view-image ref="viewImage" /> | ||
| 59 | + </div> | ||
| 60 | +</template> | ||
| 61 | + | ||
| 62 | +<script> | ||
| 63 | +import { queryAttendanceLog } from '@/api/oa/attendanceLogManageApi' | ||
| 64 | +import ViewImage from '@/components/system/viewImage' | ||
| 65 | + | ||
| 66 | +export default { | ||
| 67 | + name: 'AttendanceLogManageList', | ||
| 68 | + components: { | ||
| 69 | + ViewImage | ||
| 70 | + }, | ||
| 71 | + data() { | ||
| 72 | + return { | ||
| 73 | + loading: false, | ||
| 74 | + searchForm: { | ||
| 75 | + classesName: '', | ||
| 76 | + departmentName: '', | ||
| 77 | + date: new Date().toISOString().split('T')[0], | ||
| 78 | + staffName: '' | ||
| 79 | + }, | ||
| 80 | + tableData: [], | ||
| 81 | + page: { | ||
| 82 | + current: 1, | ||
| 83 | + size: 10, | ||
| 84 | + total: 0 | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + }, | ||
| 88 | + created() { | ||
| 89 | + this.getList() | ||
| 90 | + }, | ||
| 91 | + methods: { | ||
| 92 | + async getList() { | ||
| 93 | + try { | ||
| 94 | + this.loading = true | ||
| 95 | + const params = { | ||
| 96 | + page: this.page.current, | ||
| 97 | + row: this.page.size, | ||
| 98 | + ...this.searchForm | ||
| 99 | + } | ||
| 100 | + const { data, total } = await queryAttendanceLog(params) | ||
| 101 | + this.tableData = data | ||
| 102 | + this.page.total = total | ||
| 103 | + } catch (error) { | ||
| 104 | + this.$message.error(this.$t('attendanceLogManage.fetchError')) | ||
| 105 | + } finally { | ||
| 106 | + this.loading = false | ||
| 107 | + } | ||
| 108 | + }, | ||
| 109 | + handleSearch() { | ||
| 110 | + this.page.current = 1 | ||
| 111 | + this.getList() | ||
| 112 | + }, | ||
| 113 | + handleReset() { | ||
| 114 | + this.searchForm = { | ||
| 115 | + classesName: '', | ||
| 116 | + departmentName: '', | ||
| 117 | + date: new Date().toISOString().split('T')[0], | ||
| 118 | + staffName: '' | ||
| 119 | + } | ||
| 120 | + this.handleSearch() | ||
| 121 | + }, | ||
| 122 | + handleSizeChange(val) { | ||
| 123 | + this.page.size = val | ||
| 124 | + this.getList() | ||
| 125 | + }, | ||
| 126 | + handleCurrentChange(val) { | ||
| 127 | + this.page.current = val | ||
| 128 | + this.getList() | ||
| 129 | + }, | ||
| 130 | + handleViewImage(url) { | ||
| 131 | + if (url) { | ||
| 132 | + this.$refs.viewImage.open(url) | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | +} | ||
| 137 | +</script> | ||
| 138 | + | ||
| 139 | +<style lang="scss" scoped> | ||
| 140 | +.attendance-log-manage-container { | ||
| 141 | + padding: 20px; | ||
| 142 | + | ||
| 143 | + .search-wrapper { | ||
| 144 | + margin-bottom: 20px; | ||
| 145 | + | ||
| 146 | + .el-row { | ||
| 147 | + margin-bottom: -20px; | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + .el-col { | ||
| 151 | + margin-bottom: 20px; | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + .list-wrapper { | ||
| 156 | + .el-pagination { | ||
| 157 | + margin-top: 20px; | ||
| 158 | + text-align: right; | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | +} | ||
| 162 | +</style> | ||
| 0 | \ No newline at end of file | 163 | \ No newline at end of file |
src/views/oa/monthAttendanceManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + monthAttendance: { | ||
| 4 | + search: { | ||
| 5 | + title: 'Search Conditions', | ||
| 6 | + classes: 'Classes', | ||
| 7 | + selectClasses: 'Please select classes', | ||
| 8 | + staffName: 'Staff Name', | ||
| 9 | + inputStaffName: 'Please input staff name', | ||
| 10 | + date: 'Date', | ||
| 11 | + selectDate: 'Please select date' | ||
| 12 | + }, | ||
| 13 | + table: { | ||
| 14 | + title: 'Monthly Attendance', | ||
| 15 | + classes: 'Classes', | ||
| 16 | + staffName: 'Staff Name', | ||
| 17 | + clockIn: 'Normal Attendance', | ||
| 18 | + late: 'Late', | ||
| 19 | + early: 'Leave Early', | ||
| 20 | + noClockIn: 'Absenteeism', | ||
| 21 | + free: 'No Need Attendance' | ||
| 22 | + }, | ||
| 23 | + work: 'Work', | ||
| 24 | + offWork: 'Off Work', | ||
| 25 | + notTime: 'Not time yet', | ||
| 26 | + noNeed: 'No need attendance', | ||
| 27 | + fetchError: 'Failed to fetch attendance data', | ||
| 28 | + fetchClassesError: 'Failed to fetch classes data', | ||
| 29 | + exportSuccess: 'Export successfully', | ||
| 30 | + exportError: 'Failed to export' | ||
| 31 | + } | ||
| 32 | + }, | ||
| 33 | + zh: { | ||
| 34 | + monthAttendance: { | ||
| 35 | + search: { | ||
| 36 | + title: '查询条件', | ||
| 37 | + classes: '班次', | ||
| 38 | + selectClasses: '请选择班次', | ||
| 39 | + staffName: '员工名称', | ||
| 40 | + inputStaffName: '请输入员工名称', | ||
| 41 | + date: '打卡时间', | ||
| 42 | + selectDate: '请选择打卡时间' | ||
| 43 | + }, | ||
| 44 | + table: { | ||
| 45 | + title: '月考勤', | ||
| 46 | + classes: '考勤组', | ||
| 47 | + staffName: '员工名称', | ||
| 48 | + clockIn: '正常考勤', | ||
| 49 | + late: '迟到', | ||
| 50 | + early: '早退', | ||
| 51 | + noClockIn: '旷工', | ||
| 52 | + free: '免考勤' | ||
| 53 | + }, | ||
| 54 | + work: '上班', | ||
| 55 | + offWork: '下班', | ||
| 56 | + notTime: '未到时间', | ||
| 57 | + noNeed: '无需考勤', | ||
| 58 | + fetchError: '获取考勤数据失败', | ||
| 59 | + fetchClassesError: '获取班次数据失败', | ||
| 60 | + exportSuccess: '导出成功', | ||
| 61 | + exportError: '导出失败' | ||
| 62 | + } | ||
| 63 | + } | ||
| 64 | +} | ||
| 0 | \ No newline at end of file | 65 | \ No newline at end of file |
src/views/oa/monthAttendanceManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="month-attendance-container"> | ||
| 3 | + <!-- 查询条件 --> | ||
| 4 | + <el-card class="search-wrapper"> | ||
| 5 | + <div slot="header" class="flex justify-between"> | ||
| 6 | + <span>{{ $t('monthAttendance.search.title') }}</span> | ||
| 7 | + </div> | ||
| 8 | + <el-row :gutter="20"> | ||
| 9 | + <el-col :span="6"> | ||
| 10 | + <el-select v-model="searchForm.classesId" :placeholder="$t('monthAttendance.search.selectClasses')" | ||
| 11 | + style="width:100%"> | ||
| 12 | + <el-option v-for="item in attendanceClasses" :key="item.classesId" :label="item.classesName" | ||
| 13 | + :value="item.classesId" /> | ||
| 14 | + </el-select> | ||
| 15 | + </el-col> | ||
| 16 | + <el-col :span="6"> | ||
| 17 | + <el-input v-model="searchForm.staffName" :placeholder="$t('monthAttendance.search.inputStaffName')" /> | ||
| 18 | + </el-col> | ||
| 19 | + <el-col :span="6"> | ||
| 20 | + <el-date-picker v-model="searchForm.date" type="month" | ||
| 21 | + :placeholder="$t('monthAttendance.search.selectDate')" value-format="yyyy-MM" style="width:100%" | ||
| 22 | + @change="handleDateChange" /> | ||
| 23 | + </el-col> | ||
| 24 | + <el-col :span="6" class="text-right"> | ||
| 25 | + <el-button type="primary" @click="handleSearch">{{ $t('common.search') }}</el-button> | ||
| 26 | + <el-button @click="handleReset">{{ $t('common.reset') }}</el-button> | ||
| 27 | + </el-col> | ||
| 28 | + </el-row> | ||
| 29 | + </el-card> | ||
| 30 | + | ||
| 31 | + <!-- 数据表格 --> | ||
| 32 | + <el-card class="table-wrapper"> | ||
| 33 | + <div slot="header" class="flex justify-between"> | ||
| 34 | + <span>{{ $t('monthAttendance.table.title') }}</span> | ||
| 35 | + <el-button type="primary" size="small" class="float-right" @click="handleExport"> | ||
| 36 | + {{ $t('common.export') }} | ||
| 37 | + </el-button> | ||
| 38 | + </div> | ||
| 39 | + | ||
| 40 | + <div class="table-content" :style="{ width: tableWidth }"> | ||
| 41 | + <el-table :data="tableData" border style="width: 100%" v-loading="loading"> | ||
| 42 | + <el-table-column prop="classesName" :label="$t('monthAttendance.table.classes')" align="center" /> | ||
| 43 | + <el-table-column prop="staffName" :label="$t('monthAttendance.table.staffName')" align="center" /> | ||
| 44 | + <el-table-column v-for="day in curDays" :key="day" :label="`${day}日`" align="center"> | ||
| 45 | + <template slot-scope="scope"> | ||
| 46 | + <span v-html="getDayDetail(scope.row.days, day)"></span> | ||
| 47 | + </template> | ||
| 48 | + </el-table-column> | ||
| 49 | + <el-table-column prop="clockIn" :label="$t('monthAttendance.table.clockIn')" align="center" /> | ||
| 50 | + <el-table-column prop="late" :label="$t('monthAttendance.table.late')" align="center" /> | ||
| 51 | + <el-table-column prop="early" :label="$t('monthAttendance.table.early')" align="center" /> | ||
| 52 | + <el-table-column prop="noClockIn" :label="$t('monthAttendance.table.noClockIn')" align="center" /> | ||
| 53 | + <el-table-column prop="free" :label="$t('monthAttendance.table.free')" align="center" /> | ||
| 54 | + </el-table> | ||
| 55 | + </div> | ||
| 56 | + | ||
| 57 | + <!-- 分页 --> | ||
| 58 | + <el-pagination class="pagination-wrapper" :current-page="pagination.current" :page-sizes="[10, 20, 30, 50]" | ||
| 59 | + :page-size="pagination.size" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" | ||
| 60 | + @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | ||
| 61 | + </el-card> | ||
| 62 | + </div> | ||
| 63 | +</template> | ||
| 64 | + | ||
| 65 | +<script> | ||
| 66 | +import { getMonthAttendance, getAttendanceClasses, exportMonthAttendance } from '@/api/oa/monthAttendanceManageApi' | ||
| 67 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 68 | + | ||
| 69 | +export default { | ||
| 70 | + name: 'MonthAttendanceManageList', | ||
| 71 | + data() { | ||
| 72 | + return { | ||
| 73 | + loading: false, | ||
| 74 | + searchForm: { | ||
| 75 | + classesId: '', | ||
| 76 | + staffName: '', | ||
| 77 | + date: '', | ||
| 78 | + taskYear: '', | ||
| 79 | + taskMonth: '' | ||
| 80 | + }, | ||
| 81 | + tableData: [], | ||
| 82 | + attendanceClasses: [], | ||
| 83 | + curDays: 30, | ||
| 84 | + pagination: { | ||
| 85 | + current: 1, | ||
| 86 | + size: 10, | ||
| 87 | + total: 0 | ||
| 88 | + }, | ||
| 89 | + communityId: '' | ||
| 90 | + } | ||
| 91 | + }, | ||
| 92 | + computed: { | ||
| 93 | + tableWidth() { | ||
| 94 | + const bodyWidth = document.body.clientWidth | ||
| 95 | + const menuWidth = document.getElementById('menu-nav').offsetWidth || 0 | ||
| 96 | + return `${bodyWidth - menuWidth - 55}px` | ||
| 97 | + } | ||
| 98 | + }, | ||
| 99 | + created() { | ||
| 100 | + this.communityId = getCommunityId() | ||
| 101 | + this.initData() | ||
| 102 | + }, | ||
| 103 | + methods: { | ||
| 104 | + async initData() { | ||
| 105 | + await this.getAttendanceClasses() | ||
| 106 | + this.handleDateChange() | ||
| 107 | + this.getList() | ||
| 108 | + }, | ||
| 109 | + | ||
| 110 | + async getList() { | ||
| 111 | + try { | ||
| 112 | + this.loading = true | ||
| 113 | + const params = { | ||
| 114 | + page: this.pagination.current, | ||
| 115 | + row: this.pagination.size, | ||
| 116 | + ...this.searchForm, | ||
| 117 | + communityId: this.communityId | ||
| 118 | + } | ||
| 119 | + const { data, total } = await getMonthAttendance(params) | ||
| 120 | + this.tableData = data | ||
| 121 | + this.pagination.total = total | ||
| 122 | + } catch (error) { | ||
| 123 | + this.$message.error(this.$t('monthAttendance.fetchError')) | ||
| 124 | + } finally { | ||
| 125 | + this.loading = false | ||
| 126 | + } | ||
| 127 | + }, | ||
| 128 | + | ||
| 129 | + async getAttendanceClasses() { | ||
| 130 | + try { | ||
| 131 | + const params = { | ||
| 132 | + page: 1, | ||
| 133 | + row: 100 | ||
| 134 | + } | ||
| 135 | + const { data } = await getAttendanceClasses(params) | ||
| 136 | + this.attendanceClasses = data | ||
| 137 | + if (data.length > 0) { | ||
| 138 | + this.searchForm.classesId = data[0].classesId | ||
| 139 | + } | ||
| 140 | + } catch (error) { | ||
| 141 | + this.$message.error(this.$t('monthAttendance.fetchClassesError')) | ||
| 142 | + } | ||
| 143 | + }, | ||
| 144 | + | ||
| 145 | + handleDateChange() { | ||
| 146 | + if (this.searchForm.date) { | ||
| 147 | + const [year, month] = this.searchForm.date.split('-') | ||
| 148 | + this.searchForm.taskYear = year | ||
| 149 | + this.searchForm.taskMonth = month | ||
| 150 | + this.curDays = this.daysInMonth(year, month - 1) | ||
| 151 | + } else { | ||
| 152 | + const date = new Date() | ||
| 153 | + this.searchForm.taskYear = date.getFullYear() | ||
| 154 | + this.searchForm.taskMonth = date.getMonth() + 1 | ||
| 155 | + this.searchForm.date = `${date.getFullYear()}-${date.getMonth() + 1}` | ||
| 156 | + this.curDays = this.daysInMonth(date.getFullYear(), date.getMonth()) | ||
| 157 | + } | ||
| 158 | + }, | ||
| 159 | + | ||
| 160 | + daysInMonth(year, month) { | ||
| 161 | + return new Date(year, month + 1, 0).getDate() | ||
| 162 | + }, | ||
| 163 | + | ||
| 164 | + getDayDetail(detail, day) { | ||
| 165 | + const currentDate = new Date() | ||
| 166 | + if ( | ||
| 167 | + this.searchForm.taskYear == currentDate.getFullYear() && | ||
| 168 | + this.searchForm.taskMonth == (currentDate.getMonth() + 1) && | ||
| 169 | + day > currentDate.getDate() | ||
| 170 | + ) { | ||
| 171 | + return this.$t('monthAttendance.notTime') | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + if (!detail || !Object.prototype.hasOwnProperty.call(detail, day)) { | ||
| 175 | + return this.$t('monthAttendance.noNeed') | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + const details = detail[day] | ||
| 179 | + if (!details) { | ||
| 180 | + return this.$t('monthAttendance.noNeed') | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + let value = '' | ||
| 184 | + details.forEach(element => { | ||
| 185 | + if (element.specCd === '1001') { | ||
| 186 | + value += `<div>${this.$t('monthAttendance.work')}:` | ||
| 187 | + } else { | ||
| 188 | + value += `<div>${this.$t('monthAttendance.offWork')}:` | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + if (element.state !== '10000') { | ||
| 192 | + value += `${this.timeFormat(element.checkTime)}(${element.stateName})</div>` | ||
| 193 | + } else { | ||
| 194 | + value += `-(${element.stateName})</div>` | ||
| 195 | + } | ||
| 196 | + }) | ||
| 197 | + return value | ||
| 198 | + }, | ||
| 199 | + | ||
| 200 | + timeFormat(time) { | ||
| 201 | + if (!time) return '' | ||
| 202 | + return new Date(time).toLocaleTimeString() | ||
| 203 | + }, | ||
| 204 | + | ||
| 205 | + handleSearch() { | ||
| 206 | + this.pagination.current = 1 | ||
| 207 | + this.getList() | ||
| 208 | + }, | ||
| 209 | + | ||
| 210 | + handleReset() { | ||
| 211 | + this.searchForm = { | ||
| 212 | + classesId: this.attendanceClasses.length > 0 ? this.attendanceClasses[0].classesId : '', | ||
| 213 | + staffName: '', | ||
| 214 | + date: '', | ||
| 215 | + taskYear: '', | ||
| 216 | + taskMonth: '' | ||
| 217 | + } | ||
| 218 | + this.handleDateChange() | ||
| 219 | + this.handleSearch() | ||
| 220 | + }, | ||
| 221 | + | ||
| 222 | + async handleExport() { | ||
| 223 | + try { | ||
| 224 | + this.loading = true | ||
| 225 | + const params = { | ||
| 226 | + ...this.searchForm, | ||
| 227 | + communityId: this.communityId, | ||
| 228 | + pagePath: 'monthAttendance' | ||
| 229 | + } | ||
| 230 | + await exportMonthAttendance(params) | ||
| 231 | + this.$message.success(this.$t('monthAttendance.exportSuccess')) | ||
| 232 | + } catch (error) { | ||
| 233 | + this.$message.error(this.$t('monthAttendance.exportError')) | ||
| 234 | + } finally { | ||
| 235 | + this.loading = false | ||
| 236 | + } | ||
| 237 | + }, | ||
| 238 | + | ||
| 239 | + handleSizeChange(val) { | ||
| 240 | + this.pagination.size = val | ||
| 241 | + this.getList() | ||
| 242 | + }, | ||
| 243 | + | ||
| 244 | + handleCurrentChange(val) { | ||
| 245 | + this.pagination.current = val | ||
| 246 | + this.getList() | ||
| 247 | + } | ||
| 248 | + } | ||
| 249 | +} | ||
| 250 | +</script> | ||
| 251 | + | ||
| 252 | +<style lang="scss" scoped> | ||
| 253 | +.month-attendance-container { | ||
| 254 | + padding: 20px; | ||
| 255 | + | ||
| 256 | + .search-wrapper { | ||
| 257 | + margin-bottom: 20px; | ||
| 258 | + | ||
| 259 | + .el-form-item { | ||
| 260 | + margin-bottom: 0; | ||
| 261 | + } | ||
| 262 | + } | ||
| 263 | + | ||
| 264 | + .table-wrapper { | ||
| 265 | + .table-content { | ||
| 266 | + overflow-x: auto; | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + .pagination-wrapper { | ||
| 270 | + margin-top: 20px; | ||
| 271 | + text-align: right; | ||
| 272 | + } | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + .text-right { | ||
| 276 | + text-align: right; | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + .float-right { | ||
| 280 | + float: right; | ||
| 281 | + } | ||
| 282 | +} | ||
| 283 | +</style> | ||
| 0 | \ No newline at end of file | 284 | \ No newline at end of file |
src/views/oa/newOaWorkflowDoingLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + newOaWorkflowDoing: { | ||
| 4 | + myPending: 'My Pending', | ||
| 5 | + myCompleted: 'My Completed', | ||
| 6 | + refresh: 'Refresh', | ||
| 7 | + repairPending: 'Repair Pending', | ||
| 8 | + complaintPending: 'Complaint Pending', | ||
| 9 | + purchasePending: 'Purchase Pending', | ||
| 10 | + collectionPending: 'Collection Pending', | ||
| 11 | + contractApplyPending: 'Contract Draft Pending', | ||
| 12 | + contractChangePending: 'Contract Change Pending', | ||
| 13 | + allocationPending: 'Allocation Pending', | ||
| 14 | + itemReleasePending: 'Item Release Pending', | ||
| 15 | + visitPending: 'Visit Pending', | ||
| 16 | + repairCompleted: 'Repair Completed', | ||
| 17 | + complaintCompleted: 'Complaint Completed', | ||
| 18 | + purchaseCompleted: 'Purchase Completed', | ||
| 19 | + collectionCompleted: 'Collection Completed', | ||
| 20 | + contractApplyCompleted: 'Contract Draft Completed', | ||
| 21 | + contractChangeCompleted: 'Contract Change Completed', | ||
| 22 | + allocationCompleted: 'Allocation Completed', | ||
| 23 | + itemReleaseCompleted: 'Item Release Completed', | ||
| 24 | + visitCompleted: 'Visit Completed', | ||
| 25 | + pending: 'Pending', | ||
| 26 | + completed: 'Completed' | ||
| 27 | + } | ||
| 28 | + }, | ||
| 29 | + zh: { | ||
| 30 | + newOaWorkflowDoing: { | ||
| 31 | + myPending: '我的待办', | ||
| 32 | + myCompleted: '我的已办', | ||
| 33 | + refresh: '刷新', | ||
| 34 | + repairPending: '报修待办', | ||
| 35 | + complaintPending: '投诉待办', | ||
| 36 | + purchasePending: '采购待办', | ||
| 37 | + collectionPending: '物品领用待办', | ||
| 38 | + contractApplyPending: '合同起草待办', | ||
| 39 | + contractChangePending: '合同变更待办', | ||
| 40 | + allocationPending: '调拨待办', | ||
| 41 | + itemReleasePending: '物品放行待办', | ||
| 42 | + visitPending: '访客待办', | ||
| 43 | + repairCompleted: '报修已办', | ||
| 44 | + complaintCompleted: '投诉已办', | ||
| 45 | + purchaseCompleted: '采购已办', | ||
| 46 | + collectionCompleted: '物品领用已办', | ||
| 47 | + contractApplyCompleted: '合同起草已办', | ||
| 48 | + contractChangeCompleted: '合同变更已办', | ||
| 49 | + allocationCompleted: '调拨已办', | ||
| 50 | + itemReleaseCompleted: '物品放行已办', | ||
| 51 | + visitCompleted: '访客已办', | ||
| 52 | + pending: '待办', | ||
| 53 | + completed: '已办' | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | +} | ||
| 0 | \ No newline at end of file | 57 | \ No newline at end of file |
src/views/oa/newOaWorkflowDoingList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="new-oa-workflow-doing-container"> | ||
| 3 | + <el-card class="box-card"> | ||
| 4 | + <div slot="header" class="flex justify-between "> | ||
| 5 | + <span>{{ $t('newOaWorkflowDoing.myPending') }}</span> | ||
| 6 | + <div class="header-tools"> | ||
| 7 | + <el-button type="primary" size="small" @click="_queryUndoOrderMethod"> | ||
| 8 | + <i class="el-icon-refresh"></i> | ||
| 9 | + {{ $t('newOaWorkflowDoing.refresh') }} | ||
| 10 | + </el-button> | ||
| 11 | + </div> | ||
| 12 | + </div> | ||
| 13 | + <div class="card-content"> | ||
| 14 | + <el-row :gutter="20"> | ||
| 15 | + <el-col :span="4" v-for="(item, index) in pendingItems" :key="'pending-' + index"> | ||
| 16 | + <div class="item-wrapper text-center"> | ||
| 17 | + <el-link type="primary" :underline="true" @click="_toGo(item.url)"> | ||
| 18 | + <span>{{ $t(`newOaWorkflowDoing.${item.key}`) }}</span> | ||
| 19 | + <span :class="{ 'red-text': item.count > 0 }">({{ item.count }})</span> | ||
| 20 | + </el-link> | ||
| 21 | + </div> | ||
| 22 | + </el-col> | ||
| 23 | + <el-col :span="4" v-for="(item, index) in newOaWorkflowDoingInfo.newOaWorkflows" :key="'workflow-' + index"> | ||
| 24 | + <div class="item-wrapper text-center"> | ||
| 25 | + <el-link type="primary" :underline="true" @click="_toGoA(item)"> | ||
| 26 | + {{ item.flowName }} | ||
| 27 | + <span>{{ $t('newOaWorkflowDoing.pending') }}</span> | ||
| 28 | + <span :class="{ 'red-text': item.undoCount > 0 }">({{ item.undoCount }})</span> | ||
| 29 | + </el-link> | ||
| 30 | + </div> | ||
| 31 | + </el-col> | ||
| 32 | + </el-row> | ||
| 33 | + </div> | ||
| 34 | + </el-card> | ||
| 35 | + | ||
| 36 | + <el-card class="box-card margin-top"> | ||
| 37 | + <div slot="header" class="flex justify-between"> | ||
| 38 | + <span>{{ $t('newOaWorkflowDoing.myCompleted') }}</span> | ||
| 39 | + </div> | ||
| 40 | + <div class="card-content"> | ||
| 41 | + <el-row :gutter="20"> | ||
| 42 | + <el-col :span="4" v-for="(item, index) in completedItems" :key="'completed-' + index"> | ||
| 43 | + <div class="item-wrapper text-center"> | ||
| 44 | + <el-link type="primary" :underline="true" @click="_toGo(item.url)"> | ||
| 45 | + <span>{{ $t(`newOaWorkflowDoing.${item.key}`) }}</span> | ||
| 46 | + </el-link> | ||
| 47 | + </div> | ||
| 48 | + </el-col> | ||
| 49 | + <el-col :span="4" v-for="(item, index) in newOaWorkflowDoingInfo.newOaWorkflows" | ||
| 50 | + :key="'workflow-completed-' + index"> | ||
| 51 | + <div class="item-wrapper text-center"> | ||
| 52 | + <el-link type="primary" :underline="true" @click="_toGoB(item)"> | ||
| 53 | + {{ item.flowName }} | ||
| 54 | + <span>{{ $t('newOaWorkflowDoing.completed') }}</span> | ||
| 55 | + </el-link> | ||
| 56 | + </div> | ||
| 57 | + </el-col> | ||
| 58 | + </el-row> | ||
| 59 | + </div> | ||
| 60 | + </el-card> | ||
| 61 | + </div> | ||
| 62 | +</template> | ||
| 63 | + | ||
| 64 | +<script> | ||
| 65 | +import { getUndoInfo, listNewOaWorkflows } from '@/api/oa/newOaWorkflowDoingApi' | ||
| 66 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 67 | + | ||
| 68 | +export default { | ||
| 69 | + name: 'NewOaWorkflowDoingList', | ||
| 70 | + data() { | ||
| 71 | + return { | ||
| 72 | + newOaWorkflowDoingInfo: { | ||
| 73 | + newOaWorkflows: [], | ||
| 74 | + repair: 0, | ||
| 75 | + complaint: 0, | ||
| 76 | + purchase: 0, | ||
| 77 | + collection: 0, | ||
| 78 | + contractApply: 0, | ||
| 79 | + contractChange: 0, | ||
| 80 | + allocation: 0, | ||
| 81 | + itemReleaseCount: 0, | ||
| 82 | + visitUndoCount: 0, | ||
| 83 | + ownerSettledApplyCount: 0 | ||
| 84 | + }, | ||
| 85 | + pendingItems: [ | ||
| 86 | + { key: 'repairPending', url: '/#/pages/property/repairDispatchManage', count: 0 }, | ||
| 87 | + { key: 'complaintPending', url: '/#/pages/complaint/uodoComplaints', count: 0 }, | ||
| 88 | + { key: 'purchasePending', url: '/#/pages/admin/myAuditOrders', count: 0 }, | ||
| 89 | + { key: 'collectionPending', url: '/#/pages/admin/myItemOutAuditOrders', count: 0 }, | ||
| 90 | + { key: 'contractApplyPending', url: '/#/pages/admin/contractApplyAuditOrders', count: 0 }, | ||
| 91 | + { key: 'contractChangePending', url: '/#/pages/admin/contractChangeAuditOrders', count: 0 }, | ||
| 92 | + { key: 'allocationPending', url: '/#/pages/admin/allocationStorehouseAuditOrders', count: 0 }, | ||
| 93 | + { key: 'itemReleasePending', url: '/#/pages/property/itemReleaseUndo', count: 0 }, | ||
| 94 | + { key: 'visitPending', url: '/#/pages/property/visitUndo', count: 0 } | ||
| 95 | + ], | ||
| 96 | + completedItems: [ | ||
| 97 | + { key: 'repairCompleted', url: '/#/pages/property/myRepairDispatchManage' }, | ||
| 98 | + { key: 'complaintCompleted', url: '/#/pages/complaint/doHistoryComplaints' }, | ||
| 99 | + { key: 'purchaseCompleted', url: '/#/pages/admin/myAuditHistoryOrders' }, | ||
| 100 | + { key: 'collectionCompleted', url: '/#/pages/admin/myItemOutAuditHistoryOrders' }, | ||
| 101 | + { key: 'contractApplyCompleted', url: '/#/pages/admin/contractApplyAuditHistoryOrders' }, | ||
| 102 | + { key: 'contractChangeCompleted', url: '/#/pages/admin/contractChangeAuditHistoryOrders' }, | ||
| 103 | + { key: 'allocationCompleted', url: '/#/pages/admin/allocationStorehouseHistoryAuditOrders' }, | ||
| 104 | + { key: 'itemReleaseCompleted', url: '/#/pages/property/itemReleaseFinish' }, | ||
| 105 | + { key: 'visitCompleted', url: '/#/pages/property/visitFinish' } | ||
| 106 | + ], | ||
| 107 | + communityId: '' | ||
| 108 | + } | ||
| 109 | + }, | ||
| 110 | + created() { | ||
| 111 | + this.communityId = getCommunityId() | ||
| 112 | + this._initMethod() | ||
| 113 | + }, | ||
| 114 | + methods: { | ||
| 115 | + _initMethod() { | ||
| 116 | + this._listNewOaWorkflows() | ||
| 117 | + this._loadUndoInfo() | ||
| 118 | + }, | ||
| 119 | + _toGo(url) { | ||
| 120 | + this.$router.push(url) | ||
| 121 | + }, | ||
| 122 | + _toGoA(item) { | ||
| 123 | + this.$router.push(`/form.html#/pages/property/newOaWorkflow?flowId=${item.flowId}&switchValue=newOaWorkflowUndo`) | ||
| 124 | + }, | ||
| 125 | + _toGoB(item) { | ||
| 126 | + this.$router.push(`/form.html#/pages/property/newOaWorkflow?flowId=${item.flowId}&switchValue=newOaWorkflowFinish`) | ||
| 127 | + }, | ||
| 128 | + async _listNewOaWorkflows() { | ||
| 129 | + try { | ||
| 130 | + const params = { | ||
| 131 | + state: 'C', | ||
| 132 | + page: 1, | ||
| 133 | + row: 100, | ||
| 134 | + flowType: '1001' | ||
| 135 | + } | ||
| 136 | + const { data } = await listNewOaWorkflows(params) | ||
| 137 | + this.newOaWorkflowDoingInfo.newOaWorkflows = data | ||
| 138 | + } catch (error) { | ||
| 139 | + console.error('Failed to fetch workflows:', error) | ||
| 140 | + } | ||
| 141 | + }, | ||
| 142 | + async _loadUndoInfo() { | ||
| 143 | + try { | ||
| 144 | + const params = { | ||
| 145 | + communityId: this.communityId, | ||
| 146 | + page: 1, | ||
| 147 | + row: 10 | ||
| 148 | + } | ||
| 149 | + const data = await getUndoInfo(params) | ||
| 150 | + Object.assign(this.newOaWorkflowDoingInfo, data) | ||
| 151 | + | ||
| 152 | + // Update counts in pendingItems | ||
| 153 | + this.pendingItems[0].count = this.newOaWorkflowDoingInfo.repair | ||
| 154 | + this.pendingItems[1].count = this.newOaWorkflowDoingInfo.complaint | ||
| 155 | + this.pendingItems[2].count = this.newOaWorkflowDoingInfo.purchase | ||
| 156 | + this.pendingItems[3].count = this.newOaWorkflowDoingInfo.collection | ||
| 157 | + this.pendingItems[4].count = this.newOaWorkflowDoingInfo.contractApply | ||
| 158 | + this.pendingItems[5].count = this.newOaWorkflowDoingInfo.contractChange | ||
| 159 | + this.pendingItems[6].count = this.newOaWorkflowDoingInfo.allocation | ||
| 160 | + this.pendingItems[7].count = this.newOaWorkflowDoingInfo.itemReleaseCount | ||
| 161 | + this.pendingItems[8].count = this.newOaWorkflowDoingInfo.visitUndoCount | ||
| 162 | + } catch (error) { | ||
| 163 | + console.error('Failed to fetch undo info:', error) | ||
| 164 | + } | ||
| 165 | + }, | ||
| 166 | + _queryUndoOrderMethod() { | ||
| 167 | + this._listNewOaWorkflows() | ||
| 168 | + this._loadUndoInfo() | ||
| 169 | + } | ||
| 170 | + } | ||
| 171 | +} | ||
| 172 | +</script> | ||
| 173 | + | ||
| 174 | +<style lang="scss" scoped> | ||
| 175 | +.new-oa-workflow-doing-container { | ||
| 176 | + padding: 20px; | ||
| 177 | + | ||
| 178 | + .box-card { | ||
| 179 | + margin-bottom: 20px; | ||
| 180 | + | ||
| 181 | + .header-tools { | ||
| 182 | + float: right; | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + .card-content { | ||
| 186 | + padding: 10px; | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + .item-wrapper { | ||
| 191 | + padding: 10px 0; | ||
| 192 | + | ||
| 193 | + .red-text { | ||
| 194 | + color: #F56C6C; | ||
| 195 | + } | ||
| 196 | + } | ||
| 197 | + | ||
| 198 | + .margin-top { | ||
| 199 | + margin-top: 20px; | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + .text-center { | ||
| 203 | + text-align: center; | ||
| 204 | + } | ||
| 205 | +} | ||
| 206 | +</style> | ||
| 0 | \ No newline at end of file | 207 | \ No newline at end of file |
src/views/oa/newOaWorkflowManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + newOaWorkflowManage: { | ||
| 4 | + noPublishedFlow: 'You have not published any workflows, please publish them in the workflow instances first', | ||
| 5 | + flowNotDeployed: 'Workflow is not deployed' | ||
| 6 | + } | ||
| 7 | + }, | ||
| 8 | + zh: { | ||
| 9 | + newOaWorkflowManage: { | ||
| 10 | + noPublishedFlow: '您还没有发布流程,请先到流程实例中发布流程', | ||
| 11 | + flowNotDeployed: '流程未部署' | ||
| 12 | + } | ||
| 13 | + } | ||
| 14 | +} | ||
| 0 | \ No newline at end of file | 15 | \ No newline at end of file |
src/views/oa/newOaWorkflowManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="new-oa-workflow-manage-container"> | ||
| 3 | + <el-card class="box-card"> | ||
| 4 | + <div v-if="newOaWorkflowManageInfo.newOaWorkflows && newOaWorkflowManageInfo.newOaWorkflows.length > 0"> | ||
| 5 | + <el-row :gutter="20"> | ||
| 6 | + <el-col | ||
| 7 | + v-for="(item, index) in newOaWorkflowManageInfo.newOaWorkflows" | ||
| 8 | + :key="index" | ||
| 9 | + :span="4" | ||
| 10 | + class="text-center workflow-item" | ||
| 11 | + @click.native="newFlow(item)" | ||
| 12 | + > | ||
| 13 | + <div> | ||
| 14 | + <img src="/img/flow.png" width="80px" /> | ||
| 15 | + </div> | ||
| 16 | + <div class="margin-top"> | ||
| 17 | + <span style="color: #333333;">{{ item.flowName }}</span> | ||
| 18 | + </div> | ||
| 19 | + </el-col> | ||
| 20 | + </el-row> | ||
| 21 | + </div> | ||
| 22 | + <div v-else class="empty-tip"> | ||
| 23 | + <span>{{ $t('newOaWorkflowManage.noPublishedFlow') }}</span> | ||
| 24 | + </div> | ||
| 25 | + </el-card> | ||
| 26 | + </div> | ||
| 27 | +</template> | ||
| 28 | + | ||
| 29 | +<script> | ||
| 30 | +import { queryOaWorkflow } from '@/api/oa/newOaWorkflowManageApi' | ||
| 31 | + | ||
| 32 | +export default { | ||
| 33 | + name: 'NewOaWorkflowManageList', | ||
| 34 | + data() { | ||
| 35 | + return { | ||
| 36 | + newOaWorkflowManageInfo: { | ||
| 37 | + newOaWorkflows: [], | ||
| 38 | + conditions: { | ||
| 39 | + state: 'C', | ||
| 40 | + flowType: '1001', | ||
| 41 | + page: 1, | ||
| 42 | + row: 100 | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + }, | ||
| 47 | + created() { | ||
| 48 | + this._listNewOaWorkflows() | ||
| 49 | + }, | ||
| 50 | + methods: { | ||
| 51 | + async _listNewOaWorkflows() { | ||
| 52 | + try { | ||
| 53 | + const { data } = await queryOaWorkflow(this.newOaWorkflowManageInfo.conditions) | ||
| 54 | + this.newOaWorkflowManageInfo.newOaWorkflows = data | ||
| 55 | + } catch (error) { | ||
| 56 | + console.error('Failed to fetch workflows:', error) | ||
| 57 | + } | ||
| 58 | + }, | ||
| 59 | + async newFlow(flow) { | ||
| 60 | + try { | ||
| 61 | + const { data } = await queryOaWorkflow({ | ||
| 62 | + page: 1, | ||
| 63 | + row: 1, | ||
| 64 | + flowId: flow.flowId, | ||
| 65 | + state: 'C' | ||
| 66 | + }) | ||
| 67 | + | ||
| 68 | + if (data.length < 1) { | ||
| 69 | + this.$message.error(this.$t('newOaWorkflowManage.flowNotDeployed')) | ||
| 70 | + return | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + this.$router.push({ | ||
| 74 | + path: '/form.html#/pages/property/newOaWorkflow', | ||
| 75 | + query: { flowId: flow.flowId } | ||
| 76 | + }) | ||
| 77 | + } catch (error) { | ||
| 78 | + console.error('Failed to check flow deployment:', error) | ||
| 79 | + } | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | +} | ||
| 83 | +</script> | ||
| 84 | + | ||
| 85 | +<style lang="scss" scoped> | ||
| 86 | +.new-oa-workflow-manage-container { | ||
| 87 | + padding: 20px; | ||
| 88 | + | ||
| 89 | + .box-card { | ||
| 90 | + min-height: 400px; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + .workflow-item { | ||
| 94 | + margin-bottom: 20px; | ||
| 95 | + cursor: pointer; | ||
| 96 | + transition: all 0.3s; | ||
| 97 | + | ||
| 98 | + &:hover { | ||
| 99 | + transform: translateY(-5px); | ||
| 100 | + } | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + .empty-tip { | ||
| 104 | + padding: 20px; | ||
| 105 | + text-align: center; | ||
| 106 | + color: #909399; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + .margin-top { | ||
| 110 | + margin-top: 10px; | ||
| 111 | + } | ||
| 112 | +} | ||
| 113 | +</style> | ||
| 0 | \ No newline at end of file | 114 | \ No newline at end of file |
src/views/oa/staffAttendanceManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + staffAttendance: { | ||
| 4 | + monthPlaceholder: 'Select month', | ||
| 5 | + orgPlaceholder: 'Select organization', | ||
| 6 | + staffPlaceholder: 'Staff name', | ||
| 7 | + replenish: 'Replenish', | ||
| 8 | + work: 'Work', | ||
| 9 | + offWork: 'Off work', | ||
| 10 | + log: 'Attendance log', | ||
| 11 | + notTime: 'Not time yet', | ||
| 12 | + noNeed: 'No need', | ||
| 13 | + noTask: 'No replenish task', | ||
| 14 | + chooseOrg: 'Select Organization', | ||
| 15 | + loadOrgFailed: 'Failed to load organizations', | ||
| 16 | + selectOrgFirst: 'Please select an organization first', | ||
| 17 | + attendanceDetail: 'Attendance Details', | ||
| 18 | + staffName: 'Staff Name', | ||
| 19 | + face: 'Face', | ||
| 20 | + clockTime: 'Clock Time', | ||
| 21 | + loadDetailFailed: 'Failed to load attendance details', | ||
| 22 | + replenishCheckIn: 'Replenish Attendance', | ||
| 23 | + attendanceTask: 'Attendance Task', | ||
| 24 | + selectTask: 'Please select task', | ||
| 25 | + selectTaskFirst: 'Please select a task first', | ||
| 26 | + reason: 'Reason', | ||
| 27 | + inputReason: 'Please input reason', | ||
| 28 | + inputReasonFirst: 'Please input reason first', | ||
| 29 | + submitReplenish: 'Submit Replenish', | ||
| 30 | + replenishSuccess: 'Replenish successfully', | ||
| 31 | + replenishFailed: 'Replenish failed' | ||
| 32 | + } | ||
| 33 | + }, | ||
| 34 | + zh: { | ||
| 35 | + staffAttendance: { | ||
| 36 | + monthPlaceholder: '请选择月份', | ||
| 37 | + orgPlaceholder: '请选择组织', | ||
| 38 | + staffPlaceholder: '员工名称', | ||
| 39 | + replenish: '补考勤', | ||
| 40 | + work: '上班', | ||
| 41 | + offWork: '下班', | ||
| 42 | + log: '考勤记录', | ||
| 43 | + notTime: '未到时间', | ||
| 44 | + noNeed: '无需考勤', | ||
| 45 | + noTask: '不存在补考勤任务', | ||
| 46 | + chooseOrg: '选择组织', | ||
| 47 | + loadOrgFailed: '加载组织失败', | ||
| 48 | + selectOrgFirst: '请先选择组织', | ||
| 49 | + attendanceDetail: '考勤明细', | ||
| 50 | + staffName: '员工名称', | ||
| 51 | + face: '人脸', | ||
| 52 | + clockTime: '考勤时间', | ||
| 53 | + loadDetailFailed: '加载考勤明细失败', | ||
| 54 | + replenishCheckIn: '补考勤', | ||
| 55 | + attendanceTask: '考勤任务', | ||
| 56 | + selectTask: '请选择考勤任务', | ||
| 57 | + selectTaskFirst: '请先选择考勤任务', | ||
| 58 | + reason: '原因', | ||
| 59 | + inputReason: '请填写原因', | ||
| 60 | + inputReasonFirst: '请先填写原因', | ||
| 61 | + submitReplenish: '提交补考勤', | ||
| 62 | + replenishSuccess: '补考勤成功', | ||
| 63 | + replenishFailed: '补考勤失败' | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | +} | ||
| 0 | \ No newline at end of file | 67 | \ No newline at end of file |
src/views/oa/staffAttendanceManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="staff-attendance-manage-container"> | ||
| 3 | + <el-row :gutter="20"> | ||
| 4 | + <el-col :span="4"> | ||
| 5 | + <el-card class="box-card"> | ||
| 6 | + <div class="filter-wrapper"> | ||
| 7 | + <el-date-picker v-model="staffAttendanceManageInfo.curDate" type="month" | ||
| 8 | + :placeholder="$t('staffAttendance.monthPlaceholder')" value-format="yyyy-MM" class="filter-item" | ||
| 9 | + @change="handleDateChange"></el-date-picker> | ||
| 10 | + | ||
| 11 | + <el-input v-model="staffAttendanceManageInfo.orgName" readonly | ||
| 12 | + :placeholder="$t('staffAttendance.orgPlaceholder')" class="filter-item" | ||
| 13 | + @focus="handleOrgFocus"></el-input> | ||
| 14 | + | ||
| 15 | + <el-input v-model="staffAttendanceManageInfo.staffNameLike" | ||
| 16 | + :placeholder="$t('staffAttendance.staffPlaceholder')" class="filter-item" | ||
| 17 | + @keyup.enter.native="loadStaffs"></el-input> | ||
| 18 | + | ||
| 19 | + <el-button type="primary" class="filter-item" @click="loadStaffs"> | ||
| 20 | + {{ $t('common.search') }} | ||
| 21 | + </el-button> | ||
| 22 | + </div> | ||
| 23 | + | ||
| 24 | + <div class="staff-list"> | ||
| 25 | + <ul class="staff-ul"> | ||
| 26 | + <li v-for="(item, index) in staffAttendanceManageInfo.staffs" :key="index" class="staff-item" | ||
| 27 | + :class="{ 'active': staffAttendanceManageInfo.curStaffId === item.userId }" @click="switchStaff(item)"> | ||
| 28 | + {{ item.name }} | ||
| 29 | + </li> | ||
| 30 | + </ul> | ||
| 31 | + </div> | ||
| 32 | + </el-card> | ||
| 33 | + </el-col> | ||
| 34 | + | ||
| 35 | + <el-col :span="20"> | ||
| 36 | + <el-card class="box-card"> | ||
| 37 | + <div class="attendance-grid"> | ||
| 38 | + <el-row :gutter="20"> | ||
| 39 | + <el-col v-for="index in staffAttendanceManageInfo.maxDay" :key="index" :span="4" class="attendance-day" | ||
| 40 | + :style="{ backgroundColor: getBgColor(index) }"> | ||
| 41 | + <div class="day-header"> | ||
| 42 | + {{ staffAttendanceManageInfo.curYear }}-{{ staffAttendanceManageInfo.curMonth }}-{{ index }} | ||
| 43 | + </div> | ||
| 44 | + | ||
| 45 | + <div v-if="getDayAttendance(index) && getDayAttendance(index).state !== '30000'" class="replenish-btn" | ||
| 46 | + @click="replenishCheckIn(index)"> | ||
| 47 | + {{ $t('staffAttendance.replenish') }} | ||
| 48 | + </div> | ||
| 49 | + | ||
| 50 | + <div v-for="(item, detailIndex) in getAttendanceDetail(index)" :key="detailIndex" | ||
| 51 | + class="attendance-detail"> | ||
| 52 | + <div v-if="item.rest">{{ item.rest }}</div> | ||
| 53 | + <div v-else> | ||
| 54 | + {{ item.specCd === '1001' ? $t('staffAttendance.work') : $t('staffAttendance.offWork') }}: | ||
| 55 | + <span v-if="item.state !== '10000'">{{ formatTime(item.checkTime) }}</span> | ||
| 56 | + <span v-else> - </span> | ||
| 57 | + <span>({{ item.stateName }})</span> | ||
| 58 | + </div> | ||
| 59 | + </div> | ||
| 60 | + | ||
| 61 | + <div class="log-link"> | ||
| 62 | + <el-link type="primary" @click="checkInLog(index)"> | ||
| 63 | + {{ $t('staffAttendance.log') }} | ||
| 64 | + </el-link> | ||
| 65 | + </div> | ||
| 66 | + </el-col> | ||
| 67 | + </el-row> | ||
| 68 | + </div> | ||
| 69 | + </el-card> | ||
| 70 | + </el-col> | ||
| 71 | + </el-row> | ||
| 72 | + | ||
| 73 | + <!-- 子组件 --> | ||
| 74 | + <choose-org-tree ref="chooseOrgTree" @switchOrg="handleSwitchOrg"></choose-org-tree> | ||
| 75 | + <staff-attendance-detail ref="staffAttendanceDetail"></staff-attendance-detail> | ||
| 76 | + <staff-attendance-replenish-check-in ref="staffAttendanceReplenishCheckIn"></staff-attendance-replenish-check-in> | ||
| 77 | + </div> | ||
| 78 | +</template> | ||
| 79 | + | ||
| 80 | +<script> | ||
| 81 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 82 | +import { | ||
| 83 | + queryStaffInfos, | ||
| 84 | + queryAttendanceClassesTask, | ||
| 85 | + | ||
| 86 | +} from '@/api/oa/staffAttendanceManageApi' | ||
| 87 | + | ||
| 88 | +export default { | ||
| 89 | + name: 'StaffAttendanceManageList', | ||
| 90 | + components: { | ||
| 91 | + 'choose-org-tree': () => import('@/components/oa/chooseOrgTree'), | ||
| 92 | + 'staff-attendance-detail': () => import('@/components/oa/staffAttendanceDetail'), | ||
| 93 | + 'staff-attendance-replenish-check-in': () => import('@/components/oa/staffAttendanceReplenishCheckIn') | ||
| 94 | + }, | ||
| 95 | + data() { | ||
| 96 | + return { | ||
| 97 | + communityId: '', | ||
| 98 | + staffAttendanceManageInfo: { | ||
| 99 | + staffs: [], | ||
| 100 | + attendances: [], | ||
| 101 | + classesId: '', | ||
| 102 | + orgId: '', | ||
| 103 | + orgName: '', | ||
| 104 | + curDate: '', | ||
| 105 | + curYear: '', | ||
| 106 | + curMonth: '', | ||
| 107 | + curStaffId: '', | ||
| 108 | + maxDay: 31, | ||
| 109 | + staffNameLike: '' | ||
| 110 | + } | ||
| 111 | + } | ||
| 112 | + }, | ||
| 113 | + created() { | ||
| 114 | + this.communityId = getCommunityId() | ||
| 115 | + this.initStaffDate() | ||
| 116 | + this.loadStaffs() | ||
| 117 | + }, | ||
| 118 | + methods: { | ||
| 119 | + initStaffDate() { | ||
| 120 | + const date = new Date() | ||
| 121 | + this.staffAttendanceManageInfo.curMonth = date.getMonth() + 1 | ||
| 122 | + this.staffAttendanceManageInfo.curYear = date.getFullYear() | ||
| 123 | + this.staffAttendanceManageInfo.curDate = `${date.getFullYear()}-${date.getMonth() + 1}` | ||
| 124 | + this.staffAttendanceManageInfo.maxDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() | ||
| 125 | + }, | ||
| 126 | + | ||
| 127 | + handleDateChange(val) { | ||
| 128 | + if (val) { | ||
| 129 | + const values = val.split('-') | ||
| 130 | + this.staffAttendanceManageInfo.curYear = values[0] | ||
| 131 | + this.staffAttendanceManageInfo.curMonth = values[1] | ||
| 132 | + this.staffAttendanceManageInfo.maxDay = new Date(values[0], values[1], 0).getDate() | ||
| 133 | + this.loadStaffAttendances() | ||
| 134 | + } | ||
| 135 | + }, | ||
| 136 | + | ||
| 137 | + async loadStaffs() { | ||
| 138 | + try { | ||
| 139 | + const params = { | ||
| 140 | + page: 1, | ||
| 141 | + row: 100, | ||
| 142 | + orgId: this.staffAttendanceManageInfo.orgId, | ||
| 143 | + staffName: this.staffAttendanceManageInfo.staffNameLike, | ||
| 144 | + communityId: this.communityId | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + const { data } = await queryStaffInfos(params) | ||
| 148 | + this.staffAttendanceManageInfo.staffs = data.staffs || [] | ||
| 149 | + | ||
| 150 | + if (this.staffAttendanceManageInfo.staffs.length > 0) { | ||
| 151 | + this.staffAttendanceManageInfo.curStaffId = this.staffAttendanceManageInfo.staffs[0].userId | ||
| 152 | + this.loadStaffAttendances() | ||
| 153 | + } | ||
| 154 | + } catch (error) { | ||
| 155 | + console.error('Failed to load staffs:', error) | ||
| 156 | + } | ||
| 157 | + }, | ||
| 158 | + | ||
| 159 | + async loadStaffAttendances() { | ||
| 160 | + if (!this.staffAttendanceManageInfo.curStaffId || !this.staffAttendanceManageInfo.curDate) return | ||
| 161 | + | ||
| 162 | + try { | ||
| 163 | + const params = { | ||
| 164 | + page: 1, | ||
| 165 | + row: 1000, | ||
| 166 | + date: this.staffAttendanceManageInfo.curDate, | ||
| 167 | + staffId: this.staffAttendanceManageInfo.curStaffId, | ||
| 168 | + communityId: this.communityId | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + const { data } = await queryAttendanceClassesTask(params) | ||
| 172 | + this.staffAttendanceManageInfo.attendances = data || [] | ||
| 173 | + } catch (error) { | ||
| 174 | + console.error('Failed to load staff attendances:', error) | ||
| 175 | + } | ||
| 176 | + }, | ||
| 177 | + | ||
| 178 | + handleOrgFocus() { | ||
| 179 | + this.$refs.chooseOrgTree.open() | ||
| 180 | + }, | ||
| 181 | + | ||
| 182 | + handleSwitchOrg(org) { | ||
| 183 | + this.staffAttendanceManageInfo.orgId = org.orgId | ||
| 184 | + this.staffAttendanceManageInfo.orgName = org.allOrgName | ||
| 185 | + this.loadStaffs() | ||
| 186 | + }, | ||
| 187 | + | ||
| 188 | + switchStaff(staff) { | ||
| 189 | + this.staffAttendanceManageInfo.curStaffId = staff.userId | ||
| 190 | + this.loadStaffAttendances() | ||
| 191 | + }, | ||
| 192 | + | ||
| 193 | + getDayAttendance(day) { | ||
| 194 | + if (!this.staffAttendanceManageInfo.attendances) return null | ||
| 195 | + | ||
| 196 | + return this.staffAttendanceManageInfo.attendances.find(item => item.taskDay === day) || null | ||
| 197 | + }, | ||
| 198 | + | ||
| 199 | + getAttendanceDetail(day) { | ||
| 200 | + const attendance = this.getDayAttendance(day) | ||
| 201 | + | ||
| 202 | + if (!attendance) { | ||
| 203 | + const date = new Date() | ||
| 204 | + const taskYear = this.staffAttendanceManageInfo.curYear | ||
| 205 | + const taskMonth = this.staffAttendanceManageInfo.curMonth | ||
| 206 | + | ||
| 207 | + if (taskYear == date.getFullYear() && parseInt(taskMonth) == (date.getMonth() + 1) && day > date.getDate()) { | ||
| 208 | + return [{ rest: this.$t('staffAttendance.notTime') }] | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + return [{ rest: this.$t('staffAttendance.noNeed') }] | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + return attendance.attendanceClassesTaskDetails || [] | ||
| 215 | + }, | ||
| 216 | + | ||
| 217 | + getBgColor(day) { | ||
| 218 | + console.log(day) | ||
| 219 | + return '#fff' | ||
| 220 | + }, | ||
| 221 | + | ||
| 222 | + formatTime(time) { | ||
| 223 | + if (!time) return '' | ||
| 224 | + return this.$moment(time).format('HH:mm:ss') | ||
| 225 | + }, | ||
| 226 | + | ||
| 227 | + checkInLog(day) { | ||
| 228 | + let month = this.staffAttendanceManageInfo.curMonth | ||
| 229 | + if (month < 10) month = `0${month}` | ||
| 230 | + if (day < 10) day = `0${day}` | ||
| 231 | + | ||
| 232 | + const date = `${this.staffAttendanceManageInfo.curYear}-${month}-${day}` | ||
| 233 | + this.$refs.staffAttendanceDetail.open({ | ||
| 234 | + staffId: this.staffAttendanceManageInfo.curStaffId, | ||
| 235 | + date: date | ||
| 236 | + }) | ||
| 237 | + }, | ||
| 238 | + | ||
| 239 | + replenishCheckIn(day) { | ||
| 240 | + const details = this.getAttendanceDetail(day) | ||
| 241 | + if (!details || details.length < 1) { | ||
| 242 | + this.$message.warning(this.$t('staffAttendance.noTask')) | ||
| 243 | + return | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + const newDetails = details.filter(item => item.state === '10000') | ||
| 247 | + if (newDetails.length < 1) { | ||
| 248 | + this.$message.warning(this.$t('staffAttendance.noTask')) | ||
| 249 | + return | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + this.$refs.staffAttendanceReplenishCheckIn.open(newDetails) | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | +} | ||
| 256 | +</script> | ||
| 257 | + | ||
| 258 | +<style lang="scss" scoped> | ||
| 259 | +.staff-attendance-manage-container { | ||
| 260 | + padding: 20px; | ||
| 261 | + | ||
| 262 | + .box-card { | ||
| 263 | + margin-bottom: 20px; | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + .filter-wrapper { | ||
| 267 | + .filter-item { | ||
| 268 | + margin-bottom: 15px; | ||
| 269 | + width: 100%; | ||
| 270 | + } | ||
| 271 | + } | ||
| 272 | + | ||
| 273 | + .staff-list { | ||
| 274 | + max-height: 600px; | ||
| 275 | + overflow-y: auto; | ||
| 276 | + | ||
| 277 | + .staff-ul { | ||
| 278 | + list-style: none; | ||
| 279 | + padding: 0; | ||
| 280 | + margin: 0; | ||
| 281 | + | ||
| 282 | + .staff-item { | ||
| 283 | + padding: 10px; | ||
| 284 | + margin-bottom: 5px; | ||
| 285 | + cursor: pointer; | ||
| 286 | + border-radius: 4px; | ||
| 287 | + text-align: center; | ||
| 288 | + | ||
| 289 | + &:hover { | ||
| 290 | + background-color: #f5f7fa; | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + &.active { | ||
| 294 | + background-color: #409eff; | ||
| 295 | + color: #fff; | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + } | ||
| 299 | + } | ||
| 300 | + | ||
| 301 | + .attendance-grid { | ||
| 302 | + .attendance-day { | ||
| 303 | + padding: 10px; | ||
| 304 | + margin-bottom: 20px; | ||
| 305 | + border-radius: 4px; | ||
| 306 | + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); | ||
| 307 | + | ||
| 308 | + .day-header { | ||
| 309 | + font-weight: bold; | ||
| 310 | + margin-bottom: 10px; | ||
| 311 | + text-align: center; | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + .replenish-btn { | ||
| 315 | + color: #409eff; | ||
| 316 | + cursor: pointer; | ||
| 317 | + text-align: center; | ||
| 318 | + margin-bottom: 10px; | ||
| 319 | + | ||
| 320 | + &:hover { | ||
| 321 | + text-decoration: underline; | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + .attendance-detail { | ||
| 326 | + margin-bottom: 5px; | ||
| 327 | + font-size: 12px; | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + .log-link { | ||
| 331 | + text-align: center; | ||
| 332 | + margin-top: 10px; | ||
| 333 | + } | ||
| 334 | + } | ||
| 335 | + } | ||
| 336 | +} | ||
| 337 | +</style> | ||
| 0 | \ No newline at end of file | 338 | \ No newline at end of file |
src/views/oa/todayAttendanceManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + todayAttendanceManage: { | ||
| 4 | + search: { | ||
| 5 | + title: 'Search Conditions', | ||
| 6 | + classesName: 'Please enter team name', | ||
| 7 | + departmentName: 'Please enter department name', | ||
| 8 | + date: 'Please select attendance date', | ||
| 9 | + staffName: 'Please enter staff name' | ||
| 10 | + }, | ||
| 11 | + list: { | ||
| 12 | + title: 'Today Attendance' | ||
| 13 | + }, | ||
| 14 | + table: { | ||
| 15 | + staffName: 'Staff Name', | ||
| 16 | + classesName: 'Attendance Team', | ||
| 17 | + attendanceDate: 'Attendance Date', | ||
| 18 | + stateName: 'Attendance Status', | ||
| 19 | + operation: 'Operation' | ||
| 20 | + }, | ||
| 21 | + fetchError: 'Failed to fetch attendance data' | ||
| 22 | + }, | ||
| 23 | + todayAttendanceDetail: { | ||
| 24 | + title: 'Attendance Details', | ||
| 25 | + table: { | ||
| 26 | + name: 'Name', | ||
| 27 | + work: 'Work', | ||
| 28 | + offWork: 'Off Work', | ||
| 29 | + attendanceRange: 'Attendance Range', | ||
| 30 | + lateEarly: 'Late/Early', | ||
| 31 | + status: 'Status', | ||
| 32 | + attendanceTime: 'Attendance Time', | ||
| 33 | + notCheckIn: 'Not Checked In', | ||
| 34 | + snapshot: 'Snapshot' | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + }, | ||
| 38 | + zh: { | ||
| 39 | + todayAttendanceManage: { | ||
| 40 | + search: { | ||
| 41 | + title: '查询条件', | ||
| 42 | + classesName: '请输入班组名称', | ||
| 43 | + departmentName: '请输入部门名称', | ||
| 44 | + date: '请选择打卡时间', | ||
| 45 | + staffName: '请输入员工名称' | ||
| 46 | + }, | ||
| 47 | + list: { | ||
| 48 | + title: '今日考勤' | ||
| 49 | + }, | ||
| 50 | + table: { | ||
| 51 | + staffName: '员工名称', | ||
| 52 | + classesName: '考勤班组', | ||
| 53 | + attendanceDate: '考勤时间', | ||
| 54 | + stateName: '考勤状态', | ||
| 55 | + operation: '操作' | ||
| 56 | + }, | ||
| 57 | + fetchError: '获取考勤数据失败' | ||
| 58 | + }, | ||
| 59 | + todayAttendanceDetail: { | ||
| 60 | + title: '考勤详情', | ||
| 61 | + table: { | ||
| 62 | + name: '名称', | ||
| 63 | + work: '上班', | ||
| 64 | + offWork: '下班', | ||
| 65 | + attendanceRange: '打卡范围', | ||
| 66 | + lateEarly: '迟到/早退', | ||
| 67 | + status: '状态', | ||
| 68 | + attendanceTime: '打卡时间', | ||
| 69 | + notCheckIn: '未打卡', | ||
| 70 | + snapshot: '抓拍' | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | +} | ||
| 0 | \ No newline at end of file | 75 | \ No newline at end of file |
src/views/oa/todayAttendanceManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="today-attendance-container"> | ||
| 3 | + <!-- 查询条件 --> | ||
| 4 | + <el-card class="search-wrapper"> | ||
| 5 | + <div slot="header" class="flex justify-between"> | ||
| 6 | + <span>{{ $t('todayAttendanceManage.search.title') }}</span> | ||
| 7 | + </div> | ||
| 8 | + <el-row :gutter="20"> | ||
| 9 | + <el-col :span="4"> | ||
| 10 | + <el-input | ||
| 11 | + v-model="searchForm.classesName" | ||
| 12 | + :placeholder="$t('todayAttendanceManage.search.classesName')" | ||
| 13 | + clearable | ||
| 14 | + @keyup.enter.native="handleSearch" | ||
| 15 | + /> | ||
| 16 | + </el-col> | ||
| 17 | + <el-col :span="4"> | ||
| 18 | + <el-input | ||
| 19 | + v-model="searchForm.departmentName" | ||
| 20 | + :placeholder="$t('todayAttendanceManage.search.departmentName')" | ||
| 21 | + clearable | ||
| 22 | + @keyup.enter.native="handleSearch" | ||
| 23 | + /> | ||
| 24 | + </el-col> | ||
| 25 | + <el-col :span="4"> | ||
| 26 | + <el-date-picker | ||
| 27 | + v-model="searchForm.date" | ||
| 28 | + type="date" | ||
| 29 | + :placeholder="$t('todayAttendanceManage.search.date')" | ||
| 30 | + value-format="yyyy-MM-dd" | ||
| 31 | + style="width: 100%" | ||
| 32 | + /> | ||
| 33 | + </el-col> | ||
| 34 | + <el-col :span="4"> | ||
| 35 | + <el-input | ||
| 36 | + v-model="searchForm.staffName" | ||
| 37 | + :placeholder="$t('todayAttendanceManage.search.staffName')" | ||
| 38 | + clearable | ||
| 39 | + @keyup.enter.native="handleSearch" | ||
| 40 | + /> | ||
| 41 | + </el-col> | ||
| 42 | + <el-col :span="4"> | ||
| 43 | + <el-button type="primary" @click="handleSearch">{{ $t('common.search') }}</el-button> | ||
| 44 | + <el-button @click="handleReset">{{ $t('common.reset') }}</el-button> | ||
| 45 | + </el-col> | ||
| 46 | + </el-row> | ||
| 47 | + </el-card> | ||
| 48 | + | ||
| 49 | + <!-- 今日考勤列表 --> | ||
| 50 | + <el-card class="list-wrapper"> | ||
| 51 | + <div slot="header" class="flex justify-between"> | ||
| 52 | + <span>{{ $t('todayAttendanceManage.list.title') }}</span> | ||
| 53 | + </div> | ||
| 54 | + <el-table | ||
| 55 | + v-loading="loading" | ||
| 56 | + :data="tableData" | ||
| 57 | + border | ||
| 58 | + style="width: 100%" | ||
| 59 | + > | ||
| 60 | + <el-table-column | ||
| 61 | + prop="staffName" | ||
| 62 | + :label="$t('todayAttendanceManage.table.staffName')" | ||
| 63 | + align="center" | ||
| 64 | + /> | ||
| 65 | + <el-table-column | ||
| 66 | + prop="classesName" | ||
| 67 | + :label="$t('todayAttendanceManage.table.classesName')" | ||
| 68 | + align="center" | ||
| 69 | + /> | ||
| 70 | + <el-table-column | ||
| 71 | + :label="$t('todayAttendanceManage.table.attendanceDate')" | ||
| 72 | + align="center" | ||
| 73 | + > | ||
| 74 | + <template slot-scope="scope"> | ||
| 75 | + {{ `${scope.row.taskYear}-${scope.row.taskMonth}-${scope.row.taskDay}` }} | ||
| 76 | + </template> | ||
| 77 | + </el-table-column> | ||
| 78 | + <el-table-column | ||
| 79 | + prop="stateName" | ||
| 80 | + :label="$t('todayAttendanceManage.table.stateName')" | ||
| 81 | + align="center" | ||
| 82 | + /> | ||
| 83 | + <el-table-column | ||
| 84 | + :label="$t('common.operation')" | ||
| 85 | + align="center" | ||
| 86 | + width="120" | ||
| 87 | + > | ||
| 88 | + <template slot-scope="scope"> | ||
| 89 | + <el-button | ||
| 90 | + size="mini" | ||
| 91 | + type="primary" | ||
| 92 | + @click="handleDetail(scope.row)" | ||
| 93 | + > | ||
| 94 | + {{ $t('common.detail') }} | ||
| 95 | + </el-button> | ||
| 96 | + </template> | ||
| 97 | + </el-table-column> | ||
| 98 | + </el-table> | ||
| 99 | + | ||
| 100 | + <el-pagination | ||
| 101 | + :current-page.sync="page.current" | ||
| 102 | + :page-sizes="[10, 20, 30, 50]" | ||
| 103 | + :page-size="page.size" | ||
| 104 | + :total="page.total" | ||
| 105 | + layout="total, sizes, prev, pager, next, jumper" | ||
| 106 | + @size-change="handleSizeChange" | ||
| 107 | + @current-change="handleCurrentChange" | ||
| 108 | + /> | ||
| 109 | + </el-card> | ||
| 110 | + | ||
| 111 | + <!-- 考勤详情弹窗 --> | ||
| 112 | + <today-attendance-detail ref="detailDialog" /> | ||
| 113 | + </div> | ||
| 114 | +</template> | ||
| 115 | + | ||
| 116 | +<script> | ||
| 117 | +import { queryAttendanceClassesTask } from '@/api/oa/todayAttendanceManageApi' | ||
| 118 | +import TodayAttendanceDetail from '@/components/oa/todayAttendanceDetail' | ||
| 119 | + | ||
| 120 | +export default { | ||
| 121 | + name: 'TodayAttendanceManageList', | ||
| 122 | + components: { | ||
| 123 | + TodayAttendanceDetail | ||
| 124 | + }, | ||
| 125 | + data() { | ||
| 126 | + return { | ||
| 127 | + loading: false, | ||
| 128 | + searchForm: { | ||
| 129 | + classesName: '', | ||
| 130 | + departmentName: '', | ||
| 131 | + date: '', | ||
| 132 | + staffName: '' | ||
| 133 | + }, | ||
| 134 | + tableData: [], | ||
| 135 | + page: { | ||
| 136 | + current: 1, | ||
| 137 | + size: 10, | ||
| 138 | + total: 0 | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + }, | ||
| 142 | + created() { | ||
| 143 | + this.getList() | ||
| 144 | + }, | ||
| 145 | + methods: { | ||
| 146 | + async getList() { | ||
| 147 | + try { | ||
| 148 | + this.loading = true | ||
| 149 | + const params = { | ||
| 150 | + page: this.page.current, | ||
| 151 | + row: this.page.size, | ||
| 152 | + ...this.searchForm | ||
| 153 | + } | ||
| 154 | + const { data, total } = await queryAttendanceClassesTask(params) | ||
| 155 | + this.tableData = data | ||
| 156 | + this.page.total = total | ||
| 157 | + } catch (error) { | ||
| 158 | + this.$message.error(this.$t('todayAttendanceManage.fetchError')) | ||
| 159 | + } finally { | ||
| 160 | + this.loading = false | ||
| 161 | + } | ||
| 162 | + }, | ||
| 163 | + handleSearch() { | ||
| 164 | + this.page.current = 1 | ||
| 165 | + this.getList() | ||
| 166 | + }, | ||
| 167 | + handleReset() { | ||
| 168 | + this.searchForm = { | ||
| 169 | + classesName: '', | ||
| 170 | + departmentName: '', | ||
| 171 | + date: '', | ||
| 172 | + staffName: '' | ||
| 173 | + } | ||
| 174 | + this.page.current = 1 | ||
| 175 | + this.getList() | ||
| 176 | + }, | ||
| 177 | + handleDetail(row) { | ||
| 178 | + this.$refs.detailDialog.open(row) | ||
| 179 | + }, | ||
| 180 | + handleSizeChange(val) { | ||
| 181 | + this.page.size = val | ||
| 182 | + this.getList() | ||
| 183 | + }, | ||
| 184 | + handleCurrentChange(val) { | ||
| 185 | + this.page.current = val | ||
| 186 | + this.getList() | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | +} | ||
| 190 | +</script> | ||
| 191 | + | ||
| 192 | +<style lang="scss" scoped> | ||
| 193 | +.today-attendance-container { | ||
| 194 | + padding: 20px; | ||
| 195 | + | ||
| 196 | + .search-wrapper { | ||
| 197 | + margin-bottom: 20px; | ||
| 198 | + | ||
| 199 | + .el-col { | ||
| 200 | + margin-bottom: 10px; | ||
| 201 | + } | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + .list-wrapper { | ||
| 205 | + .el-pagination { | ||
| 206 | + margin-top: 20px; | ||
| 207 | + text-align: right; | ||
| 208 | + } | ||
| 209 | + } | ||
| 210 | +} | ||
| 211 | +</style> | ||
| 0 | \ No newline at end of file | 212 | \ No newline at end of file |