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