From c94791b7fde443ac3b26910715bed364e799bbec Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期一, 01 九月 2025 16:17:46 +0800
Subject: [PATCH] Merge branch 'dev-juli' into dev

---
 src/views/personnelManagement/scheduling/index.vue  |  634 ++++++++++++++++++++
 src/views/personnelManagement/analytics/index.vue   |  698 ++++++++++++++++++++++
 src/views/personnelManagement/selfService/index.vue |  525 ++++++++++++++++
 3 files changed, 1,857 insertions(+), 0 deletions(-)

diff --git a/src/views/personnelManagement/analytics/index.vue b/src/views/personnelManagement/analytics/index.vue
new file mode 100644
index 0000000..06b868b
--- /dev/null
+++ b/src/views/personnelManagement/analytics/index.vue
@@ -0,0 +1,698 @@
+<template>
+  <div class="app-container analytics-container">
+
+    <!-- 鍏抽敭鎸囨爣鍗$墖 -->
+    <el-row :gutter="20" class="metrics-cards">
+      <el-col :span="6" v-for="(item, index) in keyMetrics" :key="index">
+        <el-card class="metric-card" :class="item.type">
+          <div class="card-content">
+            <div class="card-icon">
+              <el-icon :size="32">
+                <component :is="item.icon" />
+              </el-icon>
+            </div>
+            <div class="card-info">
+              <div class="card-number">
+                <el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" />
+                <span v-else>{{ item.value }}{{ item.unit }}</span>
+              </div>
+              <div class="card-label">{{ item.label }}</div>
+              <div class="card-trend" :class="item.trend > 0 ? 'positive' : 'negative'">
+                <el-icon>
+                  <component :is="item.trend > 0 ? 'ArrowUp' : 'ArrowDown'" />
+                </el-icon>
+                {{ Math.abs(item.trend) }}%
+              </div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 鍥捐〃鍖哄煙 -->
+    <el-row :gutter="20" class="charts-section">
+      <!-- 鍛樺伐娴佸姩鐜囪秼鍔垮浘 -->
+      <el-col :span="12">
+        <el-card class="chart-card">
+          <template #header>
+            <div class="card-header">
+              <span>鍛樺伐娴佸姩鐜囪秼鍔�</span>
+              <el-tag type="info">杩�12涓湀</el-tag>
+            </div>
+          </template>
+          <div class="chart-container">
+            <div ref="turnoverChartRef" class="chart"></div>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 閮ㄩ棬浜哄憳鍒嗗竷 -->
+      <el-col :span="12">
+        <el-card class="chart-card">
+          <template #header>
+            <div class="card-header">
+              <span>閮ㄩ棬浜哄憳鍒嗗竷</span>
+              <el-tag type="success">褰撳墠鐘舵��</el-tag>
+            </div>
+          </template>
+          <div class="chart-container">
+            <div ref="departmentChartRef" class="chart"></div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 绗簩琛屽浘琛� -->
+    <el-row :gutter="20" class="charts-section">
+      <!-- 缂栧埗杈炬垚鐜� -->
+      <el-col :span="12">
+        <el-card class="chart-card">
+          <template #header>
+            <div class="card-header">
+              <span>缂栧埗杈炬垚鐜�</span>
+              <el-tag type="warning">鍚勯儴闂ㄥ姣�</el-tag>
+            </div>
+          </template>
+          <div class="chart-container">
+            <div ref="staffingChartRef" class="chart"></div>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 鍛樺伐娴佸け鍘熷洜鍒嗘瀽 -->
+      <el-col :span="12">
+        <el-card class="chart-card">
+          <template #header>
+            <div class="card-header">
+              <span>鍛樺伐娴佸け鍘熷洜鍒嗘瀽</span>
+              <el-tag type="danger">骞村害缁熻</el-tag>
+            </div>
+          </template>
+          <div class="chart-container">
+            <div ref="attritionChartRef" class="chart"></div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import { 
+  Refresh, 
+  User, 
+  TrendCharts, 
+  DataAnalysis, 
+  PieChart,
+  ArrowUp,
+  ArrowDown
+} from '@element-plus/icons-vue'
+import * as echarts from 'echarts'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const autoRefreshEnabled = ref(true)
+const autoRefreshInterval = ref(null)
+
+// 鍥捐〃寮曠敤
+const turnoverChartRef = ref(null)
+const departmentChartRef = ref(null)
+const staffingChartRef = ref(null)
+const attritionChartRef = ref(null)
+
+// 鍥捐〃瀹炰緥
+let turnoverChart = null
+let departmentChart = null
+let staffingChart = null
+let attritionChart = null
+
+// 鑷姩鏇存柊闂撮殧锛�10鍒嗛挓锛�
+const AUTO_REFRESH_INTERVAL = 10 * 60 * 1000
+
+// 鍏抽敭鎸囨爣鏁版嵁
+const keyMetrics = ref([
+  {
+    label: '鍛樺伐娴佸姩鐜�',
+    value: 0,
+    unit: '%',
+    icon: 'TrendCharts',
+    type: 'primary',
+    trend: 0
+  },
+  {
+    label: '鍛樺伐娴佸け鐜�',
+    value: 0,
+    unit: '%',
+    icon: 'User',
+    type: 'danger',
+    trend: 0
+  },
+  {
+    label: '缂栧埗杈炬垚鐜�',
+    value: 0,
+    unit: '%',
+    icon: 'DataAnalysis',
+    type: 'success',
+    trend: 0
+  },
+  {
+    label: '鍦ㄨ亴鍛樺伐鏁�',
+    value: 0,
+    unit: '浜�',
+    icon: 'PieChart',
+    type: 'warning',
+    trend: 0
+  }
+])
+
+// 閮ㄩ棬鏁版嵁
+const departmentData = ref([])
+
+// 鍚姩鑷姩鍒锋柊
+const startAutoRefresh = () => {
+  if (autoRefreshInterval.value) {
+    clearInterval(autoRefreshInterval.value)
+  }
+  if (autoRefreshEnabled.value) {
+    autoRefreshInterval.value = setInterval(() => {
+      refreshData()
+    }, AUTO_REFRESH_INTERVAL)
+  }
+}
+
+// 鍋滄鑷姩鍒锋柊
+const stopAutoRefresh = () => {
+  if (autoRefreshInterval.value) {
+    clearInterval(autoRefreshInterval.value)
+    autoRefreshInterval.value = null
+  }
+}
+
+// 鍒囨崲鑷姩鍒锋柊鐘舵��
+const toggleAutoRefresh = (value) => {
+  if (value) {
+    startAutoRefresh()
+  } else {
+    stopAutoRefresh()
+  }
+}
+
+// 鐢熸垚妯℃嫙鏁版嵁
+const generateMockData = () => {
+  // 鐢熸垚鍏抽敭鎸囨爣鏁版嵁
+  keyMetrics.value[0].value = (Math.random() * 5 + 2).toFixed(1)
+  keyMetrics.value[0].trend = (Math.random() * 3 - 1.5).toFixed(1)
+  
+  keyMetrics.value[1].value = (Math.random() * 3 + 1).toFixed(1)
+  keyMetrics.value[1].trend = (Math.random() * 2 - 1).toFixed(1)
+  
+  keyMetrics.value[2].value = (Math.random() * 15 + 85).toFixed(1)
+  keyMetrics.value[2].trend = (Math.random() * 3 - 1.5).toFixed(1)
+  
+  keyMetrics.value[3].value = Math.floor(Math.random() * 50 + 200)
+  keyMetrics.value[3].trend = (Math.random() * 2 - 1).toFixed(1)
+
+  // 鐢熸垚閮ㄩ棬鏁版嵁
+  const departments = ['鎶�鏈儴', '閿�鍞儴', '浜轰簨閮�', '璐㈠姟閮�', '鐢熶骇閮�', '甯傚満閮�']
+  departmentData.value = departments.map(dept => ({
+    department: dept,
+    currentStaff: Math.floor(Math.random() * 30 + 20),
+    plannedStaff: Math.floor(Math.random() * 10 + 35),
+    staffingRate: Math.floor(Math.random() * 20 + 80),
+    turnoverRate: (Math.random() * 4 + 1).toFixed(1),
+    attritionRate: (Math.random() * 2 + 0.5).toFixed(1),
+    newHires: Math.floor(Math.random() * 5 + 1),
+    resignations: Math.floor(Math.random() * 3 + 1),
+    status: Math.random() > 0.7 ? '寮傚父' : '姝e父'
+  }))
+}
+
+// 鍒锋柊鏁版嵁
+const refreshData = async () => {
+  loading.value = true
+  try {
+    // 妯℃嫙API璋冪敤寤惰繜
+    await new Promise(resolve => setTimeout(resolve, 500))
+    
+    generateMockData()
+    renderAllCharts()
+    
+    if (!autoRefreshEnabled.value) {
+      ElMessage.success('鏁版嵁鍒锋柊鎴愬姛')
+    }
+  } catch (error) {
+    console.error('鍒锋柊鏁版嵁澶辫触:', error)
+    ElMessage.error('鍒锋柊鏁版嵁澶辫触')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+  setTimeout(() => {
+    if (turnoverChartRef.value) {
+      turnoverChart = echarts.init(turnoverChartRef.value)
+    }
+    if (departmentChartRef.value) {
+      departmentChart = echarts.init(departmentChartRef.value)
+    }
+    if (staffingChartRef.value) {
+      staffingChart = echarts.init(staffingChartRef.value)
+    }
+    if (attritionChartRef.value) {
+      attritionChart = echarts.init(attritionChartRef.value)
+    }
+    
+    renderAllCharts()
+  }, 300)
+}
+
+// 娓叉煋鎵�鏈夊浘琛�
+const renderAllCharts = () => {
+  renderTurnoverChart()
+  renderDepartmentChart()
+  renderStaffingChart()
+  renderAttritionChart()
+}
+
+// 娓叉煋鍛樺伐娴佸姩鐜囪秼鍔垮浘
+const renderTurnoverChart = () => {
+  if (!turnoverChart) return
+  
+  const months = ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�']
+  const turnoverData = months.map(() => (Math.random() * 5 + 2).toFixed(1))
+  const attritionData = months.map(() => (Math.random() * 3 + 1).toFixed(1))
+  
+  const option = {
+    title: {
+      text: '鍛樺伐娴佸姩鐜囪秼鍔�',
+      left: 'center',
+      textStyle: { fontSize: 16, fontWeight: 'normal' }
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'cross' }
+    },
+    legend: {
+      data: ['娴佸姩鐜�', '娴佸け鐜�'],
+      bottom: 10
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '15%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: months,
+      boundaryGap: false
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: { formatter: '{value}%' }
+    },
+    series: [
+      {
+        name: '娴佸姩鐜�',
+        type: 'line',
+        data: turnoverData,
+        smooth: true,
+        lineStyle: { color: '#409EFF' },
+        itemStyle: { color: '#409EFF' }
+      },
+      {
+        name: '娴佸け鐜�',
+        type: 'line',
+        data: attritionData,
+        smooth: true,
+        lineStyle: { color: '#F56C6C' },
+        itemStyle: { color: '#F56C6C' }
+      }
+    ]
+  }
+  
+  turnoverChart.setOption(option)
+}
+
+// 娓叉煋閮ㄩ棬浜哄憳鍒嗗竷鍥�
+const renderDepartmentChart = () => {
+  if (!departmentChart) return
+  
+  const data = departmentData.value.map(item => ({
+    name: item.department,
+    value: item.currentStaff
+  }))
+  
+  const option = {
+    title: {
+      text: '閮ㄩ棬浜哄憳鍒嗗竷',
+      left: 'center',
+      textStyle: { fontSize: 16, fontWeight: 'normal' }
+    },
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b}: {c}浜� ({d}%)'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left',
+      top: 'middle'
+    },
+    series: [
+      {
+        name: '浜哄憳鏁伴噺',
+        type: 'pie',
+        radius: ['40%', '70%'],
+        center: ['60%', '50%'],
+        data: data,
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+  
+  departmentChart.setOption(option)
+}
+
+// 娓叉煋缂栧埗杈炬垚鐜囧浘
+const renderStaffingChart = () => {
+  if (!staffingChart) return
+  
+  const departments = departmentData.value.map(item => item.department)
+  const rates = departmentData.value.map(item => item.staffingRate)
+  
+  const option = {
+    title: {
+      text: '缂栧埗杈炬垚鐜�',
+      left: 'center',
+      textStyle: { fontSize: 16, fontWeight: 'normal' }
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '15%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: departments,
+      axisLabel: { rotate: 45 }
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: { formatter: '{value}%' },
+      max: 100
+    },
+    series: [
+      {
+        name: '杈炬垚鐜�',
+        type: 'bar',
+        data: rates,
+        itemStyle: {
+          color: function(params) {
+            const value = params.value
+            if (value >= 90) return '#67C23A'
+            if (value >= 80) return '#E6A23C'
+            return '#F56C6C'
+          }
+        }
+      }
+    ]
+  }
+  
+  staffingChart.setOption(option)
+}
+
+// 娓叉煋鍛樺伐娴佸け鍘熷洜鍒嗘瀽鍥�
+const renderAttritionChart = () => {
+  if (!attritionChart) return
+  
+  const reasons = ['钖祫寰呴亣', '鑱屼笟鍙戝睍', '宸ヤ綔鐜', '涓汉鍘熷洜', '鍏朵粬']
+  const data = reasons.map(() => Math.floor(Math.random() * 20 + 5))
+  
+  const option = {
+    title: {
+      text: '鍛樺伐娴佸け鍘熷洜鍒嗘瀽',
+      left: 'center',
+      textStyle: { fontSize: 16, fontWeight: 'normal' }
+    },
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b}: {c}浜� ({d}%)'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left',
+      top: 'middle'
+    },
+    series: [
+      {
+        name: '娴佸け浜烘暟',
+        type: 'pie',
+        radius: '50%',
+        center: ['60%', '50%'],
+        data: reasons.map((reason, index) => ({
+          name: reason,
+          value: data[index]
+        })),
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+  
+  attritionChart.setOption(option)
+}
+ 
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+  generateMockData()
+  initCharts()
+  startAutoRefresh()
+})
+
+onUnmounted(() => {
+  stopAutoRefresh()
+})
+</script>
+
+<style scoped>
+.analytics-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 0 15px 0;
+}
+
+.header-controls {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 20px;
+}
+
+.refresh-btn {
+  margin-left: 20px;
+}
+
+.metrics-cards {
+  margin-bottom: 30px;
+}
+
+.metric-card {
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  border: none;
+  overflow: hidden;
+}
+
+.metric-card:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.metric-card.primary {
+  border-left: 4px solid #409EFF;
+  background: linear-gradient(135deg, #409EFF 0%, #36a3f7 100%);
+}
+
+.metric-card.danger {
+  border-left: 4px solid #F56C6C;
+  background: linear-gradient(135deg, #F56C6C 0%, #f78989 100%);
+}
+
+.metric-card.success {
+  border-left: 4px solid #67C23A;
+  background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
+}
+
+.metric-card.warning {
+  border-left: 4px solid #E6A23C;
+  background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%);
+}
+
+.card-content {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+}
+
+.card-icon {
+  margin-right: 20px;
+  color: white;
+}
+
+.card-info {
+  flex: 1;
+}
+
+.card-number {
+  font-size: 32px;
+  font-weight: 600;
+  color: white;
+  margin-bottom: 5px;
+}
+
+.card-label {
+  font-size: 14px;
+  color: rgba(255, 255, 255, 0.9);
+  margin-bottom: 5px;
+}
+
+.card-trend {
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.card-trend.positive {
+  color: #67C23A;
+}
+
+.card-trend.negative {
+  color: #F56C6C;
+}
+
+.charts-section {
+  margin-bottom: 30px;
+}
+
+.chart-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;
+  padding: 15px 20px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.chart-container {
+  height: 350px;
+  padding: 20px;
+}
+
+.chart {
+  width: 100%;
+  height: 100%;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+  .analytics-container {
+    padding: 10px;
+  }
+  
+  .page-header {
+    padding: 15px;
+  }
+  
+  .page-header h2 {
+    font-size: 24px;
+  }
+  
+  .header-controls {
+    flex-direction: column;
+    gap: 15px;
+  }
+  
+  .refresh-btn {
+    margin-left: 0;
+  }
+  
+  .metrics-cards .el-col {
+    margin-bottom: 15px;
+  }
+  
+  .charts-section .el-col {
+    margin-bottom: 20px;
+  }
+  
+  .chart-container {
+    height: 300px;
+  }
+}
+
+@media (max-width: 480px) {
+  .page-header h2 {
+    font-size: 20px;
+  }
+  
+  .card-number {
+    font-size: 24px;
+  }
+  
+  .chart-container {
+    height: 250px;
+  }
+}
+</style>
diff --git a/src/views/personnelManagement/scheduling/index.vue b/src/views/personnelManagement/scheduling/index.vue
new file mode 100644
index 0000000..8a7174d
--- /dev/null
+++ b/src/views/personnelManagement/scheduling/index.vue
@@ -0,0 +1,634 @@
+<template>
+  <div class="app-container scheduling-container">
+    <!-- 绛涢�夊尯鍩� -->
+    <div class="filter-section">
+      <el-form :inline="true" :model="filterForm" class="filter-form">
+        <el-form-item label="鍛樺伐濮撳悕锛�">
+          <el-input
+            v-model="filterForm.employeeName"
+            placeholder="璇疯緭鍏ュ憳宸ュ鍚�"
+            clearable
+            style="width: 150px"
+          />
+        </el-form-item>
+        <el-form-item label="鐝绫诲瀷锛�">
+          <el-select v-model="filterForm.shiftType" placeholder="璇烽�夋嫨鐝" clearable style="width: 120px">
+            <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="filterForm.dateRange"
+            type="daterange"
+            range-separator="鑷�"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            style="width: 250px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleFilter">
+            <el-icon><Search /></el-icon>
+            绛涢��
+          </el-button>
+          <el-button @click="resetFilter">
+            <el-icon><Refresh /></el-icon>
+            閲嶇疆
+          </el-button>
+          <el-button type="primary" @click="openScheduleDialog('add')">
+          <el-icon><Plus /></el-icon>
+          鏂板鎺掔彮
+        </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <!-- 鎺掔彮琛ㄦ牸 -->
+    <div class="table-section">
+      <el-table
+        :data="filteredScheduleList"
+        border
+        stripe
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="employeeName" label="鍛樺伐濮撳悕" width="120" />
+        <el-table-column prop="employeeId" label="鍛樺伐宸ュ彿" width="100" />
+        <el-table-column prop="department" label="閮ㄩ棬" width="120" />
+        <el-table-column prop="shiftType" label="鐝绫诲瀷" width="100">
+          <template #default="scope">
+            <el-tag :type="getShiftTagType(scope.row.shiftType)">
+              {{ scope.row.shiftType }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="workDate" label="宸ヤ綔鏃ユ湡" width="120" />
+        <el-table-column prop="startTime" label="寮�濮嬫椂闂�" width="100" />
+        <el-table-column prop="endTime" label="缁撴潫鏃堕棿" width="100" />
+        <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" width="100">
+          <template #default="scope">
+            {{ scope.row.workHours }}灏忔椂
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="鐘舵��" width="100">
+          <template #default="scope">
+            <el-tag :type="getStatusTagType(scope.row.status)">
+              {{ scope.row.status }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="remark" label="澶囨敞" min-width="150" />
+        <el-table-column label="鎿嶄綔" width="200" fixed="right">
+          <template #default="scope">
+            <el-button
+              type="primary"
+              size="small"
+              @click="openScheduleDialog('edit', scope.row)"
+            >
+              缂栬緫
+            </el-button>
+            <el-button
+              type="danger"
+              size="small"
+              @click="handleDelete(scope.row)"
+            >
+              鍒犻櫎
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 鎵归噺鎿嶄綔 -->
+    <div class="batch-actions" v-if="selectedRows.length > 0">
+      <el-button
+        type="danger"
+        @click="handleBatchDelete"
+        :disabled="selectedRows.length === 0"
+      >
+        鎵归噺鍒犻櫎 ({{ selectedRows.length }})
+      </el-button>
+    </div>
+
+    <!-- 鎺掔彮鏂板/缂栬緫瀵硅瘽妗� -->
+    <el-dialog
+      v-model="scheduleDialog"
+      :title="dialogType === 'add' ? '鏂板鎺掔彮' : '缂栬緫鎺掔彮'"
+      width="700px"
+      @close="closeScheduleDialog"
+    >
+      <el-form
+        :model="scheduleForm"
+        :rules="scheduleRules"
+        ref="scheduleFormRef"
+        label-width="120px"
+      >
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鍛樺伐濮撳悕锛�" prop="employeeName">
+              <el-input v-model="scheduleForm.employeeName" placeholder="璇疯緭鍏ュ憳宸ュ鍚�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍛樺伐宸ュ彿锛�" prop="employeeId">
+              <el-input v-model="scheduleForm.employeeId" placeholder="璇疯緭鍏ュ憳宸ュ伐鍙�" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="閮ㄩ棬锛�" prop="department">
+              <el-select v-model="scheduleForm.department" placeholder="璇烽�夋嫨閮ㄩ棬" style="width: 100%">
+                <el-option label="鎶�鏈儴" value="鎶�鏈儴" />
+                <el-option label="閿�鍞儴" value="閿�鍞儴" />
+                <el-option label="浜轰簨閮�" value="浜轰簨閮�" />
+                <el-option label="璐㈠姟閮�" value="璐㈠姟閮�" />
+                <el-option label="鐢熶骇閮�" value="鐢熶骇閮�" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鐝绫诲瀷锛�" prop="shiftType">
+              <el-select v-model="scheduleForm.shiftType" placeholder="璇烽�夋嫨鐝" style="width: 100%">
+                <el-option label="鏃╃彮" value="鏃╃彮" />
+                <el-option label="涓彮" value="涓彮" />
+                <el-option label="鏅氱彮" value="鏅氱彮" />
+                <el-option label="澶滅彮" value="澶滅彮" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="宸ヤ綔鏃ユ湡锛�" prop="workDate">
+              <el-date-picker
+                v-model="scheduleForm.workDate"
+                type="date"
+                placeholder="閫夋嫨宸ヤ綔鏃ユ湡"
+                style="width: 100%"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鐘舵�侊細" prop="status">
+              <el-select v-model="scheduleForm.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+                <el-option label="宸插畨鎺�" value="宸插畨鎺�" />
+                <el-option label="宸茬‘璁�" value="宸茬‘璁�" />
+                <el-option label="宸插畬鎴�" value="宸插畬鎴�" />
+                <el-option label="宸插彇娑�" value="宸插彇娑�" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="寮�濮嬫椂闂达細" prop="startTime">
+              <el-time-picker
+                v-model="scheduleForm.startTime"
+                placeholder="閫夋嫨寮�濮嬫椂闂�"
+                style="width: 100%"
+                format="HH:mm"
+                value-format="HH:mm"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="缁撴潫鏃堕棿锛�" prop="endTime">
+              <el-time-picker
+                v-model="scheduleForm.endTime"
+                placeholder="閫夋嫨缁撴潫鏃堕棿"
+                style="width: 100%"
+                format="HH:mm"
+                value-format="HH:mm"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="澶囨敞锛�" prop="remark">
+              <el-input
+                v-model="scheduleForm.remark"
+                type="textarea"
+                :rows="3"
+                placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitScheduleForm">纭</el-button>
+          <el-button @click="closeScheduleDialog">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus, Download, Search, Refresh } from '@element-plus/icons-vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const scheduleDialog = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+const scheduleFormRef = ref()
+
+// 绛涢�夎〃鍗�
+const filterForm = reactive({
+  employeeName: '',
+  shiftType: '',
+  dateRange: []
+})
+
+// 鎺掔彮琛ㄥ崟
+const scheduleForm = reactive({
+  id: '',
+  employeeName: '',
+  employeeId: '',
+  department: '',
+  shiftType: '',
+  workDate: '',
+  startTime: '',
+  endTime: '',
+  workHours: 0,
+  status: '宸插畨鎺�',
+  remark: ''
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const scheduleRules = reactive({
+  employeeName: [{ required: true, message: '璇疯緭鍏ュ憳宸ュ鍚�', trigger: 'blur' }],
+  employeeId: [{ required: true, message: '璇疯緭鍏ュ憳宸ュ伐鍙�', trigger: 'blur' }],
+  department: [{ required: true, message: '璇烽�夋嫨閮ㄩ棬', trigger: 'change' }],
+  shiftType: [{ required: true, message: '璇烽�夋嫨鐝绫诲瀷', trigger: 'change' }],
+  workDate: [{ required: true, message: '璇烽�夋嫨宸ヤ綔鏃ユ湡', trigger: 'change' }],
+  startTime: [{ required: true, message: '璇烽�夋嫨寮�濮嬫椂闂�', trigger: 'change' }],
+  endTime: [{ required: true, message: '璇烽�夋嫨缁撴潫鏃堕棿', trigger: 'change' }],
+  status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+})
+
+// 妯℃嫙鎺掔彮鏁版嵁
+const scheduleList = ref([
+  {
+    id: 1,
+    employeeName: '寮犳捣娲�',
+    employeeId: 'EMP001',
+    department: '鎶�鏈儴',
+    shiftType: '鏃╃彮',
+    workDate: '2024-01-15',
+    startTime: '08:00',
+    endTime: '17:00',
+    workHours: 9,
+    status: '宸插畨鎺�',
+    remark: '姝e父鎺掔彮'
+  },
+  {
+    id: 2,
+    employeeName: '鏉庤秴',
+    employeeId: 'EMP002',
+    department: '閿�鍞儴',
+    shiftType: '涓彮',
+    workDate: '2024-01-15',
+    startTime: '14:00',
+    endTime: '22:00',
+    workHours: 8,
+    status: '宸茬‘璁�',
+    remark: '瀹㈡埛浼氳'
+  },
+  {
+    id: 3,
+    employeeName: '鐜嬫澃',
+    employeeId: 'EMP003',
+    department: '鐢熶骇閮�',
+    shiftType: '鏅氱彮',
+    workDate: '2024-01-15',
+    startTime: '22:00',
+    endTime: '06:00',
+    workHours: 8,
+    status: '宸插畨鎺�',
+    remark: '澶滅彮鐢熶骇'
+  }
+])
+
+// 璁$畻灞炴�э細绛涢�夊悗鐨勬帓鐝垪琛�
+const filteredScheduleList = computed(() => {
+  let result = scheduleList.value
+
+  if (filterForm.employeeName) {
+    result = result.filter(item => 
+      item.employeeName.includes(filterForm.employeeName)
+    )
+  }
+
+  if (filterForm.shiftType) {
+    result = result.filter(item => item.shiftType === filterForm.shiftType)
+  }
+
+  if (filterForm.dateRange && filterForm.dateRange.length === 2) {
+    result = result.filter(item => {
+      const workDate = new Date(item.workDate)
+      const startDate = new Date(filterForm.dateRange[0])
+      const endDate = new Date(filterForm.dateRange[1])
+      return workDate >= startDate && workDate <= endDate
+    })
+  }
+
+  return result
+})
+
+// 鑾峰彇鐝鏍囩绫诲瀷
+const getShiftTagType = (shiftType) => {
+  const typeMap = {
+    '鏃╃彮': 'success',
+    '涓彮': 'warning',
+    '鏅氱彮': 'info',
+    '澶滅彮': 'danger'
+  }
+  return typeMap[shiftType] || 'info'
+}
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusTagType = (status) => {
+  const typeMap = {
+    '宸插畨鎺�': 'info',
+    '宸茬‘璁�': 'warning',
+    '宸插畬鎴�': 'success',
+    '宸插彇娑�': 'danger'
+  }
+  return typeMap[status] || 'info'
+}
+
+// 绛涢��
+const handleFilter = () => {
+  // 绛涢�夐�昏緫宸插湪璁$畻灞炴�т腑瀹炵幇
+}
+
+// 閲嶇疆绛涢��
+const resetFilter = () => {
+  filterForm.employeeName = ''
+  filterForm.shiftType = ''
+  filterForm.dateRange = []
+}
+
+// 鎵撳紑鎺掔彮瀵硅瘽妗�
+const openScheduleDialog = (type, data) => {
+  dialogType.value = type
+  scheduleDialog.value = true
+  
+  if (type === 'edit' && data) {
+    // 缂栬緫妯″紡锛屽鍒舵暟鎹�
+    Object.assign(scheduleForm, { ...data })
+  } else {
+    // 鏂板妯″紡锛岄噸缃〃鍗�
+    Object.keys(scheduleForm).forEach(key => {
+      scheduleForm[key] = ''
+    })
+    scheduleForm.status = '宸插畨鎺�'
+    scheduleForm.workDate = new Date().toISOString().split('T')[0]
+  }
+}
+
+// 鍏抽棴鎺掔彮瀵硅瘽妗�
+const closeScheduleDialog = () => {
+  scheduleFormRef.value?.resetFields()
+  scheduleDialog.value = false
+}
+
+// 璁$畻宸ヤ綔鏃堕暱
+const calculateWorkHours = () => {
+  if (scheduleForm.startTime && scheduleForm.endTime) {
+    const start = new Date(`2000-01-01 ${scheduleForm.startTime}`)
+    const end = new Date(`2000-01-01 ${scheduleForm.endTime}`)
+    
+    if (end < start) {
+      // 璺ㄥぉ鐨勬儏鍐�
+      end.setDate(end.getDate() + 1)
+    }
+    
+    const diffMs = end - start
+    const diffHours = diffMs / (1000 * 60 * 60)
+    scheduleForm.workHours = Math.round(diffHours * 100) / 100
+  }
+}
+
+// 鎻愪氦鎺掔彮琛ㄥ崟
+const submitScheduleForm = () => {
+  scheduleFormRef.value.validate((valid) => {
+    if (valid) {
+      // 璁$畻宸ヤ綔鏃堕暱
+      calculateWorkHours()
+      
+      if (dialogType.value === 'add') {
+        // 鏂板
+        const newSchedule = {
+          ...scheduleForm,
+          id: Date.now() // 浣跨敤鏃堕棿鎴充綔涓轰复鏃禝D
+        }
+        scheduleList.value.push(newSchedule)
+        ElMessage.success('鏂板鎺掔彮鎴愬姛')
+      } else {
+        // 缂栬緫
+        const index = scheduleList.value.findIndex(item => item.id === scheduleForm.id)
+        if (index !== -1) {
+          scheduleList.value[index] = { ...scheduleForm }
+          ElMessage.success('缂栬緫鎺掔彮鎴愬姛')
+        }
+      }
+      
+      closeScheduleDialog()
+    }
+  })
+}
+
+// 鍒犻櫎鎺掔彮
+const handleDelete = (row) => {
+  ElMessageBox.confirm(
+    `纭畾瑕佸垹闄� ${row.employeeName} 鐨勬帓鐝褰曞悧锛焋,
+    '鍒犻櫎鎻愮ず',
+    {
+      confirmButtonText: '纭',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
+    }
+  ).then(() => {
+    const index = scheduleList.value.findIndex(item => item.id === row.id)
+    if (index !== -1) {
+      scheduleList.value.splice(index, 1)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+    }
+  }).catch(() => {
+    ElMessage.info('宸插彇娑堝垹闄�')
+  })
+}
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑璁板綍')
+    return
+  }
+  
+  ElMessageBox.confirm(
+    `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉℃帓鐝褰曞悧锛焋,
+    '鎵归噺鍒犻櫎鎻愮ず',
+    {
+      confirmButtonText: '纭',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
+    }
+  ).then(() => {
+    const selectedIds = selectedRows.value.map(row => row.id)
+    scheduleList.value = scheduleList.value.filter(item => !selectedIds.includes(item.id))
+    selectedRows.value = []
+    ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+  }).catch(() => {
+    ElMessage.info('宸插彇娑堝垹闄�')
+  })
+}
+
+// 閫夋嫨鍙樺寲浜嬩欢
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection
+}
+
+// 鐩戝惉鏃堕棿鍙樺寲锛岃嚜鍔ㄨ绠楀伐浣滄椂闀�
+const watchTimeChange = () => {
+  if (scheduleForm.startTime && scheduleForm.endTime) {
+    calculateWorkHours()
+  }
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+  // 椤甸潰鍒濆鍖�
+})
+</script>
+
+<style scoped>
+.scheduling-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 0 15px 0;
+}
+
+.header-controls {
+  display: flex;
+  justify-content: center;
+  gap: 15px;
+}
+
+.filter-section {
+  background: white;
+  padding: 20px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.filter-form {
+  margin: 0;
+}
+
+.table-section {
+  background: white;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
+}
+
+.batch-actions {
+  background: white;
+  padding: 15px 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.dialog-footer {
+  text-align: right;
+}
+
+:deep(.el-form-item__label) {
+  font-weight: 500;
+  color: #303133;
+}
+
+:deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px #dcdfe6 inset;
+}
+
+:deep(.el-input__wrapper:hover) {
+  box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+
+:deep(.el-input__wrapper.is-focus) {
+  box-shadow: 0 0 0 1px #409eff inset;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+  .scheduling-container {
+    padding: 10px;
+  }
+  
+  .page-header {
+    padding: 15px;
+  }
+  
+  .page-header h2 {
+    font-size: 24px;
+  }
+  
+  .header-controls {
+    flex-direction: column;
+    gap: 10px;
+  }
+}
+
+@media (max-width: 768px) {
+  .filter-form .el-form-item {
+    margin-bottom: 10px;
+  }
+}
+</style>
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