Files
tiantian-system/app/pages/oa/documents.vue
赵忠林 ecdc1cc986 feat(collaboration): 添加完整协同办公模块及设备管理和文档协同页面
- 新增现代化企业协同办公系统,包含概览仪表板、项目管理、任务看板和文档协同
- 使用Vue 3、TypeScript、Nuxt.js及Ant Design Vue实现前端结构和交互
- 设计响应式布局、渐变背景及毛玻璃视觉效果,优化移动端体验
- 创建设备管理页面,实现设备台账、巡检、维修和报警管理
- 新建文档协同页面,支持文档搜索、筛选、分类显示及多种视图切换
- 配置协同办公导航及布局文件,完善模块化和组件化架构
- 修复管理页语法错误,完善演示数据和统计信息展示
- 提供新建、导入、分享、重命名和删除文档等核心操作功能
- 添加设备健康度及维修记录展示模块,方便车间设备管理和维护
- 更新.gitignore忽略输出目录,提升项目环境整洁性
2026-04-09 12:13:35 +08:00

1307 lines
33 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

<template>
<div class="documents-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-dropdown>
<template #overlay>
<a-menu @click="handleCreateMenuClick">
<a-menu-item key="document">
<div class="menu-item-content">
<span class="menu-icon">📝</span>
<span>新建文档</span>
</div>
</a-menu-item>
<a-menu-item key="spreadsheet">
<div class="menu-item-content">
<span class="menu-icon">📊</span>
<span>新建表格</span>
</div>
</a-menu-item>
<a-menu-item key="presentation">
<div class="menu-item-content">
<span class="menu-icon">📽</span>
<span>新建演示文稿</span>
</div>
</a-menu-item>
<a-menu-item key="board">
<div class="menu-item-content">
<span class="menu-icon">📋</span>
<span>新建白板</span>
</div>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="import">
<div class="menu-item-content">
<span class="menu-icon">📥</span>
<span>导入文件</span>
</div>
</a-menu-item>
</a-menu>
</template>
<a-button type="primary">
新建
<DownOutlined />
</a-button>
</a-dropdown>
</div>
</div>
<!-- 筛选与搜索 -->
<div class="filter-section">
<div class="search-bar">
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索文档、表格、演示文稿..."
allow-clear
@search="handleSearch"
>
<template #prefix>
<SearchOutlined />
</template>
</a-input-search>
</div>
<div class="filter-controls">
<div class="filter-group">
<span class="filter-label">类型:</span>
<div class="filter-tags">
<a-tag
v-for="type in documentTypes"
:key="type.key"
:color="activeType === type.key ? type.color : 'default'"
class="filter-tag"
@click="toggleDocumentType(type.key)"
>
<span>{{ type.icon }}</span>
<span>{{ type.label }}</span>
<span class="type-count">{{ type.count }}</span>
</a-tag>
</div>
</div>
<div class="filter-group">
<span class="filter-label">排序:</span>
<a-select
v-model:value="sortBy"
style="width: 150px"
size="small"
>
<a-select-option value="updated">最近修改</a-select-option>
<a-select-option value="created">创建时间</a-select-option>
<a-select-option value="name">名称</a-select-option>
<a-select-option value="owner">负责人</a-select-option>
</a-select>
</div>
<div class="filter-group">
<a-space>
<a-button :type="viewMode === 'grid' ? 'primary' : 'default'" @click="viewMode = 'grid'">
<AppstoreOutlined />
</a-button>
<a-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
<UnorderedListOutlined />
</a-button>
<a-tooltip title="切换到表格视图">
<a-button disabled>
<TableOutlined />
</a-button>
</a-tooltip>
</a-space>
</div>
</div>
</div>
<!-- 文档列表 -->
<div class="documents-content">
<!-- 网格视图 -->
<div v-if="viewMode === 'grid'" class="documents-grid">
<a-card
v-for="doc in filteredDocuments"
:key="doc.id"
class="document-card"
@click="openDocument(doc.id)"
>
<div class="document-card-inner">
<div class="document-header">
<div class="document-type-icon" :class="`type-${doc.type}`">
{{ doc.type === 'document' ? '📝' :
doc.type === 'spreadsheet' ? '📊' :
doc.type === 'presentation' ? '📽️' : '📋' }}
</div>
<div class="document-badges">
<a-badge v-if="doc.isNew" color="blue" text="NEW" />
<a-badge v-if="doc.isShared" color="green" dot />
<a-badge v-if="doc.isFavorited" color="gold" dot />
</div>
<a-dropdown :trigger="['click']" @click.prevent>
<template #overlay>
<a-menu>
<a-menu-item key="favorite" @click="toggleFavorite(doc)">
<StarOutlined />
{{ doc.isFavorited ? '取消收藏' : '添加到收藏' }}
</a-menu-item>
<a-menu-item key="share" @click="shareDocument(doc)">
<ShareAltOutlined />
分享
</a-menu-item>
<a-menu-item key="duplicate">
<CopyOutlined />
复制
</a-menu-item>
<a-menu-divider />
<a-menu-item key="download">
<DownloadOutlined />
下载副本
</a-menu-item>
<a-menu-divider />
<a-menu-item key="move">
<FolderOpenOutlined />
移动到...
</a-menu-item>
<a-menu-item key="rename" @click="renameDocument(doc)">
<EditOutlined />
重命名
</a-menu-item>
<a-menu-divider />
<a-menu-item key="delete" danger @click="deleteDocument(doc)">
<DeleteOutlined />
删除
</a-menu-item>
</a-menu>
</template>
<a-button type="text" class="more-button">
<MoreOutlined />
</a-button>
</a-dropdown>
</div>
<div class="document-body">
<h4 class="document-title">{{ doc.title }}</h4>
<p class="document-description" v-if="doc.description">
{{ doc.description }}
</p>
<p v-else class="document-description placeholder">
无描述内容
</p>
<div class="document-tags">
<a-tag
v-for="tag in doc.tags"
:key="tag"
size="small"
color="blue"
>
{{ tag }}
</a-tag>
</div>
</div>
<div class="document-footer">
<div class="document-meta">
<div class="meta-item">
<UserOutlined />
<span class="meta-text">{{ doc.owner }}</span>
</div>
<div class="meta-item">
<CalendarOutlined />
<span class="meta-text">{{ doc.lastModified }}</span>
</div>
</div>
<div class="document-collaborators">
<div class="collaborators-count">
<TeamOutlined />
<span>{{ doc.collaboratorsCount }}</span>
</div>
<a-avatar-group :max-count="3" size="small">
<a-avatar v-for="avatar in doc.collaboratorAvatars" :key="avatar" :src="avatar" />
</a-avatar-group>
</div>
</div>
</div>
</a-card>
</div>
<!-- 列表视图 -->
<div v-else class="documents-list">
<a-table
:data-source="filteredDocuments"
:pagination="pagination"
size="middle"
>
<a-table-column key="name" title="名称">
<template #default="{ record }">
<div class="table-document-item" @click="openDocument(record.id)">
<div class="document-icon">
<span :class="`type-icon type-${record.type}`">
{{ record.type === 'document' ? '📝' :
record.type === 'spreadsheet' ? '📊' :
record.type === 'presentation' ? '📽️' : '📋' }}
</span>
</div>
<div class="document-info">
<div class="document-name" :class="{ 'starred': record.isFavorited }">
{{ record.title }}
<span v-if="record.isFavorited" class="star-icon"></span>
</div>
<div class="document-path">{{ record.path }}</div>
</div>
</div>
</template>
</a-table-column>
<a-table-column key="owner" title="负责人" width="120">
<template #default="{ record }">
<div class="table-owner">
<a-avatar :src="record.ownerAvatar" size="small" />
<span class="owner-name">{{ record.owner }}</span>
</div>
</template>
</a-table-column>
<a-table-column key="lastModified" title="最近修改" width="150">
<template #default="{ record }">
<div class="table-time">
{{ record.lastModified }}
</div>
</template>
</a-table-column>
<a-table-column key="size" title="大小" width="100">
<template #default="{ record }">
<div class="table-size">
{{ record.size }}
</div>
</template>
</a-table-column>
<a-table-column key="collaborators" title="协作者" width="120">
<template #default="{ record }">
<div class="table-collaborators">
<a-avatar-group :max-count="2" size="small">
<a-avatar v-for="avatar in record.collaboratorAvatars" :key="avatar" :src="avatar" />
</a-avatar-group>
<span v-if="record.collaboratorsCount > 2" class="more-count">
+{{ record.collaboratorsCount - 2 }}
</span>
</div>
</template>
</a-table-column>
<a-table-column key="actions" title="操作" width="150">
<template #default="{ record }">
<div class="table-actions">
<a-space>
<a-button type="link" size="small" @click="openDocument(record.id)">
打开
</a-button>
<a-dropdown :trigger="['hover']">
<template #overlay>
<a-menu>
<a-menu-item key="share">分享</a-menu-item>
<a-menu-item key="duplicate">复制</a-menu-item>
<a-menu-item key="download">下载</a-menu-item>
<a-menu-item key="delete" danger>删除</a-menu-item>
</a-menu>
</template>
<a-button type="link" size="small">
更多
</a-button>
</a-dropdown>
</a-space>
</div>
</template>
</a-table-column>
</a-table>
</div>
</div>
<!-- 空状态 -->
<div v-if="filteredDocuments.length === 0" class="empty-state">
<div class="empty-content">
<span class="empty-icon">📄</span>
<h3>暂无文档</h3>
<p>点击"新建"按钮创建您的第一个文档或导入现有文件</p>
<a-space>
<a-button type="primary" @click="createDocument">新建文档</a-button>
<a-button @click="importDocument">导入文件</a-button>
</a-space>
</div>
</div>
<!-- 统计信息 -->
<a-card class="stats-card" :bordered="false">
<a-row :gutter="[16, 16]">
<a-col :span="24" :md="6">
<div class="stat-item">
<div class="stat-icon total">
<FileTextOutlined />
</div>
<div class="stat-content">
<div class="stat-number">{{ totalDocuments }}</div>
<div class="stat-label">文档总数</div>
</div>
</div>
</a-col>
<a-col :span="24" :md="6">
<div class="stat-item">
<div class="stat-icon shared">
<ShareAltOutlined />
</div>
<div class="stat-content">
<div class="stat-number">{{ sharedCount }}</div>
<div class="stat-label">已分享</div>
</div>
</div>
</a-col>
<a-col :span="24" :md="6">
<div class="stat-item">
<div class="stat-icon updated">
<HistoryOutlined />
</div>
<div class="stat-content">
<div class="stat-number">{{ updatedToday }}</div>
<div class="stat-label">今日更新</div>
</div>
</div>
</a-col>
<a-col :span="24" :md="6">
<div class="stat-item">
<div class="stat-icon size">
<DatabaseOutlined />
</div>
<div class="stat-content">
<div class="stat-number">{{ totalSize }}</div>
<div class="stat-label">总占用空间</div>
</div>
</div>
</a-col>
</a-row>
</a-card>
<!-- 创建文档模态框 -->
<a-modal
v-model:open="showCreateModal"
:title="`新建${createTypeLabels[createType]}`"
:footer="null"
width="500px"
@cancel="resetCreateForm"
>
<a-form layout="vertical">
<a-form-item label="名称">
<a-input
v-model:value="newDocument.title"
placeholder="请输入文档名称"
/>
</a-form-item>
<a-form-item label="描述(选填)">
<a-textarea
v-model:value="newDocument.description"
placeholder="简要描述文档内容"
:rows="3"
/>
</a-form-item>
<a-form-item label="位置">
<a-tree-select
v-model:value="newDocument.folder"
style="width: 100%"
:tree-data="folderTree"
placeholder="请选择保存位置"
tree-default-expand-all
/>
</a-form-item>
<a-form-item label="标签(选填)">
<a-select
v-model:value="newDocument.tags"
mode="tags"
style="width: 100%"
placeholder="输入标签,按回车添加"
/>
</a-form-item>
<a-form-item>
<div class="create-actions">
<a-button type="primary" block @click="handleCreateDocument">
创建并打开
</a-button>
</div>
</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 {
SearchOutlined, DownOutlined, AppstoreOutlined, UnorderedListOutlined,
TableOutlined, StarOutlined, ShareAltOutlined, CopyOutlined,
DownloadOutlined, FolderOpenOutlined, EditOutlined, DeleteOutlined,
MoreOutlined, UserOutlined, CalendarOutlined, TeamOutlined,
FileTextOutlined, HistoryOutlined, DatabaseOutlined
} from '@ant-design/icons-vue'
interface Document {
id: number
title: string
description: string
type: 'document' | 'spreadsheet' | 'presentation' | 'board'
owner: string
ownerAvatar: string
lastModified: string
path: string
size: string
collaboratorsCount: number
collaboratorAvatars: string[]
tags: string[]
isNew: boolean
isShared: boolean
isFavorited: boolean
}
// 视图模式
const viewMode = ref<'grid' | 'list'>('grid')
const activeType = ref<string>('all')
const sortBy = ref('updated')
const searchKeyword = ref('')
// 分页
const pagination = ref({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showTotal: (total: number) => `${total}`
})
// 文档数据
const documents = ref<Document[]>([
{
id: 1,
title: '产品需求文档_v3.2',
description: '完整的智慧园区产品需求规格说明,包含功能模块详细定义',
type: 'document',
owner: '张三',
ownerAvatar: 'https://randomuser.me/api/portraits/men/32.jpg',
lastModified: '今天 10:30',
path: '/产品文档/智慧园区',
size: '2.4 MB',
collaboratorsCount: 3,
collaboratorAvatars: [
'https://randomuser.me/api/portraits/men/32.jpg',
'https://randomuser.me/api/portraits/women/44.jpg',
'https://randomuser.me/api/portraits/men/67.jpg'
],
tags: ['需求文档', 'PRD', '产品'],
isNew: true,
isShared: true,
isFavorited: true
},
{
id: 2,
title: 'Q3季度销售报表',
description: '第三季度销售数据分析报告,包含各区域业绩对比',
type: 'spreadsheet',
owner: '李四',
ownerAvatar: 'https://randomuser.me/api/portraits/women/44.jpg',
lastModified: '今天 09:15',
path: '/财务报告/2024',
size: '1.8 MB',
collaboratorsCount: 5,
collaboratorAvatars: [
'https://randomuser.me/api/portraits/women/44.jpg',
'https://randomuser.me/api/portraits/men/89.jpg',
'https://randomuser.me/api/portraits/women/23.jpg'
],
tags: ['财务', '报表', '销售'],
isNew: false,
isShared: true,
isFavorited: false
},
{
id: 3,
title: '项目启动会简报',
description: '移动端App项目启动会演示文稿',
type: 'presentation',
owner: '王五',
ownerAvatar: 'https://randomuser.me/api/portraits/men/67.jpg',
lastModified: '昨天 16:45',
path: '/会议记录/10月',
size: '4.2 MB',
collaboratorsCount: 2,
collaboratorAvatars: [
'https://randomuser.me/api/portraits/men/67.jpg',
'https://randomuser.me/api/portraits/men/32.jpg'
],
tags: ['会议', '简报', '项目启动'],
isNew: false,
isShared: false,
isFavorited: true
},
{
id: 4,
title: '技术架构讨论纪要',
description: '数据中心技术架构讨论会议纪要',
type: 'document',
owner: '赵六',
ownerAvatar: 'https://randomuser.me/api/portraits/women/23.jpg',
lastModified: '昨天 14:20',
path: '/技术文档/架构',
size: '1.1 MB',
collaboratorsCount: 4,
collaboratorAvatars: [
'https://randomuser.me/api/portraits/women/23.jpg',
'https://randomuser.me/api/portraits/men/67.jpg',
'https://randomuser.me/api/portraits/men/89.jpg'
],
tags: ['技术', '架构', '会议纪要'],
isNew: false,
isShared: true,
isFavorited: false
},
{
id: 5,
title: 'UI设计稿评审',
description: '智慧园区管理平台UI设计稿评审记录',
type: 'board',
owner: '钱七',
ownerAvatar: 'https://randomuser.me/api/portraits/men/89.jpg',
lastModified: '前天 11:10',
path: '/设计文档/智慧园区',
size: '8.5 MB',
collaboratorsCount: 6,
collaboratorAvatars: [
'https://randomuser.me/api/portraits/men/89.jpg',
'https://randomuser.me/api/portraits/women/44.jpg',
'https://randomuser.me/api/portraits/men/32.jpg'
],
tags: ['UI设计', '设计稿', '评审'],
isNew: true,
isShared: true,
isFavorited: false
},
{
id: 6,
title: 'API接口规范',
description: '后端API接口设计规范文档',
type: 'document',
owner: '孙八',
ownerAvatar: 'https://randomuser.me/api/portraits/women/56.jpg',
lastModified: '前天 09:45',
path: '/开发规范/API',
size: '3.2 MB',
collaboratorsCount: 3,
collaboratorAvatars: [
'https://randomuser.me/api/portraits/women/56.jpg',
'https://randomuser.me/api/portraits/men/67.jpg',
'https://randomuser.me/api/portraits/men/32.jpg'
],
tags: ['API', '开发规范', '技术文档'],
isNew: false,
isShared: false,
isFavorited: true
}
])
// 文档类型统计
const documentTypes = computed(() => {
const allDocs = documents.value
const counts = {
all: allDocs.length,
document: allDocs.filter(d => d.type === 'document').length,
spreadsheet: allDocs.filter(d => d.type === 'spreadsheet').length,
presentation: allDocs.filter(d => d.type === 'presentation').length,
board: allDocs.filter(d => d.type === 'board').length
}
return [
{ key: 'all', label: '全部', icon: '📚', count: counts.all, color: 'blue' },
{ key: 'document', label: '文档', icon: '📝', count: counts.document, color: 'green' },
{ key: 'spreadsheet', label: '表格', icon: '📊', count: counts.spreadsheet, color: 'purple' },
{ key: 'presentation', label: '演示', icon: '📽️', count: counts.presentation, color: 'orange' },
{ key: 'board', label: '白板', icon: '📋', count: counts.board, color: 'cyan' }
]
})
// 筛选后的文档
const filteredDocuments = computed(() => {
let filtered = documents.value
// 搜索筛选
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
filtered = filtered.filter(doc =>
doc.title.toLowerCase().includes(keyword) ||
doc.description.toLowerCase().includes(keyword) ||
doc.tags.some(tag => tag.toLowerCase().includes(keyword))
)
}
// 类型筛选
if (activeType.value !== 'all') {
filtered = filtered.filter(doc => doc.type === activeType.value)
}
// 排序
filtered = [...filtered].sort((a, b) => {
switch (sortBy.value) {
case 'name':
return a.title.localeCompare(b.title)
case 'owner':
return a.owner.localeCompare(b.owner)
case 'created':
// 这里简化为按ID排序实际应该按创建时间
return a.id - b.id
case 'updated':
default:
// 这里简化为按ID倒序实际应该按修改时间
return b.id - a.id
}
})
pagination.value.total = filtered.length
const start = (pagination.value.current - 1) * pagination.value.pageSize
const end = start + pagination.value.pageSize
return filtered.slice(start, end)
})
// 统计数据
const totalDocuments = computed(() => documents.value.length)
const sharedCount = computed(() => documents.value.filter(d => d.isShared).length)
const updatedToday = computed(() => documents.value.filter(d => d.lastModified.includes('今天')).length)
const totalSize = computed(() => {
const sizes = documents.value.map(d => {
const size = parseFloat(d.size)
const unit = d.size.split(' ')[1]
if (unit === 'GB') return size * 1024
return size
})
const totalMB = sizes.reduce((sum, size) => sum + size, 0)
if (totalMB > 1024) return (totalMB / 1024).toFixed(1) + ' GB'
return totalMB.toFixed(1) + ' MB'
})
// 创建文档相关
const showCreateModal = ref(false)
const createType = ref<'document' | 'spreadsheet' | 'presentation' | 'board'>('document')
const newDocument = ref({
title: '',
description: '',
folder: null,
tags: []
})
const createTypeLabels = {
document: '文档',
spreadsheet: '表格',
presentation: '演示文稿',
board: '白板'
}
const folderTree = ref([
{
title: '我的文档',
value: '/',
key: '/',
children: [
{ title: '工作文档', value: '/work', key: '/work' },
{ title: '个人文档', value: '/personal', key: '/personal' },
{ title: '共享文档', value: '/shared', key: '/shared' }
]
}
])
// 方法
function handleSearch() {
pagination.value.current = 1
}
function toggleDocumentType(type: string) {
activeType.value = activeType.value === type ? 'all' : type
pagination.value.current = 1
}
function handleCreateMenuClick({ key }: { key: string }) {
if (['import'].includes(key)) {
importDocument()
} else {
createType.value = key as any
showCreateModal.value = true
resetCreateForm()
}
}
function openDocument(id: number) {
message.info(`打开文档 #${id}`)
// 实际应用中这里是跳转到文档编辑页面
}
function toggleFavorite(doc: Document) {
doc.isFavorited = !doc.isFavorited
message.success(doc.isFavorited ? '已添加到收藏' : '已取消收藏')
}
function shareDocument(doc: Document) {
message.info(`分享文档: ${doc.title}`)
}
function renameDocument(doc: Document) {
message.info(`重命名文档: ${doc.title}`)
}
function deleteDocument(doc: Document) {
const index = documents.value.findIndex(d => d.id === doc.id)
if (index !== -1) {
documents.value.splice(index, 1)
message.success('文档已删除')
}
}
function createDocument() {
createType.value = 'document'
showCreateModal.value = true
}
function importDocument() {
message.info('导入功能开发中...')
}
function resetCreateForm() {
newDocument.value = {
title: '',
description: '',
folder: null,
tags: []
}
}
function handleCreateDocument() {
if (!newDocument.value.title.trim()) {
message.error('请输入文档名称')
return
}
const newId = Math.max(...documents.value.map(d => d.id)) + 1
const newDoc: Document = {
id: newId,
title: newDocument.value.title,
description: newDocument.value.description,
type: createType.value,
owner: '当前用户',
ownerAvatar: 'https://randomuser.me/api/portraits/men/32.jpg',
lastModified: '刚刚',
path: newDocument.value.folder ? `${newDocument.value.folder}` : '/',
size: '0 MB',
collaboratorsCount: 1,
collaboratorAvatars: ['https://randomuser.me/api/portraits/men/32.jpg'],
tags: newDocument.value.tags,
isNew: true,
isShared: false,
isFavorited: false
}
documents.value.unshift(newDoc)
message.success('文档创建成功')
showCreateModal.value = false
resetCreateForm()
}
onMounted(() => {
// 页面初始化逻辑
})
</script>
<style scoped>
.documents-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;
}
.menu-item-content {
display: flex;
align-items: center;
gap: 8px;
}
.menu-icon {
font-size: 16px;
width: 24px;
text-align: center;
}
/* 筛选区域 */
.filter-section {
background: #fafafa;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
}
.search-bar {
margin-bottom: 16px;
}
.filter-controls {
display: flex;
flex-wrap: wrap;
gap: 24px;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
gap: 8px;
}
.filter-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
font-weight: 500;
}
.filter-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.filter-tag {
cursor: pointer;
transition: all 0.2s ease;
}
.filter-tag:hover {
opacity: 0.8;
}
.type-count {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
padding: 0 4px;
font-size: 11px;
margin-left: 4px;
}
/* 文档网格视图 */
.documents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
margin-bottom: 24px;
}
.document-card {
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
cursor: pointer;
height: 100%;
}
.document-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #1890ff;
}
.document-card-inner {
display: flex;
flex-direction: column;
height: 100%;
}
.document-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.document-type-icon {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.document-type-icon.type-document {
background: #e6f7ff;
color: #1890ff;
}
.document-type-icon.type-spreadsheet {
background: #f6ffed;
color: #52c41a;
}
.document-type-icon.type-presentation {
background: #fff7e6;
color: #fa8c16;
}
.document-type-icon.type-board {
background: #f9f0ff;
color: #722ed1;
}
.document-badges {
display: flex;
gap: 4px;
}
.more-button {
opacity: 0.6;
margin-top: -8px;
margin-right: -8px;
}
.more-button:hover {
opacity: 1;
}
.document-body {
flex: 1;
margin-bottom: 16px;
}
.document-title {
font-size: 16px;
font-weight: 600;
margin: 0 0 8px;
color: rgba(0, 0, 0, 0.85);
}
.document-description {
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
margin: 0 0 12px;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.document-description.placeholder {
color: rgba(0, 0, 0, 0.35);
font-style: italic;
}
.document-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.document-footer {
border-top: 1px solid #f0f0f0;
padding-top: 12px;
margin-top: auto;
}
.document-meta {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 12px;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
}
.meta-text {
opacity: 0.9;
}
.document-collaborators {
display: flex;
justify-content: space-between;
align-items: center;
}
.collaborators-count {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
}
/* 文档列表视图 */
.table-document-item {
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
padding: 8px 0;
}
.table-document-item:hover {
opacity: 0.8;
}
.document-icon {
flex-shrink: 0;
}
.type-icon {
font-size: 20px;
}
.document-info {
flex: 1;
min-width: 0;
}
.document-name {
font-weight: 500;
margin-bottom: 2px;
display: flex;
align-items: center;
gap: 4px;
}
.document-name.starred {
color: #1890ff;
}
.star-icon {
font-size: 12px;
}
.document-path {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-owner {
display: flex;
align-items: center;
gap: 8px;
}
.owner-name {
font-size: 13px;
color: rgba(0, 0, 0, 0.85);
}
.table-time {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.table-size {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.table-collaborators {
display: flex;
align-items: center;
gap: 8px;
}
.more-count {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
}
.table-actions {
white-space: nowrap;
}
/* 空状态 */
.empty-state {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
text-align: center;
background: #fafafa;
border-radius: 8px;
margin: 24px 0;
}
.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;
}
/* 统计卡片 */
.stats-card {
margin-top: 24px;
}
.stat-item {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
}
.stat-item:hover {
background: #f5f5f5;
border-color: #d9d9d9;
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: white;
}
.stat-icon :deep(svg) {
font-size: 20px;
}
.stat-icon.total {
background: #1890ff;
}
.stat-icon.shared {
background: #52c41a;
}
.stat-icon.updated {
background: #fa8c16;
}
.stat-icon.size {
background: #722ed1;
}
.stat-content {
flex: 1;
}
.stat-number {
font-size: 24px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 2px;
}
.stat-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
}
.create-actions {
display: flex;
justify-content: center;
}
.create-actions button {
margin-top: 8px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.documents-grid {
grid-template-columns: 1fr;
}
.filter-controls {
flex-direction: column;
align-items: stretch;
}
.filter-group {
flex-wrap: wrap;
}
.page-header {
flex-direction: column;
align-items: stretch;
}
.header-actions {
width: 100%;
}
.stat-item {
flex-direction: column;
text-align: center;
}
}
</style>