refactor: 将记分板功能模块化为可复用组件
- 创建 app/modules/scoreboard 模块目录
- 添加配置常量 (EVENT_CATEGORIES, TEAM_GROUPS, EVENT_TYPES)
- 添加 API 工具函数 (fetchEvents, fetchTeams, fetchResults 等)
- 添加可复用组件 (ModuleLayout, StatCard, DataTable)
- 添加模块导出文件 (mod.ts)
- 添加模块使用文档 (README.md)
- 更新首页使用模块 API 函数
后续可通过 import { xxx } from '~/modules/scoreboard' 导入使用
This commit is contained in:
parent
22f073d8e7
commit
94dbd0d34c
51
app/modules/scoreboard/DataTable.vue
Normal file
51
app/modules/scoreboard/DataTable.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<el-table :data="data" border stripe class="scoreboard-table">
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:width="column.width"
|
||||
:class-name="column.className"
|
||||
:sortable="column.sortable"
|
||||
>
|
||||
<template v-if="column.slot" #default="{ row }">
|
||||
<slot :name="column.slot" :row="row">
|
||||
{{ row[column.prop] }}
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Column {
|
||||
prop: string
|
||||
label: string
|
||||
width?: number | string
|
||||
className?: string
|
||||
sortable?: boolean
|
||||
slot?: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
columns: Column[]
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scoreboard-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.scoreboard-table :deep(.col-group),
|
||||
.scoreboard-table :deep(.col-medal),
|
||||
.scoreboard-table :deep(.col-time) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
78
app/modules/scoreboard/ModuleLayout.vue
Normal file
78
app/modules/scoreboard/ModuleLayout.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="scoreboard-module">
|
||||
<slot name="header">
|
||||
<header class="module-header">
|
||||
<h1>{{ title }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
</header>
|
||||
</slot>
|
||||
|
||||
<div class="module-content">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<footer class="module-footer">
|
||||
<slot name="footer">
|
||||
<p>© {{ currentYear }} 运动会记分板系统 - 版权所有</p>
|
||||
</slot>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
title?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '运动会记分板',
|
||||
description: '运动会管理系统'
|
||||
})
|
||||
|
||||
const currentYear = new Date().getFullYear()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scoreboard-module {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.module-header {
|
||||
background-color: #304156;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.module-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.module-header p {
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.module-content {
|
||||
flex: 1;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.module-footer {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.module-footer p {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
155
app/modules/scoreboard/README.md
Normal file
155
app/modules/scoreboard/README.md
Normal file
@ -0,0 +1,155 @@
|
||||
# 运动会记分板模块
|
||||
|
||||
可复用的运动会管理模块,提供完整的比赛项目、队伍、成绩管理功能。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
modules/scoreboard/
|
||||
├── index.ts # 模块配置和常量
|
||||
├── api.ts # API 工具函数
|
||||
├── mod.ts # 模块导出
|
||||
├── ModuleLayout.vue # 模块布局组件
|
||||
├── StatCard.vue # 统计卡片组件
|
||||
└── DataTable.vue # 数据表格组件
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 1. 导入配置常量
|
||||
|
||||
```ts
|
||||
import {
|
||||
EVENT_CATEGORIES,
|
||||
TEAM_GROUPS,
|
||||
EVENT_TYPES,
|
||||
SCORING_RULES
|
||||
} from '~/modules/scoreboard'
|
||||
|
||||
// 使用示例
|
||||
const categories = Object.values(EVENT_CATEGORIES)
|
||||
// ['田赛', '径赛', '团体赛']
|
||||
```
|
||||
|
||||
### 2. 使用 API 函数
|
||||
|
||||
```ts
|
||||
import {
|
||||
fetchEvents,
|
||||
fetchTeams,
|
||||
fetchResults,
|
||||
fetchScoreboard,
|
||||
createResult
|
||||
} from '~/modules/scoreboard'
|
||||
|
||||
// 获取所有比赛项目
|
||||
const events = await fetchEvents()
|
||||
|
||||
// 按类别筛选
|
||||
const trackEvents = await fetchEvents({ category: '径赛' })
|
||||
|
||||
// 获取队伍
|
||||
const teams = await fetchTeams({ group: '文化班甲组' })
|
||||
|
||||
// 录入成绩
|
||||
await createResult({
|
||||
event_id: 1,
|
||||
team_id: 1,
|
||||
score: '10.5',
|
||||
rank: 1
|
||||
})
|
||||
|
||||
// 获取记分板
|
||||
const scoreboard = await fetchScoreboard()
|
||||
```
|
||||
|
||||
### 3. 使用 UI 组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ModuleLayout title="运动会" description="管理系统">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<StatCard
|
||||
icon="Trophy"
|
||||
label="比赛项目"
|
||||
:value="12"
|
||||
color="#409eff"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<DataTable :data="scoreboard" :columns="columns" />
|
||||
</ModuleLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ModuleLayout, StatCard, DataTable } from '~/modules/scoreboard'
|
||||
|
||||
const columns = [
|
||||
{ prop: 'name', label: '队伍名称' },
|
||||
{ prop: 'total_score', label: '总分', sortable: true }
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
## 积分规则
|
||||
|
||||
| 名次 | 积分 | 奖牌 |
|
||||
|------|------|------|
|
||||
| 第 1 名 | 7 分 | 金牌 |
|
||||
| 第 2 名 | 5 分 | 银牌 |
|
||||
| 第 3 名 | 3 分 | 铜牌 |
|
||||
|
||||
## 比赛项目
|
||||
|
||||
### 田赛
|
||||
- 跳高(米)
|
||||
- 跳远(米)
|
||||
- 掷铅球(米)
|
||||
|
||||
### 径赛
|
||||
- 100m(秒)
|
||||
- 200m(秒)
|
||||
- 400m(秒)
|
||||
- 4×100m(秒)
|
||||
- 4×400m(秒)
|
||||
- 20×50m(秒)
|
||||
|
||||
### 团体赛
|
||||
- 旱地龙舟(秒)
|
||||
- 跳长绳(次)
|
||||
- 折返跑(秒)
|
||||
|
||||
## 组别
|
||||
|
||||
- 教师组
|
||||
- 航空班组
|
||||
- 体育班组
|
||||
- 文化班甲组
|
||||
- 文化班乙组
|
||||
|
||||
## API 接口
|
||||
|
||||
| 接口 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/config` | GET | 获取系统配置 |
|
||||
| `/api/events` | GET/POST | 比赛项目管理 |
|
||||
| `/api/teams` | GET/POST | 队伍管理 |
|
||||
| `/api/results` | GET/POST | 成绩管理 |
|
||||
| `/api/scoreboard` | GET | 记分板数据 |
|
||||
| `/api/seed` | POST | 初始化数据 |
|
||||
|
||||
## 扩展模块
|
||||
|
||||
可以通过以下方式扩展模块功能:
|
||||
|
||||
1. 添加新的比赛项目类型
|
||||
2. 自定义积分规则
|
||||
3. 添加数据导出功能
|
||||
4. 集成图表展示
|
||||
5. 添加实时通知
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
66
app/modules/scoreboard/StatCard.vue
Normal file
66
app/modules/scoreboard/StatCard.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<el-icon class="stat-icon" :color="color">
|
||||
<component :is="icon" />
|
||||
</el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ value }}</div>
|
||||
<div class="stat-label">{{ label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
icon: string
|
||||
label: string
|
||||
value?: string | number
|
||||
color?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
value: '0',
|
||||
color: '#409eff'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
106
app/modules/scoreboard/api.ts
Normal file
106
app/modules/scoreboard/api.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 记分板模块 API 工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取配置信息
|
||||
*/
|
||||
export const fetchConfig = async () => {
|
||||
const res = await $fetch('/api/config')
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取比赛项目列表
|
||||
*/
|
||||
export const fetchEvents = async (params?: { category?: string; group?: string }) => {
|
||||
const query = new URLSearchParams()
|
||||
if (params?.category) query.append('category', params.category)
|
||||
if (params?.group) query.append('group', params.group)
|
||||
const res = await $fetch(`/api/events?${query}`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建比赛项目
|
||||
*/
|
||||
export const createEvent = async (data: {
|
||||
name: string
|
||||
category: string
|
||||
event_group: string
|
||||
unit: string
|
||||
}) => {
|
||||
const res = await $fetch('/api/events', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队伍列表
|
||||
*/
|
||||
export const fetchTeams = async (params?: { group?: string }) => {
|
||||
const query = new URLSearchParams()
|
||||
if (params?.group) query.append('group', params.group)
|
||||
const res = await $fetch(`/api/teams?${query}`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建队伍
|
||||
*/
|
||||
export const createTeam = async (data: { name: string; team_group: string }) => {
|
||||
const res = await $fetch('/api/teams', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成绩列表
|
||||
*/
|
||||
export const fetchResults = async (params?: { event_id?: number; team_id?: number }) => {
|
||||
const query = new URLSearchParams()
|
||||
if (params?.event_id) query.append('event_id', String(params.event_id))
|
||||
if (params?.team_id) query.append('team_id', String(params.team_id))
|
||||
const res = await $fetch(`/api/results?${query}`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 录入成绩
|
||||
*/
|
||||
export const createResult = async (data: {
|
||||
event_id: number
|
||||
team_id: number
|
||||
score: string
|
||||
rank?: number
|
||||
}) => {
|
||||
const res = await $fetch('/api/results', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记分板数据
|
||||
*/
|
||||
export const fetchScoreboard = async (params?: { group?: string }) => {
|
||||
const query = new URLSearchParams()
|
||||
if (params?.group) query.append('group', params.group)
|
||||
const res = await $fetch(`/api/scoreboard?${query}`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化示例数据
|
||||
*/
|
||||
export const seedData = async () => {
|
||||
const res = await $fetch('/api/seed', {
|
||||
method: 'POST'
|
||||
})
|
||||
return res
|
||||
}
|
||||
77
app/modules/scoreboard/index.ts
Normal file
77
app/modules/scoreboard/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 运动会记分板系统模块
|
||||
*
|
||||
* 此模块提供完整的运动会管理功能
|
||||
* 可作为独立模块集成到更大的系统中
|
||||
*/
|
||||
|
||||
// 模块配置
|
||||
export const scoreboardModule = {
|
||||
name: 'scoreboard',
|
||||
version: '1.0.0',
|
||||
description: '运动会记分板管理模块'
|
||||
}
|
||||
|
||||
// 比赛类别配置
|
||||
export const EVENT_CATEGORIES = {
|
||||
FIELD: '田赛',
|
||||
TRACK: '径赛',
|
||||
TEAM: '团体赛'
|
||||
} as const
|
||||
|
||||
// 组别配置
|
||||
export const TEAM_GROUPS = {
|
||||
TEACHER: '教师组',
|
||||
AVIATION: '航空班组',
|
||||
SPORTS: '体育班组',
|
||||
CULTURE_A: '文化班甲组',
|
||||
CULTURE_B: '文化班乙组'
|
||||
} as const
|
||||
|
||||
// 项目配置
|
||||
export const EVENT_TYPES = {
|
||||
[EVENT_CATEGORIES.FIELD]: [
|
||||
{ name: '跳高', unit: '米' },
|
||||
{ name: '跳远', unit: '米' },
|
||||
{ name: '掷铅球', unit: '米' }
|
||||
],
|
||||
[EVENT_CATEGORIES.TRACK]: [
|
||||
{ name: '100m', unit: '秒' },
|
||||
{ name: '200m', unit: '秒' },
|
||||
{ name: '400m', unit: '秒' },
|
||||
{ name: '4×100m', unit: '秒' },
|
||||
{ name: '4×400m', unit: '秒' },
|
||||
{ name: '20×50m', unit: '秒' }
|
||||
],
|
||||
[EVENT_CATEGORIES.TEAM]: [
|
||||
{ name: '旱地龙舟', unit: '秒' },
|
||||
{ name: '跳长绳', unit: '次' },
|
||||
{ name: '折返跑', unit: '秒' }
|
||||
]
|
||||
}
|
||||
|
||||
// 积分规则
|
||||
export const SCORING_RULES = {
|
||||
GOLD: { rank: 1, points: 7, medal: 'gold' },
|
||||
SILVER: { rank: 2, points: 5, medal: 'silver' },
|
||||
BRONZE: { rank: 3, points: 3, medal: 'bronze' }
|
||||
}
|
||||
|
||||
// API 路由
|
||||
export const API_ROUTES = {
|
||||
EVENTS: '/api/events',
|
||||
TEAMS: '/api/teams',
|
||||
RESULTS: '/api/results',
|
||||
SCOREBOARD: '/api/scoreboard',
|
||||
CONFIG: '/api/config',
|
||||
SEED: '/api/seed'
|
||||
} as const
|
||||
|
||||
// 页面路由
|
||||
export const PAGE_ROUTES = {
|
||||
HOME: '/',
|
||||
EVENTS: '/events',
|
||||
TEAMS: '/teams',
|
||||
RESULTS: '/results',
|
||||
SCOREBOARD: '/scoreboard'
|
||||
} as const
|
||||
50
app/modules/scoreboard/mod.ts
Normal file
50
app/modules/scoreboard/mod.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 运动会记分板模块导出
|
||||
*
|
||||
* 使用示例:
|
||||
* ```ts
|
||||
* import {
|
||||
* EVENT_CATEGORIES,
|
||||
* TEAM_GROUPS,
|
||||
* EVENT_TYPES,
|
||||
* SCORING_RULES,
|
||||
* API_ROUTES,
|
||||
* fetchEvents,
|
||||
* fetchTeams,
|
||||
* fetchResults,
|
||||
* fetchScoreboard
|
||||
* } from '~/modules/scoreboard'
|
||||
* ```
|
||||
*/
|
||||
|
||||
// 配置常量
|
||||
export {
|
||||
EVENT_CATEGORIES,
|
||||
TEAM_GROUPS,
|
||||
EVENT_TYPES,
|
||||
SCORING_RULES,
|
||||
API_ROUTES,
|
||||
PAGE_ROUTES,
|
||||
scoreboardModule
|
||||
} from './index'
|
||||
|
||||
// API 函数
|
||||
export {
|
||||
fetchConfig,
|
||||
fetchEvents,
|
||||
createEvent,
|
||||
fetchTeams,
|
||||
createTeam,
|
||||
fetchResults,
|
||||
createResult,
|
||||
fetchScoreboard,
|
||||
seedData
|
||||
} from './api'
|
||||
|
||||
// Vue 组件
|
||||
export { default as ModuleLayout } from './ModuleLayout.vue'
|
||||
export { default as StatCard } from './StatCard.vue'
|
||||
export { default as DataTable } from './DataTable.vue'
|
||||
|
||||
// 类型导出
|
||||
export type { Column } from './DataTable.vue'
|
||||
Loading…
x
Reference in New Issue
Block a user