feat: 新增测试页面并优化用户界面及API调用
- 新增测试页面 `/test`,用于展示题目答案的交互式网格和列表布局 - 为 CompetitionLayout 组件添加标题背景类型支持,优化视觉一致性 - 更新 `fetchUpdateCurrentQuestionUse` API 参数,简化调用逻辑 - 优化用户界面细节,包括图标替换、样式调整和布局改进
This commit is contained in:
26
apps/admin/src/assets/imgs/user/event-card-bg-icon.svg
Normal file
26
apps/admin/src/assets/imgs/user/event-card-bg-icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 2.6 MiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 825 KiB After Width: | Height: | Size: 1.2 MiB |
21
apps/admin/src/assets/imgs/user/title-bg2.svg
Normal file
21
apps/admin/src/assets/imgs/user/title-bg2.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 4.0 MiB |
@ -1,7 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import VScaleScreen from 'v-scale-screen'
|
import VScaleScreen from 'v-scale-screen'
|
||||||
import { onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import _defaultBg from '@/assets/imgs/user/home-bg.svg'
|
import _defaultBg from '@/assets/imgs/user/home-bg.svg'
|
||||||
|
import titleBg2 from '@/assets/imgs/user/title-bg2.svg'
|
||||||
|
import _defaultTitleBg from '@/assets/imgs/user/title-bg.svg'
|
||||||
import { useRouterPush } from '@/hooks/common/router'
|
import { useRouterPush } from '@/hooks/common/router'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -25,6 +27,8 @@ interface Props {
|
|||||||
btnTheme?: 'primary' | 'light'
|
btnTheme?: 'primary' | 'light'
|
||||||
/** 是否禁用下一步按钮 */
|
/** 是否禁用下一步按钮 */
|
||||||
nextDisabled?: boolean
|
nextDisabled?: boolean
|
||||||
|
/** 标题区域背景图片URL,不传则使用默认 title-bg.svg */
|
||||||
|
titleBgType?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@ -38,6 +42,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
backBtnText: undefined,
|
backBtnText: undefined,
|
||||||
btnTheme: 'primary',
|
btnTheme: 'primary',
|
||||||
nextDisabled: false,
|
nextDisabled: false,
|
||||||
|
titleBgType: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -45,8 +50,15 @@ const emit = defineEmits<{
|
|||||||
(e: 'next'): void
|
(e: 'next'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const titleBgUrlMapping: Record<number, string> = {
|
||||||
|
1: _defaultTitleBg,
|
||||||
|
2: titleBg2,
|
||||||
|
}
|
||||||
|
|
||||||
const { routerPushByKey } = useRouterPush()
|
const { routerPushByKey } = useRouterPush()
|
||||||
|
|
||||||
|
const titleBgUrl = computed(() => titleBgUrlMapping[props.titleBgType] || _defaultBg)
|
||||||
|
|
||||||
// const router = useRouter()
|
// const router = useRouter()
|
||||||
const innerBgUrl = ref(_defaultBg)
|
const innerBgUrl = ref(_defaultBg)
|
||||||
|
|
||||||
@ -108,7 +120,7 @@ onMounted(() => {
|
|||||||
<section v-if="showTitle" class="title-section">
|
<section v-if="showTitle" class="title-section">
|
||||||
<Transition name="fade-slide-down" appear>
|
<Transition name="fade-slide-down" appear>
|
||||||
<div v-if="showContent" class="title-section">
|
<div v-if="showContent" class="title-section">
|
||||||
<div class="scroll-bg">
|
<div class="scroll-bg" :style="{ backgroundImage: `url(${titleBgUrl})` }">
|
||||||
<h1 class="main-title">
|
<h1 class="main-title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
@ -123,16 +135,20 @@ onMounted(() => {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Footer Action -->
|
<!-- Footer Action -->
|
||||||
<footer class="page-footer w-full flex items-center justify-between px-20" :class="[actionPosition, btnTheme]">
|
<footer
|
||||||
<button class="action-btn back-btn" :class="{ 'text-btn': backBtnText }" :style="{ visibility: showBack ? 'visible' : 'hidden' }" @click="handleBack">
|
class="page-footer w-full flex items-center justify-between px-20"
|
||||||
|
:class="[actionPosition, btnTheme]"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="action-btn back-btn" :class="{ 'text-btn': backBtnText }"
|
||||||
|
:style="{ visibility: showBack ? 'visible' : 'hidden' }" @click="handleBack"
|
||||||
|
>
|
||||||
<span v-if="backBtnText" class="btn-text">{{ backBtnText }}</span>
|
<span v-if="backBtnText" class="btn-text">{{ backBtnText }}</span>
|
||||||
<SvgIcon v-else icon="mdi:arrow-left" class="arrow-icon" />
|
<SvgIcon v-else icon="mdi:arrow-left" class="arrow-icon" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="showNext"
|
v-if="showNext" class="action-btn next-btn text-btn" :class="{ 'is-disabled': nextDisabled }"
|
||||||
class="action-btn next-btn text-btn"
|
|
||||||
:class="{ 'is-disabled': nextDisabled }"
|
|
||||||
@click="handleNext"
|
@click="handleNext"
|
||||||
>
|
>
|
||||||
<span class="btn-text">{{ nextBtnText || '下一步' }}</span>
|
<span class="btn-text">{{ nextBtnText || '下一步' }}</span>
|
||||||
@ -148,14 +164,14 @@ onMounted(() => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.title-section {
|
.title-section {
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
width: 405px;
|
width: 412px;
|
||||||
height: 150px;
|
height: 165px;
|
||||||
|
|
||||||
.scroll-bg {
|
.scroll-bg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: url('@/assets/imgs/user/title-bg.svg') no-repeat center center;
|
background: no-repeat center center;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||||||
results: () => import("@/views/results/index.vue"),
|
results: () => import("@/views/results/index.vue"),
|
||||||
"template_template-detail": () => import("@/views/template/template-detail/index.vue"),
|
"template_template-detail": () => import("@/views/template/template-detail/index.vue"),
|
||||||
"template_template-list": () => import("@/views/template/template-list/index.vue"),
|
"template_template-list": () => import("@/views/template/template-list/index.vue"),
|
||||||
|
test: () => import("@/views/test/index.vue"),
|
||||||
user_analysis: () => import("@/views/user/analysis/index.vue"),
|
user_analysis: () => import("@/views/user/analysis/index.vue"),
|
||||||
user_cover: () => import("@/views/user/cover/index.vue"),
|
user_cover: () => import("@/views/user/cover/index.vue"),
|
||||||
user_draw: () => import("@/views/user/draw/index.vue"),
|
user_draw: () => import("@/views/user/draw/index.vue"),
|
||||||
|
|||||||
@ -223,6 +223,15 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
path: '/test',
|
||||||
|
component: 'layout.base$view.test',
|
||||||
|
meta: {
|
||||||
|
title: 'test',
|
||||||
|
i18nKey: 'route.test'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'user',
|
name: 'user',
|
||||||
path: '/user',
|
path: '/user',
|
||||||
|
|||||||
@ -182,6 +182,7 @@ const routeMap: RouteMap = {
|
|||||||
"template": "/template",
|
"template": "/template",
|
||||||
"template_template-detail": "/template/template-detail",
|
"template_template-detail": "/template/template-detail",
|
||||||
"template_template-list": "/template/template-list",
|
"template_template-list": "/template/template-list",
|
||||||
|
"test": "/test",
|
||||||
"user": "/user",
|
"user": "/user",
|
||||||
"user_analysis": "/user/analysis",
|
"user_analysis": "/user/analysis",
|
||||||
"user_cover": "/user/cover",
|
"user_cover": "/user/cover",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { request } from '../request'
|
import { request } from '../request'
|
||||||
|
|
||||||
/** 评委消费题目,表示用了该题目 */
|
/** 评委消费题目,表示用了该题目 */
|
||||||
export function fetchUpdateCurrentQuestionUse(data: Api.Competition.GetCurrentQuestionParams) {
|
export function fetchUpdateCurrentQuestionUse(TeamGroupID: string) {
|
||||||
return request<App.Service.Response>({
|
return request<App.Service.Response>({
|
||||||
url: `/Base/ActivityMain/UpdateCurrentQuestionUse?ActivityID=${data.ActivityID}&GroupID=${data.GroupID}&RoundType=${data.RoundType}&isUse=${data.isUse}`,
|
url: `/Base/ActivityMain/UpdateCurrentQuestionUse?TeamGroupID=${TeamGroupID}`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data: { TeamGroupID },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
3
apps/admin/src/typings/elegant-router.d.ts
vendored
3
apps/admin/src/typings/elegant-router.d.ts
vendored
@ -36,6 +36,7 @@ declare module "@elegant-router/types" {
|
|||||||
"template": "/template";
|
"template": "/template";
|
||||||
"template_template-detail": "/template/template-detail";
|
"template_template-detail": "/template/template-detail";
|
||||||
"template_template-list": "/template/template-list";
|
"template_template-list": "/template/template-list";
|
||||||
|
"test": "/test";
|
||||||
"user": "/user";
|
"user": "/user";
|
||||||
"user_analysis": "/user/analysis";
|
"user_analysis": "/user/analysis";
|
||||||
"user_cover": "/user/cover";
|
"user_cover": "/user/cover";
|
||||||
@ -88,6 +89,7 @@ declare module "@elegant-router/types" {
|
|||||||
| "rank"
|
| "rank"
|
||||||
| "results"
|
| "results"
|
||||||
| "template"
|
| "template"
|
||||||
|
| "test"
|
||||||
| "user"
|
| "user"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -121,6 +123,7 @@ declare module "@elegant-router/types" {
|
|||||||
| "results"
|
| "results"
|
||||||
| "template_template-detail"
|
| "template_template-detail"
|
||||||
| "template_template-list"
|
| "template_template-list"
|
||||||
|
| "test"
|
||||||
| "user_analysis"
|
| "user_analysis"
|
||||||
| "user_cover"
|
| "user_cover"
|
||||||
| "user_draw"
|
| "user_draw"
|
||||||
|
|||||||
@ -2,15 +2,20 @@
|
|||||||
import { useIntervalFn } from '@vueuse/core'
|
import { useIntervalFn } from '@vueuse/core'
|
||||||
import { NButton } from 'naive-ui'
|
import { NButton } from 'naive-ui'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { onUnmounted, ref } from 'vue'
|
import { computed, onUnmounted, ref } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { fetchUpdateCurrentQuestionUse } from '@/service/api/result'
|
import { fetchUpdateCurrentQuestionUse } from '@/service/api/result'
|
||||||
import { useActivityInfoStore } from '@/store/modules/activityinfo'
|
import { useActivityInfoStore } from '@/store/modules/activityinfo'
|
||||||
import GroupTabs, { type GroupItem } from './modules/GroupTabs.vue'
|
import GroupTabs, { type GroupItem } from './modules/GroupTabs.vue'
|
||||||
import TeamResultCard, { type AICharacter, type TeamResult } from './modules/TeamResultCard.vue'
|
import TeamResultCard, { type AICharacter, type TeamResult } from './modules/TeamResultCard.vue'
|
||||||
|
|
||||||
const activityInfoStore = useActivityInfoStore()
|
const activityInfoStore = useActivityInfoStore()
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
const { activityId, groupId } = storeToRefs(activityInfoStore)
|
const { activityId, groupId } = storeToRefs(activityInfoStore)
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const TeamGroupID = computed(() => route.query?.TeamGroupID as string)
|
||||||
|
|
||||||
// --- 模拟数据和状态 ---
|
// --- 模拟数据和状态 ---
|
||||||
|
|
||||||
const groups = ref<GroupItem[]>(Array.from({ length: 8 }).map((_, i) => ({
|
const groups = ref<GroupItem[]>(Array.from({ length: 8 }).map((_, i) => ({
|
||||||
@ -173,19 +178,15 @@ function handleNext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePublish() {
|
function handlePublish() {
|
||||||
fetchGetQuestionDetailData()
|
if (TeamGroupID.value) {
|
||||||
window.$message?.success('结果已发布')
|
fetchGetQuestionDetailData()
|
||||||
|
window.$message?.success('结果已发布')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchGetQuestionDetailData() {
|
async function fetchGetQuestionDetailData() {
|
||||||
const params = {
|
|
||||||
ActivityID: Number(activityId.value),
|
|
||||||
GroupID: Number(groupId.value),
|
|
||||||
RoundType: 0 as Api.Competition.CompetitionRoundType,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: outlineData, error } = await fetchUpdateCurrentQuestionUse({ ...params, isUse: true })
|
const { data: outlineData, error } = await fetchUpdateCurrentQuestionUse(TeamGroupID.value)
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(outlineData, 'outlineData')
|
console.log(outlineData, 'outlineData')
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@ -131,7 +131,7 @@ async function handleSave() {
|
|||||||
|
|
||||||
const params: Api.Template.AddTemplateParams = {
|
const params: Api.Template.AddTemplateParams = {
|
||||||
id,
|
id,
|
||||||
pageNo: '1',
|
pageNo: '1761.172.8.66',
|
||||||
name: templateInfo.value.name,
|
name: templateInfo.value.name,
|
||||||
backGroundUrl: templateInfo.value.backGroundUrl || '',
|
backGroundUrl: templateInfo.value.backGroundUrl || '',
|
||||||
width: Math.round(templateInfo.value.width),
|
width: Math.round(templateInfo.value.width),
|
||||||
|
|||||||
229
apps/admin/src/views/test/index.vue
Normal file
229
apps/admin/src/views/test/index.vue
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { NCard, NImage, NSpace } from 'naive-ui'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
interface AnswerItem {
|
||||||
|
id: number
|
||||||
|
label?: string
|
||||||
|
imageUrl: string // 在真实场景中是学生答题截图的URL
|
||||||
|
status: 'correct' | 'wrong' // 初始状态由大模型判定
|
||||||
|
type: 'grid' | 'line' // 'grid'对应方格题(如汉字),'line'对应横线题(如词语)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QuestionGroup {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
items: AnswerItem[]
|
||||||
|
layout: 'grid' | 'list' // 布局方式
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Round {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
subTitle?: string
|
||||||
|
groups: QuestionGroup[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const rounds = ref<Round[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '第一轮',
|
||||||
|
subTitle: '汉字大比拼',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
title: '汉字听写 1',
|
||||||
|
layout: 'grid',
|
||||||
|
items: [
|
||||||
|
{ id: 1, label: '1', imageUrl: 'https://placehold.co/100x100?text=汉', status: 'correct', type: 'grid' },
|
||||||
|
{ id: 2, label: '2', imageUrl: 'https://placehold.co/100x100?text=字', status: 'correct', type: 'grid' },
|
||||||
|
{ id: 3, label: '3', imageUrl: 'https://placehold.co/100x100?text=听', status: 'correct', type: 'grid' },
|
||||||
|
{ id: 4, label: '4', imageUrl: 'https://placehold.co/100x100?text=写', status: 'correct', type: 'grid' },
|
||||||
|
{ id: 5, label: '5', imageUrl: 'https://placehold.co/100x100?text=一', status: 'correct', type: 'grid' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 102,
|
||||||
|
title: '汉字听写 2',
|
||||||
|
layout: 'grid',
|
||||||
|
items: [
|
||||||
|
{ id: 6, label: '1', imageUrl: 'https://placehold.co/100x100?text=错', status: 'wrong', type: 'grid' },
|
||||||
|
{ id: 7, label: '2', imageUrl: 'https://placehold.co/100x100?text=误', status: 'wrong', type: 'grid' },
|
||||||
|
{ id: 8, label: '3', imageUrl: 'https://placehold.co/100x100?text=示', status: 'wrong', type: 'grid' },
|
||||||
|
{ id: 9, label: '4', imageUrl: 'https://placehold.co/100x100?text=例', status: 'wrong', type: 'grid' },
|
||||||
|
{ id: 10, label: '5', imageUrl: 'https://placehold.co/100x100?text=!', status: 'wrong', type: 'grid' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 103,
|
||||||
|
title: '汉字加一加',
|
||||||
|
layout: 'grid',
|
||||||
|
items: Array.from({ length: 10 }).map((_, i) => ({
|
||||||
|
id: 20 + i,
|
||||||
|
imageUrl: `https://placehold.co/100x100?text=${i + 1}`,
|
||||||
|
status: i === 4 ? 'wrong' : 'correct', // 第5个模拟错误
|
||||||
|
type: 'grid',
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: '第一轮',
|
||||||
|
subTitle: '词语大比拼',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
id: 201,
|
||||||
|
title: '词语听写',
|
||||||
|
layout: 'list',
|
||||||
|
items: [
|
||||||
|
{ id: 301, label: '1.', imageUrl: 'https://placehold.co/400x60?text=词语答案一', status: 'correct', type: 'line' },
|
||||||
|
{ id: 302, label: '2.', imageUrl: 'https://placehold.co/400x60?text=词语答案二', status: 'correct', type: 'line' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 202,
|
||||||
|
title: '成语写一写 1',
|
||||||
|
layout: 'list',
|
||||||
|
items: [
|
||||||
|
{ id: 303, label: '1.', imageUrl: 'https://placehold.co/200x60?text=成语一', status: 'correct', type: 'line' },
|
||||||
|
{ id: 304, label: '2.', imageUrl: 'https://placehold.co/200x60?text=成语二', status: 'wrong', type: 'line' },
|
||||||
|
{ id: 305, label: '3.', imageUrl: 'https://placehold.co/200x60?text=成语三', status: 'wrong', type: 'line' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 203,
|
||||||
|
title: '成语写一写 2',
|
||||||
|
layout: 'list',
|
||||||
|
items: [
|
||||||
|
{ id: 306, label: '1.', imageUrl: 'https://placehold.co/600x60?text=长成语答案测试', status: 'correct', type: 'line' },
|
||||||
|
{ id: 307, label: '2.', imageUrl: 'https://placehold.co/600x60?text=另一个答案', status: 'correct', type: 'line' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 切换状态逻辑:点击后 Correct <-> Wrong
|
||||||
|
function toggleStatus(item: AnswerItem) {
|
||||||
|
item.status = item.status === 'correct' ? 'wrong' : 'correct'
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => rounds.value, (val) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(val, 'v')
|
||||||
|
}, {
|
||||||
|
deep: true,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-gray-50 p-4">
|
||||||
|
<NSpace vertical size="large">
|
||||||
|
<NCard v-for="round in rounds" :key="round.id" :title="`${round.title} · ${round.subTitle}`" size="huge">
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
<div v-for="group in round.groups" :key="group.id">
|
||||||
|
<h3 class="mb-4 text-lg text-gray-700 font-medium">
|
||||||
|
{{ group.title }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Grid Layout (Grid Items) -->
|
||||||
|
<div v-if="group.layout === 'grid'" class="flex flex-wrap gap-4">
|
||||||
|
<div
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="group relative cursor-pointer transition-all duration-200"
|
||||||
|
@click="toggleStatus(item)"
|
||||||
|
>
|
||||||
|
<!-- Image Container with Status Border -->
|
||||||
|
<div
|
||||||
|
class="relative overflow-hidden border-2 rounded-md border-dashed bg-white p-1"
|
||||||
|
:class="[
|
||||||
|
item.status === 'correct' ? 'border-green-500' : 'border-red-500',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<NImage
|
||||||
|
:src="item.imageUrl"
|
||||||
|
preview-disabled
|
||||||
|
object-fit="contain"
|
||||||
|
:class="group.layout === 'grid' ? 'w-24 h-24' : ''"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Status Icon Overlay -->
|
||||||
|
<div class="absolute z-10 rounded-full bg-white -right-3 -top-3">
|
||||||
|
<Icon
|
||||||
|
v-if="item.status === 'correct'"
|
||||||
|
icon="mdi:check-circle"
|
||||||
|
class="text-2xl text-green-500"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
icon="mdi:close-circle"
|
||||||
|
class="text-2xl text-red-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Label if exists -->
|
||||||
|
<div v-if="item.label" class="mt-1 text-center text-sm text-gray-500">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List Layout (Line Items) -->
|
||||||
|
<div v-else class="flex flex-col gap-4">
|
||||||
|
<!-- Using a grid for lines to handle multiple per row if needed, or just flex-wrap -->
|
||||||
|
<div class="flex flex-wrap gap-x-8 gap-y-4">
|
||||||
|
<div
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="relative cursor-pointer transition-all duration-200"
|
||||||
|
@click="toggleStatus(item)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span v-if="item.label" class="w-6 text-gray-600 font-medium">{{ item.label }}</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative overflow-hidden border-2 rounded-md border-dashed bg-white px-2 py-1"
|
||||||
|
:class="[
|
||||||
|
item.status === 'correct' ? 'border-green-500' : 'border-red-500',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<NImage
|
||||||
|
:src="item.imageUrl"
|
||||||
|
preview-disabled
|
||||||
|
object-fit="contain"
|
||||||
|
height="50"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Status Icon Overlay -->
|
||||||
|
<div class="absolute z-10 rounded-full bg-white shadow-sm -right-3 -top-3">
|
||||||
|
<Icon
|
||||||
|
v-if="item.status === 'correct'"
|
||||||
|
icon="mdi:check-circle"
|
||||||
|
class="text-2xl text-green-500"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
icon="mdi:close-circle"
|
||||||
|
class="text-2xl text-red-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Ensure icons are visible and positioned correctly */
|
||||||
|
</style>
|
||||||
@ -134,7 +134,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CompetitionLayout
|
<CompetitionLayout
|
||||||
:show-back="true" :show-next="showNextButton" :title="activityName" @back="handleBack"
|
:show-back="true" :show-next="showNextButton" :title="activityName" :title-bg-type="2"
|
||||||
|
@back="handleBack"
|
||||||
@next="handleNext"
|
@next="handleNext"
|
||||||
>
|
>
|
||||||
<div class="h-screen w-full overflow-y-auto font-sans">
|
<div class="h-screen w-full overflow-y-auto font-sans">
|
||||||
|
|||||||
@ -70,24 +70,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CompetitionLayout
|
<CompetitionLayout
|
||||||
:show-back="true"
|
:show-back="true" :show-next="isAllEnd" next-btn-text="查看队伍结果" :title-bg-type="2"
|
||||||
:show-next="isAllEnd"
|
title="展示组别" @back="handleBack" @next="handleNext"
|
||||||
next-btn-text="查看队伍结果"
|
|
||||||
title="展示组别"
|
|
||||||
@back="handleBack"
|
|
||||||
@next="handleNext"
|
|
||||||
>
|
>
|
||||||
<!-- Groups Grid -->
|
<!-- Groups Grid -->
|
||||||
<div class="groups-container">
|
<div class="groups-container">
|
||||||
<TransitionGroup name="list-anim" tag="div" class="groups-grid">
|
<TransitionGroup name="list-anim" tag="div" class="groups-grid">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in groups"
|
v-for="(item, index) in groups" v-show="showContent" :key="item.Id" class="group-item"
|
||||||
v-show="showContent"
|
:class="{ 'is-disabled': item.IsEnd }" :style="{ '--delay': `${index * 0.05}s` }" @click="selectGroup(item)"
|
||||||
:key="item.Id"
|
|
||||||
class="group-item"
|
|
||||||
:class="{ 'is-disabled': item.IsEnd }"
|
|
||||||
:style="{ '--delay': `${index * 0.05}s` }"
|
|
||||||
@click="selectGroup(item)"
|
|
||||||
>
|
>
|
||||||
<div class="icon-wrapper">
|
<div class="icon-wrapper">
|
||||||
<div class="flower-bg" />
|
<div class="flower-bg" />
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { NCarousel } from 'naive-ui'
|
import { NCarousel, NImage } from 'naive-ui'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import beacon from '@/assets/imgs/user/event-card-bg-icon.svg'
|
||||||
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
||||||
import { useRouterPush } from '@/hooks/common/router'
|
import { useRouterPush } from '@/hooks/common/router'
|
||||||
import { fetchGetActivityList } from '@/service/api/competition'
|
import { fetchGetActivityList } from '@/service/api/competition'
|
||||||
@ -81,7 +82,8 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<CompetitionLayout
|
<CompetitionLayout
|
||||||
:show-back="false" :show-next="false" title="赛事总览" action-position="top" back-btn-text="返回"
|
:show-back="false" :show-next="false" title="赛事总览" action-position="top" back-btn-text="返回"
|
||||||
btn-theme="light" @back="handleBack" @next="handleNext"
|
btn-theme="light" :title-bg-type="2" @back="handleBack"
|
||||||
|
@next="handleNext"
|
||||||
>
|
>
|
||||||
<!-- Cards Section -->
|
<!-- Cards Section -->
|
||||||
<div class="cards-container">
|
<div class="cards-container">
|
||||||
@ -106,7 +108,11 @@ onMounted(() => {
|
|||||||
<div class="card-top-bar" />
|
<div class="card-top-bar" />
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="icon-wrapper">
|
<div class="icon-wrapper">
|
||||||
<SvgIcon :local-icon="item.icon" class="event-icon" />
|
<NImage
|
||||||
|
:src="beacon"
|
||||||
|
preview-disabled
|
||||||
|
object-fit="contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
<h3 class="event-title">
|
<h3 class="event-title">
|
||||||
@ -168,20 +174,12 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-wrapper {
|
.icon-wrapper {
|
||||||
width: 100px;
|
width: 170px;
|
||||||
height: 100px;
|
height: 161px;
|
||||||
background: rgba(255, 255, 255, 0.6);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 30px;
|
margin: 30px 0;
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.event-icon {
|
|
||||||
font-size: 48px;
|
|
||||||
color: $secondary-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-content {
|
.text-content {
|
||||||
@ -203,8 +201,8 @@ onMounted(() => {
|
|||||||
transform: translateY(-10px);
|
transform: translateY(-10px);
|
||||||
|
|
||||||
.icon-wrapper {
|
.icon-wrapper {
|
||||||
transform: scale(1.1) rotate(5deg);
|
transform: scale(1.1) rotate(2deg);
|
||||||
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,8 +62,6 @@ function handleNext() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '@/styles/scss/variables.scss';
|
|
||||||
|
|
||||||
.rules-content-wrapper {
|
.rules-content-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
@ -76,7 +74,6 @@ function handleNext() {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
// color: #5d4037; // Dark brown/grey text
|
|
||||||
|
|
||||||
.intro-text {
|
.intro-text {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
|||||||
Reference in New Issue
Block a user