feat(admin): 新增模板管理功能并完善用户认证
- 新增模板管理模块,包含模板列表和详情页面 - 重构用户认证逻辑,支持用户ID存储和API调用 - 新增排行榜API接口和类型定义 - 更新环境配置,添加开发环境配置 - 优化表格操作钩子,增强类型安全性 - 新增文件上传工具类和验证工具函数 - 更新路由配置,支持模板详情页面导航 - 修复登录表单默认值问题
This commit is contained in:
@ -20,7 +20,7 @@ interface FormModel {
|
||||
}
|
||||
|
||||
const model: FormModel = reactive({
|
||||
userName: 'Admin',
|
||||
userName: '111111',
|
||||
password: '123456',
|
||||
})
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ const competitionData = ref({
|
||||
rounds: [ // 赛题包配置
|
||||
{
|
||||
id: 'round_default',
|
||||
name: '第一轮:主赛环节',
|
||||
name: '星·辞海遨游',
|
||||
roundType: roundTypeOptions[0].value,
|
||||
questions: Array.from({ length: 10 }).map(() => ({
|
||||
value: null,
|
||||
@ -203,7 +203,7 @@ function handleReset() {
|
||||
type="primary" size="large" class="px-10 text-lg font-bold shadow-blue-500/30 shadow-lg"
|
||||
@click="nextStep"
|
||||
>
|
||||
{{ currentStep === 4 ? '确认发布比赛' : '下一步' }}
|
||||
{{ currentStep === 3 ? '确认发布比赛' : '下一步' }}
|
||||
<template #icon>
|
||||
<icon-ic-baseline-arrow-forward v-if="currentStep < 4" class="text-icon" />
|
||||
<icon-ic-baseline-check v-else class="text-icon" />
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { NButton, NForm, NFormItem, NInput, NInputNumber, NModal, NSelect, type SelectOption } from 'naive-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { roundTypeOptions, scoreTypeOptions } from '@/constants/business'
|
||||
import { roundTypeOptions, scoreTypeOptions, timeOptions, uiTypeOptions } from '@/constants/business'
|
||||
import { useQuestionConfig } from './useQuestionConfig'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -11,7 +11,15 @@ const props = defineProps<{
|
||||
id: string
|
||||
name: string
|
||||
roundType: string
|
||||
questions: Array<{ id: string, value: string | null, time: number, title: string, scoreType: Api.Competition.QuestionScoreType }>
|
||||
questions: Array<{
|
||||
id: string
|
||||
value: string | null
|
||||
time: number
|
||||
title: string
|
||||
scoreType: Api.Competition.QuestionScoreType
|
||||
uiType?: string
|
||||
templateId?: string
|
||||
}>
|
||||
}>
|
||||
}
|
||||
}>()
|
||||
@ -20,7 +28,6 @@ const {
|
||||
getRoundTime,
|
||||
totalStats,
|
||||
questionOptions,
|
||||
timeOptions,
|
||||
addRound,
|
||||
removeRound,
|
||||
addQuestion,
|
||||
@ -29,6 +36,25 @@ const {
|
||||
reset,
|
||||
} = useQuestionConfig(props)
|
||||
|
||||
const templateIdOptions: SelectOption[] = [
|
||||
{
|
||||
label: '模板1',
|
||||
value: 't1',
|
||||
},
|
||||
{
|
||||
label: '模版2',
|
||||
value: 't2',
|
||||
},
|
||||
{
|
||||
label: '模版3',
|
||||
value: 't3',
|
||||
},
|
||||
{
|
||||
label: '模版4',
|
||||
value: 't4',
|
||||
},
|
||||
]
|
||||
|
||||
// 模态框控制
|
||||
const showAddModal = ref(false)
|
||||
const newRoundForm = ref({
|
||||
@ -39,7 +65,7 @@ const newRoundForm = ref({
|
||||
|
||||
function handleOpenAddModal() {
|
||||
newRoundForm.value = {
|
||||
name: `第${(props.modelValue?.rounds?.length || 0) + 1}轮 汉字听写`,
|
||||
name: `第${(props.modelValue?.rounds?.length || 0) + 1}轮 星·诗词大会`,
|
||||
count: 10,
|
||||
roundType: roundTypeOptions[0].value,
|
||||
}
|
||||
@ -51,7 +77,7 @@ function handleConfirmAdd() {
|
||||
window.$message?.warning('请输入环节名称')
|
||||
return
|
||||
}
|
||||
addRound(newRoundForm.value.name, newRoundForm.value.count, newRoundForm.value.roundType)
|
||||
addRound(newRoundForm.value.name, newRoundForm.value.count, newRoundForm.value.roundType as Api.Competition.CompetitionRoundType)
|
||||
showAddModal.value = false
|
||||
}
|
||||
|
||||
@ -145,7 +171,7 @@ defineExpose({ validate, reset })
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="h-8 w-1.5 rounded-full bg-blue-500" />
|
||||
<NTag type="primary" class="!text-white">
|
||||
<NTag type="primary" class="!text-blue-500">
|
||||
{{ roundTypeOptions.find((item) => item.value === round.roundType)?.label || '未知类型' }}
|
||||
</NTag>
|
||||
<div class="flex items-center gap-3">
|
||||
@ -196,70 +222,97 @@ defineExpose({ validate, reset })
|
||||
|
||||
<!-- 题目列表 -->
|
||||
<VueDraggable
|
||||
v-model="round.questions" :animation="300" handle=".drag-handle" class="flex flex-col gap-2"
|
||||
v-model="round.questions" :animation="300" handle=".drag-handle" class="flex flex-col gap-3"
|
||||
ghost-class="ghost"
|
||||
>
|
||||
<div
|
||||
v-for="(item, qIdx) in round.questions" :key="item.id"
|
||||
class="group/item flex items-center gap-2 border border-gray-100 rounded-xl bg-white p-2 px-2 transition-all hover:border-blue-200 hover:shadow-sm"
|
||||
class="group/item flex flex-col gap-3 border border-gray-100 rounded-xl bg-white p-3 transition-all hover:border-blue-200 hover:shadow-sm"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="drag-handle flex cursor-grab items-center justify-center rounded p-1 text-gray-400 transition-colors active:cursor-grabbing hover:bg-gray-100 hover:text-blue-500"
|
||||
>
|
||||
<icon-mdi-drag class="text-xl" />
|
||||
<!-- 第一行:序号、标题、删除按钮 -->
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex flex-1 items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="drag-handle flex cursor-grab items-center justify-center rounded p-1 text-gray-400 transition-colors active:cursor-grabbing hover:bg-gray-100 hover:text-blue-500"
|
||||
>
|
||||
<icon-mdi-drag class="text-xl" />
|
||||
</div>
|
||||
<span
|
||||
class="w-6 flex-shrink-0 text-center text-sm text-gray-300 font-bold transition-colors group-hover/item:text-blue-500"
|
||||
>
|
||||
{{ String(qIdx + 1).padStart(2, '0') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<div class="flex flex-1 items-center gap-2 rounded-lg bg-gray-50 px-3 py-1.5">
|
||||
<icon-ic-outline-title class="text-gray-400" />
|
||||
<span class="whitespace-nowrap text-sm text-gray-400 font-bold">标题:</span>
|
||||
<NInput
|
||||
v-model:value="item.title" class="flex-1 !bg-transparent" size="small" :bordered="false"
|
||||
placeholder="请输入题目标题"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="w-6 flex-shrink-0 text-center text-sm text-gray-300 font-bold transition-colors group-hover/item:text-blue-500"
|
||||
>{{
|
||||
String(qIdx + 1).padStart(2, '0') }}</span>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<NButton
|
||||
text class="transition-opacity !text-gray-300 hover:!text-red-500"
|
||||
@click="removeQuestion(roundIndex, qIdx)"
|
||||
>
|
||||
<icon-ic-outline-delete class="!text-xl" />
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<!-- 题型 -->
|
||||
<div class="flex-1">
|
||||
<NSelect
|
||||
v-model:value="item.value" :options="questionOptions" placeholder="请选择题目类型..."
|
||||
:bordered="false" class="font-medium"
|
||||
/>
|
||||
</div>
|
||||
<!-- 第二行:配置项 -->
|
||||
<div class="grid grid-cols-12 gap-3">
|
||||
<!-- 题型 -->
|
||||
<div class="col-span-4">
|
||||
<NSelect
|
||||
v-model:value="item.value" :options="questionOptions" placeholder="请选择题目类型" size="small"
|
||||
class="font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 时间 -->
|
||||
<div class="flex items-center gap-2 rounded-lg bg-gray-50 px-3 py-1.5">
|
||||
<icon-ic-outline-timer class="text-gray-400" />
|
||||
<span class="whitespace-nowrap text-sm text-gray-400 font-bold">答题时间:</span>
|
||||
<NSelect
|
||||
v-model:value="item.time" :options="timeOptions" class="w-26 !bg-transparent" size="small"
|
||||
:bordered="false"
|
||||
/>
|
||||
<span class="text-xs text-gray-400 font-bold">S</span>
|
||||
</div>
|
||||
<!-- UItype -->
|
||||
<div class="col-span-4">
|
||||
<NSelect
|
||||
v-model:value="item.uiType" :options="uiTypeOptions" placeholder="请选择UI类型"
|
||||
size="small" class="font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分数规则 -->
|
||||
<div class="flex items-center gap-2 rounded-lg bg-gray-50 px-3 py-1.5">
|
||||
<icon-streamline-sharp:type-area-remix class="text-gray-400" />
|
||||
<span class="whitespace-nowrap text-sm text-gray-400 font-bold">分数规则:</span>
|
||||
<NSelect
|
||||
v-model:value="item.scoreType" :options="scoreTypeOptions as unknown as SelectOption[]"
|
||||
class="w-28 !bg-transparent" size="small" :bordered="false"
|
||||
/>
|
||||
<span class="text-xs text-gray-400 font-bold">S</span>
|
||||
</div>
|
||||
<!-- templateId -->
|
||||
<div class="col-span-4">
|
||||
<NSelect
|
||||
v-model:value="item.templateId" :options="templateIdOptions" placeholder="请选择模板"
|
||||
size="small" class="font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<div class="flex items-center gap-2 rounded-lg bg-gray-50 px-3 py-1.5">
|
||||
<icon-ic-outline-title class="text-gray-400" />
|
||||
<span class="whitespace-nowrap text-sm text-gray-400 font-bold">标题:</span>
|
||||
<NInput v-model:value="item.title" class="w-28 !bg-transparent" size="small" :bordered="false" />
|
||||
</div>
|
||||
<!-- 时间 -->
|
||||
<div class="col-span-6 flex items-center gap-2 rounded-lg bg-gray-50 px-3 py-1.5">
|
||||
<icon-ic-outline-timer class="text-gray-400" />
|
||||
<span class="whitespace-nowrap text-sm text-gray-400 font-bold">答题时间:</span>
|
||||
<NSelect
|
||||
v-model:value="item.time" :options="timeOptions" class="flex-1 !bg-transparent" size="small"
|
||||
:bordered="false"
|
||||
/>
|
||||
<span class="text-xs text-gray-400 font-bold">S</span>
|
||||
</div>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<NButton
|
||||
text
|
||||
class="ml-2 opacity-0 transition-opacity !text-gray-300 group-hover/item:opacity-100 hover:!text-red-500"
|
||||
@click="removeQuestion(roundIndex, qIdx)"
|
||||
>
|
||||
<icon-ic-outline-delete class="!text-xl" />
|
||||
</NButton>
|
||||
<!-- 分数规则 -->
|
||||
<div class="col-span-6 flex items-center gap-2 rounded-lg bg-gray-50 px-3 py-1.5">
|
||||
<icon-streamline-sharp:type-area-remix class="text-gray-400" />
|
||||
<span class="whitespace-nowrap text-sm text-gray-400 font-bold">分数规则:</span>
|
||||
<NSelect
|
||||
v-model:value="item.scoreType" :options="scoreTypeOptions as unknown as SelectOption[]"
|
||||
class="flex-1 !bg-transparent" size="small" :bordered="false"
|
||||
/>
|
||||
<span class="text-xs text-gray-400 font-bold">分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
@ -285,7 +338,7 @@ defineExpose({ validate, reset })
|
||||
|
||||
<NForm size="large">
|
||||
<NFormItem label="环节/题包名称">
|
||||
<NInput v-model:value="newRoundForm.name" placeholder="例如:第一轮 汉字听写" />
|
||||
<NInput v-model:value="newRoundForm.name" placeholder="例如:第一轮 星·诗词大会" />
|
||||
</NFormItem>
|
||||
<NFormItem label="环节/题包类型">
|
||||
<NSelect
|
||||
|
||||
@ -8,6 +8,8 @@ export interface QuestionItem {
|
||||
time: number
|
||||
title: string
|
||||
scoreType: Api.Competition.QuestionScoreType
|
||||
uiType?: string
|
||||
templateId?: string
|
||||
}
|
||||
|
||||
export interface RoundModel {
|
||||
@ -49,12 +51,12 @@ export function useQuestionConfig(props: any) {
|
||||
})
|
||||
|
||||
const questionOptions = [
|
||||
{ label: '汉字听写 - 根据提示书写汉字', value: 'hanzi' },
|
||||
{ label: '同音字辨析 - 根据拼音书写', value: 'tongyin' },
|
||||
{ label: '偏旁部首 - 按要求写字', value: 'pianpang' },
|
||||
{ label: '词语听写 - 综合练习', value: 'ciyu' },
|
||||
{ label: '成语填空 - 四字成语', value: 'chengyu' },
|
||||
{ label: '反义词挑战', value: 'fanyi' },
|
||||
{ label: '请根据提示书写正确的汉字-jiū 表示小鸟的叫声。', value: 'hanzi' },
|
||||
{ label: '请根据提示书写正确的汉字-“春色满园关不住,一枝红杏出墙来”。请书写“杏”字。', value: 'tongyin' },
|
||||
{ label: '请写出含有“车”的汉字。', value: 'pianpang' },
|
||||
{ label: '请根据拼音书写正确的词语。', value: 'ciyu' },
|
||||
{ label: '请写出含有反义字的四字成语。 - 例如:“不日而月”', value: 'chengyu' },
|
||||
{ label: '请根据图片书写正确的成语。', value: 'fanyi' },
|
||||
{ label: '近义词挑战', value: 'jinyi' },
|
||||
{ label: '看图猜字', value: 'kantu' },
|
||||
{ label: '古诗词接龙', value: 'gushi' },
|
||||
@ -63,14 +65,6 @@ export function useQuestionConfig(props: any) {
|
||||
{ label: '极速成语接龙 - 每题20秒', value: 'speed20' },
|
||||
]
|
||||
|
||||
const timeOptions = [
|
||||
{ label: '15s', value: 15 },
|
||||
{ label: '30s', value: 30 },
|
||||
{ label: '45s', value: 45 },
|
||||
{ label: '60s', value: 60 },
|
||||
{ label: '90s', value: 90 },
|
||||
]
|
||||
|
||||
// 新增轮次
|
||||
function addRound(customName?: string, customCount?: number, roundType?: Api.Competition.CompetitionRoundType) {
|
||||
if (!props.modelValue.rounds) {
|
||||
@ -141,8 +135,35 @@ export function useQuestionConfig(props: any) {
|
||||
return false
|
||||
}
|
||||
for (let j = 0; j < round.questions.length; j++) {
|
||||
if (!round.questions[j].value) {
|
||||
window.$message?.error(`${round.name} 第 ${j + 1} 题未选择题型`)
|
||||
const question = round.questions[j]
|
||||
const questionPrefix = `${round.name} 第 ${j + 1} 题`
|
||||
|
||||
if (!question.value) {
|
||||
window.$message?.error(`${questionPrefix}未选择题型`)
|
||||
return false
|
||||
}
|
||||
if (!question.uiType) {
|
||||
window.$message?.error(`${questionPrefix}未选择UI类型`)
|
||||
return false
|
||||
}
|
||||
if (!question.templateId) {
|
||||
window.$message?.error(`${questionPrefix}未选择模板`)
|
||||
return false
|
||||
}
|
||||
if (!question.time) {
|
||||
window.$message?.error(`${questionPrefix}未设置答题时间`)
|
||||
return false
|
||||
}
|
||||
if (!question.scoreType) {
|
||||
window.$message?.error(`${questionPrefix}未设置分数规则`)
|
||||
return false
|
||||
}
|
||||
if (!question.title) {
|
||||
window.$message?.error(`${questionPrefix}未填写标题`)
|
||||
return false
|
||||
}
|
||||
if (question.score === null || question.score === undefined) {
|
||||
window.$message?.error(`${questionPrefix}未设置分数`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -161,7 +182,6 @@ export function useQuestionConfig(props: any) {
|
||||
getRoundTime,
|
||||
totalStats,
|
||||
questionOptions,
|
||||
timeOptions,
|
||||
addRound,
|
||||
removeRound,
|
||||
addQuestion,
|
||||
|
||||
@ -90,6 +90,7 @@ const questionForm = ref({
|
||||
answer: '',
|
||||
score: 10,
|
||||
time: 30,
|
||||
imageUrl: '',
|
||||
})
|
||||
|
||||
const isLeafSelected = computed(() => {
|
||||
@ -119,7 +120,7 @@ function handleAddQuestion() {
|
||||
}
|
||||
questionOperation.value = 'add'
|
||||
currentQuestionId.value = null
|
||||
questionForm.value = { content: '', answer: '', score: 10, time: 30 }
|
||||
questionForm.value = { content: '', answer: '', score: 10, time: 30, imageUrl: '' }
|
||||
showQuestionModal.value = true
|
||||
}
|
||||
|
||||
@ -133,6 +134,7 @@ function handleEditQuestion(id: string) {
|
||||
answer: question.answer,
|
||||
score: question.score,
|
||||
time: question.time,
|
||||
imageUrl: (question as any).imageUrl || '',
|
||||
}
|
||||
showQuestionModal.value = true
|
||||
}
|
||||
@ -159,6 +161,8 @@ function submitQuestion() {
|
||||
window.$message?.error('请填写完整信息')
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('questionForm.value', questionForm.value)
|
||||
|
||||
if (questionOperation.value === 'add') {
|
||||
// Mock add
|
||||
@ -306,6 +310,11 @@ function submitQuestion() {
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<!-- 上传图片 -->
|
||||
<NFormItem label="上传图片">
|
||||
<RestUpload v-model="questionForm.imageUrl" width="100px" height="100px" hint="请上传题目图片" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="参考标准答案">
|
||||
<NInput v-model:value="questionForm.answer" placeholder="正确答案" />
|
||||
</NFormItem>
|
||||
|
||||
@ -3,22 +3,18 @@ import { NButton, NCard, NTag } from 'naive-ui'
|
||||
import { reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table'
|
||||
import { fetchGetUserList } from '@/service/api'
|
||||
import { fetchRankList } from '@/service/api'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import RankSearch from './modules/rank-search.vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
|
||||
const searchParams: Api.SystemManage.UserSearchParams = reactive({
|
||||
current: 1,
|
||||
size: 10,
|
||||
status: null,
|
||||
userName: null,
|
||||
userGender: null,
|
||||
nickName: null,
|
||||
userPhone: null,
|
||||
userEmail: null,
|
||||
const searchParams: Api.Rank.UserSearchParams = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
publishStatus: undefined,
|
||||
competitionActivityName: '',
|
||||
})
|
||||
|
||||
const rankTitle = '排行榜管理列表'
|
||||
@ -28,8 +24,8 @@ const statusMap: Record<string, string> = {
|
||||
2: '待审核',
|
||||
}
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination } = useNaivePaginatedTable({
|
||||
api: () => fetchGetUserList(searchParams),
|
||||
const { columns, data, getData, getDataByPage, loading, mobilePagination } = useNaivePaginatedTable({
|
||||
api: () => fetchRankList(searchParams as any),
|
||||
transform: (response) => {
|
||||
const transformed = defaultTransform(response)
|
||||
// Mocking data for Rank Management based on User API response
|
||||
@ -39,12 +35,12 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
const mockScores = ['145', '180', '120']
|
||||
const mockTimes = ['2026-01-22 16:30:12', '2026-01-22 12:00:00', '2026-01-21 18:22:45']
|
||||
|
||||
transformed.data.forEach((item, index) => {
|
||||
transformed.data.forEach((item: any, index) => {
|
||||
const mockIndex = index % 3
|
||||
item.userName = mockCompetitions[mockIndex] // Competition Name
|
||||
item.userEmail = mockDates[mockIndex] // Activity Date (using userEmail field)
|
||||
item.userPhone = mockScales[mockIndex] // Team Scale (using userPhone field)
|
||||
item.nickName = mockScores[mockIndex] // Max Score (using nickName field)
|
||||
item.competitionActivityName = mockCompetitions[mockIndex] // Competition Name
|
||||
item.competitionActivityDate = mockDates[mockIndex] // Activity Date (using userEmail field)
|
||||
item.competitionTeamScale = mockScales[mockIndex] // Team Scale (using userPhone field)
|
||||
item.competitionMaxScore = mockScores[mockIndex] // Max Score (using nickName field)
|
||||
// item.createTime is typically available, we'll simulate update time
|
||||
item.createTime = mockTimes[mockIndex]
|
||||
// Status: 1 (Published), 2 (Pending)
|
||||
@ -53,42 +49,45 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
return transformed
|
||||
},
|
||||
onPaginationParamsChange: (params) => {
|
||||
searchParams.current = params.page
|
||||
searchParams.size = params.pageSize
|
||||
searchParams.currentPage = params.page || 1
|
||||
searchParams.pageSize = params.pageSize || 10
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48,
|
||||
width: 40,
|
||||
},
|
||||
{
|
||||
key: 'userName',
|
||||
title: '序号',
|
||||
align: 'center',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
render: (row, index) => index + 1,
|
||||
},
|
||||
{
|
||||
key: 'competitionActivityName',
|
||||
title: '比赛活动名称',
|
||||
align: 'left',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
key: 'userEmail',
|
||||
key: 'competitionActivityDate',
|
||||
title: '活动日期',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
key: 'userPhone',
|
||||
key: 'competitionTeamScale',
|
||||
title: '队伍规模',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
key: 'nickName',
|
||||
key: 'competitionMaxScore',
|
||||
title: '最高积分',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
render: row => (
|
||||
<span class="text-primary font-bold">
|
||||
{row.nickName}
|
||||
{' '}
|
||||
{row.competitionMaxScore}
|
||||
Pts
|
||||
</span>
|
||||
),
|
||||
@ -97,13 +96,11 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
key: 'createTime',
|
||||
title: '最后更新时间',
|
||||
align: 'center',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: '发布状态',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
if (row.status === null)
|
||||
return null
|
||||
@ -116,7 +113,6 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
key: 'operate',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
render: row => (
|
||||
<div class="flex-center">
|
||||
<NButton type="primary" text onClick={() => edit(row.id)}>
|
||||
@ -129,12 +125,7 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
})
|
||||
|
||||
const {
|
||||
drawerVisible,
|
||||
operateType,
|
||||
editingData,
|
||||
handleEdit,
|
||||
checkedRowKeys,
|
||||
onBatchDeleted,
|
||||
} = useTableOperate(data, 'id', getData)
|
||||
|
||||
function edit(id: number) {
|
||||
@ -149,7 +140,7 @@ function handleBatchPublish() {
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<RankSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<NCard :bordered="false" page-size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@ -173,7 +164,7 @@ function handleBatchPublish() {
|
||||
:loading="loading" @add="handleAdd" @delete="handleBatchDelete" @refresh="getData" />
|
||||
</template> -->
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" striped
|
||||
v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" page-size="small" striped
|
||||
:flex-height="!appStore.isMobile" :scroll-x="962" :loading="loading" remote :row-key="row => row.id"
|
||||
:pagination="mobilePagination" class="sm:h-full"
|
||||
/>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { jsonClone } from '@sa/utils'
|
||||
import { computed, toRaw } from 'vue'
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form'
|
||||
import { toRaw } from 'vue'
|
||||
import { useNaiveForm } from '@/hooks/common/form'
|
||||
|
||||
defineOptions({
|
||||
name: 'RankSearch',
|
||||
@ -15,18 +15,7 @@ interface Emits {
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm()
|
||||
|
||||
const model = defineModel<Api.SystemManage.UserSearchParams>('model', { required: true })
|
||||
|
||||
type RuleKey = Extract<keyof Api.SystemManage.UserSearchParams, 'userEmail' | 'userPhone'>
|
||||
|
||||
const rules = computed<Record<RuleKey, App.Global.FormRule>>(() => {
|
||||
const { patternRules } = useFormRules()
|
||||
|
||||
return {
|
||||
userEmail: patternRules.email,
|
||||
userPhone: patternRules.phone,
|
||||
}
|
||||
})
|
||||
const model = defineModel<Api.Rank.UserSearchParams>('model', { required: true })
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value))
|
||||
|
||||
@ -54,14 +43,14 @@ const publishStatusOptions = [
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem title="筛选查询" name="rank-search">
|
||||
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="100">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:8" label="比赛活动名称" path="userName" class="pr-24px">
|
||||
<NInput v-model:value="model.userName" placeholder="请输入比赛活动名称" />
|
||||
<NFormItemGi span="24 s:12 m:8" label="比赛活动名称" path="competitionActivityName" class="pr-24px">
|
||||
<NInput v-model:value="model.competitionActivityName" placeholder="请输入比赛活动名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:8" label="发布状态" path="status" class="pr-24px">
|
||||
<NFormItemGi span="24 s:12 m:8" label="发布状态" path="publishStatus" class="pr-24px">
|
||||
<NSelect
|
||||
v-model:value="model.status"
|
||||
v-model:value="model.publishStatus"
|
||||
placeholder="请选择"
|
||||
:options="publishStatusOptions"
|
||||
clearable
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { jsonClone } from '@sa/utils'
|
||||
import { computed, toRaw } from 'vue'
|
||||
import { toRaw } from 'vue'
|
||||
import { enableStatusOptions } from '@/constants/business'
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form'
|
||||
import { useNaiveForm } from '@/hooks/common/form'
|
||||
|
||||
defineOptions({
|
||||
name: 'TemplateSearch',
|
||||
@ -16,18 +16,7 @@ interface Emits {
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm()
|
||||
|
||||
const model = defineModel<Api.SystemManage.UserSearchParams>('model', { required: true })
|
||||
|
||||
type RuleKey = Extract<keyof Api.SystemManage.UserSearchParams, 'userEmail' | 'userPhone'>
|
||||
|
||||
const rules = computed<Record<RuleKey, App.Global.FormRule>>(() => {
|
||||
const { patternRules } = useFormRules()
|
||||
|
||||
return {
|
||||
userEmail: patternRules.email,
|
||||
userPhone: patternRules.phone,
|
||||
}
|
||||
})
|
||||
const model = defineModel<Api.Template.TemplateSearchParams>('model', { required: true })
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value))
|
||||
|
||||
@ -60,22 +49,22 @@ const sizeOptions = [
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem title="基础信息" name="template-search">
|
||||
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="模板名称" path="userName" class="pr-24px">
|
||||
<NInput v-model:value="model.userName" placeholder="请输入名称" />
|
||||
<NFormItemGi span="24 s:12 m:6" label="模板名称" path="templateName" class="pr-24px">
|
||||
<NInput v-model:value="model.templateName" placeholder="请输入名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="关联比赛" path="userGender" class="pr-24px">
|
||||
<NFormItemGi span="24 s:12 m:6" label="关联比赛" path="competitionId" class="pr-24px">
|
||||
<NSelect
|
||||
v-model:value="model.userGender" placeholder="请选择" :options="competitionOptions as any"
|
||||
v-model:value="model.competitionId" placeholder="请选择" :options="competitionOptions as any"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="状态" path="userStatus" class="pr-24px">
|
||||
<NFormItemGi span="24 s:12 m:6" label="状态" path="status" class="pr-24px">
|
||||
<NSelect v-model:value="model.status" placeholder="请选择" :options="enableStatusOptions as any" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="尺寸" path="userPhone" class="pr-24px">
|
||||
<NSelect v-model:value="model.userPhone" placeholder="请选择" :options="sizeOptions" clearable />
|
||||
<NFormItemGi span="24 s:12 m:6" label="尺寸" path="size" class="pr-24px">
|
||||
<NSelect v-model:value="model.size" placeholder="请选择" :options="sizeOptions" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
|
||||
8
apps/admin/src/views/template/template-detail/index.vue
Normal file
8
apps/admin/src/views/template/template-detail/index.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="">
|
||||
<h1>模板详情</h1>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,23 +1,24 @@
|
||||
<script setup lang="tsx">
|
||||
import { NButton, NPopconfirm, NTag } from 'naive-ui'
|
||||
import { reactive } from 'vue'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table'
|
||||
import { fetchGetUserList } from '@/service/api'
|
||||
import { fetchTemplateList } from '@/service/api'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import TemplateOperateDrawer from './modules/template-operate-drawer.vue'
|
||||
import TemplateSearch from './modules/template-search.vue'
|
||||
|
||||
const { routerPushByKey } = useRouterPush()
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const searchParams: Api.SystemManage.UserSearchParams = reactive({
|
||||
current: 1,
|
||||
size: 10,
|
||||
const searchParams: Api.Template.TemplateSearchParams = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
status: null,
|
||||
userName: null,
|
||||
userGender: null,
|
||||
nickName: null,
|
||||
userPhone: null,
|
||||
userEmail: null,
|
||||
templateName: undefined,
|
||||
competitionId: undefined,
|
||||
size: undefined,
|
||||
})
|
||||
|
||||
const templateTitle = '模板管理'
|
||||
@ -33,19 +34,22 @@ const statusMap: Record<string, string> = {
|
||||
}
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination } = useNaivePaginatedTable({
|
||||
api: () => fetchGetUserList(searchParams),
|
||||
api: () => fetchTemplateList(searchParams),
|
||||
transform: (response) => {
|
||||
const transformed = defaultTransform(response)
|
||||
transformed.data.forEach((item, index) => {
|
||||
item.nickName = `MD${String(index + 1).padStart(6, '0')}`
|
||||
item.userPhone = '210mmx297mm'
|
||||
item.userGender = (item.userGender === '1' || item.userGender === '2') ? item.userGender : '1'
|
||||
transformed.data.forEach((item: any, index) => {
|
||||
item.code = `MD${String(index + 1).padStart(6, '0')}`
|
||||
item.size = '210mmx297mm'
|
||||
item.competitionId = (item.competitionId === '1' || item.competitionId === '2') ? item.competitionId : '1'
|
||||
// Mock templateName if not present
|
||||
if (!item.templateName)
|
||||
item.templateName = item.userName || '测试模板'
|
||||
})
|
||||
return transformed
|
||||
},
|
||||
onPaginationParamsChange: (params) => {
|
||||
searchParams.current = params.page
|
||||
searchParams.size = params.pageSize
|
||||
searchParams.currentPage = params.page || 1
|
||||
searchParams.pageSize = params.pageSize || 10
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
@ -61,26 +65,26 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
render: (_, index) => index + 1,
|
||||
},
|
||||
{
|
||||
key: 'userName',
|
||||
key: 'templateName',
|
||||
title: '名称',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
key: 'nickName',
|
||||
key: 'code',
|
||||
title: '模板ID',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
key: 'userGender',
|
||||
key: 'competitionId',
|
||||
title: '关联比赛',
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
const label = competitionMap[row.userGender as string] || '未知比赛'
|
||||
render: (row: any) => {
|
||||
const label = competitionMap[row.competitionId as string] || '未知比赛'
|
||||
return <span>{label}</span>
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'userPhone',
|
||||
key: 'size',
|
||||
title: '设计尺寸',
|
||||
align: 'center',
|
||||
},
|
||||
@ -88,8 +92,7 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
key: 'status',
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
render: (row: Api.Template.CommonRecord) => {
|
||||
if (row.status === null) {
|
||||
return null
|
||||
}
|
||||
@ -101,7 +104,7 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
key: 'operate',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
// width: 430,
|
||||
width: 430,
|
||||
render: row => (
|
||||
<div class="flex-center gap-8px">
|
||||
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
|
||||
@ -113,7 +116,7 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
|
||||
<NButton size="small" onClick={() => { }}>
|
||||
预览
|
||||
</NButton>
|
||||
<NButton size="small" onClick={() => { }}>
|
||||
<NButton size="small" onClick={() => handlePageInfo(row.id)}>
|
||||
页面信息
|
||||
</NButton>
|
||||
<NButton size="small" onClick={() => { }}>
|
||||
@ -159,12 +162,16 @@ function handleDelete() {
|
||||
function edit(id: number) {
|
||||
handleEdit(id)
|
||||
}
|
||||
|
||||
function handlePageInfo(id: number) {
|
||||
routerPushByKey('template_template-detail', { query: { id: id.toString() } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<TemplateSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard :title="templateTitle" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<NCard :title="templateTitle" :bordered="false" page-size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks" :disabled-delete="checkedRowKeys.length === 0"
|
||||
@ -172,8 +179,8 @@ function edit(id: number) {
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" striped
|
||||
:flex-height="!appStore.isMobile" :scroll-x="962" :loading="loading" remote :row-key="row => row.id"
|
||||
v-model:checked-row-keys="checkedRowKeys" :columns="columns as any" :data="data" page-size="small"
|
||||
striped :flex-height="!appStore.isMobile" :scroll-x="962" :loading="loading" remote :row-key="row => row.id"
|
||||
:pagination="mobilePagination" class="sm:h-full"
|
||||
/>
|
||||
<TemplateOperateDrawer
|
||||
@ -0,0 +1,147 @@
|
||||
<script setup lang="ts">
|
||||
import { jsonClone } from '@sa/utils'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { enableStatusOptions } from '@/constants/business'
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form'
|
||||
|
||||
defineOptions({
|
||||
name: 'TemplateOperateDrawer',
|
||||
})
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
interface Props {
|
||||
operateType: NaiveUI.TableOperateType
|
||||
rowData?: Api.SystemManage.User | null
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void
|
||||
}
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false,
|
||||
})
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm()
|
||||
const { defaultRequiredRule } = useFormRules()
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增模板',
|
||||
edit: '编辑模板',
|
||||
}
|
||||
return titles[props.operateType]
|
||||
})
|
||||
|
||||
type Model = Pick<
|
||||
Api.SystemManage.User,
|
||||
'userName' | 'userGender' | 'nickName' | 'userPhone' | 'userEmail' | 'userRoles' | 'status'
|
||||
>
|
||||
|
||||
const model = ref(createDefaultModel())
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
userName: '',
|
||||
userGender: null,
|
||||
nickName: '',
|
||||
userPhone: '',
|
||||
userEmail: '',
|
||||
userRoles: [],
|
||||
status: null,
|
||||
}
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'userName' | 'status'>
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
userName: defaultRequiredRule,
|
||||
status: defaultRequiredRule,
|
||||
}
|
||||
|
||||
function handleInitModel() {
|
||||
model.value = createDefaultModel()
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData))
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate()
|
||||
// request
|
||||
window.$message?.success('更新成功')
|
||||
closeDrawer()
|
||||
emit('submitted')
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleInitModel()
|
||||
restoreValidation()
|
||||
}
|
||||
})
|
||||
|
||||
const competitionOptions = [
|
||||
{ label: '第九届阅读之星大赛', value: '1' },
|
||||
{ label: '科普阅读大赛', value: '2' },
|
||||
]
|
||||
|
||||
const sizeOptions = [
|
||||
{ label: '210mmx297mm', value: '1' },
|
||||
{ label: 'A3', value: '2' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" display-directive="show" :width="360">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="名称" path="userName">
|
||||
<NInput v-model:value="model.userName" placeholder="请输入名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="模板ID" path="nickName">
|
||||
<NInput v-model:value="model.nickName" placeholder="请输入模板ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="关联比赛" path="userGender">
|
||||
<NSelect
|
||||
v-model:value="model.userGender"
|
||||
:options="competitionOptions"
|
||||
placeholder="请选择关联比赛"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="设计尺寸" path="userPhone">
|
||||
<NSelect
|
||||
v-model:value="model.userPhone"
|
||||
:options="sizeOptions"
|
||||
placeholder="请选择设计尺寸"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="状态" path="status">
|
||||
<NRadioGroup v-model:value="model.status">
|
||||
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="item.label" />
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">
|
||||
确认
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import { jsonClone } from '@sa/utils'
|
||||
import { toRaw } from 'vue'
|
||||
import { enableStatusOptions } from '@/constants/business'
|
||||
import { useNaiveForm } from '@/hooks/common/form'
|
||||
|
||||
defineOptions({
|
||||
name: 'TemplateSearch',
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void
|
||||
}
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm()
|
||||
|
||||
const model = defineModel<Api.Template.TemplateSearchParams>('model', { required: true })
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value))
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel)
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation()
|
||||
resetModel()
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate()
|
||||
emit('search')
|
||||
}
|
||||
|
||||
const competitionOptions = [
|
||||
{ label: '第九届阅读之星大赛', value: '1' },
|
||||
{ label: '科普阅读大赛', value: '2' },
|
||||
]
|
||||
|
||||
const sizeOptions = [
|
||||
{ label: '210mmx297mm', value: '1' },
|
||||
{ label: 'A3', value: '2' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem title="基础信息" name="template-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="模板名称" path="templateName" class="pr-24px">
|
||||
<NInput v-model:value="model.templateName" placeholder="请输入名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="关联比赛" path="competitionId" class="pr-24px">
|
||||
<NSelect
|
||||
v-model:value="model.competitionId" placeholder="请选择" :options="competitionOptions as any"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="状态" path="status" class="pr-24px">
|
||||
<NSelect v-model:value="model.status" placeholder="请选择" :options="enableStatusOptions as any" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="尺寸" path="size" class="pr-24px">
|
||||
<NSelect v-model:value="model.size" placeholder="请选择" :options="sizeOptions" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
重置
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
搜索
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user