refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue - 移除开发者文档页面及其导航与样式实现 - 清理开发者侧功能完善工作日志文件 - 删除全局.gitignore配置文件,清理无用忽略规则 - 优化应用配置页面的参数读取和路由结构,解决刷新404问题 - 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入 - 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
792
app/pages/oa/projects.vue
Normal file
792
app/pages/oa/projects.vue
Normal 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>
|
||||
1031
app/pages/oa/tasks.vue
Normal file
1031
app/pages/oa/tasks.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user