feat(admin): 优化用户界面交互与组件功能
- 在抽题页面添加开始抽题按钮并绑定事件 - 调整队伍列表动画延迟和分组页面样式 - 修复结果发布条件逻辑,允许无结束时间时发布 - 增强TypeIt组件,支持动态配置和实例管理 - 更新Competition API类型定义,使用枚举类型 - 优化CompetitionLayout组件按钮显示时机 - 修正分析页面API调用参数类型 - 在拼写检查词典中添加Typeit
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -39,6 +39,7 @@
|
|||||||
"totvsLanguageServer.welcomePage": false,
|
"totvsLanguageServer.welcomePage": false,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"activityinfo",
|
"activityinfo",
|
||||||
"Echarts"
|
"Echarts",
|
||||||
|
"Typeit"
|
||||||
] // `
|
] // `
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,44 +2,84 @@
|
|||||||
import type { Options } from 'typeit'
|
import type { Options } from 'typeit'
|
||||||
import type { El } from 'typeit/dist/types'
|
import type { El } from 'typeit/dist/types'
|
||||||
import TypeIt from 'typeit'
|
import TypeIt from 'typeit'
|
||||||
import { onMounted, shallowRef } from 'vue'
|
import { onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'TypeIt' })
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
speed: 100,
|
||||||
|
loop: false,
|
||||||
|
cursor: true,
|
||||||
|
lifeLike: true,
|
||||||
|
strings: () => [],
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** 打字速度 */
|
||||||
|
speed?: number
|
||||||
|
/** 是否循环 */
|
||||||
|
loop?: boolean
|
||||||
|
/** 是否显示光标 */
|
||||||
|
cursor?: boolean
|
||||||
|
/** 打字内容 */
|
||||||
|
strings?: string | string[]
|
||||||
|
/** 像人一样打字 */
|
||||||
|
lifeLike?: boolean
|
||||||
|
/** 开始前的延迟 */
|
||||||
|
startDelay?: number
|
||||||
|
/** 下一段文字的延迟 */
|
||||||
|
nextStringDelay?: number
|
||||||
|
/** 循环延迟 */
|
||||||
|
loopDelay?: number
|
||||||
|
/** 兼容旧代码或用户自定义延迟参数 */
|
||||||
|
typeDelay?: number
|
||||||
|
}
|
||||||
|
|
||||||
const textRef = shallowRef<El>()
|
const textRef = shallowRef<El>()
|
||||||
|
const typeItInstance = shallowRef<TypeIt | null>(null)
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (!textRef.value)
|
if (!textRef.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
const options: Options = {
|
if (typeItInstance.value) {
|
||||||
strings: 'SoybeanAdmin是一个清新优雅、高颜值且功能强大的后台管理模板',
|
typeItInstance.value.destroy()
|
||||||
lifeLike: true,
|
|
||||||
speed: 120,
|
|
||||||
loop: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initTypeIt = new TypeIt(textRef.value, options)
|
// 清空内容,防止重叠
|
||||||
|
textRef.value.textContent = ''
|
||||||
|
|
||||||
initTypeIt.go()
|
const options: Options = {
|
||||||
|
strings: props.strings,
|
||||||
|
lifeLike: props.lifeLike,
|
||||||
|
speed: props.speed,
|
||||||
|
loop: props.loop,
|
||||||
|
cursor: props.cursor,
|
||||||
|
startDelay: props.startDelay,
|
||||||
|
nextStringDelay: props.nextStringDelay ?? props.typeDelay,
|
||||||
|
loopDelay: props.loopDelay,
|
||||||
|
}
|
||||||
|
|
||||||
|
typeItInstance.value = new TypeIt(textRef.value, options).go()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init()
|
init()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (typeItInstance.value) {
|
||||||
|
typeItInstance.value.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => [props.strings, props.speed, props.loop], () => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<span ref="textRef" class="typeit-container" />
|
||||||
<NCard title="打字机 插件" :bordered="false" class="h-full card-wrapper">
|
|
||||||
<NSpace :vertical="true">
|
|
||||||
<GithubLink link="https://github.com/alexmacarthur/typeit" />
|
|
||||||
<WebSiteLink label="文档地址:" link="https://www.typeitjs.com/docs/vanilla/usage/" />
|
|
||||||
</NSpace>
|
|
||||||
<NDivider title-placement="left">
|
|
||||||
基本示例
|
|
||||||
</NDivider>
|
|
||||||
<span ref="textRef" class="text-18px" />
|
|
||||||
</NCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@ -88,6 +88,7 @@ async function fetchSystemConfig() {
|
|||||||
|
|
||||||
const showContent = ref(false)
|
const showContent = ref(false)
|
||||||
const showSlotContent = ref(false)
|
const showSlotContent = ref(false)
|
||||||
|
const showButton = ref(false)
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
emit('back')
|
emit('back')
|
||||||
@ -115,10 +116,15 @@ onMounted(() => {
|
|||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
// 延迟显示内容,等待标题卷轴展开
|
// 延迟显示内容,等待标题卷轴展开
|
||||||
const delay = props.showTitle ? 800 : 100
|
const delay = props.showTitle ? 500 : 100
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showSlotContent.value = true
|
showSlotContent.value = true
|
||||||
}, delay)
|
}, delay)
|
||||||
|
|
||||||
|
// 延迟显示按钮,等待内容显示
|
||||||
|
setTimeout(() => {
|
||||||
|
showButton.value = true
|
||||||
|
}, delay + 500)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -160,6 +166,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- Footer Action -->
|
<!-- Footer Action -->
|
||||||
<footer
|
<footer
|
||||||
|
v-if="showButton"
|
||||||
class="page-footer w-full flex items-center justify-between px-20"
|
class="page-footer w-full flex items-center justify-between px-20"
|
||||||
:class="[actionPosition, btnTheme]"
|
:class="[actionPosition, btnTheme]"
|
||||||
>
|
>
|
||||||
|
|||||||
2
apps/admin/src/typings/api/Competition.d.ts
vendored
2
apps/admin/src/typings/api/Competition.d.ts
vendored
@ -329,7 +329,7 @@ declare namespace Api {
|
|||||||
/** 答题时间(秒) */
|
/** 答题时间(秒) */
|
||||||
QuestionTime: number
|
QuestionTime: number
|
||||||
/** 赛题包类型 */
|
/** 赛题包类型 */
|
||||||
RoundType: number
|
RoundType: CompetitionRoundType
|
||||||
/** 题目模板 id */
|
/** 题目模板 id */
|
||||||
TemplateID: number
|
TemplateID: number
|
||||||
/** UI 类型 */
|
/** UI 类型 */
|
||||||
|
|||||||
@ -582,7 +582,7 @@ const nowTs = ref(Date.now())
|
|||||||
const canPublish = computed(() => {
|
const canPublish = computed(() => {
|
||||||
const endTime = (currentQuestion.value as any)?.EndTime
|
const endTime = (currentQuestion.value as any)?.EndTime
|
||||||
if (!endTime)
|
if (!endTime)
|
||||||
return false
|
return true
|
||||||
const end = new Date(endTime).getTime()
|
const end = new Date(endTime).getTime()
|
||||||
return nowTs.value >= end
|
return nowTs.value >= end
|
||||||
})
|
})
|
||||||
|
|||||||
@ -74,7 +74,7 @@ async function initQuestions() {
|
|||||||
const { data: outlineData, error } = await fetchGetCurrentQuestion({
|
const { data: outlineData, error } = await fetchGetCurrentQuestion({
|
||||||
ActivityID: Number(activityId.value),
|
ActivityID: Number(activityId.value),
|
||||||
GroupID: Number(groupsId.value),
|
GroupID: Number(groupsId.value),
|
||||||
RoundType: Number(currentQuestionMainInfo.value?.Activity_Question.RoundType) || 0,
|
RoundType: (currentQuestionMainInfo.value?.Activity_Question.RoundType) || 0,
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(outlineData, 'outlineData')
|
console.log(outlineData, 'outlineData')
|
||||||
@ -172,23 +172,18 @@ onBeforeUnmount(() => {
|
|||||||
<span class="text-lg text-white font-bold drop-shadow">{{ team.TeamName }}</span>
|
<span class="text-lg text-white font-bold drop-shadow">{{ team.TeamName }}</span>
|
||||||
<span class="ml-2 rounded-full bg-white/40 px-2 py-0.5 text-sm">正确 <span class="text-md text-blue">{{
|
<span class="ml-2 rounded-full bg-white/40 px-2 py-0.5 text-sm">正确 <span class="text-md text-blue">{{
|
||||||
team.correctCount }}</span> 字</span>
|
team.correctCount }}</span> 字</span>
|
||||||
<span class="text-s rounded-full bg-white/40 px-2 py-0.5">积分 <span class="text-md text-blue">{{ team.Points
|
<span class="text-s rounded-full bg-white/40 px-2 py-0.5">积分 <span class="text-md text-blue">{{
|
||||||
|
team.Points
|
||||||
}}</span></span>
|
}}</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Answer Image Area -->
|
<!-- Answer Image Area -->
|
||||||
<div
|
<div class="relative mb-4 flex-1 overflow-hidden border border-gray-300 rounded bg-gray-50">
|
||||||
class="relative mb-4 flex-1 overflow-hidden border border-gray-300 rounded bg-gray-50"
|
|
||||||
>
|
|
||||||
<!-- Grid Background Simulation -->
|
<!-- Grid Background Simulation -->
|
||||||
<div class="pointer-events-none absolute inset-0 grid grid-cols-8 gap-px bg-gray-200 opacity-20">
|
<div class="pointer-events-none absolute inset-0 grid grid-cols-8 gap-px bg-gray-200 opacity-20">
|
||||||
<!-- <div v-for="n in 64" :key="n" class="bg-white" /> -->
|
<!-- <div v-for="n in 64" :key="n" class="bg-white" /> -->
|
||||||
</div>
|
</div>
|
||||||
<NImage
|
<NImage :src="team.UserAnswerPicture" class="h-full w-full" object-fit="contain" />
|
||||||
:src="team.UserAnswerPicture"
|
|
||||||
class="h-full w-full"
|
|
||||||
object-fit="contain"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats & Score -->
|
<!-- Stats & Score -->
|
||||||
|
|||||||
@ -135,7 +135,10 @@ const questionTitle = computed(() => question.value?.QuestionList?.QuestionConte
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CompetitionLayout :show-back="true" :show-next="false" :title="questionMainTitle || '抽题'" @back="handleBack">
|
<CompetitionLayout
|
||||||
|
:show-back="true" :show-next="true" :title="questionMainTitle || '抽题'" next-btn-text="开始抽题"
|
||||||
|
@next="handleCardClick(question as Api.Competition.CurrentQuestionResponse)" @back="handleBack"
|
||||||
|
>
|
||||||
<div class="h-full w-full flex flex-col items-center pt-10 font-sans">
|
<div class="h-full w-full flex flex-col items-center pt-10 font-sans">
|
||||||
<div v-if="question" class="mb-10 max-w-4xl text-center">
|
<div v-if="question" class="mb-10 max-w-4xl text-center">
|
||||||
<div class="text-2xl text-gray-800 font-medium leading-relaxed tracking-wide">
|
<div class="text-2xl text-gray-800 font-medium leading-relaxed tracking-wide">
|
||||||
|
|||||||
@ -163,7 +163,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
&.is-disabled {
|
&.is-disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
@ -187,6 +187,7 @@ onMounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
.star-img {
|
.star-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -212,14 +213,15 @@ onMounted(() => {
|
|||||||
// border: 1px solid #5d4037;
|
// border: 1px solid #5d4037;
|
||||||
|
|
||||||
&.type-moon {
|
&.type-moon {
|
||||||
padding-left: 27px; // 按比例调整 padding
|
padding-left: 20px;
|
||||||
padding-top: 8px;
|
padding-top: 12px;
|
||||||
margin-top: -5px; // 调整错落位置
|
margin-top: -5px; // 调整错落位置
|
||||||
}
|
}
|
||||||
|
|
||||||
&.type-sun {
|
&.type-sun {
|
||||||
padding-left: 40px; // 按比例调整 padding
|
padding-left: 30px;
|
||||||
margin-top: 5px;
|
padding-top: -1px;
|
||||||
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .sun-icon {
|
// .sun-icon {
|
||||||
|
|||||||
@ -108,7 +108,7 @@ onMounted(() => {
|
|||||||
<TransitionGroup name="list-anim" tag="div" class="teams-grid">
|
<TransitionGroup name="list-anim" tag="div" class="teams-grid">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in showList ? teams : []" :key="item.id" class="team-item"
|
v-for="(item, index) in showList ? teams : []" :key="item.id" class="team-item"
|
||||||
:style="{ '--delay': `${index * 0.8}s` }" @click="selectTeam(item.id)"
|
:style="{ '--delay': `${index * 0.4}s` }" @click="selectTeam(item.id)"
|
||||||
>
|
>
|
||||||
<div class="icon-wrapper">
|
<div class="icon-wrapper">
|
||||||
<img src="@/assets/imgs/user/team-title-icon.svg" alt="team-icon" class="h-full w-full object-cover">
|
<img src="@/assets/imgs/user/team-title-icon.svg" alt="team-icon" class="h-full w-full object-cover">
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user