一、超管端设计优化 - 文档管理SOP体系建立,docs目录重组 - 统一用户管理:跨租户全局视角,合并用户管理+公众用户 - 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作) - 菜单精简:移除评委管理/评审规则/通知管理 - Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一 二、UGC绘本创作社区P0 - 数据库:10张新表(user_works/user_work_pages/work_tags等) - 子女账号独立化:Child升级为独立User,家长切换+独立登录 - 用户作品库:CRUD+发布审核,8个API - AI创作流程:提交→生成→保存到作品库,4个API - 作品广场:首页改造为推荐流,标签+搜索+排序 - 内容审核(超管端):作品审核+作品管理+标签管理 - 活动联动:WorkSelector作品选择器 - 布局改造:底部5Tab(发现/创作/活动/作品库/我的) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
12 KiB
赛事列表页面生成规范
概述
本规范用于快速生成赛事管理系统中的列表页面,这类页面具有统一的结构:
- Tab 切换(个人赛/团队赛)
- 搜索表单
- 数据表格
- 操作按钮
页面结构
┌─────────────────────────────────────────────────┐
│ 标题卡片 (a-card) │
├─────────────────────────────────────────────────┤
│ Tab栏: [个人赛] [团队赛] │
├─────────────────────────────────────────────────┤
│ 搜索表单: [搜索条件...] [搜索] [重置] │
├─────────────────────────────────────────────────┤
│ 数据表格 │
│ ┌───┬──────┬──────┬──────┬──────┐ │
│ │序号│ 列1 │ 列2 │ 列3 │ 操作 │ │
│ ├───┼──────┼──────┼──────┼──────┤ │
│ │ 1 │ ... │ ... │ ... │ 详情 │ │
│ │ 2 │ ... │ ... │ ... │ 详情 │ │
│ └───┴──────┴──────┴──────┴──────┘ │
└─────────────────────────────────────────────────┘
配置参数
生成页面时需要提供以下配置:
1. 基础配置
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pageName | string | 是 | 页面名称,如"赛果发布"、"报名管理" |
| pageClass | string | 是 | CSS类名,如"results-page" |
| routePrefix | string | 是 | 路由前缀,如"results"、"registrations" |
| detailRouteName | string | 否 | 详情页路由名称 |
2. 搜索条件配置
interface SearchField {
field: string // 字段名
label: string // 标签文本
type: 'input' | 'select' | 'date' | 'dateRange' // 控件类型
placeholder?: string // 占位文本
width?: string // 宽度,默认 "200px"
options?: Array<{ label: string; value: string }> // select 类型的选项
}
示例:
const searchFields = [
{ field: 'contestName', label: '赛事名称', type: 'input', placeholder: '请输入赛事名称' },
{ field: 'status', label: '状态', type: 'select', options: [
{ label: '已发布', value: 'published' },
{ label: '未发布', value: 'unpublished' }
]}
]
3. 表格列配置
interface TableColumn {
title: string // 列标题
key: string // 列标识
dataIndex?: string // 数据字段(简单字段直接映射)
width?: number // 列宽度
fixed?: 'left' | 'right' // 固定列
render?: 'index' | 'link' | 'count' | 'tag' | 'date' | 'dateRange' | 'custom' // 渲染类型
renderConfig?: {
// link 类型
clickHandler?: string // 点击处理函数名
// count 类型
countField?: string // _count 下的字段名
// tag 类型
colorMap?: Record<string, string> // 值到颜色的映射
textMap?: Record<string, string> // 值到文本的映射
// dateRange 类型
startField?: string // 开始时间字段
endField?: string // 结束时间字段
startLabel?: string // 开始标签
endLabel?: string // 结束标签
}
}
常用渲染类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| index | 序号列 | 自动计算 (current-1)*pageSize+index+1 |
| link | 可点击链接 | 赛事名称点击跳转 |
| count | 统计数量 | record._count?.registrations |
| tag | 标签展示 | 状态标签(不同颜色) |
| date | 单个日期 | YYYY-MM-DD HH:mm |
| dateRange | 日期范围 | 开始:xxx / 结束:xxx |
| custom | 自定义渲染 | 需要单独写模板 |
4. 操作按钮配置
interface ActionButton {
text: string // 按钮文本
type?: 'link' | 'primary' | 'default' // 按钮类型
danger?: boolean // 是否危险按钮
permission?: string // 权限标识
handler: string // 处理函数名
disabled?: (record: any) => boolean // 禁用条件
}
页面模板
完整模板代码
<template>
<div class="{{pageClass}}">
<a-card class="mb-4">
<template #title>{{pageName}}</template>
</a-card>
<!-- Tab栏切换 -->
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
<a-tab-pane key="individual" tab="个人赛" />
<a-tab-pane key="team" tab="团队赛" />
</a-tabs>
<!-- 搜索表单 -->
<a-form
:model="searchParams"
layout="inline"
class="search-form"
@finish="handleSearch"
>
<!-- 根据 searchFields 配置生成 -->
<a-form-item v-for="field in searchFields" :key="field.field" :label="field.label">
<!-- input 类型 -->
<a-input
v-if="field.type === 'input'"
v-model:value="searchParams[field.field]"
:placeholder="field.placeholder"
allow-clear
:style="{ width: field.width || '200px' }"
/>
<!-- select 类型 -->
<a-select
v-else-if="field.type === 'select'"
v-model:value="searchParams[field.field]"
:placeholder="field.placeholder"
allow-clear
:style="{ width: field.width || '150px' }"
>
<a-select-option
v-for="opt in field.options"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">
<template #icon><SearchOutlined /></template>
搜索
</a-button>
<a-button style="margin-left: 8px" @click="handleReset">
<template #icon><ReloadOutlined /></template>
重置
</a-button>
</a-form-item>
</a-form>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record, index }">
<!-- 根据列配置渲染 -->
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
import { useRouter, useRoute } from "vue-router"
import { message } from "ant-design-vue"
import { SearchOutlined, ReloadOutlined } from "@ant-design/icons-vue"
import { contestsApi, type Contest, type QueryContestParams } from "@/api/contests"
import dayjs from "dayjs"
const router = useRouter()
const route = useRoute()
const tenantCode = route.params.tenantCode as string
// Tab状态
const activeTab = ref<"individual" | "team">("individual")
// 列表状态
const loading = ref(false)
const dataSource = ref<Contest[]>([])
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
})
// 搜索参数
const searchParams = reactive<QueryContestParams>({
contestName: "",
})
// 表格列定义
const columns = [
// 根据配置生成
]
// 格式化日期
const formatDateTime = (dateStr?: string) => {
if (!dateStr) return "-"
return dayjs(dateStr).format("YYYY-MM-DD HH:mm")
}
// 获取列表数据
const fetchList = async () => {
loading.value = true
try {
const params: QueryContestParams = {
...searchParams,
contestType: activeTab.value,
page: pagination.current,
pageSize: pagination.pageSize,
}
const response = await contestsApi.getList(params)
dataSource.value = response.list
pagination.total = response.total
} catch (error) {
message.error("获取列表失败")
} finally {
loading.value = false
}
}
// Tab切换
const handleTabChange = () => {
pagination.current = 1
fetchList()
}
// 搜索
const handleSearch = () => {
pagination.current = 1
fetchList()
}
// 重置
const handleReset = () => {
// 重置所有搜索参数
pagination.current = 1
fetchList()
}
// 表格变化
const handleTableChange = (pag: any) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchList()
}
// 查看详情
const handleViewDetail = (record: Contest) => {
router.push(`/${tenantCode}/contests/{{routePrefix}}/${record.id}`)
}
onMounted(() => {
fetchList()
})
</script>
<style scoped>
.{{pageClass}} {
.search-form {
margin-bottom: 16px;
}
}
</style>
使用示例
示例1:赛果发布列表
配置:
pageName: 赛果发布
pageClass: results-page
routePrefix: results
searchFields:
- field: contestName
label: 赛事名称
type: input
placeholder: 请输入赛事名称
columns:
- title: 序号
key: index
width: 70
render: index
- title: 赛事名称
key: contestName
dataIndex: contestName
width: 200
- title: 报名人数
key: registrationCount
width: 100
render: count
renderConfig:
countField: registrations
- title: 提交作品数
key: worksCount
width: 100
render: count
renderConfig:
countField: works
- title: 操作
key: action
width: 100
fixed: right
actions:
- text: 详情
handler: handleViewDetail
示例2:报名管理列表
配置:
pageName: 报名管理
pageClass: registrations-page
routePrefix: registrations
searchFields:
- field: contestName
label: 赛事名称
type: input
placeholder: 请输入赛事名称
columns:
- title: 序号
key: index
width: 70
render: index
- title: 赛事名称
key: contestName
dataIndex: contestName
width: 250
- title: 主办单位
key: organizers
width: 200
render: custom # 需要格式化JSON数组
- title: 报名人数
key: registrationCount
width: 120
render: count
renderConfig:
countField: registrations
- title: 报名时间
key: registerTime
width: 200
render: dateRange
renderConfig:
startField: registerStartTime
endField: registerEndTime
startLabel: 开始
endLabel: 结束
- title: 操作
key: action
width: 220
fixed: right
actions:
- text: 报名记录
handler: handleViewRecords
- text: 启动报名
handler: handleStartRegistration
permission: contest:update
- text: 关闭报名
handler: handleStopRegistration
permission: contest:update
danger: true
快速生成指令
向 Claude 提供以下格式的指令即可快速生成页面:
请生成一个赛事列表页面:
页面名称:赛果发布
路由前缀:results
文件路径:frontend/src/views/contests/results/Index.vue
搜索条件:
- 赛事名称(输入框)
表格列:
- 序号
- 赛事名称
- 报名人数(_count.registrations)
- 提交作品数(_count.works)
- 操作(详情按钮)
详情跳转:/${tenantCode}/contests/results/${record.id}
注意事项
- API 数据:确保后端 API 返回了所需的统计字段(如
_count) - 路由配置:生成页面后需要在
router/index.ts中配置路由 - 权限控制:操作按钮需要配置
v-permission指令 - 样式一致性:使用 Ant Design Vue 组件,保持系统风格统一
- 团队赛差异:如果个人赛和团队赛的列不同,需要定义两套 columns