feat(admin): 优化用户界面组件并新增自定义按钮样式
- 新增按钮背景图片资源(btn-bg.png、progress-title.png、title-bg3.png、title-bg4.png) - 扩展 ActionButton 组件支持自定义按钮类型,新增 custom 类型用于背景图片驱动的按钮 - 添加 btnBg 和 fontSize 属性到 ActionButton,支持动态背景和字体大小配置 - 重构 ActionButton 样式结构,分离 standard-btn 和 custom-btn 两种按钮样式 - 优化按钮禁用状态的样式处理和交互反馈 - 更新 CompetitionLayout 组件支持新的标题背景类型(titleBg3、titleBg4) - 新增 titleTextColor 和 fontSize 属性到 CompetitionLayout,增强标题和按钮的定制化能力 - 更新所有用户页面组件(cover、draw、game、groups、rules、teams)以适配新的按钮和布局配置
This commit is contained in:
BIN
apps/admin/src/assets/imgs/user/btn-bg.png
Normal file
BIN
apps/admin/src/assets/imgs/user/btn-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
apps/admin/src/assets/imgs/user/progress-title.png
Normal file
BIN
apps/admin/src/assets/imgs/user/progress-title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
BIN
apps/admin/src/assets/imgs/user/title-bg3.png
Normal file
BIN
apps/admin/src/assets/imgs/user/title-bg3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
BIN
apps/admin/src/assets/imgs/user/title-bg4.png
Normal file
BIN
apps/admin/src/assets/imgs/user/title-bg4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 306 KiB |
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
// import { defineProps, defineEmits } from 'vue';
|
||||
import { computed } from 'vue'
|
||||
import defaultBtnBg from '@/assets/imgs/user/btn-bg.png'
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
@ -15,13 +16,25 @@ const props = defineProps({
|
||||
default: 'primary',
|
||||
},
|
||||
type: {
|
||||
type: String as () => 'button' | 'text',
|
||||
type: String as () => 'button' | 'text' | 'custom',
|
||||
default: 'button',
|
||||
},
|
||||
btnBg: {
|
||||
type: String,
|
||||
default: defaultBtnBg,
|
||||
},
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['click'])
|
||||
|
||||
const isCustom = computed(() => props.type === 'custom')
|
||||
|
||||
const customBg = computed(() => `url(${props.btnBg}) no-repeat center / 100% 100%`)
|
||||
|
||||
function handleClick() {
|
||||
if (!props.disabled) {
|
||||
emits('click')
|
||||
@ -32,33 +45,52 @@ function handleClick() {
|
||||
<template>
|
||||
<button
|
||||
class="action-btn"
|
||||
:class="[theme, { 'is-disabled': disabled, 'text-btn': type === 'text' }]"
|
||||
:class="[
|
||||
isCustom ? 'custom-btn' : 'standard-btn',
|
||||
theme,
|
||||
type,
|
||||
{ 'is-disabled': disabled },
|
||||
]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span v-if="type === 'text'" class="btn-text">{{ text }}</span>
|
||||
<span v-if="type === 'text' || type === 'custom'" class="btn-text">{{ text }}</span>
|
||||
<slot v-else />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(1);
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
transform: none !important;
|
||||
box-shadow: inherit !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// standard: type="button" | type="text"
|
||||
.standard-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
background: #eb5e55;
|
||||
box-shadow: 0 8px 0 #fcc64f;
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
filter: brightness(1.05);
|
||||
@ -70,7 +102,17 @@ function handleClick() {
|
||||
box-shadow: 0 2px 0 #fcc64f;
|
||||
}
|
||||
|
||||
&.text-btn {
|
||||
&.light {
|
||||
background: white;
|
||||
color: #eb5e55;
|
||||
border: 2px solid #eb5e55;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.text {
|
||||
width: auto;
|
||||
min-width: 180px;
|
||||
padding: 0 30px;
|
||||
@ -82,35 +124,30 @@ function handleClick() {
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.light {
|
||||
background: white;
|
||||
color: #eb5e55;
|
||||
border: 2px solid #eb5e55;
|
||||
box-shadow: 0 8px 0 #fcc64f;
|
||||
// custom: type="custom",背景完全由 btnBg 决定
|
||||
.custom-btn {
|
||||
background: v-bind(customBg);
|
||||
background-color: transparent;
|
||||
width: 220px;
|
||||
height: 60px;
|
||||
box-shadow: none;
|
||||
color: rgb(0, 94, 174);
|
||||
|
||||
.btn-text {
|
||||
font-size: v-bind('`${fontSize}px`');
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 0 #fcc64f;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(4px);
|
||||
box-shadow: 0 2px 0 #fcc64f;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -4,6 +4,8 @@ import VScaleScreen from 'v-scale-screen'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import _defaultBg from '@/assets/imgs/user/home-bg.webp'
|
||||
import titleBg2 from '@/assets/imgs/user/title-bg2.webp'
|
||||
import titleBg3 from '@/assets/imgs/user/title-bg3.png'
|
||||
import titleBg4 from '@/assets/imgs/user/title-bg4.png'
|
||||
import _defaultTitleBg from '@/assets/imgs/user/title-bg.webp'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
|
||||
@ -36,6 +38,10 @@ interface Props {
|
||||
showNextBtn2?: boolean
|
||||
/** 第二个按钮的文本 */
|
||||
nextBtnText2?: string
|
||||
/** title 颜色 */
|
||||
titleTextColor?: string
|
||||
/* 按钮字体大小 */
|
||||
fontSize?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@ -52,6 +58,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
titleBgType: 1,
|
||||
showNextBtn2: false,
|
||||
nextBtnText2: undefined,
|
||||
titleTextColor: '#000000',
|
||||
fontSize: 30,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -63,6 +71,8 @@ const emit = defineEmits<{
|
||||
const titleBgUrlMapping: Record<number, string> = {
|
||||
1: _defaultTitleBg,
|
||||
2: titleBg2,
|
||||
3: titleBg3,
|
||||
4: titleBg4,
|
||||
}
|
||||
|
||||
const { routerPushByKey } = useRouterPush()
|
||||
@ -242,7 +252,7 @@ watch(titleBgUrl, (newUrl) => {
|
||||
@error="imageLoadStatus[url] = false"
|
||||
>
|
||||
<Transition name="fade-in-delay" appear>
|
||||
<h1 v-if="showContent" class="main-title">
|
||||
<h1 v-if="showContent" class="main-title" :style="{ color: titleTextColor }">
|
||||
{{ title }}
|
||||
</h1>
|
||||
</Transition>
|
||||
@ -267,7 +277,8 @@ watch(titleBgUrl, (newUrl) => {
|
||||
<div class="left">
|
||||
<ActionButton
|
||||
class="back-btn" :style="{ visibility: showBack ? 'visible' : 'hidden' }"
|
||||
:type="backBtnText ? 'text' : 'button'" :text="backBtnText || ''" :theme="btnTheme" @click="handleBack"
|
||||
type="custom" :text="backBtnText || ''" :theme="btnTheme" :font-size="fontSize"
|
||||
@click="handleBack"
|
||||
>
|
||||
<SvgIcon v-if="!backBtnText" icon="mdi:arrow-left" size="40" class="arrow-icon" />
|
||||
</ActionButton>
|
||||
@ -275,8 +286,9 @@ watch(titleBgUrl, (newUrl) => {
|
||||
|
||||
<div class="right">
|
||||
<ActionButton
|
||||
v-if="showNext" :type="nextBtnText ? 'text' : 'button'" :text="nextBtnText || '下一步'"
|
||||
:theme="btnTheme" :disabled="nextDisabled" @click="handleNext"
|
||||
v-if="showNext" type="custom" :text="nextBtnText || '下一步'"
|
||||
:theme="btnTheme" :disabled="nextDisabled" :font-size="fontSize"
|
||||
@click="handleNext"
|
||||
>
|
||||
<SvgIcon v-if="!nextBtnText" icon="mdi:arrow-right" class="arrow-icon" size="40" />
|
||||
</ActionButton>
|
||||
|
||||
@ -196,7 +196,8 @@ onMounted(async () => {
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-back="true" :show-next="showNextButton" next-btn-text="下一步" :title="activityName"
|
||||
:title-bg-type="2" @back="handleBack" @next="handleNext"
|
||||
:title-bg-type="2" back-btn-text="返回" @back="handleBack"
|
||||
@next="handleNext"
|
||||
>
|
||||
<div class="h-screen w-full overflow-y-auto font-sans">
|
||||
<div class="relative z-10 min-h-full w-full">
|
||||
|
||||
@ -132,11 +132,14 @@ function handleBack() {
|
||||
|
||||
const questionMainTitle = computed(() => question.value?.QuestionList?.Name || '')
|
||||
const questionTitle = computed(() => question.value?.QuestionList?.QuestionContent || question.value?.QuestionList?.Name || '')
|
||||
|
||||
const titleBgTypeNum = computed(() => RoundType.value === 0 ? 3 : 4)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-back="true" :show-next="true" :title="questionMainTitle || '抽题'" next-btn-text="开始抽题"
|
||||
:show-back="true" :show-next="true" back-btn-text="返回" :title="questionMainTitle || '抽题'" next-btn-text="开始抽题"
|
||||
:title-bg-type="titleBgTypeNum || 3" title-text-color="#ffffff"
|
||||
@next="handleCardClick(question as Api.Competition.CurrentQuestionResponse)" @back="handleBack"
|
||||
>
|
||||
<div class="h-full w-full flex flex-col items-center pt-10 font-sans">
|
||||
@ -169,7 +172,10 @@ const questionTitle = computed(() => question.value?.QuestionList?.QuestionConte
|
||||
}}</span>
|
||||
</div> -->
|
||||
<!-- 禁用放大 -->
|
||||
<NImage :src="drawImg" class="h-full w-full object-cover" width="300" height="300" :preview-disabled="true" />
|
||||
<NImage
|
||||
:src="drawImg" class="h-full w-full object-cover" width="300" height="300"
|
||||
:preview-disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,6 +20,7 @@ const { activityId, groupId, activityInfo, teamId, teamIds } = storeToRefs(activ
|
||||
const currentQuestionInfo = computed(() => store.currentQuestionInfo)
|
||||
const currentQuestionDetail = computed(() => store.currentQuestionDetail)
|
||||
const currentQuestionMainInfo = computed(() => store.currentQuestionMainInfo)
|
||||
const RoundType = computed(() => store.currentQuestionMainInfo?.Activity_Question?.RoundType)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
@ -246,13 +247,16 @@ async function fetchGetGameStatisticsData(_params: Api.Competition.QuestionAddPa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const titleBgTypeNum = computed(() => RoundType.value === 0 ? 3 : 4) || 3
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
action-position="top" :show-back="false" :show-next="true" :title="mainTitle || ''"
|
||||
back-btn-text="返回" :next-btn-text="isStarted ? formattedTime : '开始答题'" :next-disabled="isStarted" btn-theme="light"
|
||||
@back="handleBack" @next="handleNext"
|
||||
:title-bg-type="titleBgTypeNum" title-text-color="#ffffff" back-btn-text="返回"
|
||||
:next-btn-text="isStarted ? formattedTime : '开始答题'" :next-disabled="isStarted" btn-theme="light" @back="handleBack"
|
||||
@next="handleNext"
|
||||
>
|
||||
<div class="relative h-full w-full flex flex-col items-center overflow-hidden px-12 font-sans">
|
||||
<!-- 题目说明 -->
|
||||
|
||||
@ -83,7 +83,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-back="true" :show-next="isAllEnd" next-btn-text="查看常规赛结果" next-btn-text2="查看加时赛结果"
|
||||
:show-back="true" back-btn-text="返回" :show-next="isAllEnd" next-btn-text="查看常规赛结果" font-size="22" next-btn-text2="查看加时赛结果"
|
||||
:show-next-btn2="hasExtraTime && isAllEnd" :title-bg-type="2" title="展示组别" @back="handleBack" @next="handleNext"
|
||||
@next2="handleNext2"
|
||||
>
|
||||
|
||||
@ -49,8 +49,8 @@ function handleNext() {
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-back="true" :show-next="true" :title="activityName" next-btn-text="下一步" @back="handleBack"
|
||||
@next="handleNext"
|
||||
:show-back="true" back-btn-text="返回" :show-next="true" :title="activityName" next-btn-text="下一步"
|
||||
@back="handleBack" @next="handleNext"
|
||||
>
|
||||
<div class="items-flex-start h-full w-full flex justify-center font-sans">
|
||||
<div class="rules-content-wrapper">
|
||||
|
||||
@ -87,7 +87,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CompetitionLayout :show-back="true" :show-next="true" next-btn-text="下一步" :show-title="false" @next="handleNext" @back="handleBack">
|
||||
<CompetitionLayout :show-back="true" back-btn-text="返回" :show-next="true" next-btn-text="下一步" :show-title="false" @next="handleNext" @back="handleBack">
|
||||
<!-- Scroll Content -->
|
||||
<div class="scroll-container">
|
||||
<!-- 卷轴背景动画容器 -->
|
||||
|
||||
Reference in New Issue
Block a user