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

792
app/pages/oa/projects.vue Normal file
View File

@@ -0,0 +1,792 @@
<template>
<div class="projects-page">
<div class="page-header">
<div class="header-content">
<h1 class="page-title">项目管理</h1>
<p class="page-description">团队协作的核心从规划到交付的全流程管理</p>
</div>
<div class="header-actions">
<a-space>
<a-button type="primary" @click="showCreateModal = true">
<template #icon>
<PlusOutlined />
</template>
新建项目
</a-button>
<a-button @click="exportProjects">导出</a-button>
</a-space>
</div>
</div>
<!-- 项目筛选 -->
<a-card class="filter-card">
<a-row :gutter="[16, 16]">
<a-col :span="24" :md="12" :lg="8">
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索项目名称、负责人..."
allow-clear
@search="handleSearch"
/>
</a-col>
<a-col :span="24" :md="12" :lg="8">
<a-select
v-model:value="filterStatus"
placeholder="项目状态"
style="width: 100%"
allow-clear
>
<a-select-option value="all">全部状态</a-select-option>
<a-select-option value="planning">规划中</a-select-option>
<a-select-option value="in_progress">进行中</a-select-option>
<a-select-option value="delayed">已延期</a-select-option>
<a-select-option value="completed">已完成</a-select-option>
<a-select-option value="archived">已归档</a-select-option>
</a-select>
</a-col>
<a-col :span="24" :md="12" :lg="8">
<a-select
v-model:value="filterOwner"
placeholder="负责人"
style="width: 100%"
allow-clear
>
<a-select-option v-for="owner in owners" :key="owner" :value="owner">
{{ owner }}
</a-select-option>
</a-select>
</a-col>
</a-row>
</a-card>
<!-- 项目网格 -->
<div class="projects-grid">
<a-card
v-for="project in filteredProjects"
:key="project.id"
class="project-card"
:class="{ 'highlight-card': project.priority === 'high' }"
>
<template #title>
<div class="project-title">
<span class="project-icon">{{ project.icon }}</span>
<span>{{ project.name }}</span>
<a-badge v-if="project.isNew" color="blue" text="NEW" class="new-badge" />
</div>
</template>
<div class="project-meta">
<div class="meta-item">
<span class="meta-label">负责人</span>
<span class="meta-value">{{ project.owner }}</span>
</div>
<div class="meta-item">
<span class="meta-label">截止日期</span>
<span class="meta-value" :class="{ 'deadline-warning': isDeadlineWarning(project.deadline) }">
{{ project.deadline }}
</span>
</div>
</div>
<div class="project-progress">
<div class="progress-header">
<span>项目进度</span>
<span>{{ project.progress }}%</span>
</div>
<a-progress
:percent="project.progress"
:stroke-color="getProgressColor(project.progress)"
:show-info="false"
size="small"
/>
</div>
<div class="project-stats">
<div class="stat-item">
<div class="stat-number">{{ project.taskCount }}</div>
<div class="stat-label">任务数</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ project.completedCount }}</div>
<div class="stat-label">已完成</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ project.memberCount }}</div>
<div class="stat-label">成员数</div>
</div>
</div>
<div class="project-tags">
<a-tag v-for="tag in project.tags" :key="tag" color="blue">
{{ tag }}
</a-tag>
</div>
<template #actions>
<a-space class="project-actions">
<a-button type="link" size="small" @click="viewProject(project.id)">
<EyeOutlined />
查看
</a-button>
<a-button type="link" size="small" @click="editProject(project.id)">
<EditOutlined />
编辑
</a-button>
<a-button type="link" size="small" danger @click="archiveProject(project.id)">
<DeleteOutlined />
归档
</a-button>
</a-space>
</template>
</a-card>
</div>
<!-- 空状态 -->
<div v-if="filteredProjects.length === 0" class="empty-state">
<div class="empty-content">
<span class="empty-icon">📋</span>
<h3>暂无项目</h3>
<p>点击"新建项目"按钮开始您的第一个项目</p>
<a-button type="primary" @click="showCreateModal = true">创建项目</a-button>
</div>
</div>
<!-- 新建项目模态框 -->
<a-modal
v-model:open="showCreateModal"
title="新建项目"
:footer="null"
width="600px"
@cancel="resetForm"
>
<a-form
ref="formRef"
:model="newProject"
:rules="formRules"
layout="vertical"
>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item label="项目名称" name="name">
<a-input v-model:value="newProject.name" placeholder="请输入项目名称" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="项目描述" name="description">
<a-textarea
v-model:value="newProject.description"
placeholder="请输入项目描述"
:rows="3"
show-count
:maxlength="500"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="负责人" name="owner">
<a-select
v-model:value="newProject.owner"
placeholder="选择负责人"
style="width: 100%"
>
<a-select-option v-for="owner in owners" :key="owner" :value="owner">
{{ owner }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="优先级" name="priority">
<a-select
v-model:value="newProject.priority"
placeholder="选择优先级"
style="width: 100%"
>
<a-select-option value="low"></a-select-option>
<a-select-option value="medium"></a-select-option>
<a-select-option value="high"></a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="开始日期" name="startDate">
<a-date-picker
v-model:value="newProject.startDate"
placeholder="选择开始日期"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="截止日期" name="deadline">
<a-date-picker
v-model:value="newProject.deadline"
placeholder="选择截止日期"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="项目标签" name="tags">
<a-select
v-model:value="newProject.tags"
mode="tags"
placeholder="输入标签,按回车添加"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item>
<a-space>
<a-button type="primary" :loading="creating" @click="createProject">
创建项目
</a-button>
<a-button @click="resetForm">重置</a-button>
</a-space>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import type { FormInstance } from 'ant-design-vue'
import { PlusOutlined, EyeOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
interface Project {
id: number
name: string
description: string
icon: string
owner: string
progress: number
taskCount: number
completedCount: number
memberCount: number
deadline: string
priority: 'low' | 'medium' | 'high'
status: 'planning' | 'in_progress' | 'delayed' | 'completed' | 'archived'
tags: string[]
isNew: boolean
startDate: string
}
// 数据
const projects = ref<Project[]>([
{
id: 1,
name: '智慧园区管理系统',
description: '现代化的智慧园区综合管理平台,包含安防、能源、物业等功能模块',
icon: '🏢',
owner: '张三',
progress: 85,
taskCount: 42,
completedCount: 36,
memberCount: 8,
deadline: '2024-10-30',
priority: 'high',
status: 'in_progress',
tags: ['智慧园区', '物联网', '大数据'],
isNew: true,
startDate: '2024-08-01'
},
{
id: 2,
name: '客户关系管理升级',
description: 'CRM系统升级优化客户管理流程提升销售效率',
icon: '📊',
owner: '李四',
progress: 45,
taskCount: 28,
completedCount: 13,
memberCount: 6,
deadline: '2024-11-15',
priority: 'medium',
status: 'in_progress',
tags: ['CRM', '销售', '客户管理'],
isNew: false,
startDate: '2024-09-01'
},
{
id: 3,
name: '移动端App开发',
description: '新一代移动应用开发包含iOS和Android双平台',
icon: '📱',
owner: '王五',
progress: 92,
taskCount: 35,
completedCount: 32,
memberCount: 10,
deadline: '2024-10-25',
priority: 'high',
status: 'in_progress',
tags: ['移动端', 'React Native', 'App'],
isNew: false,
startDate: '2024-07-15'
},
{
id: 4,
name: '数据中心建设',
description: '企业级数据中心建设与迁移项目',
icon: '🖥️',
owner: '赵六',
progress: 68,
taskCount: 56,
completedCount: 38,
memberCount: 12,
deadline: '2024-11-05',
priority: 'high',
status: 'delayed',
tags: ['数据中心', '云计算', '服务器'],
isNew: false,
startDate: '2024-06-01'
},
{
id: 5,
name: 'Q4市场活动策划',
description: '第四季度市场推广活动整体策划',
icon: '🎯',
owner: '钱七',
progress: 20,
taskCount: 18,
completedCount: 4,
memberCount: 5,
deadline: '2024-12-10',
priority: 'medium',
status: 'planning',
tags: ['市场活动', '推广', '策划'],
isNew: true,
startDate: '2024-10-01'
},
{
id: 6,
name: '内部分享平台',
description: '企业内部知识分享与学习平台',
icon: '📚',
owner: '孙八',
progress: 100,
taskCount: 24,
completedCount: 24,
memberCount: 4,
deadline: '2024-09-30',
priority: 'low',
status: 'completed',
tags: ['知识管理', '学习平台', '内部工具'],
isNew: false,
startDate: '2024-07-01'
}
])
// 负责人列表
const owners = computed(() => {
const allOwners = projects.value.map(p => p.owner)
return [...new Set(allOwners)]
})
// 筛选条件
const searchKeyword = ref('')
const filterStatus = ref('all')
const filterOwner = ref('')
// 筛选后的项目
const filteredProjects = computed(() => {
return projects.value.filter(project => {
// 搜索关键词
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
const matches = project.name.toLowerCase().includes(keyword) ||
project.description.toLowerCase().includes(keyword) ||
project.owner.toLowerCase().includes(keyword)
if (!matches) return false
}
// 状态筛选
if (filterStatus.value && filterStatus.value !== 'all') {
if (project.status !== filterStatus.value) return false
}
// 负责人筛选
if (filterOwner.value) {
if (project.owner !== filterOwner.value) return false
}
return true
})
})
// 新建项目相关
const showCreateModal = ref(false)
const creating = ref(false)
const formRef = ref<FormInstance>()
const newProject = ref({
name: '',
description: '',
owner: '',
priority: 'medium' as const,
startDate: null,
deadline: null,
tags: []
})
// 表单验证规则
const formRules = {
name: [
{ required: true, message: '请输入项目名称', trigger: 'blur' },
{ min: 2, max: 50, message: '项目名称长度为2-50个字符', trigger: 'blur' }
],
description: [
{ required: true, message: '请输入项目描述', trigger: 'blur' }
],
owner: [
{ required: true, message: '请选择负责人', trigger: 'change' }
]
}
// 方法
function handleSearch() {
// 搜索逻辑已在computed中实现
}
function getProgressColor(progress: number) {
if (progress < 30) return '#ff4d4f'
if (progress < 70) return '#faad14'
return '#52c41a'
}
function isDeadlineWarning(deadline: string) {
const deadlineDate = new Date(deadline)
const today = new Date()
const diffDays = Math.ceil((deadlineDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
return diffDays <= 7 && diffDays > 0
}
function viewProject(id: number) {
navigateTo(`/oa/projects/${id}`)
}
function editProject(id: number) {
const project = projects.value.find(p => p.id === id)
if (project) {
Object.assign(newProject.value, {
name: project.name,
description: project.description,
owner: project.owner,
priority: project.priority,
tags: [...project.tags]
})
showCreateModal.value = true
message.info('编辑功能开发中...')
}
}
function archiveProject(id: number) {
const projectIndex = projects.value.findIndex(p => p.id === id)
if (projectIndex !== -1) {
projects.value[projectIndex].status = 'archived'
message.success('项目已归档')
}
}
function exportProjects() {
const data = JSON.stringify(filteredProjects.value, null, 2)
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `projects-${new Date().getTime()}.json`
link.click()
URL.revokeObjectURL(url)
message.success('导出成功')
}
async function createProject() {
try {
await formRef.value?.validate()
creating.value = true
// 模拟API调用延迟
await new Promise(resolve => setTimeout(resolve, 1000))
const newId = projects.value.length + 1
const project: Project = {
id: newId,
name: newProject.value.name,
description: newProject.value.description,
icon: getRandomIcon(),
owner: newProject.value.owner,
progress: 0,
taskCount: 0,
completedCount: 0,
memberCount: 1,
deadline: newProject.value.deadline?.format('YYYY-MM-DD') || '',
priority: newProject.value.priority,
status: 'planning',
tags: newProject.value.tags,
isNew: true,
startDate: newProject.value.startDate?.format('YYYY-MM-DD') || ''
}
projects.value.unshift(project)
message.success('项目创建成功')
showCreateModal.value = false
resetForm()
} catch (error) {
console.error('创建失败:', error)
} finally {
creating.value = false
}
}
function resetForm() {
newProject.value = {
name: '',
description: '',
owner: '',
priority: 'medium',
startDate: null,
deadline: null,
tags: []
}
formRef.value?.resetFields()
}
function getRandomIcon() {
const icons = ['🏢', '📊', '📱', '🖥️', '🎯', '📚', '🚀', '💼', '🏭', '🏥', '🏫', '🛒']
return icons[Math.floor(Math.random() * icons.length)]
}
onMounted(() => {
// 页面初始化逻辑
})
</script>
<style scoped>
.projects-page {
min-height: calc(100vh - 140px);
max-width: 100%;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.header-content {
flex: 1;
min-width: 0;
}
.page-title {
font-size: 24px;
font-weight: 600;
margin: 0;
color: rgba(0, 0, 0, 0.85);
}
.page-description {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin: 4px 0 0;
}
.header-actions {
flex-shrink: 0;
}
.filter-card {
margin-bottom: 24px;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin-top: 24px;
}
.project-card {
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
height: 100%;
display: flex;
flex-direction: column;
}
.project-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.highlight-card {
border-left: 4px solid #1890ff;
}
.project-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.project-icon {
font-size: 20px;
flex-shrink: 0;
}
.new-badge {
font-size: 11px;
padding: 1px 6px;
border-radius: 4px;
}
.project-meta {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
}
.meta-item {
display: flex;
flex-direction: column;
}
.meta-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 2px;
}
.meta-value {
font-size: 14px;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
}
.deadline-warning {
color: #fa541c;
font-weight: 600;
}
.project-progress {
margin-bottom: 16px;
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.project-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 16px;
}
.stat-item {
text-align: center;
padding: 8px;
background: #fafafa;
border-radius: 6px;
transition: all 0.2s ease;
}
.stat-item:hover {
background: #f0f0f0;
}
.stat-number {
font-size: 18px;
font-weight: 600;
color: #1890ff;
margin-bottom: 2px;
}
.stat-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
}
.project-tags {
margin-top: auto;
margin-bottom: 16px;
}
.project-tags :deep(.ant-tag) {
margin-bottom: 4px;
}
.project-actions {
width: 100%;
display: flex;
justify-content: center;
border-top: 1px solid #f0f0f0;
padding-top: 16px;
margin-top: auto;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
text-align: center;
background: #fafafa;
border-radius: 8px;
margin-top: 24px;
}
.empty-content {
padding: 48px;
}
.empty-icon {
font-size: 64px;
margin-bottom: 16px;
display: block;
}
.empty-state h3 {
font-size: 20px;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 8px;
}
.empty-state p {
color: rgba(0, 0, 0, 0.45);
margin-bottom: 24px;
}
@media (max-width: 768px) {
.projects-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: stretch;
}
.header-actions {
width: 100%;
}
}
</style>