Merge branch 'dev_vite' of ssh://18.167.251.121:10002/aidlab/aida_front into dev_vite

This commit is contained in:
X1627315083@163.com
2026-04-16 15:58:25 +08:00
8 changed files with 509 additions and 125 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

View File

@@ -25,7 +25,7 @@
{
"paragraph": [
{
"text": "Click the “View Details” button for more information and to join the competition! The AiDA Global Design Award 2026 is an international design competition hosted by CodeCreate, a globally leading AI fashion solutions provider, celebrating the future of creativity powered by artificial intelligence. Open to designers from Hong Kong, China, Singapore, South Korea, and beyond, the competition brings together global talent, empowering AI as a creative partner—pushing fashion beyond traditional boundaries and unlocking new possibilities where technology amplifies human imagination."
"text": "Click the “View Details” button for more information and to join the competition! The AiDA Global Design Award 2026 is an international design competition hosted by CodeCreate, a globally leading AI fashion solutions provider, celebrating the future of creativity powered by artificial intelligence. Open to designers worldwide the competition brings together global talent, empowering AI as a creative partner—pushing fashion beyond traditional boundaries and unlocking new possibilities where technology amplifies human imagination."
}
]
},

View File

@@ -25,7 +25,7 @@
{
"paragraph": [
{
"text": "秉承推动 AI 赋能创意设计的初衷CodeCreate 举办了「AiDA 全球设计大奖 2026」面向来自香港、中国、新加坡、韩国及全球的设计师,鼓励大家探索 AI 与时尚设计的无限可能,突破传统界限,释放科技与想象力的创新潜能。点击“查看详情”按钮获取更多比赛信息,抓住成为 AI 时尚先锋的机会吧!"
"text": "秉承推动 AI 赋能创意设计的初衷CodeCreate 举办了「AiDA 全球设计大奖 2026」面向来全球的设计师鼓励大家探索 AI 与时尚设计的无限可能,突破传统界限,释放科技与想象力的创新潜能。点击“查看详情”按钮获取更多比赛信息,抓住成为 AI 时尚先锋的机会吧!"
}
]
},

View File

@@ -21,7 +21,11 @@
{{ $t("event.detail") }}
</div>
</div>
<div class="modal_title_text content" v-for="item in eventsDetail.textList">
<div
class="modal_title_text content"
v-for="item in eventsDetail.textList"
:class="{ award: eventsDetail.id === 3 }"
>
<div class="eventsDetail_content_right_btn_box">
<div
class="eventsDetail_content_right_btn"
@@ -270,18 +274,21 @@ export default defineComponent({
.eventsDetail_content_right_btn_box {
display: flex;
justify-content: space-evenly;
.eventsDetail_content_right_btn {
}
// .eventsDetail_content_right_btn {
// }
}
}
.modal_title_text:last-child {
}
// .modal_title_text:last-child {
// }
.modal_title_text:last-child::after {
content: "";
display: block;
border-top: 3px solid;
height: 6rem;
}
.modal_title_text.award:last-child:after {
display: none;
}
}
}
}

View File

@@ -483,7 +483,6 @@ const routes: Array<RouteRecordRaw> = [
},
component: () => import("@/views/userManual.vue")
},
{
path: "/:catchAll(.*)",
redirect: "/404"

View File

@@ -0,0 +1,55 @@
<template>
<div class="edit-detail-wrapper">
<div class="edit-detail-header flex align-center space-between">
<div class="bread-crumb">导航</div>
<div class="operate-menu flex">
<div class="menu-btn flex align-center save">
<span>Save Draft</span>
<SvgIcon name="CSave" color="#000000" size="16" />
</div>
<div class="menu-btn flex align-center publish">
<span>Publish</span>
<SvgIcon name="CPublish" color="#ffffff" size="16" />
</div>
</div>
</div>
<div class="edit-detail-content"></div>
</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped>
.c-svg {
width: initial;
}
.edit-detail-wrapper {
.menu-btn {
height: 6rem;
border: 0.15rem solid #000000;
border-radius: 4rem;
text-align: center;
line-height: 6rem;
padding: 0 2rem;
font-weight: 400;
font-size: 1.6rem;
column-gap: 0.8rem;
cursor: pointer;
}
.edit-detail-header {
width: 100%;
height: 6rem;
margin-bottom: 2rem;
.operate-menu {
column-gap: 2rem;
.publish {
background-color: #000000;
color: #ffffff;
}
}
}
.edit-detail-content{
padding-right: 6.4rem;
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="radio-button-group">
<button
v-for="item in options"
:key="item.key"
type="button"
:class="['radio-button', { 'is-active': modelValue === item.key }]"
@click="selectOption(item.key)"
>
{{ item.name }}
</button>
</div>
</template>
<script setup lang="ts">
interface Option {
name: string | number
value: string | number | boolean
key: string
optype: boolean
}
const props = defineProps<{
modelValue: string | number | boolean | null // v-model 绑定的值
options: Option[] // 按钮选项数组
}>()
const emit = defineEmits<{
(e: "update:modelValue", value: any): void
}>()
const selectOption = (value: any) => {
if (props.modelValue !== value) {
emit("update:modelValue", value)
}
}
</script>
<style lang="less" scoped>
.radio-button-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.radio-button {
border: 1px solid #d9d9d9;
height: 3.2rem;
min-width: 8rem;
padding: 0 1.7rem;
color: #000;
cursor: pointer;
border-radius: 2rem;
outline: none;
background-color: #fff;
font-size: 1.2rem;
transition: all 0.2s ease;
}
.radio-button:hover {
border-color: #000;
}
.radio-button.is-active {
color: #fff;
background-color: #000;
border-color: #000;
font-family: pingfang_medium;
}
</style>

View File

@@ -14,16 +14,16 @@
<div class="operate-menu flex">
<div class="menu-btn flex align-center save">
<span>{{ $t("SellerListEdit.saveDraft") }}</span>
<SvgIcon name="CSave" color="#000000" size="16" />
<SvgIcon name="CSave" size="16" />
</div>
<div class="menu-btn flex align-center publish">
<span>{{ $t("SellerListEdit.publish") }}</span>
<SvgIcon name="CPublish" color="#ffffff" size="16" />
<SvgIcon name="CPublish" size="16" />
</div>
</div>
</template>
</seller-header>
<div class="edit-detail-content flex space-between">
<div class="edit-detail-content flex">
<div class="left">
<div class="main-image-container flex">
<div
@@ -32,25 +32,20 @@
:class="`main-image-item flex flex-col align-center ${type}`"
>
<div class="title" :class="{ required: type !== 'mainProductImage' }">
{{ $t(`SellerListEdit.${type}`) }}
{{ topImageTitleMap[type] }}
</div>
<div class="sketch-item flex flex-center" :class="type">
<div
v-show="selectList[currentIndex][type]"
class="crop-tool flex flex-center"
>
<div v-if="previewImageMap[type]" class="crop-tool flex flex-center">
<SvgIcon name="CCrop" color="#fff" size="12" />
</div>
<img
v-show="selectList[currentIndex][type]"
:src="selectList[currentIndex][type]"
v-if="previewImageMap[type]"
:src="previewImageMap[type]"
class="sketch-img"
:class="{ cover: type === 'cover' }"
alt=""
/>
<div
class="trigger flex flex-col align-center"
v-show="!selectList[currentIndex][type]"
>
<div v-else class="trigger flex flex-col align-center">
<template v-if="type === 'cover'">
<SvgIcon
class="trigger-icon"
@@ -63,10 +58,7 @@
</div>
</template>
<template v-else>
<img
src="@/assets/images/seller/image-placeholder.png"
class="trigger-img"
/>
<div class="trigger-img placeholder"></div>
<div class="trigger-tips">
{{ $t("SellerListEdit.productImageDesc") }}
</div>
@@ -92,17 +84,18 @@
<div class="product-image-list flex">
<div
v-for="(item, index) in prodImgList"
:key="index"
class="product-image-item flex flex-center"
:class="{ selected: item.selected }"
:key="index"
@click="handleSelectProdImg(index)"
>
<img
v-show="item.selected"
v-if="item.selected"
src="@/assets/images/seller/checked.png"
class="checked"
alt=""
/>
<img class="img-src" :src="item.url" />
<img class="img-src" :src="item.url" alt="" />
<div
v-if="item.selected && index === firstSelectedIndex"
class="main-pic"
@@ -127,7 +120,7 @@
:key="index"
class="sketch-element flex flex-center"
>
<img class="img-src" :src="item.url" />
<img class="img-src" :src="item.url" alt="" />
<div class="crop-tool flex flex-center">
<SvgIcon name="CCrop" color="#fff" size="12" />
</div>
@@ -141,9 +134,8 @@
<div class="form-item-label required">Product Name</div>
<div class="form-item-value product-name">
<a-input
v-model:value="selectList[currentIndex].productName"
v-model:value="currentListing.productName"
show-count
:rows="2"
placeholder="Enter product name"
:bordered="false"
:maxlength="60"
@@ -155,12 +147,59 @@
<div class="form-item-value price flex align-center">
<span>HK$</span>
<a-input
v-model:value="selectList[currentIndex].desc"
v-model:value="currentListing.price"
placeholder="0.00"
:bordered="false"
/>
</div>
</div>
<div class="form-item">
<div class="form-item-label required">Product Description</div>
<div class="form-item-value desc">
<a-textarea
v-model:value="currentListing.desc"
show-count
:rows="4"
placeholder="Enter product description"
:bordered="false"
:maxlength="500"
/>
</div>
</div>
<div class="form-item">
<div class="form-item-label required">Design for</div>
<div class="form-item-value no-border">
<Radio :options="genderOptions" v-model="currentListing.gender" />
</div>
</div>
<div class="form-item">
<div class="form-item-label with-tip">
<span class="required">Product Category</span>
<span class="help-text">select all that apply</span>
</div>
<div class="form-item-value no-border">
<Radio :options="categoryOptions" v-model="currentListing.category" />
</div>
</div>
<div class="license-note flex align-center">
<img src="@/assets/images/seller/tips.png" class="info-icon" />
<div class="note-copy">
By default, all sales follow the platform's licensing policy — buyers
will receive a usage license upon download.
<a href="javascript:void(0)">Learn more</a>
</div>
</div>
</div>
<div class="page-control flex align-center">
<a-pagination
v-model:current="currentPage"
:total="selectList.length"
:page-size="1"
showQuickJumper
showLessItems
responsive
:showSizeChanger="false"
/>
</div>
</div>
</div>
@@ -168,14 +207,55 @@
</template>
<script setup lang="ts">
import { ref, computed } from "vue"
import { computed, ref, watch } from "vue"
import SellerHeader from "../../seller-header.vue"
import testImg from "@/assets/images/test.png"
import { ElInput } from "element-plus"
import Radio from "./components/Radio.vue"
import { Https } from "@/tool/https"
import { useStore } from "vuex"
const topImageList = ["sketch", "mainProductImage", "cover"]
const currentIndex = ref(0)
const selectList = ref([
const STORE = useStore()
type CategoryOption = {
label: string
value: string
}
type ListingItem = {
sketch: string
mainProductImage: string
cover: string
productImage: string[]
apparelSketch: string[]
productName: string
price: string
desc: string
gender: string
category: string
prodImageList: Array<{
url: string
selected: boolean
}>
}
const topImageList = ["sketch", "mainProductImage", "cover"] as const
const topImageTitleMap: Record<(typeof topImageList)[number], string> = {
sketch: "Sketch",
mainProductImage: "Main Product Image",
cover: "Cover"
}
const genderOptions = STORE.state.UserHabit?.sex.value||[]
const fallbackCategoryOptions: Record<string, CategoryOption[]> = {
MALE: STORE.state.UserHabit?.MalePosition || [],
FEMALE: STORE.state.UserHabit?.FemalePosition || []
}
const currentPage = ref(1)
const currentIndex = computed(() => currentPage.value - 1)
const selectList = ref<ListingItem[]>([
{
sketch: testImg,
mainProductImage: "",
@@ -183,69 +263,105 @@ const selectList = ref([
productImage: [],
apparelSketch: [],
productName: "",
price: "",
price: "12",
desc: "",
gender: "FEMALE",
category: "",
prodImageList: [
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false }
]
},
{
sketch: testImg,
mainProductImage: "",
cover: "",
productImage: [],
apparelSketch: [],
productName: "",
price: "12",
desc: "",
gender: "",
category: ""
}
])
const prodImgList = ref([
{
url: testImg,
selected: false
category: "",
prodImageList: [
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false }
]
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
},
{
url: testImg,
selected: false
sketch: testImg,
mainProductImage: "",
cover: "",
productImage: [],
apparelSketch: [],
productName: "",
price: "12",
desc: "",
gender: "",
category: "",
prodImageList: [
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false },
{ url: testImg, selected: false }
]
}
])
const prodImgList = computed(() => currentListing.value.prodImageList || [])
const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
const categoryOptions = computed(() => {
const gender = selectList.value[currentIndex.value].gender
return fallbackCategoryOptions[gender] || []
})
const currentListing = computed(() => selectList.value[currentIndex.value])
const previewImageMap = computed(() => ({
sketch: currentListing.value.sketch,
mainProductImage: currentListing.value.mainProductImage,
cover:
currentListing.value.cover ||
currentListing.value.mainProductImage ||
currentListing.value.sketch
}))
const firstSelectedIndex = ref(null) //显示main标签的图片索引
const selectedProdImgs = computed(() => {
return prodImgList.value.filter((item) => item.selected).length
})
const firstSelectedIndex = ref(-1)
const handleSelectProdImg = (index: number) => {
const item = prodImgList.value[index]
item.selected = !item.selected
const target = prodImgList.value[index]
if (item.selected) {
if (firstSelectedIndex.value === -1) {
firstSelectedIndex.value = index
selectList.value[currentIndex.value].mainProductImage = item.url
}
} else if (firstSelectedIndex.value === index) {
selectList.value[currentIndex.value].mainProductImage = null
firstSelectedIndex.value = -1
const willSelect = !target.selected
target.selected = willSelect
if (willSelect && !currentListing.value.mainProductImage) {
currentListing.value.mainProductImage = target.url
firstSelectedIndex.value = index
}
if (!willSelect && currentListing.value.mainProductImage === target.url) {
firstSelectedIndex.value = null
currentListing.value.mainProductImage = ""
}
}
const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
</script>
<style lang="less" scoped>
@@ -255,8 +371,13 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
}
.edit-detail-wrapper {
overflow-y: auto;
// set the scollbar hidden
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
margin-top: -1rem;
overflow: hidden;
&::-webkit-scrollbar {
display: none;
}
@@ -268,15 +389,26 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
text-align: center;
line-height: 6rem;
padding: 0 2rem;
font-weight: 400;
font-size: 1.6rem;
column-gap: 0.8rem;
cursor: pointer;
transition: all 0.25s ease;
&.save:hover {
background: #000;
color: #fff;
}
&.publish:hover {
background: #fff;
color: #000;
}
}
.edit-detail-header {
margin-bottom: 2rem;
}
.operate-menu {
column-gap: 2rem;
@@ -286,8 +418,18 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
}
}
}
.edit-detail-content {
padding-right: 6.4rem;
flex: 1;
min-height: 0;
overflow-y: auto;
justify-content: space-between;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
.required {
&::after {
content: "*";
@@ -295,17 +437,23 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
margin-left: 0.4rem;
}
}
.left {
// flex: 1;
// min-width: 0;
.main-image-container {
column-gap: 3.6rem;
// max-width: 80.2rem;
.main-image-item {
flex-shrink: 0;
.title {
font-weight: 400;
font-style: bold;
font-size: 1.4rem;
margin-bottom: 0.8rem;
text-align: center;
}
.sketch-item {
width: 11.6rem;
height: 20.4rem;
@@ -313,62 +461,77 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
border-radius: 1rem;
position: relative;
background-color: #f6f6f6;
overflow: hidden;
&.cover {
width: 16.17rem;
// border-style: dashed;
width: 16.2rem;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' rx='11' ry='11' fill='none' stroke='%23D1D1D1' stroke-width='1.5' stroke-dasharray='8%2c 5' stroke-linecap='square'/%3e%3c/svg%3e");
border: none;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23D1D1D1' stroke-width='1.5' stroke-dasharray='13%2c 5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
}
.crop-tool {
position: absolute;
top: 0.4rem;
right: 0.4rem;
top: 0.8rem;
right: 0.8rem;
width: 2rem;
height: 2rem;
border-radius: 50%;
cursor: pointer;
background-color: #000000;
z-index: 1;
cursor: pointer;
}
.sketch-img {
// width: 100%;
height: 100%;
}
.trigger {
row-gap: 1.2rem;
cursor: pointer;
height: 100%;
padding-top: 6.8rem;
.trigger-img {
padding: 6rem 2rem 0;
.placeholder {
width: 2.4rem;
height: 2.4rem;
border-radius: 0.6rem;
background: linear-gradient(135deg, #efefef 0%, #cdcdcd 100%);
}
.trigger-tips {
font-size: 1.2rem;
width: 9rem;
text-align: center;
color: #585858;
line-height: 1.3;
}
}
}
}
}
.product-image-list-container {
margin-top: 3rem;
.title {
font-weight: 400;
font-style: bold;
font-size: 1.4rem;
margin-bottom: 1.2rem;
.main-title {
font-weight: 400;
font-style: bold;
}
.sub-title {
font-size: 1.2rem;
color: #999;
}
.title-right {
color: #585858;
font-size: 1.4rem;
font-weight: 400;
}
}
.product-image-list {
overflow-x: auto;
overflow-y: hidden;
@@ -376,19 +539,18 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
max-width: 80.2rem;
padding-bottom: 1.2rem;
//滚动条高度0.8rem,背景色#D9D9D9,滚动条圆角0.4rem,小方块为黑色
&::-webkit-scrollbar {
height: 0.8rem;
}
&::-webkit-scrollbar-track {
background: #d9d9d9;
border-radius: 0.8rem;
height: 0.8rem;
}
&::-webkit-scrollbar-thumb {
background: #000000;
border-radius: 0.8rem;
height: 0.8rem;
}
.product-image-item {
@@ -398,6 +560,9 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
border: 0.15rem solid #c7c7c7;
position: relative;
cursor: pointer;
overflow: hidden;
flex-shrink: 0;
&::after {
position: absolute;
top: 0;
@@ -409,22 +574,28 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
opacity: 0.7;
border-radius: 1rem;
}
&.selected {
// border-color: #000000;
border-color: #000;
&::after {
display: none;
}
}
.checked {
width: 2rem;
height: 2rem;
position: absolute;
top: 0.8rem;
right: 0.8rem;
z-index: 1;
}
.img-src {
height: 100%;
}
.main-pic {
position: absolute;
height: 2.4rem;
@@ -433,8 +604,7 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
right: 0.8rem;
bottom: 0.8rem;
z-index: 1;
background: #000000cc;
font-weight: 400;
background: rgba(0, 0, 0, 0.8);
color: #fff;
font-size: 1.4rem;
border-radius: 1.2rem;
@@ -443,34 +613,46 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
}
}
}
.apparel-container {
margin-top: 3rem;
.title {
font-weight: 400;
font-style: bold;
font-size: 1.4rem;
margin-bottom: 0.8rem;
.main-title {
font-weight: 400;
font-style: bold;
&::after {
content: "*";
color: #df2b2c;
}
}
.sub-title {
font-size: 1.2rem;
color: #999;
}
}
.sketch-list-container {
column-gap: 1rem;
flex-wrap: wrap;
.sketch-element {
width: 10rem;
height: 14.6rem;
border: 0.15rem solid #c7c7c7;
border-radius: 1.2rem;
position: relative;
overflow: hidden;
.img-src {
width: 100%;
height: 100%;
}
.crop-tool {
position: absolute;
top: 0.4rem;
@@ -478,24 +660,40 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
width: 2rem;
height: 2rem;
border-radius: 50%;
cursor: pointer;
background-color: #000000;
}
}
}
}
}
.right {
width: 55.2rem;
flex-shrink: 0;
.form-container {
row-gap: 3rem;
.form-item {
.form-item-label {
font-size: 1.4rem;
font-weight: 400;
font-style: bold;
margin-bottom: 0.6rem;
line-height: 1.5;
&.with-tip {
display: flex;
align-items: center;
column-gap: 0.8rem;
}
.help-text {
font-size: 1rem;
color: #999;
}
}
.form-item-value {
border: 0.16rem solid #d1d1d1;
border-radius: 1.2rem;
@@ -503,32 +701,87 @@ const sketchList = ref([{ url: testImg }, { url: testImg }, { url: testImg }])
padding: 1.6rem;
box-sizing: border-box;
font-size: 1.2rem;
font-weight: 400;
color: #000;
&.no-border {
border: none;
padding: 0;
}
&.price {
column-gap: 0.6rem;
}
:deep(.ant-input) {
:deep(.ant-input),
:deep(.ant-input-affix-wrapper),
:deep(.ant-input-textarea textarea) {
border: none;
padding: 0;
font-size: 1.2rem;
font-weight: 400;
color: #000;
box-shadow: none;
}
:deep(.ant-input-affix-wrapper) {
padding: 0;
font-weight: 400;
input {
font-size: 1.2rem;
}
:deep(.ant-input) {
line-height: 1.5;
}
:deep(.ant-input-show-count-suffix) {
color: #df2c2c;
font-size: 1rem;
}
:deep(textarea.ant-input) {
resize: none;
min-height: 5.4rem;
padding-bottom: 1.8rem;
line-height: 1.5;
}
:deep(.ant-input-textarea-show-count) {
position: relative;
&::after {
float: none;
position: absolute;
color: #df2c2c;
bottom: 0;
right: 1.6rem;
}
}
}
}
}
.license-note {
padding: 1.6rem;
column-gap: 1.6rem;
background: #f7f7f7;
border-radius: 0.8rem;
.info-icon {
width: 2.4rem;
height: 2.4rem;
flex-shrink: 0;
}
.note-copy {
font-size: 1.2rem;
line-height: 1.5;
color: #000;
font-weight: 400;
font-style: medium;
a {
color: #0080ed;
text-decoration: underline;
margin-left: 0.4rem;
}
}
}
.page-control {
justify-content: flex-end;
margin-top: 4rem;
}
}
}
</style>