diff --git a/src/api/martial/attachment.js b/src/api/martial/attachment.js
new file mode 100644
index 0000000..32bef24
--- /dev/null
+++ b/src/api/martial/attachment.js
@@ -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: '图片直播'
+}
diff --git a/src/views/martial/competition/create.vue b/src/views/martial/competition/create.vue
index f2556ad..3ce22d3 100644
--- a/src/views/martial/competition/create.vue
+++ b/src/views/martial/competition/create.vue
@@ -188,7 +188,7 @@
-
@@ -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;
+}
diff --git a/src/views/martial/competition/list.vue b/src/views/martial/competition/list.vue
index 1bfc1bb..ff233e3 100644
--- a/src/views/martial/competition/list.vue
+++ b/src/views/martial/competition/list.vue
@@ -284,7 +284,7 @@
-
+