Compare commits
84
Commits
6385acd43b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f986c63cd1 | ||
|
|
45d7e9f36f | ||
|
|
5f5ab37e97 | ||
|
|
066f13f48d | ||
|
|
6eff8a08b6 | ||
|
|
ba2535f163 | ||
|
|
9035a6aafa | ||
|
|
23d1856dcc | ||
|
|
0cd46d125f | ||
|
|
1643acf767 | ||
|
|
0c6437084b | ||
|
|
0419af8561 | ||
|
|
d85833ede5 | ||
|
|
11dc18c367 | ||
|
|
d7af8d1394 | ||
|
|
c04193bf84 | ||
|
|
940e2f55e1 | ||
|
|
279af5f8b6 | ||
|
|
e1f13dae0d | ||
|
|
bb3122a843 | ||
|
|
e146e74fe7 | ||
|
|
c3a0ad1a7d | ||
|
|
1f694dce21 | ||
|
|
fa34616909 | ||
|
|
8ac66b09bd | ||
|
|
b2643c966d | ||
|
|
3eb6a2b31d | ||
|
|
d4d0710115 | ||
|
|
8e7bb90c60 | ||
|
|
1cf134eb44 | ||
|
|
dcae1a4542 | ||
|
|
198792e5e3 | ||
|
|
b3b59b8ce4 | ||
|
|
73784808a7 | ||
|
|
231fc9b550 | ||
|
|
ac29290d69 | ||
|
|
8429179f85 | ||
|
|
099d2af9b8 | ||
|
|
ed688f596a | ||
|
|
454097f791 | ||
|
|
e39553baea | ||
|
|
cc6762a4f4 | ||
|
|
8b26b8c717 | ||
|
|
12f66f44c1 | ||
|
|
09257b7069 | ||
|
|
55c19d1ca8 | ||
|
|
f05c6f9b62 | ||
|
|
b0e0f56f62 | ||
|
|
bb604caa12 | ||
|
|
1339e684a9 | ||
|
|
87ddbb312f | ||
|
|
6313b28c6d | ||
|
|
1bfe992f04 | ||
|
|
bf3021331f | ||
|
|
ffcbafd855 | ||
|
|
bbe4933e3a | ||
|
|
fae2a3a5e6 | ||
|
|
5288a74559 | ||
|
|
56657e3e1c | ||
|
|
434017e6f8 | ||
|
|
a9e335e0a5 | ||
|
|
6d8c3b9a37 | ||
|
|
a99d521683 | ||
|
|
3291ee16c6 | ||
|
|
d1eeac4877 | ||
|
|
c110ea4ec2 | ||
|
|
02b73f9f86 | ||
|
|
87822220f6 | ||
|
|
461ced449e | ||
|
|
fa53b19052 | ||
|
|
fe7a3825ad | ||
|
|
284a2b3eab | ||
|
|
44fd5077b4 | ||
|
|
d6b698ca17 | ||
|
|
2234837deb | ||
|
|
123ca9245c | ||
|
|
f4bb887d5c | ||
|
|
60b4144276 | ||
|
|
6d687c250f | ||
|
|
b6b6a04e51 | ||
|
|
1423bb89ab | ||
|
|
49090bb3ac | ||
|
|
d77baf0687 | ||
|
|
e253079860 |
@@ -0,0 +1,37 @@
|
|||||||
|
# 待处理问题
|
||||||
|
|
||||||
|
## Issue 1: 调度页面"状态"列数据应从后端获取
|
||||||
|
|
||||||
|
### 问题描述
|
||||||
|
|
||||||
|
调度页面中的"状态"列(已完赛/未完赛)目前是前端硬编码为 `false`,并非从后端 API 获取的真实状态。
|
||||||
|
|
||||||
|
### 当前行为
|
||||||
|
|
||||||
|
- 前端在加载调度数据时,将 `completed` 字段默认设置为 `false`
|
||||||
|
- 所有参赛者的状态都显示为"未完赛"
|
||||||
|
|
||||||
|
### 期望行为
|
||||||
|
|
||||||
|
- 从后端 API 获取每个参赛者的真实完赛状态
|
||||||
|
- 正确显示"已完赛"或"未完赛"
|
||||||
|
|
||||||
|
### 相关代码
|
||||||
|
|
||||||
|
**文件:** `src/views/martial/dispatch/index.vue`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 第320行 - 当前硬编码
|
||||||
|
completed: false,
|
||||||
|
|
||||||
|
// 第69-70行 - 状态显示逻辑
|
||||||
|
<el-tag v-if="scope.row.completed" type="success" size="small">已完赛</el-tag>
|
||||||
|
<el-tag v-else type="warning" size="small">未完赛</el-tag>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 待办
|
||||||
|
|
||||||
|
- [ ] 后端 API 返回参赛者的完赛状态字段
|
||||||
|
- [ ] 前端从 API 响应中读取真实状态
|
||||||
|
|
||||||
|
---
|
||||||
@@ -188,6 +188,15 @@ export const saveDispatch = (data) => {
|
|||||||
* 导出赛程表
|
* 导出赛程表
|
||||||
* @param {Number} competitionId - 赛事ID
|
* @param {Number} competitionId - 赛事ID
|
||||||
*/
|
*/
|
||||||
|
// 预览赛程表数据
|
||||||
|
export const getScheduleExportPreview = (competitionId, template, venueId) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/martial/export/schedule/preview",
|
||||||
|
method: "get",
|
||||||
|
params: { competitionId, template, venueId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const exportSchedule = (competitionId) => {
|
export const exportSchedule = (competitionId) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/martial/export/schedule',
|
url: '/martial/export/schedule',
|
||||||
@@ -196,3 +205,13 @@ export const exportSchedule = (competitionId) => {
|
|||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const exportScheduleTemplate2 = (competitionId, venueId, venueName, timeSlot) => {
|
||||||
|
return request({
|
||||||
|
url: '/martial/export/schedule2',
|
||||||
|
method: 'get',
|
||||||
|
params: { competitionId, venueId, venueName, timeSlot },
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,3 +141,16 @@ export const getOrderAmountStats = (orderId) => {
|
|||||||
params: { orderId }
|
params: { orderId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单位统计
|
||||||
|
* @param {Number} competitionId - 赛事ID
|
||||||
|
*/
|
||||||
|
export const getOrganizationStats = (competitionId) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/martial/registrationOrder/organization-stats',
|
||||||
|
method: 'get',
|
||||||
|
params: { competitionId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default [
|
|||||||
redirect: '/martial/order/list',
|
redirect: '/martial/order/list',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'competition/list',
|
path: 'competition/index',
|
||||||
name: '赛事管理',
|
name: '赛事管理',
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
@@ -65,7 +65,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'order/list',
|
path: 'order/list',
|
||||||
name: '订单管理',
|
name: '赛事管理',
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,10 +85,10 @@
|
|||||||
>
|
>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag
|
<el-tag
|
||||||
:type="getStatusType(scope.row.status)"
|
:type="getStatusType(calculateStatus(scope.row))"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
{{ getStatusText(scope.row.status) }}
|
{{ getStatusText(calculateStatus(scope.row)) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -647,124 +647,51 @@
|
|||||||
项目列表
|
项目列表
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form-item label="" class="button-right">
|
<el-alert
|
||||||
|
title="项目配置提示"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 15px;"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<p>请先保存赛事基本信息,然后前往 <strong>项目管理</strong> 功能配置比赛项目。</p>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="currentView !== 'view'"
|
v-if="competitionId"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="el-icon-plus"
|
|
||||||
size="small"
|
size="small"
|
||||||
@click="handleAddProject"
|
style="margin-top: 10px;"
|
||||||
|
@click="goToProjectManagement"
|
||||||
>
|
>
|
||||||
添加项目
|
前往项目管理
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
:data="formData.projects"
|
|
||||||
border
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-table-column
|
|
||||||
label="项目名称"
|
|
||||||
min-width="150"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-input
|
|
||||||
v-if="currentView !== 'view'"
|
|
||||||
v-model="scope.row.projectName"
|
|
||||||
placeholder="例如:太极拳"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ scope.row.projectName }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
label="项目代码"
|
|
||||||
width="120"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-input
|
|
||||||
v-if="currentView !== 'view'"
|
|
||||||
v-model="scope.row.projectCode"
|
|
||||||
placeholder="例如:TJQ001"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ scope.row.projectCode }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
label="组别"
|
|
||||||
width="120"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-input
|
|
||||||
v-if="currentView !== 'view'"
|
|
||||||
v-model="scope.row.category"
|
|
||||||
placeholder="例如:成年男子组"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ scope.row.category }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
label="报名费用(元)"
|
|
||||||
width="130"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-input-number
|
|
||||||
v-if="currentView !== 'view'"
|
|
||||||
v-model="scope.row.price"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:step="10"
|
|
||||||
size="small"
|
|
||||||
style="width: 100%"
|
|
||||||
placeholder="0.00"
|
|
||||||
/>
|
|
||||||
<span v-else style="color: #f56c6c">¥{{ scope.row.price || 0 }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
label="项目说明"
|
|
||||||
min-width="200"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-input
|
|
||||||
v-if="currentView !== 'view'"
|
|
||||||
v-model="scope.row.description"
|
|
||||||
placeholder="请输入项目说明"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ scope.row.description }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
v-if="currentView !== 'view'"
|
|
||||||
label="操作"
|
|
||||||
width="80"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
size="small"
|
|
||||||
icon="el-icon-delete"
|
|
||||||
@click="handleDeleteProject(scope.$index)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<div v-if="formData.projects && formData.projects.length > 0">
|
||||||
|
<el-table :data="formData.projects" border size="small" style="width: 100%">
|
||||||
|
<el-table-column prop="projectCode" label="项目编码" width="120" />
|
||||||
|
<el-table-column prop="projectName" label="项目名称" min-width="150" />
|
||||||
|
<el-table-column prop="category" label="组别" width="120" />
|
||||||
|
<el-table-column label="项目类型" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.eventType === 1" type="primary" size="small">套路</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.eventType === 2" type="danger" size="small">散打</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.eventType === 3" type="success" size="small">器械</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.eventType === 4" type="warning" size="small">对练</el-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="price" label="报名费(元)" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span style="color: #f56c6c">¥{{ scope.row.price || 0 }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="maxParticipants" label="容纳人数" width="100" align="center" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
<el-empty v-else description="暂无项目,请前往项目管理添加" :image-size="60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 场地配置 -->
|
<!-- 场地配置 -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
@@ -1003,7 +930,7 @@ export default {
|
|||||||
propsHttp: {
|
propsHttp: {
|
||||||
res: 'data',
|
res: 'data',
|
||||||
},
|
},
|
||||||
headers: { 'Blade-Auth': 'bearer ' + getToken() },
|
|
||||||
action: '/blade-resource/oss/endpoint/put-file'
|
action: '/blade-resource/oss/endpoint/put-file'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1274,18 +1201,7 @@ export default {
|
|||||||
console.log('项目列表长度:', projectList.length);
|
console.log('项目列表长度:', projectList.length);
|
||||||
|
|
||||||
if (projectList.length > 0) {
|
if (projectList.length > 0) {
|
||||||
this.formData.projects = projectList.map(item => ({
|
this.formData.projects = projectList.map(item => ({ ...item }));
|
||||||
id: item.id,
|
|
||||||
projectName: item.projectName,
|
|
||||||
projectCode: item.projectCode,
|
|
||||||
category: item.category,
|
|
||||||
type: item.type,
|
|
||||||
minParticipants: item.minParticipants,
|
|
||||||
maxParticipants: item.maxParticipants,
|
|
||||||
estimatedDuration: item.estimatedDuration,
|
|
||||||
price: item.price,
|
|
||||||
description: item.description
|
|
||||||
}));
|
|
||||||
console.log('✅ 加载的项目列表:', this.formData.projects);
|
console.log('✅ 加载的项目列表:', this.formData.projects);
|
||||||
console.log('✅ 项目数量:', this.formData.projects.length);
|
console.log('✅ 项目数量:', this.formData.projects.length);
|
||||||
} else {
|
} else {
|
||||||
@@ -1335,7 +1251,8 @@ export default {
|
|||||||
venueCode: item.venueCode,
|
venueCode: item.venueCode,
|
||||||
capacity: item.capacity,
|
capacity: item.capacity,
|
||||||
location: item.location,
|
location: item.location,
|
||||||
remark: item.facilities || ''
|
remark: item.facilities || '',
|
||||||
|
venueType: item.venueType || 'indoor'
|
||||||
}));
|
}));
|
||||||
console.log('✅ 加载的场地列表:', this.formData.venues);
|
console.log('✅ 加载的场地列表:', this.formData.venues);
|
||||||
console.log('✅ 场地数量:', this.formData.venues.length);
|
console.log('✅ 场地数量:', this.formData.venues.length);
|
||||||
@@ -1407,6 +1324,7 @@ export default {
|
|||||||
handleOpenAttachmentUpload(type) {
|
handleOpenAttachmentUpload(type) {
|
||||||
this.currentAttachmentType = type;
|
this.currentAttachmentType = type;
|
||||||
this.attachmentUploadForm = {};
|
this.attachmentUploadForm = {};
|
||||||
|
this.attachmentUploadOption.column[0].headers = { "Blade-Auth": "bearer " + getToken() };
|
||||||
this.attachmentUploadDialogVisible = true;
|
this.attachmentUploadDialogVisible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1579,7 +1497,7 @@ export default {
|
|||||||
|
|
||||||
handleCreate() {
|
handleCreate() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/martial/competition/list',
|
path: '/martial/competition/index',
|
||||||
query: { mode: 'create' }
|
query: { mode: 'create' }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1587,21 +1505,21 @@ export default {
|
|||||||
handleView(row) {
|
handleView(row) {
|
||||||
console.log(row)
|
console.log(row)
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/martial/competition/list',
|
path: '/martial/competition/index',
|
||||||
query: { mode: 'view', id: row.id }
|
query: { mode: 'view', id: row.id }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleEdit(row) {
|
handleEdit(row) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/martial/competition/list',
|
path: '/martial/competition/index',
|
||||||
query: { mode: 'edit', id: row.id }
|
query: { mode: 'edit', id: row.id }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
switchToEdit() {
|
switchToEdit() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/martial/competition/list',
|
path: '/martial/competition/index',
|
||||||
query: { mode: 'edit', id: this.competitionId }
|
query: { mode: 'edit', id: this.competitionId }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1643,20 +1561,16 @@ export default {
|
|||||||
this.formData.schedule.splice(index, 1);
|
this.formData.schedule.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 项目列表管理
|
// 项目列表管理 - 跳转到项目管理页面
|
||||||
handleAddProject() {
|
goToProjectManagement() {
|
||||||
this.formData.projects.push({
|
if (this.competitionId) {
|
||||||
projectName: '',
|
this.$router.push({
|
||||||
projectCode: '',
|
path: '/martial/project/list',
|
||||||
category: '',
|
query: { competitionId: this.competitionId }
|
||||||
maxParticipants: null,
|
|
||||||
price: 0,
|
|
||||||
description: ''
|
|
||||||
});
|
});
|
||||||
},
|
} else {
|
||||||
|
this.$message.warning('请先保存赛事基本信息');
|
||||||
handleDeleteProject(index) {
|
}
|
||||||
this.formData.projects.splice(index, 1);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 场地配置管理
|
// 场地配置管理
|
||||||
@@ -1705,10 +1619,10 @@ export default {
|
|||||||
savePromises.push(this.saveActivitySchedules(savedCompetitionId));
|
savePromises.push(this.saveActivitySchedules(savedCompetitionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 保存项目列表
|
// 2. 项目列表 - 已移至项目管理功能配置
|
||||||
if (this.formData.projects && this.formData.projects.length > 0) {
|
// if (this.formData.projects && this.formData.projects.length > 0) {
|
||||||
savePromises.push(this.saveProjects(savedCompetitionId));
|
// savePromises.push(this.saveProjects(savedCompetitionId));
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 3. 保存场地配置
|
// 3. 保存场地配置
|
||||||
if (this.formData.venues && this.formData.venues.length > 0) {
|
if (this.formData.venues && this.formData.venues.length > 0) {
|
||||||
@@ -1792,9 +1706,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async saveProjects(competitionId) {
|
async saveProjects(competitionId) {
|
||||||
const projects = this.formData.projects.filter(item =>
|
const projects = this.formData.projects.filter(item => item.projectName);
|
||||||
item.projectName && item.projectCode
|
|
||||||
);
|
|
||||||
|
|
||||||
if (projects.length === 0) {
|
if (projects.length === 0) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -1812,16 +1724,19 @@ export default {
|
|||||||
|
|
||||||
const promises = projects.map(project => {
|
const promises = projects.map(project => {
|
||||||
const projectData = {
|
const projectData = {
|
||||||
|
...project,
|
||||||
competitionId: finalCompetitionId,
|
competitionId: finalCompetitionId,
|
||||||
projectName: project.projectName,
|
projectName: project.projectName,
|
||||||
projectCode: project.projectCode,
|
|
||||||
category: project.category || '',
|
category: project.category || '',
|
||||||
type: project.type || 1, // 默认个人项目
|
eventType: project.eventType || 1,
|
||||||
|
type: project.type || 1,
|
||||||
minParticipants: project.minParticipants || 1,
|
minParticipants: project.minParticipants || 1,
|
||||||
maxParticipants: project.maxParticipants || 1,
|
maxParticipants: project.maxParticipants || 100,
|
||||||
estimatedDuration: project.estimatedDuration || 5,
|
estimatedDuration: project.estimatedDuration || 5,
|
||||||
price: project.price || 0,
|
price: project.price || 0,
|
||||||
description: project.description || ''
|
sortOrder: project.sortOrder || 0,
|
||||||
|
description: project.description || '',
|
||||||
|
rules: project.rules || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果有 id,说明是编辑已有的项目
|
// 如果有 id,说明是编辑已有的项目
|
||||||
@@ -1862,7 +1777,8 @@ export default {
|
|||||||
venueCode: venue.venueCode,
|
venueCode: venue.venueCode,
|
||||||
capacity: venue.capacity || 100,
|
capacity: venue.capacity || 100,
|
||||||
location: venue.location || '',
|
location: venue.location || '',
|
||||||
facilities: venue.remark || ''
|
facilities: venue.remark || '',
|
||||||
|
venueType: venue.venueType || 'indoor'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果有 id,说明是编辑已有的场地
|
// 如果有 id,说明是编辑已有的场地
|
||||||
@@ -1879,7 +1795,7 @@ export default {
|
|||||||
|
|
||||||
backToList() {
|
backToList() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/martial/competition/list'
|
path: '/martial/competition/index'
|
||||||
});
|
});
|
||||||
// 路由跳转后,在 initPage 中会自动加载列表
|
// 路由跳转后,在 initPage 中会自动加载列表
|
||||||
},
|
},
|
||||||
@@ -2050,7 +1966,7 @@ export default {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
// 报名中
|
// 报名中
|
||||||
if (regStart && regEnd && now >= regStart && now <= regEnd) {
|
if (regStart && regEnd && now >= regStart && now <= new Date(regEnd.getFullYear(), regEnd.getMonth(), regEnd.getDate(), 23, 59, 59)) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
// 未开始(默认状态)
|
// 未开始(默认状态)
|
||||||
|
|||||||
@@ -60,10 +60,10 @@
|
|||||||
>
|
>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag
|
<el-tag
|
||||||
:type="getStatusType(scope.row.status)"
|
:type="getStatusType(calculateStatus(scope.row))"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
{{ getStatusText(scope.row.status) }}
|
{{ getStatusText(calculateStatus(scope.row)) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ export default {
|
|||||||
selectedVenueId: null,
|
selectedVenueId: null,
|
||||||
venues: [], // 场地列表
|
venues: [], // 场地列表
|
||||||
timeSlots: [], // 时间段列表
|
timeSlots: [], // 时间段列表
|
||||||
|
timeSlotIndexMap: {}, // 显示索引到实际时间段索引的映射
|
||||||
dispatchGroups: [], // 调度分组列表
|
dispatchGroups: [], // 调度分组列表
|
||||||
hasChanges: false, // 是否有未保存的更改
|
hasChanges: false, // 是否有未保存的更改
|
||||||
originalData: null // 原始数据(用于取消时恢复)
|
originalData: null // 原始数据(用于取消时恢复)
|
||||||
@@ -155,12 +156,13 @@ export default {
|
|||||||
async checkScheduleStatus() {
|
async checkScheduleStatus() {
|
||||||
try {
|
try {
|
||||||
const res = await getScheduleResult(this.competitionId)
|
const res = await getScheduleResult(this.competitionId)
|
||||||
const scheduleStatus = res.data?.data?.scheduleStatus || res.data?.scheduleStatus || 0
|
const scheduleData = res.data?.data
|
||||||
|
const isCompleted = scheduleData?.isCompleted || false
|
||||||
|
|
||||||
console.log('编排状态:', scheduleStatus)
|
console.log('编排完成状态:', isCompleted)
|
||||||
|
|
||||||
// 如果编排未完成(状态不是2),提示用户并返回
|
// 如果编排未完成,提示用户并返回
|
||||||
if (scheduleStatus !== 2) {
|
if (!isCompleted) {
|
||||||
this.$message.warning('请先完成编排并锁定后,才能进行调度操作')
|
this.$message.warning('请先完成编排并锁定后,才能进行调度操作')
|
||||||
// 延迟返回上一页
|
// 延迟返回上一页
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -188,8 +190,16 @@ export default {
|
|||||||
const data = res.data?.data
|
const data = res.data?.data
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// 生成时间段
|
// 获取编排数据以确定有项目的时间段
|
||||||
this.generateTimeSlots(data.competitionStartTime || data.competition_start_time, data.competitionEndTime || data.competition_end_time)
|
let scheduleGroups = []
|
||||||
|
try {
|
||||||
|
const scheduleRes = await getScheduleResult(this.competitionId)
|
||||||
|
scheduleGroups = scheduleRes.data?.data?.competitionGroups || scheduleRes.data?.data?.scheduleGroups || []
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取编排数据失败:', e)
|
||||||
|
}
|
||||||
|
// 生成时间段(只显示有项目的时间段)
|
||||||
|
this.generateTimeSlots(data.competitionStartTime || data.competition_start_time, data.competitionEndTime || data.competition_end_time, scheduleGroups)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载赛事信息失败', err)
|
console.error('加载赛事信息失败', err)
|
||||||
@@ -199,17 +209,29 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 生成时间段列表
|
// 生成时间段列表(只显示有项目的时间段)
|
||||||
generateTimeSlots(startTime, endTime) {
|
generateTimeSlots(startTime, endTime, scheduleGroups) {
|
||||||
if (!startTime || !endTime) {
|
if (!startTime || !endTime) {
|
||||||
this.timeSlots = ['2025年11月6日 上午8:30', '2025年11月6日 下午13:30']
|
this.timeSlots = ['2025年11月6日 上午8:30', '2025年11月6日 下午13:30']
|
||||||
|
this.timeSlotIndexMap = { 0: 0, 1: 1 }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const slots = []
|
// 从编排数据中提取实际有项目的时间段索引
|
||||||
|
const usedTimeSlotIndices = new Set()
|
||||||
|
if (scheduleGroups && scheduleGroups.length > 0) {
|
||||||
|
scheduleGroups.forEach(group => {
|
||||||
|
if (group.timeSlotIndex !== undefined && group.timeSlotIndex !== null) {
|
||||||
|
usedTimeSlotIndices.add(group.timeSlotIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSlots = []
|
||||||
const start = new Date(startTime)
|
const start = new Date(startTime)
|
||||||
const end = new Date(endTime)
|
const end = new Date(endTime)
|
||||||
let currentDate = new Date(start)
|
let currentDate = new Date(start)
|
||||||
|
let slotIndex = 0
|
||||||
|
|
||||||
while (currentDate <= end) {
|
while (currentDate <= end) {
|
||||||
const year = currentDate.getFullYear()
|
const year = currentDate.getFullYear()
|
||||||
@@ -217,13 +239,28 @@ export default {
|
|||||||
const day = currentDate.getDate()
|
const day = currentDate.getDate()
|
||||||
const dateStr = `${year}年${month}月${day}日`
|
const dateStr = `${year}年${month}月${day}日`
|
||||||
|
|
||||||
slots.push(`${dateStr} 上午8:30`)
|
allSlots.push({ label: `${dateStr} 上午8:30`, index: slotIndex++ })
|
||||||
slots.push(`${dateStr} 下午13:30`)
|
allSlots.push({ label: `${dateStr} 下午13:30`, index: slotIndex++ })
|
||||||
|
|
||||||
currentDate.setDate(currentDate.getDate() + 1)
|
currentDate.setDate(currentDate.getDate() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timeSlots = slots
|
// 如果有编排数据,只显示有项目的时间段
|
||||||
|
if (usedTimeSlotIndices.size > 0) {
|
||||||
|
const filteredSlots = allSlots.filter(slot => usedTimeSlotIndices.has(slot.index))
|
||||||
|
this.timeSlots = filteredSlots.map(slot => slot.label)
|
||||||
|
// 保存映射关系:显示索引 -> 实际时间段索引
|
||||||
|
this.timeSlotIndexMap = {}
|
||||||
|
filteredSlots.forEach((slot, displayIndex) => {
|
||||||
|
this.timeSlotIndexMap[displayIndex] = slot.index
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.timeSlots = allSlots.map(slot => slot.label)
|
||||||
|
this.timeSlotIndexMap = {}
|
||||||
|
allSlots.forEach((slot, displayIndex) => {
|
||||||
|
this.timeSlotIndexMap[displayIndex] = slot.index
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加载场地列表
|
// 加载场地列表
|
||||||
@@ -268,7 +305,7 @@ export default {
|
|||||||
const res = await getDispatchData({
|
const res = await getDispatchData({
|
||||||
competitionId: this.competitionId,
|
competitionId: this.competitionId,
|
||||||
venueId: this.selectedVenueId,
|
venueId: this.selectedVenueId,
|
||||||
timeSlotIndex: this.selectedTime
|
timeSlotIndex: this.timeSlotIndexMap[this.selectedTime] !== undefined ? this.timeSlotIndexMap[this.selectedTime] : this.selectedTime
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="martial-order-container">
|
<div class="martial-order-container">
|
||||||
<el-card shadow="hover">
|
<el-card shadow="hover">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2 class="page-title">订单管理</h2>
|
<h2 class="page-title">赛事管理</h2>
|
||||||
</div>
|
</div>
|
||||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag :type="getStatusType(scope.row.status)" size="small">
|
<el-tag :type="getStatusType(calculateStatus(scope.row))" size="small">
|
||||||
{{ getStatusText(scope.row.status) }}
|
{{ getStatusText(calculateStatus(scope.row)) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -252,6 +252,19 @@ export default {
|
|||||||
return `${start} ~ ${end}`
|
return `${start} ~ ${end}`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 根据时间计算赛事状态
|
||||||
|
calculateStatus(row) {
|
||||||
|
const now = new Date()
|
||||||
|
const regStart = row.registrationStartTime ? new Date(row.registrationStartTime) : null
|
||||||
|
const regEnd = row.registrationEndTime ? new Date(row.registrationEndTime) : null
|
||||||
|
const compStart = row.competitionStartTime ? new Date(row.competitionStartTime) : null
|
||||||
|
const compEnd = row.competitionEndTime ? new Date(row.competitionEndTime) : null
|
||||||
|
if (compEnd && now > compEnd) return 4
|
||||||
|
if (compStart && now >= compStart) return 3
|
||||||
|
if (regStart && regEnd && now >= regStart && now <= new Date(regEnd.getFullYear(), regEnd.getMonth(), regEnd.getDate(), 23, 59, 59)) return 2
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
|
||||||
getStatusType(status) {
|
getStatusType(status) {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
1: 'info', // 未开始
|
1: 'info', // 未开始
|
||||||
|
|||||||
@@ -135,7 +135,10 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="category" label="分组类别" width="100" align="center">
|
<el-table-column prop="category" label="分组类别" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ row.category || '-' }}</span>
|
<el-tag v-if="row.category == 1" type="primary" size="small">男子</el-tag>
|
||||||
|
<el-tag v-else-if="row.category == 2" type="danger" size="small">女子</el-tag>
|
||||||
|
<el-tag v-else-if="row.category == 3" type="success" size="small">混合</el-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="eventType" label="项目类型" width="100" align="center">
|
<el-table-column prop="eventType" label="项目类型" width="100" align="center">
|
||||||
@@ -174,6 +177,7 @@
|
|||||||
{{ row.maxParticipants || 0 }}
|
{{ row.maxParticipants || 0 }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="createTime"
|
prop="createTime"
|
||||||
label="创建时间"
|
label="创建时间"
|
||||||
@@ -235,7 +239,6 @@
|
|||||||
placeholder="请选择赛事"
|
placeholder="请选择赛事"
|
||||||
filterable
|
filterable
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleCompetitionChangeInForm"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in competitionList"
|
v-for="item in competitionList"
|
||||||
@@ -246,34 +249,6 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="所属场地" prop="venueId">
|
|
||||||
<el-select
|
|
||||||
v-model="form.venueId"
|
|
||||||
placeholder="请选择场地"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in venueList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.venueName"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="项目编码" prop="projectCode">
|
|
||||||
<el-input
|
|
||||||
v-model="form.projectCode"
|
|
||||||
placeholder="请输入项目编码"
|
|
||||||
maxlength="50"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -481,7 +456,6 @@ import {
|
|||||||
exportProjects
|
exportProjects
|
||||||
} from '@/api/martial/project'
|
} from '@/api/martial/project'
|
||||||
import { getCompetitionList } from '@/api/martial/competition'
|
import { getCompetitionList } from '@/api/martial/competition'
|
||||||
import { getVenuesByCompetition } from '@/api/martial/venue'
|
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
@@ -492,7 +466,6 @@ const tableData = ref([])
|
|||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const selection = ref([])
|
const selection = ref([])
|
||||||
const competitionList = ref([])
|
const competitionList = ref([])
|
||||||
const venueList = ref([])
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const dialogTitle = ref('')
|
const dialogTitle = ref('')
|
||||||
@@ -514,7 +487,6 @@ const queryParams = reactive({
|
|||||||
const form = reactive({
|
const form = reactive({
|
||||||
id: null,
|
id: null,
|
||||||
competitionId: '',
|
competitionId: '',
|
||||||
venueId: null,
|
|
||||||
projectCode: '',
|
projectCode: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
category: null,
|
category: null,
|
||||||
@@ -543,10 +515,6 @@ const rules = {
|
|||||||
competitionId: [
|
competitionId: [
|
||||||
{ required: true, message: '请选择所属赛事', trigger: 'change' }
|
{ required: true, message: '请选择所属赛事', trigger: 'change' }
|
||||||
],
|
],
|
||||||
projectCode: [
|
|
||||||
{ required: true, message: '请输入项目编码', trigger: 'blur' },
|
|
||||||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
projectName: [
|
projectName: [
|
||||||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||||
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
|
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
|
||||||
@@ -583,20 +551,6 @@ const loadCompetitionList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单中赛事变更时加载场地列表
|
|
||||||
const handleCompetitionChangeInForm = async (competitionId) => {
|
|
||||||
form.venueId = null
|
|
||||||
venueList.value = []
|
|
||||||
if (competitionId) {
|
|
||||||
try {
|
|
||||||
const res = await getVenuesByCompetition(competitionId)
|
|
||||||
venueList.value = res.data?.data?.records || []
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载场地列表失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -663,15 +617,6 @@ const handleEdit = async (row) => {
|
|||||||
if (row.price !== undefined) {
|
if (row.price !== undefined) {
|
||||||
form.registrationFee = row.price
|
form.registrationFee = row.price
|
||||||
}
|
}
|
||||||
// 加载该赛事的场地列表
|
|
||||||
if (row.competitionId) {
|
|
||||||
try {
|
|
||||||
const res = await getVenuesByCompetition(row.competitionId)
|
|
||||||
venueList.value = res.data?.data?.records || []
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载场地列表失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,931 @@
|
|||||||
|
<template>
|
||||||
|
<div class="project-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-card">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||||
|
<el-form-item label="赛事">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.competitionId"
|
||||||
|
placeholder="请选择赛事"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in competitionList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.competitionName"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目名称">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.projectName"
|
||||||
|
placeholder="请输入项目名称"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="分组类别">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.category"
|
||||||
|
placeholder="请输入分组类别"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目类型">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.eventType"
|
||||||
|
placeholder="请选择项目类型"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="套路" :value="1" />
|
||||||
|
<el-option label="散打" :value="2" />
|
||||||
|
<el-option label="器械" :value="3" />
|
||||||
|
<el-option label="对练" :value="4" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="参赛类型">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.type"
|
||||||
|
placeholder="请选择参赛类型"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="单人" :value="1" />
|
||||||
|
<el-option label="集体" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="handleReset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<el-card shadow="never" class="toolbar-card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">
|
||||||
|
新增项目
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
:icon="Delete"
|
||||||
|
:disabled="!selection.length"
|
||||||
|
@click="handleBatchDelete"
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</el-button>
|
||||||
|
<el-upload
|
||||||
|
:action="uploadUrl"
|
||||||
|
:headers="uploadHeaders"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:on-success="handleUploadSuccess"
|
||||||
|
:on-error="handleUploadError"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
>
|
||||||
|
<el-button type="success" :icon="Upload">导入Excel</el-button>
|
||||||
|
</el-upload>
|
||||||
|
<el-button type="warning" :icon="Download" @click="handleExport">
|
||||||
|
导出Excel
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<el-tooltip content="刷新" placement="top">
|
||||||
|
<el-button circle :icon="Refresh" @click="fetchData" />
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<el-card shadow="never" class="table-card">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="tableData"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
prop="projectCode"
|
||||||
|
label="项目编码"
|
||||||
|
width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="projectName"
|
||||||
|
label="项目名称"
|
||||||
|
min-width="180"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column label="所属赛事" min-width="150" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getCompetitionName(row.competitionId) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="category" label="分组类别" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.category || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="eventType" label="项目类型" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.eventType === 1" type="primary" size="small">套路</el-tag>
|
||||||
|
<el-tag v-else-if="row.eventType === 2" type="danger" size="small">散打</el-tag>
|
||||||
|
<el-tag v-else-if="row.eventType === 3" type="success" size="small">器械</el-tag>
|
||||||
|
<el-tag v-else-if="row.eventType === 4" type="warning" size="small">对练</el-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="type" label="参赛类型" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.type === 1" type="success" size="small">单人</el-tag>
|
||||||
|
<el-tag v-else-if="row.type === 2" type="warning" size="small">集体</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="price"
|
||||||
|
label="报名费(元)"
|
||||||
|
width="110"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span style="color: #f56c6c">¥{{ row.price || 0 }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="estimatedDuration" label="预计时长" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.estimatedDuration || 5 }}分钟</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="单位容纳人数" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.maxParticipants || 0 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="所属场地" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ getVenueName(row.venueId) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="createTime"
|
||||||
|
label="创建时间"
|
||||||
|
width="160"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.createTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" :icon="Edit" @click="handleEdit(row)">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="primary" :icon="View" @click="handleView(row)">
|
||||||
|
查看
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.current"
|
||||||
|
v-model:page-size="queryParams.size"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="fetchData"
|
||||||
|
@current-change="fetchData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="800px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleDialogClose"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="所属赛事" prop="competitionId">
|
||||||
|
<el-select
|
||||||
|
v-model="form.competitionId"
|
||||||
|
placeholder="请选择赛事"
|
||||||
|
filterable
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleCompetitionChangeInForm"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in competitionList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.competitionName"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="所属场地" prop="venueId">
|
||||||
|
<el-select
|
||||||
|
v-model="form.venueId"
|
||||||
|
placeholder="请选择场地"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in venueList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.venueName"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目编码" prop="projectCode">
|
||||||
|
<el-input
|
||||||
|
v-model="form.projectCode"
|
||||||
|
placeholder="请输入项目编码"
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目名称" prop="projectName">
|
||||||
|
<el-input
|
||||||
|
v-model="form.projectName"
|
||||||
|
placeholder="请输入项目名称"
|
||||||
|
maxlength="100"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="分组类别" prop="category">
|
||||||
|
<el-select
|
||||||
|
v-model="form.category"
|
||||||
|
placeholder="请选择分组类别"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="男子" :value="1" />
|
||||||
|
<el-option label="女子" :value="2" />
|
||||||
|
<el-option label="团体" :value="3" />
|
||||||
|
<el-option label="混合" :value="4" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目类型" prop="eventType">
|
||||||
|
<el-select
|
||||||
|
v-model="form.eventType"
|
||||||
|
placeholder="请选择项目类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="套路" :value="1" />
|
||||||
|
<el-option label="散打" :value="2" />
|
||||||
|
<el-option label="器械" :value="3" />
|
||||||
|
<el-option label="对练" :value="4" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="参赛类型" prop="type">
|
||||||
|
<el-select
|
||||||
|
v-model="form.type"
|
||||||
|
placeholder="请选择参赛类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="单人" :value="1" />
|
||||||
|
<el-option label="集体" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="预计时长(分钟)" prop="estimatedDuration">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.estimatedDuration"
|
||||||
|
:min="1"
|
||||||
|
:max="120"
|
||||||
|
placeholder="每人/队预计比赛时长"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="报名费(元)" prop="registrationFee">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.registrationFee"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="10"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="单位容纳人数" prop="maxParticipants">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.maxParticipants"
|
||||||
|
:min="1"
|
||||||
|
:max="1000"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="排序序号" prop="sortOrder">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.sortOrder"
|
||||||
|
:min="0"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="比赛规则" prop="rules">
|
||||||
|
<el-input
|
||||||
|
v-model="form.rules"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="请输入比赛规则说明"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入备注信息"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 查看详情弹窗 -->
|
||||||
|
<el-dialog v-model="detailVisible" title="项目详情" width="700px">
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="项目编码">
|
||||||
|
{{ detailData.projectCode }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="项目名称">
|
||||||
|
{{ detailData.projectName }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="所属赛事">
|
||||||
|
{{ getCompetitionName(detailData.competitionId) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="分组类别">
|
||||||
|
<el-tag v-if="detailData.category === 1" type="primary">男子</el-tag>
|
||||||
|
<el-tag v-else-if="detailData.category === 2" type="danger">女子</el-tag>
|
||||||
|
<el-tag v-else-if="detailData.category === 3" type="success">团体</el-tag>
|
||||||
|
<el-tag v-else-if="detailData.category === 4" type="warning">混合</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="项目类型">
|
||||||
|
<span v-if="detailData.eventType === 1">套路</span>
|
||||||
|
<span v-else-if="detailData.eventType === 2">散打</span>
|
||||||
|
<span v-else-if="detailData.eventType === 3">器械</span>
|
||||||
|
<span v-else-if="detailData.eventType === 4">对练</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="参赛类型">
|
||||||
|
<el-tag v-if="detailData.type === 1" type="success" size="small">单人</el-tag>
|
||||||
|
<el-tag v-else-if="detailData.type === 2" type="warning" size="small">集体</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="报名费">
|
||||||
|
<span style="color: #f56c6c; font-weight: bold">
|
||||||
|
¥{{ detailData.registrationFee || 0 }}
|
||||||
|
</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="单位容纳人数">
|
||||||
|
{{ detailData.maxParticipants }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="已报名人数">
|
||||||
|
<span :style="{
|
||||||
|
color: detailData.currentCount >= detailData.maxParticipants ? '#f56c6c' : '#67c23a',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}">
|
||||||
|
{{ detailData.currentCount || 0 }}
|
||||||
|
</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间" :span="2">
|
||||||
|
{{ formatDate(detailData.createTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="比赛规则" :span="2">
|
||||||
|
{{ detailData.rules || '暂无' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注" :span="2">
|
||||||
|
{{ detailData.remark || '暂无' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import {
|
||||||
|
Search,
|
||||||
|
Refresh,
|
||||||
|
Plus,
|
||||||
|
Delete,
|
||||||
|
Edit,
|
||||||
|
View,
|
||||||
|
Upload,
|
||||||
|
Download
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
import {
|
||||||
|
getProjectList,
|
||||||
|
submitProject,
|
||||||
|
removeProject,
|
||||||
|
importProjects,
|
||||||
|
exportProjects
|
||||||
|
} from '@/api/martial/project'
|
||||||
|
import { getCompetitionList } from '@/api/martial/competition'
|
||||||
|
import { getVenuesByCompetition } from '@/api/martial/venue'
|
||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
// 数据定义
|
||||||
|
const loading = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const selection = ref([])
|
||||||
|
const competitionList = ref([])
|
||||||
|
const venueList = ref([])
|
||||||
|
const allVenuesCache = ref(new Map()) // 全局场地缓存
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formRef = ref(null)
|
||||||
|
const detailData = ref({})
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
competitionId: '',
|
||||||
|
projectName: '',
|
||||||
|
category: '',
|
||||||
|
eventType: '',
|
||||||
|
type: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = reactive({
|
||||||
|
id: null,
|
||||||
|
competitionId: '',
|
||||||
|
venueId: null,
|
||||||
|
projectCode: '',
|
||||||
|
projectName: '',
|
||||||
|
category: null,
|
||||||
|
eventType: null,
|
||||||
|
type: null,
|
||||||
|
estimatedDuration: 5,
|
||||||
|
registrationFee: 0,
|
||||||
|
maxParticipants: 100,
|
||||||
|
sortOrder: 0,
|
||||||
|
rules: '',
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 上传配置
|
||||||
|
const uploadUrl = computed(() => {
|
||||||
|
return import.meta.env.VITE_APP_BASE_URL + '/api/blade-martial/project/import'
|
||||||
|
})
|
||||||
|
const uploadHeaders = computed(() => {
|
||||||
|
return {
|
||||||
|
'Blade-Auth': 'bearer ' + getToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
competitionId: [
|
||||||
|
{ required: true, message: '请选择所属赛事', trigger: 'change' }
|
||||||
|
],
|
||||||
|
projectCode: [
|
||||||
|
{ required: true, message: '请输入项目编码', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
projectName: [
|
||||||
|
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
category: [
|
||||||
|
{ required: true, message: '请选择分组类别', trigger: 'change' }
|
||||||
|
],
|
||||||
|
eventType: [
|
||||||
|
{ required: true, message: '请选择项目类型', trigger: 'change' }
|
||||||
|
],
|
||||||
|
estimatedDuration: [
|
||||||
|
{ required: true, message: '请输入预计时长', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
{ required: true, message: '请选择参赛类型', trigger: 'change' }
|
||||||
|
],
|
||||||
|
registrationFee: [
|
||||||
|
{ required: true, message: '请输入报名费', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
maxParticipants: [
|
||||||
|
{ required: true, message: '请输入单位容纳人数', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载赛事列表
|
||||||
|
const loadCompetitionList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getCompetitionList(1, 1000, {})
|
||||||
|
if (res.data && res.data.data && res.data.data.records) {
|
||||||
|
competitionList.value = res.data.data.records
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载赛事列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单中赛事变更时加载场地列表
|
||||||
|
const handleCompetitionChangeInForm = async (competitionId) => {
|
||||||
|
form.venueId = null
|
||||||
|
venueList.value = []
|
||||||
|
if (competitionId) {
|
||||||
|
try {
|
||||||
|
const res = await getVenuesByCompetition(competitionId)
|
||||||
|
venueList.value = res.data?.data?.records || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载场地列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const fetchData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// Only pass parameters that backend supports
|
||||||
|
const params = {
|
||||||
|
competitionId: queryParams.competitionId || undefined,
|
||||||
|
projectName: queryParams.projectName || undefined,
|
||||||
|
category: queryParams.category || undefined,
|
||||||
|
eventType: queryParams.eventType || undefined,
|
||||||
|
type: queryParams.type || undefined
|
||||||
|
}
|
||||||
|
const res = await getProjectList(
|
||||||
|
queryParams.current,
|
||||||
|
queryParams.size,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
if (res.data && res.data.data) {
|
||||||
|
tableData.value = res.data.data.records || []
|
||||||
|
total.value = res.data.data.total || 0
|
||||||
|
// 加载项目对应的场地信息
|
||||||
|
await loadVenuesForProjects(tableData.value)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('获取数据失败')
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载项目对应的场地信息
|
||||||
|
const loadVenuesForProjects = async (projects) => {
|
||||||
|
// 获取所有不同的赛事ID
|
||||||
|
const competitionIds = [...new Set(projects.map(p => p.competitionId).filter(Boolean))]
|
||||||
|
for (const compId of competitionIds) {
|
||||||
|
try {
|
||||||
|
const res = await getVenuesByCompetition(compId)
|
||||||
|
const venues = res.data?.data?.records || []
|
||||||
|
// 缓存场地信息
|
||||||
|
venues.forEach(v => {
|
||||||
|
allVenuesCache.value.set(v.id, v.venueName)
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载场地失败:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
queryParams.current = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(queryParams, {
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
competitionId: '',
|
||||||
|
projectName: '',
|
||||||
|
category: '',
|
||||||
|
eventType: '',
|
||||||
|
type: ''
|
||||||
|
})
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogTitle.value = '新增项目'
|
||||||
|
resetForm()
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = async (row) => {
|
||||||
|
dialogTitle.value = '编辑项目'
|
||||||
|
Object.keys(form).forEach((key) => {
|
||||||
|
form[key] = row[key]
|
||||||
|
})
|
||||||
|
// 处理字段名映射:后端返回 price,表单使用 registrationFee
|
||||||
|
if (row.price !== undefined) {
|
||||||
|
form.registrationFee = row.price
|
||||||
|
}
|
||||||
|
// 加载该赛事的场地列表
|
||||||
|
if (row.competitionId) {
|
||||||
|
try {
|
||||||
|
const res = await getVenuesByCompetition(row.competitionId)
|
||||||
|
venueList.value = res.data?.data?.records || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载场地列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看
|
||||||
|
const handleView = (row) => {
|
||||||
|
detailData.value = { ...row }
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm('确定要删除该项目吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
await removeProject(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
ElMessageBox.confirm(`确定要删除选中的 ${selection.value.length} 个项目吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const ids = selection.value.map((item) => item.id).join(',')
|
||||||
|
await removeProject(ids)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
// 构建提交数据,确保字段名与后端一致
|
||||||
|
const submitData = {
|
||||||
|
...form,
|
||||||
|
price: form.registrationFee // 后端使用 price 字段
|
||||||
|
}
|
||||||
|
if (form.id) {
|
||||||
|
await submitProject(submitData)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await submitProject(submitData)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(form.id ? '修改失败' : '新增失败')
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择变化
|
||||||
|
const handleSelectionChange = (val) => {
|
||||||
|
selection.value = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
Object.assign(form, {
|
||||||
|
id: null,
|
||||||
|
competitionId: '',
|
||||||
|
projectCode: '',
|
||||||
|
projectName: '',
|
||||||
|
category: null,
|
||||||
|
eventType: null,
|
||||||
|
type: null,
|
||||||
|
estimatedDuration: 5,
|
||||||
|
registrationFee: 0,
|
||||||
|
maxParticipants: 100,
|
||||||
|
sortOrder: 0,
|
||||||
|
rules: '',
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
if (formRef.value) {
|
||||||
|
formRef.value.clearValidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传前检查
|
||||||
|
const beforeUpload = (file) => {
|
||||||
|
if (!queryParams.competitionId) {
|
||||||
|
ElMessage.warning('请先选择赛事')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const isExcel =
|
||||||
|
file.type === 'application/vnd.ms-excel' ||
|
||||||
|
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
if (!isExcel) {
|
||||||
|
ElMessage.error('只能上传 Excel 文件!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const isLt5M = file.size / 1024 / 1024 < 5
|
||||||
|
if (!isLt5M) {
|
||||||
|
ElMessage.error('文件大小不能超过 5MB!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传成功
|
||||||
|
const handleUploadSuccess = (response) => {
|
||||||
|
if (response.success) {
|
||||||
|
ElMessage.success('导入成功')
|
||||||
|
fetchData()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(response.msg || '导入失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传失败
|
||||||
|
const handleUploadError = () => {
|
||||||
|
ElMessage.error('导入失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
const res = await exportProjects(queryParams)
|
||||||
|
const blob = new Blob([res], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
})
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = window.URL.createObjectURL(blob)
|
||||||
|
link.download = `项目列表_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(link.href)
|
||||||
|
ElMessage.success('导出成功')
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('导出失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取赛事名称
|
||||||
|
const getCompetitionName = (competitionId) => {
|
||||||
|
if (!competitionId) return '-'
|
||||||
|
const competition = competitionList.value.find(item => item.id === competitionId)
|
||||||
|
return competition ? competition.competitionName : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVenueName = (venueId) => {
|
||||||
|
if (!venueId) return '-'
|
||||||
|
// 先从当前场地列表查找
|
||||||
|
let venue = venueList.value.find(item => item.id === venueId)
|
||||||
|
if (venue) return venue.venueName
|
||||||
|
// 再从全局缓存查找
|
||||||
|
if (allVenuesCache.value.has(venueId)) {
|
||||||
|
return allVenuesCache.value.get(venueId)
|
||||||
|
}
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (date) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
loadCompetitionList()
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.project-container {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.search-card,
|
||||||
|
.toolbar-card,
|
||||||
|
.table-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -123,10 +123,11 @@
|
|||||||
<div v-if="scope.row.hint" class="row-hint">{{ scope.row.hint }}</div>
|
<div v-if="scope.row.hint" class="row-hint">{{ scope.row.hint }}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="participantCategory" label="参人/单位" width="100"></el-table-column>
|
<el-table-column prop="projectType" label="单人/集体" width="100" align="center"></el-table-column>
|
||||||
<el-table-column prop="teamCount" label="队伍" width="80" align="center"></el-table-column>
|
<el-table-column prop="athleteCount" label="人数/集体" width="100" align="center"></el-table-column>
|
||||||
<el-table-column prop="singleTeamPeople" label="报名人数" width="120" align="center"></el-table-column>
|
<el-table-column prop="groupCount" label="组数" width="80" align="center"></el-table-column>
|
||||||
<el-table-column prop="estimatedDuration" label="预计时长(分钟)" width="150" align="center"></el-table-column>
|
<el-table-column prop="estimatedDuration" label="时长(分)" width="100" align="center"></el-table-column>
|
||||||
|
<el-table-column prop="projectCode" label="项目编码" width="120" align="center"></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -161,6 +162,7 @@ import {
|
|||||||
import { getCompetitionDetail } from '@/api/martial/competition'
|
import { getCompetitionDetail } from '@/api/martial/competition'
|
||||||
import { getProjectDetail } from '@/api/martial/project'
|
import { getProjectDetail } from '@/api/martial/project'
|
||||||
import { getParticipantList } from '@/api/martial/participant'
|
import { getParticipantList } from '@/api/martial/participant'
|
||||||
|
import { getScheduleResult } from '@/api/martial/activitySchedule'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MartialRegistrationDetail',
|
name: 'MartialRegistrationDetail',
|
||||||
@@ -387,39 +389,59 @@ export default {
|
|||||||
// 使用缓存的参赛者列表
|
// 使用缓存的参赛者列表
|
||||||
const participants = await this.getParticipants()
|
const participants = await this.getParticipants()
|
||||||
|
|
||||||
// 2. 按项目ID分组
|
// 预加载项目信息
|
||||||
const projectMap = new Map()
|
await this.preloadProjectInfo(participants)
|
||||||
|
|
||||||
|
// 获取编排数据,统计每个项目的分组数
|
||||||
|
let projectGroupCount = new Map()
|
||||||
|
try {
|
||||||
|
const scheduleRes = await getScheduleResult(this.competitionId)
|
||||||
|
const scheduleData = scheduleRes.data?.data
|
||||||
|
const scheduleGroups = scheduleData?.competitionGroups || scheduleData?.scheduleGroups || []
|
||||||
|
|
||||||
|
scheduleGroups.forEach(group => {
|
||||||
|
const groupProjectId = group.projectId
|
||||||
|
let key = null
|
||||||
|
|
||||||
|
if (groupProjectId && groupProjectId !== -1 && groupProjectId !== '-1') {
|
||||||
|
key = String(groupProjectId)
|
||||||
|
} else if (group.title) {
|
||||||
|
key = group.title.split(' ')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
projectGroupCount.set(key, (projectGroupCount.get(key) || 0) + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (scheduleErr) {
|
||||||
|
console.warn("获取编排数据失败,组数将显示为-:", scheduleErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按项目ID分组统计人数
|
||||||
|
const projectAthleteCount = new Map()
|
||||||
participants.forEach(athlete => {
|
participants.forEach(athlete => {
|
||||||
// 兼容驼峰和下划线命名
|
|
||||||
const projectId = athlete.projectId || athlete.project_id
|
const projectId = athlete.projectId || athlete.project_id
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
if (!projectMap.has(projectId)) {
|
projectAthleteCount.set(projectId, (projectAthleteCount.get(projectId) || 0) + 1)
|
||||||
projectMap.set(projectId, [])
|
|
||||||
}
|
|
||||||
projectMap.get(projectId).push(athlete)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 3. 从缓存中获取项目信息并统计(项目信息已经在 loadRegistrationStats 中预加载)
|
// 从缓存中获取项目信息并构建统计数据
|
||||||
const projectStats = []
|
const projectStats = []
|
||||||
for (const [projectId, athleteList] of projectMap) {
|
for (const [projectId, count] of projectAthleteCount) {
|
||||||
const project = this.projectCache.get(projectId)
|
const project = this.projectCache.get(projectId)
|
||||||
if (project) {
|
if (project) {
|
||||||
|
const projectType = project.type || 1 // 1=单人, 2=集体
|
||||||
projectStats.push({
|
projectStats.push({
|
||||||
projectName: project.projectName || project.project_name || '未知项目',
|
projectName: project.projectName || project.project_name || '未知项目',
|
||||||
participantCategory: project.category || '',
|
projectType: projectType === 1 ? '单人' : '集体',
|
||||||
teamCount: 1, // 简化处理,设为1
|
athleteCount: count,
|
||||||
singleTeamPeople: athleteList.length,
|
groupCount:
|
||||||
estimatedDuration: project.estimatedDuration || project.estimated_duration || 0
|
projectGroupCount.get(String(projectId)) ||
|
||||||
})
|
projectGroupCount.get(project.projectName || project.project_name || '') ||
|
||||||
} else {
|
'-',
|
||||||
// 如果缓存中没有(理论上���应该发生),添加基本信息
|
estimatedDuration: project.estimatedDuration || project.estimated_duration || 0,
|
||||||
projectStats.push({
|
projectCode: project.projectCode || project.project_code || ''
|
||||||
projectName: `项目ID:${projectId}`,
|
|
||||||
participantCategory: '',
|
|
||||||
teamCount: 1,
|
|
||||||
singleTeamPeople: athleteList.length,
|
|
||||||
estimatedDuration: 0
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="导出预览"
|
||||||
|
:model-value="visible"
|
||||||
|
@update:model-value="handleVisibleChange"
|
||||||
|
width="90%"
|
||||||
|
top="5vh"
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<div class="export-options">
|
||||||
|
<el-form :inline="true" size="small">
|
||||||
|
<el-form-item label="导出模板">
|
||||||
|
<el-select v-model="exportTemplate" placeholder="选择模板" style="width: 200px">
|
||||||
|
<el-option label="模板1-详细赛程表" value="template1" />
|
||||||
|
<el-option label="模板2-比赛时间表" value="template2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="场地" v-if="exportTemplate === 'template2'">
|
||||||
|
<el-select v-model="selectedVenue" placeholder="选择场地" style="width: 150px">
|
||||||
|
<el-option label="全部场地" :value="null" />
|
||||||
|
<el-option v-for="venue in venues" :key="venue.id" :label="venue.venueName" :value="venue.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="loadPreviewData" :loading="loading">刷新预览</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="preview-table" v-loading="loading">
|
||||||
|
<el-table v-if="exportTemplate === 'template1'" :data="previewData" border stripe max-height="500" style="width: 100%">
|
||||||
|
<el-table-column prop="scheduleDate" label="比赛日期" width="120" />
|
||||||
|
<el-table-column prop="timeSlot" label="时间段" width="100" />
|
||||||
|
<el-table-column prop="venueName" label="场地" width="120" />
|
||||||
|
<el-table-column prop="projectName" label="项目名称" width="180" />
|
||||||
|
<el-table-column prop="category" label="组别" width="80" />
|
||||||
|
<el-table-column prop="athleteName" label="运动员姓名" width="120" />
|
||||||
|
<el-table-column prop="teamName" label="单位/队伍" width="150" />
|
||||||
|
<el-table-column prop="sortOrder" label="出场顺序" width="90" />
|
||||||
|
<el-table-column prop="status" label="状态" width="80" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-table v-if="exportTemplate === 'template2'" :data="previewData" border stripe max-height="500" style="width: 100%">
|
||||||
|
<el-table-column prop="sequenceNo" label="序号" width="80" />
|
||||||
|
<el-table-column prop="projectName" label="项目" width="200" />
|
||||||
|
<el-table-column prop="participantCount" label="人数" width="80" />
|
||||||
|
<el-table-column prop="groupCount" label="组数" width="80" />
|
||||||
|
<el-table-column prop="durationMinutes" label="时间" width="80" />
|
||||||
|
<el-table-column prop="tableNo" label="表号" width="100" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div v-if="previewData.length === 0 && !loading" class="empty-tip">暂无数据</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="preview-stats" v-if="previewData.length > 0">
|
||||||
|
<span>共 {{ previewData.length }} 条记录</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleExport" :loading="exporting">确认导出</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getScheduleExportPreview, exportSchedule, exportScheduleTemplate2 } from '@/api/martial/activitySchedule'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ExportPreview',
|
||||||
|
props: {
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
competitionId: { type: [String, Number], required: true },
|
||||||
|
competitionName: { type: String, default: '' },
|
||||||
|
venues: { type: Array, default: () => [] }
|
||||||
|
},
|
||||||
|
emits: ['update:visible'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
exportTemplate: 'template1',
|
||||||
|
selectedVenue: null,
|
||||||
|
previewData: [],
|
||||||
|
loading: false,
|
||||||
|
exporting: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(val) { if (val) this.loadPreviewData() },
|
||||||
|
exportTemplate() { this.loadPreviewData() },
|
||||||
|
selectedVenue() { if (this.exportTemplate === 'template2') this.loadPreviewData() }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleVisibleChange(val) { this.$emit('update:visible', val) },
|
||||||
|
async loadPreviewData() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const res = await getScheduleExportPreview(this.competitionId, this.exportTemplate, this.selectedVenue)
|
||||||
|
this.previewData = res.data?.data || res.data || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载预览数据失败:', error)
|
||||||
|
this.$message.error('加载预览数据失败')
|
||||||
|
this.previewData = []
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleExport() {
|
||||||
|
this.exporting = true
|
||||||
|
try {
|
||||||
|
let res, filename
|
||||||
|
if (this.exportTemplate === 'template1') {
|
||||||
|
res = await exportSchedule(this.competitionId)
|
||||||
|
filename = '赛程表_' + (this.competitionName || this.competitionId) + '.xlsx'
|
||||||
|
} else {
|
||||||
|
const venue = this.venues.find(v => v.id === this.selectedVenue)
|
||||||
|
const venueName = venue ? venue.venueName : null
|
||||||
|
res = await exportScheduleTemplate2(this.competitionId, this.selectedVenue, venueName, null)
|
||||||
|
filename = '比赛时间_' + (venueName || '全部场地') + '_' + (this.competitionName || this.competitionId) + '.xlsx'
|
||||||
|
}
|
||||||
|
const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' })
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = window.URL.createObjectURL(blob)
|
||||||
|
link.download = filename
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(link.href)
|
||||||
|
this.$message.success('导出成功')
|
||||||
|
this.handleClose()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
this.$message.error('导出失败')
|
||||||
|
} finally {
|
||||||
|
this.exporting = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClose() { this.$emit('update:visible', false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.export-options { margin-bottom: 20px; padding: 15px; background: #f5f7fa; border-radius: 4px; }
|
||||||
|
.preview-table { min-height: 300px; }
|
||||||
|
.preview-stats { margin-top: 15px; color: #666; font-size: 14px; }
|
||||||
|
.empty-tip { text-align: center; padding: 60px 0; color: #999; }
|
||||||
|
</style>
|
||||||
@@ -7,12 +7,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="!isScheduleCompleted"
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="autoArrangeLoading"
|
:loading="autoArrangeLoading"
|
||||||
@click="handleAutoArrange"
|
@click="handleAutoArrange"
|
||||||
>
|
>
|
||||||
{{ isScheduleCompleted ? '重新编排' : '自动编排' }}
|
自动编排
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" type="danger" @click="showExceptionDialog">
|
<el-button size="small" type="danger" @click="showExceptionDialog">
|
||||||
异常组 <el-badge :value="exceptionList.length" :hidden="exceptionList.length === 0" />
|
异常组 <el-badge :value="exceptionList.length" :hidden="exceptionList.length === 0" />
|
||||||
@@ -85,8 +86,9 @@
|
|||||||
<span class="project-title">{{ group.title }}</span>
|
<span class="project-title">{{ group.title }}</span>
|
||||||
<span class="project-meta">{{ group.type }}</span>
|
<span class="project-meta">{{ group.type }}</span>
|
||||||
<span class="project-meta">{{ getTeamCount(group) }}队</span>
|
<span class="project-meta">{{ getTeamCount(group) }}队</span>
|
||||||
<span class="project-meta">{{ group.items?.length || 0 }}组</span>
|
<span class="project-meta">{{ group.type === '集体' ? getTeamCount(group) : 1 }}组</span>
|
||||||
<span class="project-meta">{{ group.code }}</span>
|
<!-- <span class="project-meta">{{ group.code }}</span> -->
|
||||||
|
<span class="project-table-no">表号: {{ generateTableNo(group) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="project-actions" @click.stop>
|
<div class="project-actions" @click.stop>
|
||||||
<el-popover
|
<el-popover
|
||||||
@@ -102,7 +104,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="move-popover">
|
<div class="move-popover">
|
||||||
<div class="move-label">移动到</div>
|
<div class="move-label">移动到</div>
|
||||||
<el-select v-model="moveTargetVenueId" placeholder="选择场地" size="small" style="width: 100%; margin-bottom: 10px;">
|
<el-select v-model="moveTargetVenueId" placeholder="选择场地" size="small" style="width: 100%; margin-bottom: 10px;" :teleported="false">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="venue in venues"
|
v-for="venue in venues"
|
||||||
:key="venue.id"
|
:key="venue.id"
|
||||||
@@ -184,36 +186,7 @@
|
|||||||
>
|
>
|
||||||
<span class="player-detail-index">{{ playerIndex + 1 }}</span>
|
<span class="player-detail-index">{{ playerIndex + 1 }}</span>
|
||||||
<span class="player-detail-name">{{ player.playerName }}</span>
|
<span class="player-detail-name">{{ player.playerName }}</span>
|
||||||
<span class="player-detail-status">
|
<!-- 签到状态和异常操作只在调度功能中显示 -->
|
||||||
<el-tag
|
|
||||||
:type="player.status === '已签到' ? 'success' : player.status === '异常' ? 'danger' : 'info'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ player.status || '未签到' }}
|
|
||||||
</el-tag>
|
|
||||||
</span>
|
|
||||||
<span class="player-detail-actions">
|
|
||||||
<el-button
|
|
||||||
v-if="(player.status || '未签到') === '未签到'"
|
|
||||||
link
|
|
||||||
size="small"
|
|
||||||
@click="markPlayerAsException(group, team, playerIndex)"
|
|
||||||
:disabled="isScheduleCompleted"
|
|
||||||
style="color: #f56c6c;"
|
|
||||||
>
|
|
||||||
标记异常
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="player.status === '异常'"
|
|
||||||
link
|
|
||||||
size="small"
|
|
||||||
@click="removePlayerException(group, team, playerIndex)"
|
|
||||||
:disabled="isScheduleCompleted"
|
|
||||||
style="color: #67c23a;"
|
|
||||||
>
|
|
||||||
取消异常
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -267,7 +240,7 @@
|
|||||||
<el-table-column prop="duration" label="合计时间" width="100" align="center"></el-table-column>
|
<el-table-column prop="duration" label="合计时间" width="100" align="center"></el-table-column>
|
||||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-if="scope.row.status" class="status-active">{{ scope.row.status }}</span>
|
<span :class="['status-tag', scope.row.status === 2 ? 'status-completed' : scope.row.status === 1 ? 'status-progress' : 'status-pending']">{{ scope.row.status === 2 ? '已完成' : scope.row.status === 1 ? '进行中' : '未开始' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -276,7 +249,7 @@
|
|||||||
|
|
||||||
<div class="footer-actions">
|
<div class="footer-actions">
|
||||||
<el-button size="small" @click="handleSaveDraft" v-if="!isScheduleCompleted">保存草稿</el-button>
|
<el-button size="small" @click="handleSaveDraft" v-if="!isScheduleCompleted">保存草稿</el-button>
|
||||||
<el-button size="small" @click="handleExport" v-if="isScheduleCompleted">导出</el-button>
|
<el-button v-if="isScheduleCompleted" size="small" @click="exportPreviewVisible = true">导出</el-button>
|
||||||
<el-button size="small" type="primary" @click="handleConfirm" v-if="!isScheduleCompleted">完成编排</el-button>
|
<el-button size="small" type="primary" @click="handleConfirm" v-if="!isScheduleCompleted">完成编排</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -373,6 +346,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<!-- 导出预览对话框 -->
|
||||||
|
<ExportPreview
|
||||||
|
v-model:visible="exportPreviewVisible"
|
||||||
|
:competition-id="competitionId"
|
||||||
|
:competition-name="competitionInfo.competitionName"
|
||||||
|
:venues="venues"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -380,12 +360,14 @@
|
|||||||
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
||||||
import { getVenuesByCompetition } from '@/api/martial/venue'
|
import { getVenuesByCompetition } from '@/api/martial/venue'
|
||||||
import { getCompetitionDetail } from '@/api/martial/competition'
|
import { getCompetitionDetail } from '@/api/martial/competition'
|
||||||
import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule, triggerAutoArrange, moveScheduleGroup, exportSchedule } from '@/api/martial/activitySchedule'
|
import { getScheduleResult, saveAndLockSchedule, saveDraftSchedule, triggerAutoArrange, moveScheduleGroup, exportSchedule, exportScheduleTemplate2 } from '@/api/martial/activitySchedule'
|
||||||
import { updateCheckInStatus, getScheduleConfig } from '@/api/martial/schedulePlan'
|
import { updateCheckInStatus, getScheduleConfig } from '@/api/martial/schedulePlan'
|
||||||
|
import ExportPreview from './ExportPreview.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MartialScheduleList',
|
name: 'MartialScheduleList',
|
||||||
components: {
|
components: {
|
||||||
|
ExportPreview,
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowRight
|
ArrowRight
|
||||||
},
|
},
|
||||||
@@ -399,6 +381,7 @@ export default {
|
|||||||
selectedVenueIdForVenueTab: null, // 选中的场地ID(场地Tab)
|
selectedVenueIdForVenueTab: null, // 选中的场地ID(场地Tab)
|
||||||
confirmDialogVisible: false,
|
confirmDialogVisible: false,
|
||||||
isScheduleCompleted: false, // 是否已完成编排
|
isScheduleCompleted: false, // 是否已完成编排
|
||||||
|
exportPreviewVisible: false, // 导出预览对话框
|
||||||
loading: false,
|
loading: false,
|
||||||
autoArrangeLoading: false, // 自动编排加载状态
|
autoArrangeLoading: false, // 自动编排加载状态
|
||||||
venues: [], // 场地列表(从后端加载)
|
venues: [], // 场地列表(从后端加载)
|
||||||
@@ -439,7 +422,7 @@ export default {
|
|||||||
|
|
||||||
const filtered = this.competitionGroups.filter(group => {
|
const filtered = this.competitionGroups.filter(group => {
|
||||||
console.log(`分组"${group.title}": venueId=${group.venueId}(${typeof group.venueId}), timeSlotIndex=${group.timeSlotIndex}(${typeof group.timeSlotIndex})`)
|
console.log(`分组"${group.title}": venueId=${group.venueId}(${typeof group.venueId}), timeSlotIndex=${group.timeSlotIndex}(${typeof group.timeSlotIndex})`)
|
||||||
const venueMatch = group.venueId === this.selectedVenueId || Number(group.venueId) === Number(this.selectedVenueId)
|
const venueMatch = String(group.venueId) === String(this.selectedVenueId)
|
||||||
const timeMatch = group.timeSlotIndex === this.selectedTime
|
const timeMatch = group.timeSlotIndex === this.selectedTime
|
||||||
console.log(` 场地匹配:${venueMatch}, 时间匹配:${timeMatch}`)
|
console.log(` 场地匹配:${venueMatch}, 时间匹配:${timeMatch}`)
|
||||||
return venueMatch && timeMatch
|
return venueMatch && timeMatch
|
||||||
@@ -463,8 +446,7 @@ export default {
|
|||||||
// 如果选中了场地,进一步过滤
|
// 如果选中了场地,进一步过滤
|
||||||
if (this.selectedVenueIdForVenueTab) {
|
if (this.selectedVenueIdForVenueTab) {
|
||||||
groupsInTimeSlot = groupsInTimeSlot.filter(group => {
|
groupsInTimeSlot = groupsInTimeSlot.filter(group => {
|
||||||
return group.venueId === this.selectedVenueIdForVenueTab ||
|
return String(group.venueId) === String(this.selectedVenueIdForVenueTab)
|
||||||
Number(group.venueId) === Number(this.selectedVenueIdForVenueTab)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,8 +456,8 @@ export default {
|
|||||||
participant: group.type,
|
participant: group.type,
|
||||||
team: 1, // 队伍数量,可以从分组中计算
|
team: 1, // 队伍数量,可以从分组中计算
|
||||||
number: group.items?.length || 0, // 参赛人数
|
number: group.items?.length || 0, // 参赛人数
|
||||||
duration: 0, // 合计时间,如果后端有数据可以显示
|
duration: group.estimatedDuration || 0, // 合计时间
|
||||||
status: group.venueName, // 显示场地名称
|
status: group.status, // 状态: 0-未开始, 1-进行中, 2-已完成
|
||||||
venueName: group.venueName,
|
venueName: group.venueName,
|
||||||
hint: '' // 可以根据需要添加提示信息
|
hint: '' // 可以根据需要添加提示信息
|
||||||
}))
|
}))
|
||||||
@@ -496,6 +478,43 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 生成表号: 场地(1位) + 时段(1位,上午=1/下午=2) + 序号(2位)
|
||||||
|
generateTableNo(group) {
|
||||||
|
// 1. 获取场地编号
|
||||||
|
let venueNo = 1
|
||||||
|
if (group.venueId) {
|
||||||
|
const venue = this.venues.find(v => v.id === group.venueId || String(v.id) === String(group.venueId))
|
||||||
|
if (venue && venue.venueName) {
|
||||||
|
// 从场地名称提取数字
|
||||||
|
const match = venue.venueName.match(/\d+/)
|
||||||
|
if (match) {
|
||||||
|
venueNo = parseInt(match[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取时段:上午=1, 下午=2
|
||||||
|
let period = 1
|
||||||
|
if (group.timeSlot) {
|
||||||
|
const hour = parseInt(group.timeSlot.split(':')[0])
|
||||||
|
period = hour < 12 ? 1 : 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取序号:在同场地同时段中的顺序(与显示顺序一致)
|
||||||
|
const sameSlotGroups = this.competitionGroups.filter(g => {
|
||||||
|
const gVenueMatch = String(g.venueId) === String(group.venueId)
|
||||||
|
if (!gVenueMatch) return false
|
||||||
|
const gHour = parseInt((g.timeSlot || '08:30').split(':')[0])
|
||||||
|
const gPeriod = gHour < 12 ? 1 : 2
|
||||||
|
return gPeriod === period
|
||||||
|
})
|
||||||
|
// 使用原始数组顺序(与显示顺序一致),不再按id排序
|
||||||
|
const orderIndex = sameSlotGroups.findIndex(g => g.id === group.id) + 1
|
||||||
|
|
||||||
|
// 4. 格式化: 场地(1位) + 时段(1位) + 序号(2位)
|
||||||
|
return `${venueNo}${period}${String(orderIndex).padStart(2, '0')}`
|
||||||
|
},
|
||||||
|
|
||||||
// 检查项目是否展开
|
// 检查项目是否展开
|
||||||
isProjectExpanded(groupId) {
|
isProjectExpanded(groupId) {
|
||||||
return this.expandedProjects[groupId] === true
|
return this.expandedProjects[groupId] === true
|
||||||
@@ -601,8 +620,22 @@ export default {
|
|||||||
playerIds: []
|
playerIds: []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// If item has members array (from API), use it as players
|
||||||
|
if (item.members && item.members.length > 0) {
|
||||||
|
item.members.forEach(member => {
|
||||||
|
teamMap.get(key).players.push({
|
||||||
|
id: member.id,
|
||||||
|
playerName: member.name || member.playerName,
|
||||||
|
organization: member.organization,
|
||||||
|
status: member.status
|
||||||
|
})
|
||||||
|
teamMap.get(key).playerIds.push(member.id)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Fallback: use item itself as player
|
||||||
teamMap.get(key).players.push(item)
|
teamMap.get(key).players.push(item)
|
||||||
teamMap.get(key).playerIds.push(item.id)
|
teamMap.get(key).playerIds.push(item.id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return Array.from(teamMap.values())
|
return Array.from(teamMap.values())
|
||||||
},
|
},
|
||||||
@@ -623,6 +656,14 @@ export default {
|
|||||||
|
|
||||||
const targetVenue = this.venues.find(v => v.id === this.moveTargetVenueId)
|
const targetVenue = this.venues.find(v => v.id === this.moveTargetVenueId)
|
||||||
|
|
||||||
|
// In draft mode (negative groupId), just update frontend state
|
||||||
|
if (group.id && group.id.toString().startsWith("-")) {
|
||||||
|
group.venueId = this.moveTargetVenueId
|
||||||
|
group.venueName = targetVenue ? targetVenue.venueName : ""
|
||||||
|
this.$message.success(`已移动到 ${group.venueName}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 调用后端API移动分组
|
// 调用后端API移动分组
|
||||||
const res = await moveScheduleGroup({
|
const res = await moveScheduleGroup({
|
||||||
@@ -836,6 +877,49 @@ export default {
|
|||||||
console.log('生成的时间段:', this.timeSlots)
|
console.log('生成的时间段:', this.timeSlots)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 根据实际编排数据更新显示的时间段(只显示有比赛的时段)
|
||||||
|
updateTimeSlotsFromGroups() {
|
||||||
|
if (!this.competitionGroups || this.competitionGroups.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 从分组数据中提取实际使用的时段索引
|
||||||
|
const usedTimeSlotIndexes = new Set()
|
||||||
|
this.competitionGroups.forEach(group => {
|
||||||
|
if (group.timeSlotIndex !== null && group.timeSlotIndex !== undefined) {
|
||||||
|
usedTimeSlotIndexes.add(group.timeSlotIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (usedTimeSlotIndexes.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 保存原始时段用于映射
|
||||||
|
const allSlots = [...this.timeSlots]
|
||||||
|
const filteredSlots = []
|
||||||
|
const indexMapping = {}
|
||||||
|
let newIndex = 0
|
||||||
|
allSlots.forEach((slot, oldIndex) => {
|
||||||
|
if (usedTimeSlotIndexes.has(oldIndex)) {
|
||||||
|
filteredSlots.push(slot)
|
||||||
|
indexMapping[oldIndex] = newIndex
|
||||||
|
newIndex++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 更新分组的时段索引
|
||||||
|
this.competitionGroups.forEach(group => {
|
||||||
|
if (group.timeSlotIndex !== null && group.timeSlotIndex !== undefined) {
|
||||||
|
const newIdx = indexMapping[group.timeSlotIndex]
|
||||||
|
if (newIdx !== undefined) {
|
||||||
|
group.timeSlotIndex = newIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.timeSlots = filteredSlots
|
||||||
|
console.log("过滤后的时间段:", this.timeSlots)
|
||||||
|
if (this.selectedTime >= this.timeSlots.length) {
|
||||||
|
this.selectedTime = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 加载场地列表
|
// 加载场地列表
|
||||||
async loadVenues() {
|
async loadVenues() {
|
||||||
try {
|
try {
|
||||||
@@ -897,12 +981,14 @@ export default {
|
|||||||
venueName: group.venueName,
|
venueName: group.venueName,
|
||||||
timeSlot: group.timeSlot,
|
timeSlot: group.timeSlot,
|
||||||
timeSlotIndex: group.timeSlotIndex,
|
timeSlotIndex: group.timeSlotIndex,
|
||||||
|
estimatedDuration: group.estimatedDuration || 0,
|
||||||
items: (group.participants || []).map(p => ({
|
items: (group.participants || []).map(p => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
schoolUnit: p.schoolUnit,
|
schoolUnit: p.schoolUnit,
|
||||||
playerName: p.playerName,
|
playerName: p.playerName,
|
||||||
status: p.status || '未签到',
|
status: p.status || '未签到',
|
||||||
sortOrder: p.sortOrder
|
sortOrder: p.sortOrder,
|
||||||
|
members: p.members || []
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -925,6 +1011,7 @@ export default {
|
|||||||
console.log('解析后的competitionGroups:', this.competitionGroups)
|
console.log('解析后的competitionGroups:', this.competitionGroups)
|
||||||
console.log('当前selectedVenueId:', this.selectedVenueId)
|
console.log('当前selectedVenueId:', this.selectedVenueId)
|
||||||
console.log('当前selectedTime:', this.selectedTime)
|
console.log('当前selectedTime:', this.selectedTime)
|
||||||
|
this.updateTimeSlotsFromGroups()
|
||||||
this.$message.success(data.isDraft ? '已加载草稿数据' : '已加载编排数据')
|
this.$message.success(data.isDraft ? '已加载草稿数据' : '已加载编排数据')
|
||||||
} else {
|
} else {
|
||||||
// 后端还没有数据,使用默认数据进行展示
|
// 后端还没有数据,使用默认数据进行展示
|
||||||
@@ -1110,6 +1197,7 @@ export default {
|
|||||||
venueName: group.venueName,
|
venueName: group.venueName,
|
||||||
timeSlot: group.timeSlot,
|
timeSlot: group.timeSlot,
|
||||||
timeSlotIndex: group.timeSlotIndex,
|
timeSlotIndex: group.timeSlotIndex,
|
||||||
|
estimatedDuration: group.estimatedDuration || 0,
|
||||||
participants: group.items.map((item, index) => ({
|
participants: group.items.map((item, index) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
schoolUnit: item.schoolUnit,
|
schoolUnit: item.schoolUnit,
|
||||||
@@ -1148,6 +1236,34 @@ export default {
|
|||||||
group.items.splice(itemIndex + 1, 0, temp)
|
group.items.splice(itemIndex + 1, 0, temp)
|
||||||
this.$message.success('下移成功')
|
this.$message.success('下移成功')
|
||||||
},
|
},
|
||||||
|
handleExportCommand(command) {
|
||||||
|
if (command === 'template1') {
|
||||||
|
this.handleExport()
|
||||||
|
} else if (command === 'template2') {
|
||||||
|
this.handleExportTemplate2()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleExportTemplate2() {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
const venueId = this.selectedVenueId
|
||||||
|
const venue = this.venues.find(v => v.id === venueId)
|
||||||
|
const venueName = venue ? venue.venueName : null
|
||||||
|
const res = await exportScheduleTemplate2(this.competitionId, venueId, venueName, null)
|
||||||
|
const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' })
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = window.URL.createObjectURL(blob)
|
||||||
|
link.download = `比赛时间_${venueName || '全部场地'}_${this.competitionInfo.competitionName || this.competitionId}.xlsx`
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(link.href)
|
||||||
|
this.$message.success('导出成功')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
this.$message.error('导出失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
async handleExport() {
|
async handleExport() {
|
||||||
try {
|
try {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
@@ -1198,6 +1314,7 @@ export default {
|
|||||||
venueName: group.venueName,
|
venueName: group.venueName,
|
||||||
timeSlot: group.timeSlot,
|
timeSlot: group.timeSlot,
|
||||||
timeSlotIndex: group.timeSlotIndex,
|
timeSlotIndex: group.timeSlotIndex,
|
||||||
|
estimatedDuration: group.estimatedDuration || 0,
|
||||||
participants: group.items.map((item, index) => ({
|
participants: group.items.map((item, index) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
schoolUnit: item.schoolUnit,
|
schoolUnit: item.schoolUnit,
|
||||||
@@ -1421,6 +1538,16 @@ export default {
|
|||||||
color: #606266;
|
color: #606266;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-table-no {
|
||||||
|
color: #409EFF;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-actions {
|
.project-actions {
|
||||||
|
|||||||
@@ -94,7 +94,8 @@
|
|||||||
|
|
||||||
<el-table-column label="总裁判分数" width="120" align="center" fixed="right">
|
<el-table-column label="总裁判分数" width="120" align="center" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span class="total-score">{{ formatScore(scope.row.totalScore) }}</span>
|
<span v-if="scope.row.scoreStatus === 2" class="total-score">{{ formatScore(scope.row.chiefJudgeScore) }}</span>
|
||||||
|
<span v-else class="pending-score">待确认</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
@@ -153,12 +154,17 @@
|
|||||||
|
|
||||||
<div class="total-score-display">
|
<div class="total-score-display">
|
||||||
<span class="label">总分:</span>
|
<span class="label">总分:</span>
|
||||||
<span class="value">{{ formatScore(currentDetail.totalScore) }}</span>
|
<template v-if="currentDetail.scoreStatus === 2">
|
||||||
|
<span class="value">{{ formatScore(currentDetail.chiefJudgeScore) }}</span>
|
||||||
|
<div class="calculation-note">(主裁判已确认)</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="value pending">待确认最终得分</span>
|
||||||
<div class="calculation-note">
|
<div class="calculation-note">
|
||||||
<span v-if="currentDetail.judgeScores.length > 2">
|
裁判员评分: {{ formatScore(currentDetail.totalScore) }}
|
||||||
(去掉最高分和最低分后的平均分)
|
<span v-if="currentDetail.judgeScores.length > 2">(去掉最高最低分后平均)</span>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -398,7 +404,9 @@ export default {
|
|||||||
playerNo: score.playerNo || '',
|
playerNo: score.playerNo || '',
|
||||||
judgeScores: [],
|
judgeScores: [],
|
||||||
scoreDetails: [],
|
scoreDetails: [],
|
||||||
totalScore: 0
|
totalScore: 0,
|
||||||
|
chiefJudgeScore: score.chiefJudgeScore,
|
||||||
|
scoreStatus: score.scoreStatus || 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,6 +573,16 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.total-score {
|
.total-score {
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-score {
|
||||||
|
color: #e6a23c;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.pending {
|
||||||
|
color: #e6a23c;
|
||||||
|
font-weight: 500;
|
||||||
color: #1b7c5e;
|
color: #1b7c5e;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<i class="el-icon-s-order"></i>
|
<i class="el-icon-s-order"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-info">
|
<div class="card-info">
|
||||||
<h3>订单管理</h3>
|
<h3>赛事管理</h3>
|
||||||
<p>查看和管理订单</p>
|
<p>查看和管理订单</p>
|
||||||
</div>
|
</div>
|
||||||
<i class="card-arrow el-icon-arrow-right"></i>
|
<i class="card-arrow el-icon-arrow-right"></i>
|
||||||
|
|||||||
Reference in New Issue
Block a user