This commit is contained in:
2025-12-26 11:06:38 +08:00
parent 179f7ea85d
commit 1744adcf92
4 changed files with 605 additions and 9 deletions

View File

@@ -0,0 +1,136 @@
import request from '@/axios';
// ==================== 赛事附件管理接口 ====================
/**
* 获取附件详情
* @param {Number} id - 附件ID
*/
export const getAttachmentDetail = (id) => {
return request({
url: '/api/martial/competition/attachment/detail',
method: 'get',
params: { id }
})
}
/**
* 附件列表查询(分页)
* @param {Number} current - 当前页
* @param {Number} size - 每页条数
* @param {Object} params - 查询参数
*/
export const getAttachmentList = (current, size, params) => {
return request({
url: '/api/martial/competition/attachment/list',
method: 'get',
params: {
current,
size,
...params
}
})
}
/**
* 根据赛事ID和类型获取附件列表
* @param {Number} competitionId - 赛事ID
* @param {String} attachmentType - 附件类型info-赛事发布, rules-赛事规程, schedule-活动日程, results-成绩, medals-奖牌榜, photos-图片直播
*/
export const getAttachmentsByType = (competitionId, attachmentType) => {
return request({
url: '/api/martial/competition/attachment/getByType',
method: 'get',
params: { competitionId, attachmentType }
})
}
/**
* 根据赛事ID获取所有附件
* @param {Number} competitionId - 赛事ID
*/
export const getAttachmentsByCompetition = (competitionId) => {
return request({
url: '/api/martial/competition/attachment/getByCompetition',
method: 'get',
params: { competitionId }
})
}
/**
* 新增或修改附件
* @param {Object} data - 附件数据
* @param {Number} data.id - ID修改时必传
* @param {Number} data.competitionId - 赛事ID
* @param {String} data.attachmentType - 附件类型
* @param {String} data.fileName - 文件名称
* @param {String} data.fileUrl - 文件URL
* @param {Number} data.fileSize - 文件大小(字节)
* @param {String} data.fileType - 文件类型(扩展名)
* @param {Number} data.orderNum - 排序序号
* @param {Number} data.status - 状态1-启用 0-禁用)
*/
export const submitAttachment = (data) => {
return request({
url: '/api/martial/competition/attachment/submit',
method: 'post',
data
})
}
/**
* 批量保存附件
* @param {Array} attachments - 附件列表
*/
export const batchSubmitAttachments = (attachments) => {
return request({
url: '/api/martial/competition/attachment/batchSubmit',
method: 'post',
data: attachments
})
}
/**
* 删除附件
* @param {String} ids - 附件ID,多个用逗号分隔
*/
export const removeAttachment = (ids) => {
return request({
url: '/api/martial/competition/attachment/remove',
method: 'post',
params: { ids }
})
}
/**
* 删除赛事的指定类型附件
* @param {Number} competitionId - 赛事ID
* @param {String} attachmentType - 附件类型
*/
export const removeAttachmentByType = (competitionId, attachmentType) => {
return request({
url: '/api/martial/competition/attachment/removeByType',
method: 'post',
params: { competitionId, attachmentType }
})
}
// 附件类型常量
export const ATTACHMENT_TYPES = {
INFO: 'info', // 赛事发布
RULES: 'rules', // 赛事规程
SCHEDULE: 'schedule', // 活动日程
RESULTS: 'results', // 成绩
MEDALS: 'medals', // 奖牌榜
PHOTOS: 'photos' // 图片直播
}
// 附件类型标签映射
export const ATTACHMENT_TYPE_LABELS = {
info: '赛事发布',
rules: '赛事规程',
schedule: '活动日程',
results: '成绩',
medals: '奖牌榜',
photos: '图片直播'
}

View File

@@ -188,7 +188,7 @@
</div>
<!-- 活动日程 -->
<div class="form-section">
<!-- <div class="form-section">
<div class="section-title">
<i class="el-icon-date"></i>
活动日程
@@ -290,7 +290,7 @@
</template>
</el-table-column>
</el-table>
</div>
</div> -->
<!-- 项目列表 -->
<div class="form-section">

View File

@@ -370,8 +370,134 @@
</el-form-item>
</div>
<!-- 活动日程 -->
<!-- 附件管理 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-folder-opened"></i>
附件管理
</div>
<!-- 附件类型选项卡 -->
<el-tabs v-model="activeAttachmentTab" type="card">
<el-tab-pane
v-for="tab in attachmentTabs"
:key="tab.type"
:label="tab.label"
:name="tab.type"
>
<div class="attachment-section">
<!-- 上传按钮 -->
<div class="attachment-upload" v-if="currentView !== 'view'">
<el-button
type="primary"
icon="el-icon-upload"
size="small"
@click="handleOpenAttachmentUpload(tab.type)"
>
上传{{ tab.label }}附件
</el-button>
<span class="upload-tip">支持 PDFWordExcel图片等格式单个文件不超过 50MB</span>
</div>
<!-- 附件列表 -->
<el-table
:data="formData.attachments[tab.type] || []"
border
style="width: 100%; margin-top: 15px;"
v-if="(formData.attachments[tab.type] || []).length > 0"
>
<el-table-column
label="文件名"
min-width="200"
show-overflow-tooltip
>
<template #default="scope">
<div class="file-name-cell">
<i :class="getFileIcon(scope.row.fileType)" class="file-icon"></i>
<span>{{ scope.row.fileName }}</span>
</div>
</template>
</el-table-column>
<el-table-column
label="文件大小"
width="120"
align="center"
>
<template #default="scope">
{{ formatFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column
label="上传时间"
width="180"
align="center"
>
<template #default="scope">
{{ scope.row.createTime || '-' }}
</template>
</el-table-column>
<el-table-column
label="排序"
width="100"
align="center"
>
<template #default="scope">
<el-input-number
v-if="currentView !== 'view'"
v-model="scope.row.orderNum"
:min="0"
:max="999"
size="small"
style="width: 80px"
controls-position="right"
/>
<span v-else>{{ scope.row.orderNum }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
width="150"
align="center"
fixed="right"
>
<template #default="scope">
<el-button
type="primary"
link
size="small"
@click="handlePreviewAttachment(scope.row)"
>
预览
</el-button>
<el-button
v-if="currentView !== 'view'"
type="danger"
link
size="small"
@click="handleDeleteAttachment(tab.type, scope.$index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 空状态 -->
<div class="empty-attachment" v-else>
<i class="el-icon-folder-opened"></i>
<p>暂无{{ tab.label }}附件</p>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 活动日程 -->
<!-- <div class="form-section">
<div class="section-title">
<i class="el-icon-date"></i>
活动日程
@@ -512,7 +638,7 @@
</template>
</el-table-column>
</el-table>
</div>
</div> -->
<!-- 项目列表 -->
<div class="form-section">
@@ -781,6 +907,22 @@
</el-form>
</el-card>
</div>
<!-- 附件上传对话框 -->
<el-dialog
title="上传附件"
v-model="attachmentUploadDialogVisible"
width="555px"
append-to-body
:close-on-click-modal="false"
>
<avue-form
ref="attachmentUploadForm"
:option="attachmentUploadOption"
v-model="attachmentUploadForm"
:upload-after="attachmentUploadAfter"
/>
</el-dialog>
</div>
</template>
@@ -806,6 +948,14 @@ import {
removeVenue,
getVenuesByCompetition
} from '@/api/martial/venue'
import {
getAttachmentsByCompetition,
submitAttachment,
removeAttachment,
batchSubmitAttachments,
ATTACHMENT_TYPES,
ATTACHMENT_TYPE_LABELS
} from '@/api/martial/attachment'
export default {
name: 'CompetitionManagement',
@@ -820,6 +970,39 @@ export default {
size: 10,
total: 0
},
// 附件相关数据
activeAttachmentTab: 'info',
attachmentTabs: [
{ type: 'info', label: '赛事发布' },
{ type: 'rules', label: '赛事规程' },
{ type: 'schedule', label: '活动日程' },
{ type: 'results', label: '成绩' },
{ type: 'medals', label: '奖牌榜' },
{ type: 'photos', label: '图片直播' }
],
attachmentUploadDialogVisible: false,
currentAttachmentType: '',
attachmentUploadForm: {},
attachmentUploadOption: {
submitBtn: false,
emptyBtn: false,
column: [
{
label: '附件上传',
prop: 'attachmentFile',
type: 'upload',
drag: true,
loadText: '文件上传中,请稍等',
span: 24,
accept: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.zip,.rar',
tip: '支持 PDF、Word、Excel、PPT、图片、压缩包等格式单个文件不超过 50MB',
propsHttp: {
res: 'data',
},
action: '/blade-resource/oss/endpoint/put-file'
}
]
},
formData: {
competitionName: '',
competitionCode: '', // 比赛编码
@@ -840,7 +1023,15 @@ export default {
awards: '',
schedule: [],
projects: [],
venues: []
venues: [],
attachments: {
info: [],
rules: [],
schedule: [],
results: [],
medals: [],
photos: []
}
},
formRules: {
competitionName: [
@@ -988,10 +1179,11 @@ export default {
try {
this.formData = this.formatBackendData(detailData);
console.log('格式化后的表单数据:', this.formData);
// 加载关联数据:活动日程、项目列表、场地配置
// 加载关联数据:活动日程、项目列表、场地配置、附件
this.loadActivitySchedules();
this.loadProjects();
this.loadVenues();
this.loadAttachments();
} catch (error) {
console.error('格式化数据时出错:', error);
this.$message.error('数据格式化失败: ' + error.message);
@@ -1154,6 +1346,212 @@ export default {
});
},
// 加载附件列表
loadAttachments() {
if (!this.competitionId) {
console.warn('loadAttachments: competitionId 为空,跳过加载');
return;
}
console.log('开始加载附件列表competitionId:', this.competitionId);
getAttachmentsByCompetition(this.competitionId)
.then(res => {
console.log('附件列表返回数据:', res);
const responseData = res.data?.data;
// 初始化附件对象
const attachments = {
info: [],
rules: [],
schedule: [],
results: [],
medals: [],
photos: []
};
if (responseData && Array.isArray(responseData)) {
// 按类型分组
responseData.forEach(item => {
const type = item.attachmentType;
if (attachments[type]) {
attachments[type].push({
id: item.id,
fileName: item.fileName,
fileUrl: item.fileUrl,
fileSize: item.fileSize,
fileType: item.fileType,
orderNum: item.orderNum || 0,
createTime: item.createTime
});
}
});
console.log('✅ 加载的附件列表:', attachments);
} else {
console.log('⚠️ 附件列表为空');
}
this.formData.attachments = attachments;
})
.catch(err => {
console.error('❌ 加载附件列表失败:', err);
// 不显示错误消息,因为可能是新建赛事还没有附件
});
},
// 打开附件上传对话框
handleOpenAttachmentUpload(type) {
this.currentAttachmentType = type;
this.attachmentUploadForm = {};
this.attachmentUploadDialogVisible = true;
},
// 附件上传成功回调
attachmentUploadAfter(res, done, loading, column) {
console.log('附件上传响应:', res);
if (res && (res.link || res.url)) {
const fileUrl = res.link || res.url;
const fileName = res.originalName || res.name || this.getFileNameFromUrl(fileUrl);
const fileType = this.getFileExtension(fileName);
const fileSize = res.size || 0;
// 添加到对应类型的附件列表
if (!this.formData.attachments[this.currentAttachmentType]) {
this.formData.attachments[this.currentAttachmentType] = [];
}
this.formData.attachments[this.currentAttachmentType].push({
fileName: fileName,
fileUrl: fileUrl,
fileSize: fileSize,
fileType: fileType,
orderNum: this.formData.attachments[this.currentAttachmentType].length,
isNew: true // 标记为新上传的附件
});
this.$message.success('附件上传成功');
this.attachmentUploadDialogVisible = false;
} else {
this.$message.error('上传失败,未获取到文件地址');
}
done();
},
// 预览附件
handlePreviewAttachment(attachment) {
if (!attachment.fileUrl) {
this.$message.warning('文件地址不存在');
return;
}
window.open(attachment.fileUrl, '_blank');
},
// 删除附件
handleDeleteAttachment(type, index) {
this.$confirm('确定要删除该附件吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const attachment = this.formData.attachments[type][index];
// 如果是已保存的附件,需要调用后端删除
if (attachment.id) {
removeAttachment(attachment.id.toString())
.then(() => {
this.formData.attachments[type].splice(index, 1);
this.$message.success('删除成功');
})
.catch(err => {
console.error('删除附件失败:', err);
this.$message.error('删除失败');
});
} else {
// 新上传的附件直接从列表中移除
this.formData.attachments[type].splice(index, 1);
this.$message.success('删除成功');
}
}).catch(() => {});
},
// 获取文件图标
getFileIcon(fileType) {
const iconMap = {
'pdf': 'el-icon-document',
'doc': 'el-icon-document',
'docx': 'el-icon-document',
'xls': 'el-icon-document',
'xlsx': 'el-icon-document',
'ppt': 'el-icon-document',
'pptx': 'el-icon-document',
'jpg': 'el-icon-picture',
'jpeg': 'el-icon-picture',
'png': 'el-icon-picture',
'gif': 'el-icon-picture',
'zip': 'el-icon-folder',
'rar': 'el-icon-folder'
};
return iconMap[fileType?.toLowerCase()] || 'el-icon-document';
},
// 格式化文件大小
formatFileSize(bytes) {
if (!bytes || bytes === 0) return '0 B';
if (typeof bytes === 'string') return bytes;
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i];
},
// 从URL获取文件名
getFileNameFromUrl(url) {
if (!url) return 'unknown';
const parts = url.split('/');
return parts[parts.length - 1] || 'unknown';
},
// 获取文件扩展名
getFileExtension(fileName) {
if (!fileName) return '';
const parts = fileName.split('.');
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
},
// 保存附件
async saveAttachments(competitionId) {
const allAttachments = [];
// 收集所有需要保存的附件
for (const type of Object.keys(this.formData.attachments)) {
const attachments = this.formData.attachments[type] || [];
for (const attachment of attachments) {
// 只保存新上传的附件或已修改的附件
if (attachment.isNew || attachment.isModified) {
allAttachments.push({
id: attachment.id || null,
competitionId: competitionId,
attachmentType: type,
fileName: attachment.fileName,
fileUrl: attachment.fileUrl,
fileSize: attachment.fileSize,
fileType: attachment.fileType,
orderNum: attachment.orderNum || 0,
status: 1
});
}
}
}
if (allAttachments.length === 0) {
return Promise.resolve();
}
console.log('准备保存的附件:', allAttachments);
return batchSubmitAttachments(allAttachments);
},
getStatusText(status) {
const statusMap = {
1: '未开始',
@@ -1311,6 +1709,9 @@ export default {
savePromises.push(this.saveVenues(savedCompetitionId));
}
// 4. 保存附件
savePromises.push(this.saveAttachments(savedCompetitionId));
// 等待所有保存操作完成
if (savePromises.length > 0) {
Promise.all(savePromises)
@@ -1498,7 +1899,15 @@ export default {
awards: '',
schedule: [],
projects: [],
venues: []
venues: [],
attachments: {
info: [],
rules: [],
schedule: [],
results: [],
medals: [],
photos: []
}
};
},
@@ -1789,4 +2198,55 @@ export default {
width: 100%;
}
}
// 附件管理样式
.attachment-section {
padding: 10px 0;
}
.attachment-upload {
display: flex;
align-items: center;
gap: 15px;
.upload-tip {
font-size: 12px;
color: #909399;
}
}
.file-name-cell {
display: flex;
align-items: center;
gap: 8px;
.file-icon {
font-size: 18px;
color: #409eff;
}
}
.empty-attachment {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: #909399;
i {
font-size: 48px;
margin-bottom: 10px;
opacity: 0.5;
}
p {
margin: 0;
font-size: 14px;
}
}
:deep(.el-tabs__content) {
padding: 10px 0;
}
</style>

View File

@@ -284,7 +284,7 @@
</div>
<!-- 活动日程 -->
<div class="form-section">
<!-- <div class="form-section">
<div class="section-title">
<i class="el-icon-date"></i>
活动日程
@@ -386,7 +386,7 @@
</template>
</el-table-column>
</el-table>
</div>
</div> -->
</el-form>
<div slot="footer" class="dialog-footer">