refactor(developer-config): 移除开发者配置页面相关代码和文档

- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue
- 移除开发者文档页面及其导航与样式实现
- 清理开发者侧功能完善工作日志文件
- 删除全局.gitignore配置文件,清理无用忽略规则
- 优化应用配置页面的参数读取和路由结构,解决刷新404问题
- 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入
- 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
2026-04-09 07:35:34 +08:00
parent 3209d92cc5
commit f9e1286ab1
130 changed files with 18656 additions and 22143 deletions

View 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>