refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue - 移除开发者文档页面及其导航与样式实现 - 清理开发者侧功能完善工作日志文件 - 删除全局.gitignore配置文件,清理无用忽略规则 - 优化应用配置页面的参数读取和路由结构,解决刷新404问题 - 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入 - 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
144
app/pages/admin/management/decision.vue
Normal file
144
app/pages/admin/management/decision.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
const { activeTab } = useNav()
|
||||
activeTab.value = 'management-decision'
|
||||
|
||||
const kpis = ref([
|
||||
{ name: '设备综合效率 OEE', value: 78.5, target: 85, unit: '%', trend: '+2.3%', color: '#6366f1' },
|
||||
{ name: '订单准时交付率', value: 96.2, target: 98, unit: '%', trend: '+1.5%', color: '#10b981' },
|
||||
{ name: '产品一次合格率', value: 97.8, target: 98.5, unit: '%', trend: '+0.3%', color: '#3b82f6' },
|
||||
{ name: '人均产值', value: 8.5, target: 9.0, unit: '万/月', trend: '+0.3', color: '#f59e0b' },
|
||||
])
|
||||
|
||||
const analysis = ref([
|
||||
{ title: '生产效能分析', desc: '本月产能利用率达85%,较上月提升5个百分点', type: 'production', icon: '📊' },
|
||||
{ title: '质量趋势分析', desc: '近30天良品率稳定在97%以上,呈上升趋势', type: 'quality', icon: '✅' },
|
||||
{ title: '成本分析报告', desc: '本月生产成本控制良好,材料利用率提升3%', type: 'cost', icon: '💰' },
|
||||
{ title: '库存周转分析', desc: '库存周转天数28天,处于健康水平', type: 'inventory', icon: '📦' },
|
||||
])
|
||||
|
||||
const typeColor: Record<string, string> = {
|
||||
production: 'indigo',
|
||||
quality: 'green',
|
||||
cost: 'orange',
|
||||
inventory: 'blue',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">决策支持</h2>
|
||||
<a-button @click="() => {}">导出报告</a-button>
|
||||
</div>
|
||||
|
||||
<!-- KPI 指标卡 -->
|
||||
<a-row :gutter="[16, 16]" class="mb-6">
|
||||
<a-col :xs="24" :sm="12" :xl="6" v-for="kpi in kpis" :key="kpi.name">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-header">
|
||||
<span class="kpi-name">{{ kpi.name }}</span>
|
||||
<span class="kpi-trend">{{ kpi.trend }}</span>
|
||||
</div>
|
||||
<div class="kpi-value" :style="{ color: kpi.color }">{{ kpi.value }}<span class="kpi-unit">{{ kpi.unit }}</span></div>
|
||||
<div class="kpi-bar">
|
||||
<a-progress :percent="Math.round(kpi.value / kpi.target * 100)" :showInfo="false" :strokeColor="kpi.color" size="small" />
|
||||
<span class="kpi-target">目标: {{ kpi.target }}{{ kpi.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="[20, 20]">
|
||||
<a-col :xs="24" :xl="16">
|
||||
<div class="card">
|
||||
<div class="card-title">经营分析报告</div>
|
||||
<div class="analysis-list">
|
||||
<div v-for="item in analysis" :key="item.title" class="analysis-item">
|
||||
<div class="analysis-icon" :class="item.type">{{ item.icon }}</div>
|
||||
<div class="analysis-body">
|
||||
<div class="analysis-title">{{ item.title }}</div>
|
||||
<div class="analysis-desc">{{ item.desc }}</div>
|
||||
</div>
|
||||
<a-button type="link" size="small">查看详情</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :xl="8">
|
||||
<div class="card">
|
||||
<div class="card-title">数据趋势</div>
|
||||
<div class="trend-list">
|
||||
<div class="trend-item">
|
||||
<div class="trend-label">营收趋势</div>
|
||||
<div class="trend-bar">
|
||||
<div class="trend-fill" style="width: 72%; background: #10b981;"></div>
|
||||
</div>
|
||||
<div class="trend-val">72%</div>
|
||||
</div>
|
||||
<div class="trend-item">
|
||||
<div class="trend-label">利润趋势</div>
|
||||
<div class="trend-bar">
|
||||
<div class="trend-fill" style="width: 65%; background: #6366f1;"></div>
|
||||
</div>
|
||||
<div class="trend-val">65%</div>
|
||||
</div>
|
||||
<div class="trend-item">
|
||||
<div class="trend-label">产能趋势</div>
|
||||
<div class="trend-bar">
|
||||
<div class="trend-fill" style="width: 85%; background: #f59e0b;"></div>
|
||||
</div>
|
||||
<div class="trend-val">85%</div>
|
||||
</div>
|
||||
<div class="trend-item">
|
||||
<div class="trend-label">质量趋势</div>
|
||||
<div class="trend-bar">
|
||||
<div class="trend-fill" style="width: 78%; background: #3b82f6;"></div>
|
||||
</div>
|
||||
<div class="trend-val">78%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-container { padding: 24px; }
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; color: #1f2937; margin: 0; }
|
||||
|
||||
.kpi-card { background: white; border-radius: 12px; padding: 18px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
|
||||
.kpi-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.kpi-name { font-size: 13px; color: #6b7280; }
|
||||
.kpi-trend { font-size: 12px; color: #10b981; font-weight: 600; }
|
||||
.kpi-value { font-size: 30px; font-weight: 700; }
|
||||
.kpi-unit { font-size: 13px; font-weight: 400; color: #9ca3af; margin-left: 2px; }
|
||||
.kpi-bar { margin-top: 10px; }
|
||||
.kpi-target { font-size: 11px; color: #9ca3af; margin-top: 4px; display: block; }
|
||||
|
||||
.card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
|
||||
.card-title { font-size: 16px; font-weight: 600; color: #1f2937; margin-bottom: 16px; }
|
||||
|
||||
.analysis-list { display: flex; flex-direction: column; gap: 14px; }
|
||||
.analysis-item { display: flex; align-items: center; gap: 14px; padding: 14px; background: #fafafa; border-radius: 10px; }
|
||||
.analysis-icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; background: #f0f0f0; }
|
||||
.analysis-icon.production { background: #eef2ff; }
|
||||
.analysis-icon.quality { background: #ecfdf5; }
|
||||
.analysis-icon.cost { background: #fffbeb; }
|
||||
.analysis-icon.inventory { background: #eff6ff; }
|
||||
.analysis-body { flex: 1; }
|
||||
.analysis-title { font-size: 14px; font-weight: 600; color: #374151; margin-bottom: 4px; }
|
||||
.analysis-desc { font-size: 12px; color: #9ca3af; }
|
||||
|
||||
.trend-list { display: flex; flex-direction: column; gap: 16px; }
|
||||
.trend-item { display: flex; align-items: center; gap: 10px; }
|
||||
.trend-label { width: 80px; font-size: 13px; color: #6b7280; }
|
||||
.trend-bar { flex: 1; height: 8px; background: #f0f0f0; border-radius: 4px; overflow: hidden; }
|
||||
.trend-fill { height: 100%; border-radius: 4px; }
|
||||
.trend-val { width: 40px; text-align: right; font-size: 13px; font-weight: 600; color: #374151; }
|
||||
|
||||
.mb-6 { margin-bottom: 20px; }
|
||||
</style>
|
||||
383
app/pages/admin/management/finance.vue
Normal file
383
app/pages/admin/management/finance.vue
Normal file
@@ -0,0 +1,383 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
|
||||
// 财务统计
|
||||
const financeStats = ref([
|
||||
{ label: '本月收入', value: '128.5', unit: '万', icon: 'fa-arrow-down', gradient: 'from-green-500 to-teal-500', change: '+18%', up: true },
|
||||
{ label: '本月支出', value: '89.5', unit: '万', icon: 'fa-arrow-up', gradient: 'from-red-500 to-pink-500', change: '+12%', up: false },
|
||||
{ label: '本月利润', value: '39.0', unit: '万', icon: 'fa-chart-line', gradient: 'from-blue-500 to-purple-500', change: '+25%', up: true },
|
||||
{ label: '应收账款', value: '256.8', unit: '万', icon: 'fa-wallet', gradient: 'from-orange-500 to-yellow-500', change: '-5%', up: true },
|
||||
])
|
||||
|
||||
// 收支记录
|
||||
const transactions = ref([
|
||||
{ id: 'TX-2026040901', type: 'income', category: '销售收款', amount: 58000, customer: '深圳市精密机械有限公司', date: '2026-04-09', status: 'completed' },
|
||||
{ id: 'TX-2026040802', type: 'expense', category: '采购付款', amount: 36000, supplier: '上海五金工具厂', date: '2026-04-08', status: 'completed' },
|
||||
{ id: 'TX-2026040801', type: 'expense', category: '工资支出', amount: 125000, supplier: '人力资源部', date: '2026-04-08', status: 'completed' },
|
||||
{ id: 'TX-2026040703', type: 'income', category: '销售收款', amount: 42000, customer: '东莞市金属材料公司', date: '2026-04-07', status: 'completed' },
|
||||
{ id: 'TX-2026040702', type: 'expense', category: '水电费', amount: 8500, supplier: '电力公司', date: '2026-04-07', status: 'completed' },
|
||||
{ id: 'TX-2026040601', type: 'income', category: '销售收款', amount: 75000, customer: '苏州液压设备厂', date: '2026-04-06', status: 'completed' },
|
||||
{ id: 'TX-2026040501', type: 'expense', category: '设备维修', amount: 12000, supplier: '设备维修部', date: '2026-04-05', status: 'completed' },
|
||||
{ id: 'TX-2026040502', type: 'expense', category: '办公费用', amount: 3200, supplier: '办公用品供应商', date: '2026-04-05', status: 'completed' },
|
||||
])
|
||||
|
||||
// 应收应付
|
||||
const receivables = ref([
|
||||
{ customer: '深圳市精密机械有限公司', amount: 85000, dueDate: '2026-04-15', status: 'pending' },
|
||||
{ customer: '东莞市金属材料公司', amount: 68000, dueDate: '2026-04-20', status: 'pending' },
|
||||
{ customer: '苏州液压设备厂', amount: 42000, dueDate: '2026-04-25', status: 'overdue' },
|
||||
{ customer: '广州电子科技有限公司', amount: 95000, dueDate: '2026-05-01', status: 'pending' },
|
||||
])
|
||||
|
||||
const typeFilter = ref('all')
|
||||
const searchKeyword = ref('')
|
||||
const activeTab = ref('transactions')
|
||||
|
||||
const filteredTransactions = computed(() => {
|
||||
return transactions.value.filter((item) => {
|
||||
const matchType = typeFilter.value === 'all' || item.type === typeFilter.value
|
||||
const matchSearch = !searchKeyword.value ||
|
||||
item.id.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
item.category.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
(item.customer && item.customer.toLowerCase().includes(searchKeyword.value.toLowerCase()))
|
||||
return matchType && matchSearch
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="finance-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-download 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 financeStats"
|
||||
: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">
|
||||
<span class="text-base text-gray-500">¥</span>{{ stat.value }}<span class="text-base text-gray-500 ml-1">{{ stat.unit }}</span>
|
||||
</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="transactions">
|
||||
<i class="fas fa-list mr-1"></i>收支记录
|
||||
</a-radio-button>
|
||||
<a-radio-button value="receivables">
|
||||
<i class="fas fa-hand-holding-usd mr-1"></i>应收账款
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 收支记录 -->
|
||||
<div v-show="activeTab === 'transactions'" class="glass rounded-2xl p-6">
|
||||
<!-- 筛选 -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-gray-600 font-medium">类型筛选:</span>
|
||||
<a-radio-group v-model:value="typeFilter" button-style="solid">
|
||||
<a-radio-button value="all">全部</a-radio-button>
|
||||
<a-radio-button value="income">收入</a-radio-button>
|
||||
<a-radio-button value="expense">支出</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索单号、类别、对象..."
|
||||
style="width: 280px"
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:dataSource="filteredTransactions"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
rowKey="id"
|
||||
:scroll="{ x: 1000 }"
|
||||
>
|
||||
<a-table-column title="单号" dataIndex="id" width="140" />
|
||||
<a-table-column title="类型" dataIndex="type" width="80" align="center">
|
||||
<template #default="{ text }">
|
||||
<a-tag :color="text === 'income' ? 'success' : 'error'">
|
||||
{{ text === 'income' ? '收入' : '支出' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="类别" dataIndex="category" width="120" />
|
||||
<a-table-column title="金额(元)" dataIndex="amount" width="130" align="right">
|
||||
<template #default="{ record, text }">
|
||||
<span class="font-medium" :class="record.type === 'income' ? 'text-green-600' : 'text-red-600'">
|
||||
{{ record.type === 'income' ? '+' : '-' }}¥{{ text.toLocaleString() }}
|
||||
</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="对方" width="200">
|
||||
<template #default="{ record }">
|
||||
<span class="font-medium">{{ record.customer || record.supplier || '-' }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="日期" dataIndex="date" width="120" />
|
||||
<a-table-column title="状态" width="100" align="center">
|
||||
<template #default>
|
||||
<a-tag color="success">已完成</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作" width="100" 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>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 应收账款 -->
|
||||
<div v-show="activeTab === 'receivables'" 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-hand-holding-usd text-orange-500 mr-2"></i>
|
||||
应收账款列表
|
||||
</h3>
|
||||
<a-button type="primary">
|
||||
<template #icon><i class="fas fa-plus mr-1"></i></template>
|
||||
登记收款
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:dataSource="receivables"
|
||||
:pagination="false"
|
||||
rowKey="customer"
|
||||
>
|
||||
<a-table-column title="客户名称" dataIndex="customer">
|
||||
<template #default="{ text }">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center text-white text-xs">
|
||||
<i class="fas fa-building"></i>
|
||||
</div>
|
||||
<span class="font-medium">{{ text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="应收金额(元)" dataIndex="amount" width="150" align="right">
|
||||
<template #default="{ text }">
|
||||
<span class="font-medium text-orange-600">¥{{ text.toLocaleString() }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="到期日期" dataIndex="dueDate" width="140" />
|
||||
<a-table-column title="状态" dataIndex="status" width="100" align="center">
|
||||
<template #default="{ text }">
|
||||
<a-tag v-if="text === 'pending'" color="processing">待收款</a-tag>
|
||||
<a-tag v-else-if="text === 'overdue'" color="error">已逾期</a-tag>
|
||||
<a-tag v-else color="success">已收款</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作" width="160" align="center" fixed="right">
|
||||
<template #default>
|
||||
<div class="flex items-center gap-2 justify-center">
|
||||
<a-button type="primary" size="small" ghost>催款</a-button>
|
||||
<a-button type="link" size="small">详情</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.finance-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);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.text-3xl {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rounded-2xl {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.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-600 {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.text-green-600 {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.text-red-600 {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.text-orange-600 {
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
477
app/pages/admin/management/office.vue
Normal file
477
app/pages/admin/management/office.vue
Normal file
@@ -0,0 +1,477 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
|
||||
// 协同办公统计
|
||||
const officeStats = ref([
|
||||
{ label: '公告通知', value: 12, icon: 'fa-bullhorn', gradient: 'from-red-500 to-pink-500', change: '+3', up: true },
|
||||
{ label: '待审批', value: 8, icon: 'fa-clock', gradient: 'from-orange-500 to-yellow-500', change: '-2', up: true },
|
||||
{ label: '已完成', value: 156, icon: 'fa-check-circle', gradient: 'from-green-500 to-teal-500', change: '+25', up: true },
|
||||
{ label: '会议预约', value: 5, icon: 'fa-video', gradient: 'from-blue-500 to-purple-500', change: '+1', up: false },
|
||||
])
|
||||
|
||||
// 公告列表
|
||||
const announcements = ref([
|
||||
{ id: 'NOTICE-001', title: '关于清明节放假安排的通知', author: '人事行政部', date: '2026-04-01', views: 1258, important: true, content: '清明节放假时间为4月4日至4月6日,共3天...' },
|
||||
{ id: 'NOTICE-002', title: '2026年第一季度财报公告', author: '财务部', date: '2026-03-28', views: 986, important: true, content: '公司2026年第一季度营收同比增长18%...' },
|
||||
{ id: 'NOTICE-003', title: '新版本系统功能更新说明', author: '技术部', date: '2026-03-25', views: 756, important: false, content: '本次更新新增设备管理模块...' },
|
||||
{ id: 'NOTICE-004', title: '生产车间设备维护通知', author: '生产部', date: '2026-03-20', views: 423, important: false, content: '2号车间将于本周六进行设备维护...' },
|
||||
])
|
||||
|
||||
// 审批列表
|
||||
const approvals = ref([
|
||||
{ id: 'APPR-001', type: 'leave', title: '张三年假申请', applicant: '张三', department: '生产部', amount: '5天', status: 'pending', date: '2026-04-09' },
|
||||
{ id: 'APPR-002', type: 'reimburse', title: '李四差旅费报销', applicant: '李四', department: '技术部', amount: '¥2,580', status: 'pending', date: '2026-04-09' },
|
||||
{ id: 'APPR-003', type: 'purchase', title: '王五办公用品采购', applicant: '王五', department: '采购部', amount: '¥3,200', status: 'pending', date: '2026-04-08' },
|
||||
{ id: 'APPR-004', type: 'leave', title: '赵六病假申请', applicant: '赵六', department: '财务部', amount: '2天', status: 'approved', date: '2026-04-08' },
|
||||
{ id: 'APPR-005', type: 'overtime', title: '孙七加班申请', applicant: '孙七', department: '生产部', amount: '8小时', status: 'approved', date: '2026-04-07' },
|
||||
])
|
||||
|
||||
const typeMap: Record<string, { label: string; color: string }> = {
|
||||
leave: { label: '请假', color: 'blue' },
|
||||
reimburse: { label: '报销', color: 'orange' },
|
||||
purchase: { label: '采购', color: 'purple' },
|
||||
overtime: { label: '加班', color: 'cyan' },
|
||||
}
|
||||
|
||||
const statusMap: Record<string, { label: string; color: string }> = {
|
||||
pending: { label: '待审批', color: 'processing' },
|
||||
approved: { label: '已通过', color: 'success' },
|
||||
rejected: { label: '已驳回', color: 'error' },
|
||||
}
|
||||
|
||||
const activeTab = ref('announcements')
|
||||
const addAnnouncementVisible = ref(false)
|
||||
const addAnnouncementForm = reactive({
|
||||
title: '',
|
||||
content: '',
|
||||
important: false,
|
||||
})
|
||||
|
||||
const approveVisible = ref(false)
|
||||
const selectedApproval = ref<typeof approvals.value[0] | null>(null)
|
||||
|
||||
function showApproveDetail(item: typeof approvals.value[0]) {
|
||||
selectedApproval.value = item
|
||||
approveVisible.value = true
|
||||
}
|
||||
|
||||
function handleApprove(status: string) {
|
||||
approveVisible.value = false
|
||||
message.success(status === 'approved' ? '已通过审批' : '已驳回申请')
|
||||
}
|
||||
|
||||
function handleAddAnnouncement() {
|
||||
addAnnouncementVisible.value = false
|
||||
message.success('公告发布成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="office-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 @click="addAnnouncementVisible = true">
|
||||
<template #icon><i class="fas fa-bullhorn 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 officeStats"
|
||||
: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="announcements">
|
||||
<i class="fas fa-bullhorn mr-1"></i>公告通知
|
||||
</a-radio-button>
|
||||
<a-radio-button value="approvals">
|
||||
<i class="fas fa-tasks mr-1"></i>审批流程
|
||||
</a-radio-button>
|
||||
<a-radio-button value="meetings">
|
||||
<i class="fas fa-video mr-1"></i>会议管理
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 公告通知 -->
|
||||
<div v-show="activeTab === 'announcements'" 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-bullhorn text-red-500 mr-2"></i>
|
||||
最新公告
|
||||
</h3>
|
||||
<a-button type="link">全部公告</a-button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="notice in announcements"
|
||||
:key="notice.id"
|
||||
class="notice-item glass rounded-xl p-5 card-hover cursor-pointer"
|
||||
:class="notice.important ? 'border-l-4 border-red-500' : 'border-l-4 border-blue-500'"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<a-tag v-if="notice.important" color="red">重要</a-tag>
|
||||
<h4 class="font-bold text-gray-800">{{ notice.title }}</h4>
|
||||
</div>
|
||||
<p class="text-gray-500 text-sm mb-3 line-clamp-2">{{ notice.content }}</p>
|
||||
<div class="flex items-center gap-4 text-xs text-gray-400">
|
||||
<span><i class="fas fa-user mr-1"></i>{{ notice.author }}</span>
|
||||
<span><i class="fas fa-clock mr-1"></i>{{ notice.date }}</span>
|
||||
<span><i class="fas fa-eye mr-1"></i>{{ notice.views }} 阅读</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<a-button type="link" size="small">查看</a-button>
|
||||
<a-button type="link" size="small">编辑</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审批流程 -->
|
||||
<div v-show="activeTab === 'approvals'" 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-tasks text-orange-500 mr-2"></i>
|
||||
待审批列表
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:dataSource="approvals"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
rowKey="id"
|
||||
:scroll="{ x: 900 }"
|
||||
>
|
||||
<a-table-column title="类型" dataIndex="type" width="100" align="center">
|
||||
<template #default="{ text }">
|
||||
<a-tag :color="typeMap[text]?.color">{{ typeMap[text]?.label }}</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="标题" dataIndex="title" width="200">
|
||||
<template #default="{ text }">
|
||||
<span class="font-medium">{{ text }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="申请人" dataIndex="applicant" width="100" />
|
||||
<a-table-column title="部门" dataIndex="department" width="100" />
|
||||
<a-table-column title="金额/时长" dataIndex="amount" width="100" align="right">
|
||||
<template #default="{ text }">
|
||||
<span class="font-medium text-orange-600">{{ text }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="状态" dataIndex="status" width="100" align="center">
|
||||
<template #default="{ text }">
|
||||
<a-tag :color="statusMap[text]?.color">{{ statusMap[text]?.label }}</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="申请日期" dataIndex="date" width="120" />
|
||||
<a-table-column title="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ record }">
|
||||
<div class="flex items-center gap-2 justify-center">
|
||||
<a-button type="primary" size="small" ghost @click="showApproveDetail(record)">
|
||||
审批
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 会议管理 -->
|
||||
<div v-show="activeTab === 'meetings'" 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-video text-blue-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>
|
||||
|
||||
<!-- 发布公告弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="addAnnouncementVisible"
|
||||
title="发布公告"
|
||||
@ok="handleAddAnnouncement"
|
||||
ok-text="立即发布"
|
||||
cancel-text="取消"
|
||||
width="600px"
|
||||
>
|
||||
<a-form :model="addAnnouncementForm" layout="vertical">
|
||||
<a-form-item label="公告标题" required>
|
||||
<a-input v-model:value="addAnnouncementForm.title" placeholder="请输入公告标题" />
|
||||
</a-form-item>
|
||||
<a-form-item label="公告内容" required>
|
||||
<a-textarea v-model:value="addAnnouncementForm.content" :rows="5" placeholder="请输入公告内容" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox v-model:checked="addAnnouncementForm.important">设为重要公告</a-checkbox>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 审批详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="approveVisible"
|
||||
:title="`审批 - ${selectedApproval?.title}`"
|
||||
width="500px"
|
||||
:footer="selectedApproval?.status === 'pending' ? [
|
||||
h('a-button', { type: 'primary', onClick: () => handleApprove('approved') }, '批准'),
|
||||
h('a-button', { danger: true, onClick: () => handleApprove('rejected') }, '驳回'),
|
||||
h('a-button', { onClick: () => approveVisible = false }, '取消'),
|
||||
] : null"
|
||||
>
|
||||
<template v-if="selectedApproval">
|
||||
<a-descriptions :column="2" bordered size="small">
|
||||
<a-descriptions-item label="申请类型" :span="2">
|
||||
<a-tag :color="typeMap[selectedApproval.type]?.color">{{ typeMap[selectedApproval.type]?.label }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请人">{{ selectedApproval.applicant }}</a-descriptions-item>
|
||||
<a-descriptions-item label="部门">{{ selectedApproval.department }}</a-descriptions-item>
|
||||
<a-descriptions-item label="申请金额/时长">{{ selectedApproval.amount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="申请日期">{{ selectedApproval.date }}</a-descriptions-item>
|
||||
<a-descriptions-item label="当前状态" :span="2">
|
||||
<a-tag :color="statusMap[selectedApproval.status]?.color">{{ statusMap[selectedApproval.status]?.label }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.office-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);
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
.space-y-4 > * + * {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ml-4 {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.text-3xl {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rounded-2xl {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.rounded-xl {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.p-5 {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.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-orange-600 {
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.border-l-4 {
|
||||
border-left-width: 4px;
|
||||
}
|
||||
|
||||
.border-red-500 {
|
||||
border-left-color: #ef4444;
|
||||
}
|
||||
|
||||
.border-blue-500 {
|
||||
border-left-color: #3b82f6;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user