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