<template>
|
<div class="meeting-management">
|
<!-- 页面头部 -->
|
<div class="page-header">
|
<h2>会议管理</h2>
|
<div class="header-actions">
|
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">
|
新建会议
|
</el-button>
|
<el-button icon="el-icon-download" @click="exportData">
|
导出数据
|
</el-button>
|
</div>
|
</div>
|
|
<!-- 搜索筛选区域 -->
|
<el-card class="filter-card">
|
<el-form :model="queryParams" inline>
|
<el-form-item label="会议类型">
|
<el-select v-model="queryParams.meetingType" clearable placeholder="请选择">
|
<el-option label="科研会议" value="research" />
|
<el-option label="日常会议" value="daily" />
|
<el-option label="项目会议" value="project" />
|
<el-option label="部门会议" value="department" />
|
<el-option label="评审会议" value="review" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="会议地点">
|
<el-input
|
v-model="queryParams.location"
|
placeholder="请输入会议地点"
|
clearable
|
style="width: 150px"
|
/>
|
</el-form-item>
|
<el-form-item label="时间范围">
|
<el-date-picker
|
v-model="queryParams.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="yyyy-MM-dd"
|
/>
|
</el-form-item>
|
<el-form-item label="状态">
|
<el-select v-model="queryParams.status" clearable placeholder="请选择">
|
<el-option label="待开始" value="pending" />
|
<el-option label="进行中" value="ongoing" />
|
<el-option label="已结束" value="completed" />
|
<el-option label="已取消" value="cancelled" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 数据表格 -->
|
<el-card>
|
<el-table
|
:data="tableData"
|
v-loading="loading"
|
border
|
style="width: 100%"
|
@sort-change="handleSortChange"
|
>
|
<el-table-column prop="id" label="ID" width="80" fixed />
|
<el-table-column prop="title" label="会议主题" width="200" fixed>
|
<template #default="scope">
|
<el-button type="text" @click="handleView(scope.row)">
|
{{ scope.row.title }}
|
</el-button>
|
</template>
|
</el-table-column>
|
<el-table-column prop="meetingType" label="会议类型" width="120">
|
<template #default="scope">
|
<el-tag :type="getMeetingTypeTag(scope.row.meetingType)">
|
{{ getMeetingTypeText(scope.row.meetingType) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="participants" label="参会人员" width="180" show-overflow-tooltip>
|
<template #default="scope">
|
<span>{{ scope.row.participants.join(', ') }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="location" label="会议地点" width="150" />
|
<el-table-column prop="startTime" label="开始时间" width="160" sortable>
|
<template #default="scope">
|
<span>{{ formatDateTime(scope.row.startTime) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="endTime" label="结束时间" width="160" sortable>
|
<template #default="scope">
|
<span>{{ formatDateTime(scope.row.endTime) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="duration" label="持续时间" width="100">
|
<template #default="scope">
|
<span>{{ calculateDuration(scope.row) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="summary" label="会议概要" min-width="200" show-overflow-tooltip />
|
<el-table-column prop="status" label="状态" width="100" fixed="right">
|
<template #default="scope">
|
<el-tag :type="getStatusTag(scope.row.status)">
|
{{ getStatusText(scope.row.status) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="200" fixed="right">
|
<template #default="scope">
|
<el-button size="mini" type="text" @click="handleView(scope.row)">
|
查看
|
</el-button>
|
<el-button size="mini" type="text" @click="handleEdit(scope.row)">
|
编辑
|
</el-button>
|
<el-button
|
size="mini"
|
type="text"
|
@click="handleCopy(scope.row)"
|
style="color: #67C23A;"
|
>
|
复制
|
</el-button>
|
<el-button
|
size="mini"
|
type="text"
|
@click="handleDelete(scope.row)"
|
style="color: #F56C6C;"
|
>
|
删除
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<div class="pagination-container">
|
<el-pagination
|
:current-page="pagination.currentPage"
|
:page-size="pagination.pageSize"
|
:total="pagination.total"
|
layout="total, sizes, prev, pager, next, jumper"
|
@size-change="handleSizeChange"
|
@current-change="handleCurrentChange"
|
/>
|
</div>
|
</el-card>
|
|
<!-- 查看详情对话框 -->
|
<el-dialog
|
:title="`会议详情 - ${currentRecord.title || ''}`"
|
:visible.sync="detailDialogVisible"
|
width="800px"
|
:before-close="handleDetailClose"
|
>
|
<el-descriptions :column="2" border v-if="currentRecord">
|
<el-descriptions-item label="会议主题">{{ currentRecord.title }}</el-descriptions-item>
|
<el-descriptions-item label="会议类型">
|
<el-tag :type="getMeetingTypeTag(currentRecord.meetingType)">
|
{{ getMeetingTypeText(currentRecord.meetingType) }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="参会人员" :span="2">
|
<el-tag
|
v-for="participant in currentRecord.participants"
|
:key="participant"
|
type="info"
|
size="small"
|
style="margin-right: 8px; margin-bottom: 8px;"
|
>
|
{{ participant }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="会议地点">{{ currentRecord.location }}</el-descriptions-item>
|
<el-descriptions-item label="会议状态">
|
<el-tag :type="getStatusTag(currentRecord.status)">
|
{{ getStatusText(currentRecord.status) }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="开始时间">{{ formatDateTime(currentRecord.startTime) }}</el-descriptions-item>
|
<el-descriptions-item label="结束时间">{{ formatDateTime(currentRecord.endTime) }}</el-descriptions-item>
|
<el-descriptions-item label="持续时间">{{ calculateDuration(currentRecord) }}</el-descriptions-item>
|
<el-descriptions-item label="创建人">{{ currentRecord.creator }}</el-descriptions-item>
|
<el-descriptions-item label="会议概要" :span="2">
|
{{ currentRecord.summary }}
|
</el-descriptions-item>
|
<el-descriptions-item label="会议内容" :span="2">
|
<div style="white-space: pre-line; max-height: 300px; overflow-y: auto;">
|
{{ currentRecord.content }}
|
</div>
|
</el-descriptions-item>
|
<el-descriptions-item label="附件" :span="2" v-if="currentRecord.attachments && currentRecord.attachments.length">
|
<div v-for="file in currentRecord.attachments" :key="file.id" class="attachment-item">
|
<el-link type="primary" :href="file.url" target="_blank">
|
<i class="el-icon-document"></i> {{ file.name }}
|
</el-link>
|
</div>
|
</el-descriptions-item>
|
</el-descriptions>
|
<span slot="footer">
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
<el-button type="primary" @click="handleEdit(currentRecord)">编辑</el-button>
|
</span>
|
</el-dialog>
|
|
<!-- 新增/编辑对话框 -->
|
<el-dialog
|
:title="`${isEditing ? '编辑' : '新增'}会议`"
|
:visible.sync="editDialogVisible"
|
width="700px"
|
:before-close="handleEditClose"
|
>
|
<el-form
|
ref="editForm"
|
:model="editForm"
|
:rules="editRules"
|
label-width="100px"
|
label-position="left"
|
>
|
<el-form-item label="会议主题" prop="title">
|
<el-input v-model="editForm.title" placeholder="请输入会议主题" />
|
</el-form-item>
|
|
<el-form-item label="会议类型" prop="meetingType">
|
<el-select v-model="editForm.meetingType" placeholder="请选择会议类型" style="width: 100%">
|
<el-option label="科研会议" value="research" />
|
<el-option label="日常会议" value="daily" />
|
<el-option label="项目会议" value="project" />
|
<el-option label="部门会议" value="department" />
|
<el-option label="评审会议" value="review" />
|
</el-select>
|
</el-form-item>
|
|
<el-form-item label="参会人员" prop="participants">
|
<el-select
|
v-model="editForm.participants"
|
multiple
|
filterable
|
placeholder="请选择参会人员"
|
style="width: 100%"
|
>
|
<el-option
|
v-for="user in userList"
|
:key="user.id"
|
:label="user.name"
|
:value="user.name"
|
/>
|
</el-select>
|
</el-form-item>
|
|
<el-form-item label="会议地点" prop="location">
|
<el-input v-model="editForm.location" placeholder="请输入会议地点" />
|
</el-form-item>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="开始时间" prop="startTime">
|
<el-date-picker
|
v-model="editForm.startTime"
|
type="datetime"
|
placeholder="选择开始时间"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="结束时间" prop="endTime">
|
<el-date-picker
|
v-model="editForm.endTime"
|
type="datetime"
|
placeholder="选择结束时间"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-form-item label="会议概要" prop="summary">
|
<el-input
|
v-model="editForm.summary"
|
type="textarea"
|
:rows="2"
|
placeholder="请输入会议概要"
|
maxlength="200"
|
show-word-limit
|
/>
|
</el-form-item>
|
|
<el-form-item label="会议内容" prop="content">
|
<el-input
|
v-model="editForm.content"
|
type="textarea"
|
:rows="6"
|
placeholder="请输入会议具体内容"
|
/>
|
</el-form-item>
|
|
<el-form-item label="附件上传">
|
<el-upload
|
action="#"
|
:auto-upload="false"
|
:on-change="handleFileChange"
|
:file-list="editForm.attachments"
|
:limit="5"
|
multiple
|
>
|
<el-button size="small" type="primary">点击上传</el-button>
|
<div slot="tip" class="el-upload__tip">支持上传文档、图片等文件,单个文件不超过10MB</div>
|
</el-upload>
|
</el-form-item>
|
</el-form>
|
|
<span slot="footer">
|
<el-button @click="handleEditClose">取消</el-button>
|
<el-button type="primary" @click="handleSave" :loading="saveLoading">
|
{{ isEditing ? '保存' : '新增' }}
|
</el-button>
|
</span>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
name: 'MeetingManagement',
|
data() {
|
return {
|
// 查询参数
|
queryParams: {
|
meetingType: '',
|
location: '',
|
dateRange: [],
|
status: ''
|
},
|
// 分页参数
|
pagination: {
|
currentPage: 1,
|
pageSize: 10,
|
total: 0
|
},
|
// 加载状态
|
loading: false,
|
saveLoading: false,
|
// 对话框显示状态
|
detailDialogVisible: false,
|
editDialogVisible: false,
|
// 当前操作记录
|
currentRecord: {},
|
// 编辑状态
|
isEditing: false,
|
// 表格数据
|
tableData: [],
|
// 用户列表(用于选择参会人员)
|
userList: [
|
{ id: 1, name: '张三' },
|
{ id: 2, name: '李四' },
|
{ id: 3, name: '王五' },
|
{ id: 4, name: '赵六' },
|
{ id: 5, name: '钱七' },
|
{ id: 6, name: '孙八' },
|
{ id: 7, name: '周九' },
|
{ id: 8, name: '吴十' }
|
],
|
// 编辑表单数据
|
editForm: {
|
title: '',
|
meetingType: '',
|
participants: [],
|
location: '',
|
startTime: '',
|
endTime: '',
|
summary: '',
|
content: '',
|
attachments: []
|
},
|
// 表单验证规则
|
editRules: {
|
title: [{ required: true, message: '请输入会议主题', trigger: 'blur' }],
|
meetingType: [{ required: true, message: '请选择会议类型', trigger: 'change' }],
|
participants: [{ required: true, message: '请选择参会人员', trigger: 'change' }],
|
location: [{ required: true, message: '请输入会议地点', trigger: 'blur' }],
|
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
summary: [{ required: true, message: '请输入会议概要', trigger: 'blur' }],
|
content: [{ required: true, message: '请输入会议内容', trigger: 'blur' }]
|
}
|
}
|
},
|
mounted() {
|
this.loadData()
|
},
|
methods: {
|
// 加载数据
|
async loadData() {
|
this.loading = true
|
try {
|
// 模拟API调用
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
// 生成模拟数据
|
this.tableData = this.generateMockData()
|
this.pagination.total = this.tableData.length
|
} catch (error) {
|
console.error('加载数据失败:', error)
|
this.$message.error('数据加载失败')
|
} finally {
|
this.loading = false
|
}
|
},
|
|
// 生成模拟数据
|
generateMockData() {
|
const meetingTypes = ['research', 'daily', 'project', 'department', 'review']
|
const statuses = ['pending', 'ongoing', 'completed', 'cancelled']
|
const participantsPool = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十']
|
|
return Array.from({ length: 10 }, (_, index) => {
|
const participantCount = Math.floor(Math.random() * 5) + 2
|
const participants = []
|
for (let i = 0; i < participantCount; i++) {
|
const randomIndex = Math.floor(Math.random() * participantsPool.length)
|
participants.push(participantsPool[randomIndex])
|
}
|
|
// 去重
|
const uniqueParticipants = [...new Set(participants)]
|
|
const startTime = new Date()
|
startTime.setDate(startTime.getDate() + Math.floor(Math.random() * 30) - 15)
|
startTime.setHours(9 + Math.floor(Math.random() * 8), Math.floor(Math.random() * 4) * 15, 0)
|
|
const endTime = new Date(startTime)
|
endTime.setHours(startTime.getHours() + Math.floor(Math.random() * 3) + 1)
|
|
return {
|
id: index + 1,
|
title: `关于${['科研项目', '日常工作', '技术评审', '部门协调'][Math.floor(Math.random() * 4)]}的会议`,
|
meetingType: meetingTypes[Math.floor(Math.random() * meetingTypes.length)],
|
participants: uniqueParticipants,
|
location: ['第一会议室', '第二会议室', '第三会议室', '线上会议'][Math.floor(Math.random() * 4)],
|
startTime: startTime.toISOString(),
|
endTime: endTime.toISOString(),
|
summary: `本次会议主要讨论${['项目进展', '技术难题', '工作计划', '问题协调'][Math.floor(Math.random() * 4)]}等相关事宜`,
|
content: `会议详细内容:\n1. 议题一讨论\n2. 议题二分析\n3. 下一步工作计划\n4. 任务分配`,
|
status: statuses[Math.floor(Math.random() * statuses.length)],
|
creator: '系统管理员',
|
attachments: []
|
}
|
})
|
},
|
|
// 获取会议类型标签样式
|
getMeetingTypeTag(type) {
|
const typeMap = {
|
research: 'primary',
|
daily: 'success',
|
project: 'warning',
|
department: 'info',
|
review: 'danger'
|
}
|
return typeMap[type] || 'info'
|
},
|
|
// 获取会议类型文本
|
getMeetingTypeText(type) {
|
const textMap = {
|
research: '科研会议',
|
daily: '日常会议',
|
project: '项目会议',
|
department: '部门会议',
|
review: '评审会议'
|
}
|
return textMap[type] || type
|
},
|
|
// 获取状态标签样式
|
getStatusTag(status) {
|
const statusMap = {
|
pending: 'primary',
|
ongoing: 'success',
|
completed: 'info',
|
cancelled: 'danger'
|
}
|
return statusMap[status] || 'info'
|
},
|
|
// 获取状态文本
|
getStatusText(status) {
|
const textMap = {
|
pending: '待开始',
|
ongoing: '进行中',
|
completed: '已结束',
|
cancelled: '已取消'
|
}
|
return textMap[status] || status
|
},
|
|
// 格式化日期时间
|
formatDateTime(dateTime) {
|
if (!dateTime) return ''
|
const date = new Date(dateTime)
|
return date.toLocaleString('zh-CN')
|
},
|
|
// 计算会议持续时间
|
calculateDuration(record) {
|
if (!record.startTime || !record.endTime) return ''
|
const start = new Date(record.startTime)
|
const end = new Date(record.endTime)
|
const duration = (end - start) / (1000 * 60) // 分钟数
|
|
if (duration < 60) {
|
return `${Math.round(duration)}分钟`
|
} else {
|
const hours = Math.floor(duration / 60)
|
const minutes = Math.round(duration % 60)
|
return minutes > 0 ? `${hours}小时${minutes}分钟` : `${hours}小时`
|
}
|
},
|
|
// 查询处理
|
handleQuery() {
|
this.pagination.currentPage = 1
|
this.loadData()
|
},
|
|
// 重置查询
|
handleReset() {
|
this.queryParams = {
|
meetingType: '',
|
location: '',
|
dateRange: [],
|
status: ''
|
}
|
this.pagination.currentPage = 1
|
this.loadData()
|
},
|
|
// 查看详情
|
handleView(record) {
|
this.currentRecord = { ...record }
|
this.detailDialogVisible = true
|
},
|
|
// 新增记录
|
handleAdd() {
|
this.isEditing = false
|
this.editForm = this.getDefaultFormData()
|
this.editDialogVisible = true
|
this.$nextTick(() => {
|
this.$refs.editForm && this.$refs.editForm.clearValidate()
|
})
|
},
|
|
// 编辑记录
|
handleEdit(record) {
|
this.isEditing = true
|
this.currentRecord = record
|
this.editForm = { ...record }
|
this.editDialogVisible = true
|
this.detailDialogVisible = false
|
this.$nextTick(() => {
|
this.$refs.editForm && this.$refs.editForm.clearValidate()
|
})
|
},
|
|
// 复制记录
|
handleCopy(record) {
|
this.isEditing = false
|
const copiedRecord = { ...record }
|
delete copiedRecord.id
|
copiedRecord.title = copiedRecord.title + '(复制)'
|
this.editForm = copiedRecord
|
this.editDialogVisible = true
|
this.$nextTick(() => {
|
this.$refs.editForm && this.$refs.editForm.clearValidate()
|
})
|
},
|
|
// 删除记录
|
handleDelete(record) {
|
this.$confirm('确定要删除这条会议记录吗?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(() => {
|
// 模拟删除操作
|
this.tableData = this.tableData.filter(item => item.id !== record.id)
|
this.pagination.total = this.tableData.length
|
this.$message.success('删除成功')
|
}).catch(() => {})
|
},
|
|
// 保存记录
|
async handleSave() {
|
try {
|
const valid = await this.$refs.editForm.validate()
|
if (!valid) return
|
|
this.saveLoading = true
|
// 模拟API调用
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
this.$message.success(this.isEditing ? '保存成功' : '新增成功')
|
this.editDialogVisible = false
|
this.loadData()
|
} catch (error) {
|
console.error('保存失败:', error)
|
this.$message.error('操作失败')
|
} finally {
|
this.saveLoading = false
|
}
|
},
|
|
// 文件上传处理
|
handleFileChange(file, fileList) {
|
this.editForm.attachments = fileList
|
},
|
|
// 关闭详情对话框
|
handleDetailClose() {
|
this.detailDialogVisible = false
|
this.currentRecord = {}
|
},
|
|
// 关闭编辑对话框
|
handleEditClose() {
|
this.editDialogVisible = false
|
this.currentRecord = {}
|
this.$nextTick(() => {
|
this.$refs.editForm && this.$refs.editForm.clearValidate()
|
})
|
},
|
|
// 导出数据
|
exportData() {
|
this.$message.success('导出功能开发中')
|
},
|
|
// 分页大小变化
|
handleSizeChange(size) {
|
this.pagination.pageSize = size
|
this.pagination.currentPage = 1
|
this.loadData()
|
},
|
|
// 当前页变化
|
handleCurrentChange(page) {
|
this.pagination.currentPage = page
|
this.loadData()
|
},
|
|
// 排序变化
|
handleSortChange(sort) {
|
console.log('排序变化:', sort)
|
},
|
|
// 获取默认表单数据
|
getDefaultFormData() {
|
return {
|
title: '',
|
meetingType: '',
|
participants: [],
|
location: '',
|
startTime: '',
|
endTime: '',
|
summary: '',
|
content: '',
|
attachments: []
|
}
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.meeting-management {
|
padding: 20px;
|
}
|
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
margin: 0;
|
color: #303133;
|
}
|
|
.filter-card {
|
margin-bottom: 20px;
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
display: flex;
|
justify-content: flex-end;
|
}
|
|
.attachment-item {
|
margin-bottom: 8px;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.page-header {
|
flex-direction: column;
|
align-items: flex-start;
|
gap: 10px;
|
}
|
|
.header-actions {
|
width: 100%;
|
justify-content: space-between;
|
}
|
}
|
</style>
|