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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 146 | \ No newline at end of file | ... | ... |
src/components/oa/orgTreeShow.vue
| 1 | 1 | <template> |
| 2 | - <div class="org-tree-show-container"> | |
| 2 | + <div class="org-tree-container"> | |
| 3 | 3 | <el-tree |
| 4 | 4 | ref="orgTree" |
| 5 | - :data="orgTreeShowInfo.orgs" | |
| 5 | + :data="orgData" | |
| 6 | 6 | :props="defaultProps" |
| 7 | 7 | node-key="id" |
| 8 | 8 | default-expand-all |
| 9 | 9 | highlight-current |
| 10 | - @node-click="handleNodeClick"> | |
| 11 | - </el-tree> | |
| 10 | + :expand-on-click-node="false" | |
| 11 | + @node-click="handleNodeClick" | |
| 12 | + ></el-tree> | |
| 12 | 13 | </div> |
| 13 | 14 | </template> |
| 14 | 15 | |
| 15 | 16 | <script> |
| 16 | -import { listOrgTree } from '@/api/oa/addExamineStaffApi' | |
| 17 | +import { listOrgTree } from '@/api/oa/addAttendanceClassesStaffApi' | |
| 17 | 18 | import { getCommunityId } from '@/api/community/communityApi' |
| 18 | 19 | |
| 19 | 20 | export default { |
| 20 | 21 | name: 'OrgTreeShow', |
| 21 | - props: { | |
| 22 | - callBackListener: { | |
| 23 | - type: String, | |
| 24 | - default: '' | |
| 25 | - } | |
| 26 | - }, | |
| 27 | 22 | data() { |
| 28 | 23 | return { |
| 29 | - orgTreeShowInfo: { | |
| 30 | - orgs: [], | |
| 31 | - orgId: '', | |
| 32 | - curOrg: {} | |
| 33 | - }, | |
| 24 | + orgData: [], | |
| 34 | 25 | defaultProps: { |
| 35 | 26 | children: 'children', |
| 36 | 27 | label: 'text' |
| 37 | - } | |
| 28 | + }, | |
| 29 | + communityId: getCommunityId() | |
| 38 | 30 | } |
| 39 | 31 | }, |
| 40 | - mounted() { | |
| 41 | - this._loadOrgsShow() | |
| 42 | - }, | |
| 43 | 32 | methods: { |
| 44 | - refreshTree() { | |
| 45 | - this._loadOrgsShow() | |
| 46 | - }, | |
| 47 | - async _loadOrgsShow() { | |
| 33 | + async refreshTree() { | |
| 48 | 34 | try { |
| 49 | 35 | const params = { |
| 50 | - communityId: getCommunityId() | |
| 36 | + communityId: this.communityId | |
| 51 | 37 | } |
| 52 | 38 | const res = await listOrgTree(params) |
| 53 | - this.orgTreeShowInfo.orgs = res.data | |
| 39 | + this.orgData = res.data | |
| 54 | 40 | } catch (error) { |
| 55 | - console.error('加载组织树失败:', error) | |
| 41 | + this.$message.error(this.$t('orgTree.loadFailed')) | |
| 56 | 42 | } |
| 57 | 43 | }, |
| 58 | 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 | 46 | orgId: data.id, |
| 63 | 47 | orgName: data.text |
| 64 | 48 | }) |
| 65 | 49 | } |
| 50 | + }, | |
| 51 | + mounted() { | |
| 52 | + this.refreshTree() | |
| 66 | 53 | } |
| 67 | 54 | } |
| 68 | 55 | </script> |
| 69 | 56 | |
| 70 | 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 | 66 | .el-tree-node__content { |
| 79 | 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 | 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 | 5 | width="70%" |
| 6 | - :close-on-click-modal="false"> | |
| 7 | - | |
| 6 | + :close-on-click-modal="false" | |
| 7 | + > | |
| 8 | 8 | <el-row :gutter="20"> |
| 9 | 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 | 12 | </div> |
| 13 | + <org-tree-show | |
| 14 | + ref="orgTree" | |
| 15 | + @switchOrg="handleSwitchOrg" | |
| 16 | + ></org-tree-show> | |
| 19 | 17 | </el-col> |
| 20 | - | |
| 18 | + | |
| 21 | 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 | 22 | </div> |
| 25 | - <div class="staff-list-container"> | |
| 23 | + <div class="staff-list"> | |
| 26 | 24 | <div |
| 27 | - v-for="(item, index) in selectStaffInfo.staffs" | |
| 25 | + v-for="(staff, index) in staffList" | |
| 28 | 26 | :key="index" |
| 29 | 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 | 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 | 34 | </div> |
| 36 | - <div>{{ item.tel }}</div> | |
| 35 | + <div>{{ staff.tel }}</div> | |
| 37 | 36 | </div> |
| 38 | 37 | </div> |
| 39 | 38 | </el-col> |
| 40 | 39 | </el-row> |
| 41 | - | |
| 40 | + | |
| 42 | 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 | 48 | </div> |
| 54 | 49 | </el-dialog> |
| 55 | 50 | </template> |
| 56 | 51 | |
| 57 | 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 | 56 | export default { |
| 62 | 57 | name: 'SelectStaff', |
| ... | ... | @@ -65,25 +60,23 @@ export default { |
| 65 | 60 | }, |
| 66 | 61 | data() { |
| 67 | 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 | 70 | methods: { |
| 81 | 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 | 80 | try { |
| 88 | 81 | const params = { |
| 89 | 82 | page: 1, |
| ... | ... | @@ -91,41 +84,35 @@ export default { |
| 91 | 84 | orgId: org.orgId |
| 92 | 85 | } |
| 93 | 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 | 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 | 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 | 136 | overflow-y: auto; |
| 154 | - padding: 10px; | |
| 155 | 137 | } |
| 156 | 138 | |
| 157 | 139 | .staff-item { |
| ... | ... | @@ -160,30 +142,14 @@ export default { |
| 160 | 142 | border: 1px solid #ebeef5; |
| 161 | 143 | border-radius: 4px; |
| 162 | 144 | cursor: pointer; |
| 163 | - | |
| 145 | + | |
| 164 | 146 | &:hover { |
| 165 | 147 | background-color: #f5f7fa; |
| 166 | 148 | } |
| 167 | - | |
| 149 | + | |
| 168 | 150 | &.selected { |
| 169 | 151 | background-color: #ecf5ff; |
| 170 | 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 | 155 | </style> |
| 190 | 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 | 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 | 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 | 111 | \ No newline at end of file | ... | ... |
src/components/oa/uploadImageUrl.vue
| ... | ... | @@ -3,34 +3,36 @@ |
| 3 | 3 | <div v-for="(image, index) in photos" :key="index" class="image-item"> |
| 4 | 4 | <el-image |
| 5 | 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 | 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 | 14 | </div> |
| 14 | - | |
| 15 | + | |
| 15 | 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 | 21 | <i class="el-icon-plus"></i> |
| 20 | 22 | </div> |
| 21 | - | |
| 23 | + | |
| 22 | 24 | <input |
| 23 | 25 | type="file" |
| 24 | - class="file-input" | |
| 26 | + ref="fileInput" | |
| 25 | 27 | accept="image/*" |
| 26 | - ref="fileInput" | |
| 27 | - hidden | |
| 28 | - @change="_choosePhoto"> | |
| 28 | + hidden | |
| 29 | + @change="handleFileChange" | |
| 30 | + > | |
| 29 | 31 | </div> |
| 30 | 32 | </template> |
| 31 | 33 | |
| 32 | 34 | <script> |
| 33 | -import { uploadImage } from '@/api/oa/editExamineStaffApi' | |
| 35 | +import { uploadFile } from '@/api/oa/addAttendanceClassesStaffApi' | |
| 34 | 36 | import { getCommunityId } from '@/api/community/communityApi' |
| 35 | 37 | |
| 36 | 38 | export default { |
| ... | ... | @@ -38,121 +40,74 @@ export default { |
| 38 | 40 | props: { |
| 39 | 41 | imageCount: { |
| 40 | 42 | type: Number, |
| 41 | - default: 99 | |
| 43 | + default: 1 | |
| 42 | 44 | } |
| 43 | 45 | }, |
| 44 | 46 | data() { |
| 45 | 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 | 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 | 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 | 61 | if (file.size > 2 * 1024 * 1024) { |
| 94 | 62 | this.$message.error(this.$t('uploadImage.imageSizeLimit')) |
| 95 | 63 | return |
| 96 | 64 | } |
| 97 | - | |
| 65 | + | |
| 66 | + // 预览图片 | |
| 98 | 67 | const reader = new FileReader() |
| 99 | 68 | reader.onload = (e) => { |
| 100 | 69 | this.photos.push(e.target.result) |
| 101 | - this._doUploadImageUrl(file) | |
| 102 | 70 | } |
| 103 | 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 | 74 | try { |
| 115 | 75 | const formData = new FormData() |
| 116 | - formData.append("uploadFile", file) | |
| 76 | + formData.append('uploadFile', file) | |
| 117 | 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 | 81 | this.$emit('notifyUploadImage', this.photosUrl) |
| 122 | 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 | 118 | display: flex; |
| 164 | 119 | flex-wrap: wrap; |
| 165 | 120 | gap: 10px; |
| 166 | - | |
| 121 | + | |
| 167 | 122 | .image-item { |
| 168 | 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 | 139 | .upload-button { |
| 188 | 140 | width: 100px; |
| 189 | 141 | height: 100px; |
| 190 | - border: 1px dashed #dcdfe6; | |
| 142 | + border: 1px dashed #d9d9d9; | |
| 143 | + border-radius: 6px; | |
| 191 | 144 | display: flex; |
| 192 | 145 | justify-content: center; |
| 193 | 146 | align-items: center; |
| 194 | 147 | cursor: pointer; |
| 195 | - color: #909399; | |
| 196 | - font-size: 24px; | |
| 197 | - | |
| 148 | + color: #8c939d; | |
| 149 | + font-size: 28px; | |
| 150 | + background-color: #fbfdff; | |
| 151 | + | |
| 198 | 152 | &:hover { |
| 199 | 153 | border-color: #409eff; |
| 200 | - color: #409eff; | |
| 201 | 154 | } |
| 202 | 155 | } |
| 203 | 156 | } | ... | ... |
src/components/oa/viewImage.vue
| 1 | 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 | 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 | 13 | </div> |
| 20 | - </el-dialog> | |
| 14 | + </div> | |
| 21 | 15 | </template> |
| 22 | 16 | |
| 23 | 17 | <script> |
| ... | ... | @@ -27,35 +21,103 @@ export default { |
| 27 | 21 | return { |
| 28 | 22 | visible: false, |
| 29 | 23 | imageUrl: '', |
| 30 | - imageStyle: { | |
| 31 | - width: '800px', | |
| 32 | - height: '800px' | |
| 33 | - } | |
| 24 | + imgWidth: 800, | |
| 25 | + imgHeight: 800 | |
| 34 | 26 | } |
| 35 | 27 | }, |
| 36 | 28 | methods: { |
| 37 | 29 | open(url) { |
| 38 | 30 | this.imageUrl = url |
| 39 | 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 | 43 | const img = new Image() |
| 47 | 44 | img.src = url |
| 48 | 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 | 123 | \ No newline at end of file |
| 124 | +</style> | |
| 63 | 125 | \ No newline at end of file | ... | ... |
src/i18n/oaI18n.js
| ... | ... | @@ -27,6 +27,16 @@ import { messages as addExamineStaffMessages } from '../views/oa/addExamineStaff |
| 27 | 27 | import { messages as editExamineStaffMessages } from '../views/oa/editExamineStaffLang' |
| 28 | 28 | import { messages as examineStaffValueMessages } from '../views/oa/examineStaffValueLang' |
| 29 | 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 | 41 | export const messages ={ |
| 32 | 42 | en:{ |
| ... | ... | @@ -59,6 +69,15 @@ export const messages ={ |
| 59 | 69 | ...editExamineStaffMessages.en, |
| 60 | 70 | ...examineStaffValueMessages.en, |
| 61 | 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 | 82 | zh:{ |
| 64 | 83 | ...activitiesTypeManageMessages.zh, |
| ... | ... | @@ -90,5 +109,14 @@ export const messages ={ |
| 90 | 109 | ...editExamineStaffMessages.zh, |
| 91 | 110 | ...examineStaffValueMessages.zh, |
| 92 | 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 | 123 | \ No newline at end of file | ... | ... |
src/router/oaRouter.js
| ... | ... | @@ -125,8 +125,53 @@ export default [ |
| 125 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 212 | \ No newline at end of file | ... | ... |