refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue - 移除开发者文档页面及其导航与样式实现 - 清理开发者侧功能完善工作日志文件 - 删除全局.gitignore配置文件,清理无用忽略规则 - 优化应用配置页面的参数读取和路由结构,解决刷新404问题 - 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入 - 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
453
app/pages/admin/management/hr.vue
Normal file
453
app/pages/admin/management/hr.vue
Normal file
@@ -0,0 +1,453 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
|
||||
// 人事统计
|
||||
const hrStats = ref([
|
||||
{ label: '员工总数', value: 56, icon: 'fa-users', gradient: 'from-blue-500 to-cyan-500', change: '+3', up: true },
|
||||
{ label: '在职', value: 52, icon: 'fa-user-check', gradient: 'from-green-500 to-teal-500', change: '+2', up: true },
|
||||
{ label: '本月入职', value: 3, icon: 'fa-user-plus', gradient: 'from-purple-500 to-pink-500', change: '+2', up: true },
|
||||
{ label: '本月离职', value: 1, icon: 'fa-user-minus', gradient: 'from-orange-500 to-red-500', change: '-1', up: true },
|
||||
])
|
||||
|
||||
// 员工列表
|
||||
const employees = ref([
|
||||
{ id: 'EMP-001', name: '张三', department: '生产部', position: '生产主管', phone: '138****1234', email: 'zhangsan@company.com', status: 'active', joinDate: '2020-03-15' },
|
||||
{ id: 'EMP-002', name: '李四', department: '技术部', position: '技术工程师', phone: '139****5678', email: 'lisi@company.com', status: 'active', joinDate: '2021-06-20' },
|
||||
{ id: 'EMP-003', name: '王五', department: '采购部', position: '采购专员', phone: '137****9012', email: 'wangwu@company.com', status: 'active', joinDate: '2022-01-10' },
|
||||
{ id: 'EMP-004', name: '赵六', department: '财务部', position: '财务经理', phone: '136****3456', email: 'zhaoliu@company.com', status: 'active', joinDate: '2019-08-25' },
|
||||
{ id: 'EMP-005', name: '孙七', department: '人事部', position: '人事专员', phone: '135****7890', email: 'sunqi@company.com', status: 'active', joinDate: '2023-02-15' },
|
||||
{ id: 'EMP-006', name: '周八', department: '生产部', position: '操作工', phone: '134****2345', email: 'zhouba@company.com', status: 'active', joinDate: '2024-01-08' },
|
||||
])
|
||||
|
||||
// 考勤记录
|
||||
const attendanceRecords = ref([
|
||||
{ date: '2026-04-09', total: 56, present: 54, absent: 0, late: 2, leave: 0, off: 0 },
|
||||
{ date: '2026-04-08', total: 56, present: 55, absent: 0, late: 1, leave: 0, off: 0 },
|
||||
{ date: '2026-04-07', total: 56, present: 56, absent: 0, late: 0, leave: 0, off: 0 },
|
||||
{ date: '2026-04-06', total: 56, present: 48, absent: 0, late: 0, leave: 3, off: 5 },
|
||||
])
|
||||
|
||||
// 部门分布
|
||||
const departmentStats = ref([
|
||||
{ name: '生产部', count: 28, percentage: 50 },
|
||||
{ name: '技术部', count: 10, percentage: 18 },
|
||||
{ name: '采购部', count: 6, percentage: 11 },
|
||||
{ name: '财务部', count: 5, percentage: 9 },
|
||||
{ name: '人事部', count: 4, percentage: 7 },
|
||||
{ name: '其他', count: 3, percentage: 5 },
|
||||
])
|
||||
|
||||
const activeTab = ref('employees')
|
||||
const searchKeyword = ref('')
|
||||
|
||||
const filteredEmployees = computed(() => {
|
||||
return employees.value.filter((emp) => {
|
||||
return !searchKeyword.value ||
|
||||
emp.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
emp.id.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
emp.department.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hr-page">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header mb-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-800">人力资源</h2>
|
||||
<p class="text-gray-500 mt-1">管理员工信息、考勤记录和薪资发放</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a-button>
|
||||
<template #icon><i class="fas fa-calendar mr-1"></i></template>
|
||||
考勤统计
|
||||
</a-button>
|
||||
<a-button type="primary">
|
||||
<template #icon><i class="fas fa-plus mr-1"></i></template>
|
||||
新增员工
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-4 gap-6 mb-6">
|
||||
<div
|
||||
v-for="stat in hrStats"
|
||||
:key="stat.label"
|
||||
class="glass rounded-2xl p-6 card-hover cursor-pointer"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-white"
|
||||
:class="`bg-gradient-to-br ${stat.gradient}`"
|
||||
>
|
||||
<i :class="`fas ${stat.icon}`"></i>
|
||||
</div>
|
||||
<span
|
||||
class="text-sm font-medium"
|
||||
:class="stat.up ? 'text-green-500' : 'text-red-500'"
|
||||
>
|
||||
<i :class="stat.up ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
|
||||
{{ stat.change }}
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="text-3xl font-bold text-gray-800 mb-1">{{ stat.value }}</h3>
|
||||
<p class="text-gray-500 text-sm">{{ stat.label }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<div class="glass rounded-2xl p-4 mb-6">
|
||||
<a-radio-group v-model:value="activeTab" button-style="solid">
|
||||
<a-radio-button value="employees">
|
||||
<i class="fas fa-users mr-1"></i>员工管理
|
||||
</a-radio-button>
|
||||
<a-radio-button value="attendance">
|
||||
<i class="fas fa-calendar-check mr-1"></i>考勤记录
|
||||
</a-radio-button>
|
||||
<a-radio-button value="salary">
|
||||
<i class="fas fa-money-bill mr-1"></i>薪资管理
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索员工姓名、工号、部门..."
|
||||
style="width: 280px; float: right"
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 员工列表 -->
|
||||
<div v-show="activeTab === 'employees'" class="glass rounded-2xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-lg text-gray-800">
|
||||
<i class="fas fa-users text-blue-500 mr-2"></i>
|
||||
员工列表
|
||||
</h3>
|
||||
<span class="text-sm text-gray-500">共 {{ filteredEmployees.length }} 名员工</span>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:dataSource="filteredEmployees"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
rowKey="id"
|
||||
:scroll="{ x: 1000 }"
|
||||
>
|
||||
<a-table-column title="工号" dataIndex="id" width="100" />
|
||||
<a-table-column title="姓名" dataIndex="name" width="100">
|
||||
<template #default="{ text }">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center text-white text-sm font-medium">
|
||||
{{ text.charAt(0) }}
|
||||
</div>
|
||||
<span class="font-medium">{{ text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="部门" dataIndex="department" width="100" />
|
||||
<a-table-column title="职位" dataIndex="position" width="120" />
|
||||
<a-table-column title="手机号" dataIndex="phone" width="130" />
|
||||
<a-table-column title="邮箱" dataIndex="email" width="180" />
|
||||
<a-table-column title="入职日期" dataIndex="joinDate" width="120" />
|
||||
<a-table-column title="状态" dataIndex="status" width="80" align="center">
|
||||
<template #default>
|
||||
<a-tag color="success">在职</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作" width="120" align="center" fixed="right">
|
||||
<template #default>
|
||||
<div class="flex items-center gap-2 justify-center">
|
||||
<a-button type="link" size="small" title="详情">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a-button>
|
||||
<a-button type="link" size="small" title="编辑">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 考勤记录 -->
|
||||
<div v-show="activeTab === 'attendance'" class="glass rounded-2xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-lg text-gray-800">
|
||||
<i class="fas fa-calendar-check text-green-500 mr-2"></i>
|
||||
考勤记录
|
||||
</h3>
|
||||
<a-button>导出考勤表</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 今日考勤统计 -->
|
||||
<a-row :gutter="16" class="mb-6">
|
||||
<a-col :span="6">
|
||||
<div class="stat-mini glass rounded-xl p-4 text-center">
|
||||
<div class="text-2xl font-bold text-blue-600">{{ attendanceRecords[0].total }}</div>
|
||||
<div class="text-sm text-gray-500">应到人数</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<div class="stat-mini glass rounded-xl p-4 text-center">
|
||||
<div class="text-2xl font-bold text-green-600">{{ attendanceRecords[0].present }}</div>
|
||||
<div class="text-sm text-gray-500">实际出勤</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<div class="stat-mini glass rounded-xl p-4 text-center">
|
||||
<div class="text-2xl font-bold text-orange-600">{{ attendanceRecords[0].late }}</div>
|
||||
<div class="text-sm text-gray-500">迟到</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<div class="stat-mini glass rounded-xl p-4 text-center">
|
||||
<div class="text-2xl font-bold text-purple-600">{{ attendanceRecords[0].leave + attendanceRecords[0].off }}</div>
|
||||
<div class="text-sm text-gray-500">请假/休息</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
:dataSource="attendanceRecords"
|
||||
:pagination="false"
|
||||
rowKey="date"
|
||||
>
|
||||
<a-table-column title="日期" dataIndex="date" width="120" />
|
||||
<a-table-column title="应到人数" dataIndex="total" width="100" align="center" />
|
||||
<a-table-column title="实际出勤" dataIndex="present" width="100" align="center">
|
||||
<template #default="{ text, record }">
|
||||
<span class="text-green-600 font-medium">{{ text }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="缺勤" dataIndex="absent" width="80" align="center">
|
||||
<template #default="{ text }">
|
||||
<span :class="text > 0 ? 'text-red-600 font-medium' : 'text-gray-400'">{{ text }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="迟到" dataIndex="late" width="80" align="center">
|
||||
<template #default="{ text }">
|
||||
<span :class="text > 0 ? 'text-orange-600 font-medium' : 'text-gray-400'">{{ text }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="请假" dataIndex="leave" width="80" align="center" />
|
||||
<a-table-column title="休息" dataIndex="off" width="80" align="center" />
|
||||
<a-table-column title="出勤率" width="120">
|
||||
<template #default="{ record }">
|
||||
<div class="flex items-center gap-2">
|
||||
<a-progress
|
||||
:percent="Math.round((record.present / record.total) * 100)"
|
||||
:showInfo="false"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
/>
|
||||
<span class="text-sm">{{ Math.round((record.present / record.total) * 100) }}%</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
|
||||
<!-- 部门分布 -->
|
||||
<div class="mt-6">
|
||||
<h4 class="font-bold text-gray-800 mb-4">部门人数分布</h4>
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :span="8" v-for="dept in departmentStats" :key="dept.name">
|
||||
<div class="glass rounded-xl p-4">
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="font-medium">{{ dept.name }}</span>
|
||||
<span class="text-blue-600 font-medium">{{ dept.count }}人</span>
|
||||
</div>
|
||||
<a-progress :percent="dept.percentage" :showInfo="false" stroke-color="#6366f1" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 薪资管理 -->
|
||||
<div v-show="activeTab === 'salary'" class="glass rounded-2xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-lg text-gray-800">
|
||||
<i class="fas fa-money-bill text-green-500 mr-2"></i>
|
||||
薪资发放记录
|
||||
</h3>
|
||||
<a-button type="primary">
|
||||
<template #icon><i class="fas fa-plus mr-1"></i></template>
|
||||
生成工资单
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-empty description="薪资数据将在每月固定日期生成" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hr-page {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-mini {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.grid-cols-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.grid-cols-4 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mt-6 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.text-3xl {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rounded-2xl {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.rounded-xl {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gap-16 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text-gray-800 {
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.text-blue-600 {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.text-green-600 {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.text-orange-600 {
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
.text-purple-600 {
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.text-red-600 {
|
||||
color: #dc2626;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user