From e0bea18c126ff9fd442491ee3a4f37f1faf76a7d Mon Sep 17 00:00:00 2001 From: spring <2396852758@qq.com> Date: 星期四, 21 八月 2025 17:15:02 +0800 Subject: [PATCH] 自助服务平台 --- src/views/personnelManagement/selfService/index.vue | 525 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 525 insertions(+), 0 deletions(-) diff --git a/src/views/personnelManagement/selfService/index.vue b/src/views/personnelManagement/selfService/index.vue new file mode 100644 index 0000000..1f4fbea --- /dev/null +++ b/src/views/personnelManagement/selfService/index.vue @@ -0,0 +1,525 @@ +<template> + <div class="app-container self-service-container"> + + <!-- 鍔熻兘瀵艰埅鍗$墖 --> + <el-row :gutter="20" class="nav-cards"> + <el-col :span="6" v-for="(item, index) in navItems" :key="index"> + <el-card class="nav-card" @click="handleNavClick(item.type)"> + <div class="nav-content"> + <el-icon :size="40" class="nav-icon"> + <component :is="item.icon" /> + </el-icon> + <h3>{{ item.title }}</h3> + <p>{{ item.desc }}</p> + </div> + </el-card> + </el-col> + </el-row> + + <!-- 涓昏鍐呭鍖哄煙 --> + <div class="main-content"> + <!-- 鑰冨嫟璁板綍 --> + <el-card v-if="currentView === 'attendance'" class="content-card"> + <template #header> + <div class="card-header"> + <span>涓汉鑰冨嫟璁板綍</span> + <el-button type="primary" @click="addAttendanceRecord">鏂板璁板綍</el-button> + </div> + </template> + <el-table :data="attendanceData" style="width: 100%"> + <el-table-column prop="date" label="鏃ユ湡" /> + <el-table-column prop="checkIn" label="绛惧埌鏃堕棿" /> + <el-table-column prop="checkOut" label="绛鹃��鏃堕棿" /> + <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" width="100" /> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="scope.row.status === '姝e父' ? 'success' : 'danger'"> + {{ scope.row.status }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="150"> + <template #default="scope"> + <el-button size="small" @click="editAttendanceRecord(scope.row)">缂栬緫</el-button> + <el-button size="small" type="danger" @click="deleteAttendanceRecord(scope.$index)">鍒犻櫎</el-button> + </template> + </el-table-column> + </el-table> + </el-card> + + <!-- 钖祫鍗� --> + <el-card v-if="currentView === 'salary'" class="content-card"> + <template #header> + <div class="card-header"> + <span>钖祫鍗曟煡璇�</span> + <el-date-picker v-model="salaryMonth" type="month" placeholder="閫夋嫨鏈堜唤" /> + </div> + </template> + <el-table :data="salaryData" style="width: 100%"> + <el-table-column prop="month" label="鏈堜唤" /> + <el-table-column prop="basicSalary" label="鍩烘湰宸ヨ祫" /> + <el-table-column prop="bonus" label="濂栭噾" /> + <el-table-column prop="deduction" label="鎵f" /> + <el-table-column prop="total" label="瀹炲彂宸ヨ祫" /> + <el-table-column prop="status" label="鐘舵��" > + <template #default="scope"> + <el-tag :type="scope.row.status === '宸插彂鏀�' ? 'success' : 'warning'"> + {{ scope.row.status }} + </el-tag> + </template> + </el-table-column> + </el-table> + </el-card> + + <!-- 鍋囨湡鐢宠 --> + <el-card v-if="currentView === 'leave'" class="content-card"> + <template #header> + <div class="card-header"> + <span>鍋囨湡鐢宠绠$悊</span> + <el-button type="primary" @click="showLeaveDialog = true">鐢宠鍋囨湡</el-button> + </div> + </template> + <el-table :data="leaveData" style="width: 100%"> + <el-table-column prop="type" label="鍋囨湡绫诲瀷" /> + <el-table-column prop="startDate" label="寮�濮嬫棩鏈�" /> + <el-table-column prop="endDate" label="缁撴潫鏃ユ湡" /> + <el-table-column prop="days" label="澶╂暟" width="80" /> + <el-table-column prop="reason" label="鐢宠鍘熷洜" /> + <el-table-column prop="status" label="瀹℃壒鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ scope.row.status }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="150"> + <template #default="scope"> + <el-button size="small" @click="editLeaveRecord(scope.row)">缂栬緫</el-button> + <el-button size="small" type="danger" @click="deleteLeaveRecord(scope.$index)">鍒犻櫎</el-button> + </template> + </el-table-column> + </el-table> + </el-card> + + <!-- 涓汉淇℃伅 --> + <el-card v-if="currentView === 'profile'" class="content-card"> + <template #header> + <div class="card-header"> + <span>涓汉淇℃伅缁存姢</span> + <el-button type="primary" @click="editProfile = true">缂栬緫淇℃伅</el-button> + </div> + </template> + <el-descriptions :column="2" border> + <el-descriptions-item label="濮撳悕">{{ profile.name }}</el-descriptions-item> + <el-descriptions-item label="宸ュ彿">{{ profile.employeeId }}</el-descriptions-item> + <el-descriptions-item label="閮ㄩ棬">{{ profile.department }}</el-descriptions-item> + <el-descriptions-item label="鑱屼綅">{{ profile.position }}</el-descriptions-item> + <el-descriptions-item label="鍏ヨ亴鏃ユ湡">{{ profile.hireDate }}</el-descriptions-item> + <el-descriptions-item label="鑱旂郴鐢佃瘽">{{ profile.phone }}</el-descriptions-item> + <el-descriptions-item label="閭">{{ profile.email }}</el-descriptions-item> + <el-descriptions-item label="鍦板潃">{{ profile.address }}</el-descriptions-item> + </el-descriptions> + </el-card> + </div> + + <!-- 鍋囨湡鐢宠寮圭獥 --> + <el-dialog v-model="showLeaveDialog" title="鐢宠鍋囨湡" width="500px"> + <el-form :model="leaveForm" label-width="100px"> + <el-form-item label="鍋囨湡绫诲瀷"> + <el-select v-model="leaveForm.type" placeholder="璇烽�夋嫨鍋囨湡绫诲瀷"> + <el-option label="骞村亣" value="骞村亣" /> + <el-option label="鐥呭亣" value="鐥呭亣" /> + <el-option label="璋冧紤" value="璋冧紤" /> + <el-option label="浜嬪亣" value="浜嬪亣" /> + </el-select> + </el-form-item> + <el-form-item label="寮�濮嬫棩鏈�"> + <el-date-picker v-model="leaveForm.startDate" type="date" placeholder="閫夋嫨寮�濮嬫棩鏈�" /> + </el-form-item> + <el-form-item label="缁撴潫鏃ユ湡"> + <el-date-picker v-model="leaveForm.endDate" type="date" placeholder="閫夋嫨缁撴潫鏃ユ湡" /> + </el-form-item> + <el-form-item label="鐢宠鍘熷洜"> + <el-input v-model="leaveForm.reason" type="textarea" rows="3" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="showLeaveDialog = false">鍙栨秷</el-button> + <el-button type="primary" @click="submitLeaveApplication">鎻愪氦鐢宠</el-button> + </template> + </el-dialog> + + <!-- 鏂板鑰冨嫟璁板綍寮圭獥 --> + <el-dialog v-model="showAttendanceDialog" title="鏂板鑰冨嫟璁板綍" width="500px"> + <el-form :model="attendanceForm" :rules="attendanceRules" ref="attendanceFormRef" label-width="100px"> + <el-form-item label="鏃ユ湡" prop="date"> + <el-date-picker v-model="attendanceForm.date" type="date" placeholder="閫夋嫨鏃ユ湡" /> + </el-form-item> + <el-form-item label="绛惧埌鏃堕棿" prop="checkIn"> + <el-time-picker v-model="attendanceForm.checkIn" placeholder="閫夋嫨绛惧埌鏃堕棿" format="HH:mm" value-format="HH:mm" /> + </el-form-item> + <el-form-item label="绛鹃��鏃堕棿" prop="checkOut"> + <el-time-picker v-model="attendanceForm.checkOut" placeholder="閫夋嫨绛鹃��鏃堕棿" format="HH:mm" value-format="HH:mm" /> + </el-form-item> + <el-form-item label="鐘舵��" prop="status"> + <el-select v-model="attendanceForm.status" placeholder="璇烽�夋嫨鐘舵��"> + <el-option label="姝e父" value="姝e父" /> + <el-option label="杩熷埌" value="杩熷埌" /> + <el-option label="鏃╅��" value="鏃╅��" /> + <el-option label="缂哄嫟" value="缂哄嫟" /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="showAttendanceDialog = false">鍙栨秷</el-button> + <el-button type="primary" @click="submitAttendance">鎻愪氦</el-button> + </template> + </el-dialog> + + <!-- 涓汉淇℃伅缂栬緫寮圭獥 --> + <el-dialog v-model="editProfile" title="缂栬緫涓汉淇℃伅" width="500px"> + <el-form :model="profileForm" label-width="100px"> + <el-form-item label="濮撳悕"> + <el-input v-model="profileForm.name" /> + </el-form-item> + <el-form-item label="鑱旂郴鐢佃瘽"> + <el-input v-model="profileForm.phone" /> + </el-form-item> + <el-form-item label="閭"> + <el-input v-model="profileForm.email" /> + </el-form-item> + <el-form-item label="鍦板潃"> + <el-input v-model="profileForm.address" type="textarea" rows="2" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="editProfile = false">鍙栨秷</el-button> + <el-button type="primary" @click="saveProfile">淇濆瓨</el-button> + </template> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, watch } from 'vue' +import { ElMessage } from 'element-plus' +import { + Calendar, + Money, + Clock, + User +} from '@element-plus/icons-vue' + +// 褰撳墠瑙嗗浘 +const currentView = ref('attendance') + +// 瀵艰埅椤� +const navItems = [ + { type: 'attendance', title: '鑰冨嫟璁板綍', desc: '鏌ヨ涓汉鑰冨嫟淇℃伅', icon: 'Calendar' }, + { type: 'salary', title: '钖祫鍗�', desc: '鏌ョ湅钖祫鍙戞斁璁板綍', icon: 'Money' }, + { type: 'leave', title: '鍋囨湡鐢宠', desc: '鍦ㄧ嚎鐢宠鍚勭被鍋囨湡', icon: 'Clock' }, + { type: 'profile', title: '涓汉淇℃伅', desc: '缁存姢涓汉鍩烘湰淇℃伅', icon: 'User' } +] + +// 鑰冨嫟鏁版嵁 +const attendanceData = ref([ + { date: '2024-01-15', checkIn: '09:00', checkOut: '18:00', workHours: '9灏忔椂', status: '姝e父' }, + { date: '2024-01-16', checkIn: '08:55', checkOut: '18:05', workHours: '9灏忔椂10鍒�', status: '姝e父' }, + { date: '2024-01-17', checkIn: '09:15', checkOut: '18:00', workHours: '8灏忔椂45鍒�', status: '杩熷埌' } +]) + +// 钖祫鏁版嵁 +const salaryData = ref([ + { month: '2024-01', basicSalary: 8000, bonus: 1000, deduction: 200, total: 8800, status: '宸插彂鏀�' }, + { month: '2023-12', basicSalary: 8000, bonus: 800, deduction: 150, total: 8650, status: '宸插彂鏀�' } +]) + +// 鍋囨湡鏁版嵁 +const leaveData = ref([ + { type: '骞村亣', startDate: '2024-02-01', endDate: '2024-02-03', days: 3, reason: '鏄ヨ妭鍥炲', status: '宸查�氳繃' }, + { type: '鐥呭亣', startDate: '2024-01-20', endDate: '2024-01-21', days: 2, reason: '鎰熷啋鍙戠儳', status: '瀹℃壒涓�' } +]) + +// 涓汉淇℃伅 +const profile = ref({ + name: '寮犳捣娲�', + employeeId: 'EMP001', + department: '鎶�鏈儴', + position: '杞欢宸ョ▼甯�', + hireDate: '2023-03-01', + phone: '13800138000', + email: 'zhangsan@company.com', + address: '鍖椾含甯傛湞闃冲尯xxx琛楅亾xxx鍙�' +}) + +// 寮圭獥鎺у埗 +const showLeaveDialog = ref(false) +const editProfile = ref(false) +const salaryMonth = ref('') + +// 琛ㄥ崟鏁版嵁 +const leaveForm = reactive({ + type: '', + startDate: '', + endDate: '', + reason: '' +}) + +const profileForm = reactive({ + name: '', + phone: '', + email: '', + address: '' +}) + +// 鏂板鑰冨嫟璁板綍锛氬脊绐椾笌琛ㄥ崟 +const showAttendanceDialog = ref(false) +const attendanceFormRef = ref(null) +const attendanceForm = reactive({ + date: '', + checkIn: '', + checkOut: '', + status: '姝e父' +}) +const attendanceRules = { + date: [{ required: true, message: '璇烽�夋嫨鏃ユ湡', trigger: 'change' }], + checkIn: [{ required: true, message: '璇烽�夋嫨绛惧埌鏃堕棿', trigger: 'change' }], + checkOut: [{ required: true, message: '璇烽�夋嫨绛鹃��鏃堕棿', trigger: 'change' }], + status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }] +} + +// 澶勭悊瀵艰埅鐐瑰嚮 +const handleNavClick = (type) => { + currentView.value = type +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStatusType = (status) => { + const types = { + '宸查�氳繃': 'success', + '瀹℃壒涓�': 'warning', + '宸叉嫆缁�': 'danger' + } + return types[status] || 'info' +} + +// 鏂板鑰冨嫟璁板綍锛堟墦寮�寮圭獥骞堕濉粯璁ゅ�硷級 +const addAttendanceRecord = () => { + attendanceForm.date = new Date().toISOString().split('T')[0] + attendanceForm.checkIn = '09:00' + attendanceForm.checkOut = '18:00' + attendanceForm.status = '姝e父' + showAttendanceDialog.value = true +} + +// 璁$畻宸ユ椂 +const computeWorkHours = (inStr, outStr) => { + const [inH, inM] = inStr.split(':').map(n => parseInt(n, 10)) + const [outH, outM] = outStr.split(':').map(n => parseInt(n, 10)) + const inMin = inH * 60 + inM + const outMin = outH * 60 + outM + const diff = Math.max(0, outMin - inMin) + const h = Math.floor(diff / 60) + const m = diff % 60 + return m === 0 ? `${h}灏忔椂` : `${h}灏忔椂${m}鍒哷 +} + +// 鎻愪氦鏂板鑰冨嫟璁板綍 +const submitAttendance = () => { + if (!attendanceFormRef.value) return + attendanceFormRef.value.validate((valid) => { + if (!valid) return + const workHours = computeWorkHours(attendanceForm.checkIn, attendanceForm.checkOut) + const newRecord = { + date: attendanceForm.date, + checkIn: attendanceForm.checkIn, + checkOut: attendanceForm.checkOut, + workHours, + status: attendanceForm.status + } + attendanceData.value.unshift(newRecord) + showAttendanceDialog.value = false + // 閲嶇疆琛ㄥ崟 + attendanceForm.date = '' + attendanceForm.checkIn = '' + attendanceForm.checkOut = '' + attendanceForm.status = '姝e父' + ElMessage.success('鑰冨嫟璁板綍娣诲姞鎴愬姛') + }) +} + +// 缂栬緫鑰冨嫟璁板綍 +const editAttendanceRecord = (row) => { + ElMessage.info('缂栬緫鍔熻兘寮�鍙戜腑...') +} + +// 鍒犻櫎鑰冨嫟璁板綍 +const deleteAttendanceRecord = (index) => { + attendanceData.value.splice(index, 1) + ElMessage.success('鑰冨嫟璁板綍鍒犻櫎鎴愬姛') +} + +// 缂栬緫鍋囨湡璁板綍 +const editLeaveRecord = (row) => { + ElMessage.info('缂栬緫鍔熻兘寮�鍙戜腑...') +} + +// 鍒犻櫎鍋囨湡璁板綍 +const deleteLeaveRecord = (index) => { + leaveData.value.splice(index, 1) + ElMessage.success('鍋囨湡璁板綍鍒犻櫎鎴愬姛') +} + +// 鎻愪氦鍋囨湡鐢宠 +const submitLeaveApplication = () => { + if (!leaveForm.type || !leaveForm.startDate || !leaveForm.endDate || !leaveForm.reason) { + ElMessage.warning('璇峰~鍐欏畬鏁翠俊鎭�') + return + } + + const newLeave = { + type: leaveForm.type, + startDate: leaveForm.startDate, + endDate: leaveForm.endDate, + days: 3, // 绠�鍗曡绠� + reason: leaveForm.reason, + status: '瀹℃壒涓�' + } + + leaveData.value.unshift(newLeave) + showLeaveDialog.value = false + + // 閲嶇疆琛ㄥ崟 + Object.keys(leaveForm).forEach(key => { + leaveForm[key] = '' + }) + + ElMessage.success('鍋囨湡鐢宠鎻愪氦鎴愬姛') +} + +// 淇濆瓨涓汉淇℃伅 +const saveProfile = () => { + Object.assign(profile.value, profileForm) + editProfile.value = false + ElMessage.success('涓汉淇℃伅淇濆瓨鎴愬姛') +} + +// 鍒濆鍖栦釜浜轰俊鎭〃鍗� +const initProfileForm = () => { + Object.assign(profileForm, { + name: profile.value.name, + phone: profile.value.phone, + email: profile.value.email, + address: profile.value.address + }) +} + +// 鐩戝惉缂栬緫涓汉淇℃伅寮圭獥 +watch(editProfile, (val) => { + if (val) { + initProfileForm() + } +}) +</script> + +<style scoped> +.self-service-container { + padding: 20px; + background-color: #f5f7fa; + min-height: 100vh; +} + +.page-header { + text-align: center; + margin-bottom: 30px; + padding: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 12px; + color: white; +} + +.page-header h2 { + color: white; + margin-bottom: 10px; + font-size: 28px; + font-weight: 600; +} + +.page-header p { + color: rgba(255, 255, 255, 0.9); + font-size: 14px; + margin: 0; +} + +.nav-cards { + margin-bottom: 30px; +} + +.nav-card { + cursor: pointer; + transition: all 0.3s ease; + border-radius: 12px; + border: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.nav-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +.nav-content { + text-align: center; + padding: 20px; +} + +.nav-icon { + color: #409EFF; + margin-bottom: 15px; +} + +.nav-content h3 { + margin: 0 0 10px 0; + color: #303133; + font-size: 18px; +} + +.nav-content p { + margin: 0; + color: #909399; + font-size: 14px; +} + +.main-content { + margin-bottom: 30px; +} + +.content-card { + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border: none; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 600; + color: #303133; +} + +/* 鍝嶅簲寮忚璁� */ +@media (max-width: 768px) { + .self-service-container { + padding: 10px; + } + + .nav-cards .el-col { + margin-bottom: 15px; + } + + .page-header h2 { + font-size: 24px; + } +} +</style> -- Gitblit v1.9.3