Files
tiantian-system/app/pages/admin/management/office.vue
赵忠林 f9e1286ab1 refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue
- 移除开发者文档页面及其导航与样式实现
- 清理开发者侧功能完善工作日志文件
- 删除全局.gitignore配置文件,清理无用忽略规则
- 优化应用配置页面的参数读取和路由结构,解决刷新404问题
- 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入
- 移除对后端配置加密字段的 secret 标记,修正加密异常问题
2026-04-09 07:35:34 +08:00

478 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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