Merge branch 'refs/heads/master' into dev

# Conflicts:
#	src/pages/article/BookList.vue
#	src/pages/article/components/VolumeSetting.vue
#	src/pages/word/DictDetail.vue
#	src/pages/word/DictList.vue
This commit is contained in:
Zyronon
2025-10-19 03:05:56 +08:00
19 changed files with 241 additions and 298 deletions

View File

@@ -214,34 +214,6 @@ a {
text-decoration: none;
}
.base-textarea {
flex: 1;
font-family: var(--font-family);
font-size: 1.1rem;
outline: none;
border: 1px solid transparent;
border-radius: .4rem;
padding: .5rem .6rem;
transition: all .3s;
min-height: 1.2rem;
width: 100%;
box-sizing: border-box;
background: var(--color-textarea-bg);
&:focus {
border: 1px solid var(--color-select-bg);
}
&[readonly] {
cursor: not-allowed;
opacity: .7;
}
}
.base-input {
@extend .base-textarea;
flex: none;
}
@supports selector(::-webkit-scrollbar) {
::-webkit-scrollbar {
@@ -348,7 +320,7 @@ a {
gap: .5rem;
color: var(--color-main-text);
span{
span {
flex-shrink: 0;
}
@@ -469,7 +441,7 @@ a {
}
}
#typing-listener{
#typing-listener {
position: fixed;
right: 0;
bottom: 0;

View File

@@ -6,7 +6,6 @@ import MiniDialog from "@/components/dialog/MiniDialog.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep, debounce, reverse, shuffle} from "@/utils";
import Input from "@/components/Input.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import Empty from "@/components/Empty.vue";
import Pagination from '@/components/base/Pagination.vue'
@@ -14,6 +13,7 @@ import Toast from '@/components/base/toast/Toast.ts'
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import Dialog from "@/components/dialog/Dialog.vue";
import BaseInput from "@/components/base/BaseInput.vue";
let list = defineModel('list')
@@ -146,11 +146,17 @@ defineRender(
{
showSearchInput ? (
<div class="flex gap-4">
<Input
prefixIcon
<BaseInput
clearable
modelValue={searchKey}
onUpdate:modelValue={debounce(e => searchKey = e)}
class="flex-1"/>
class="flex-1">
{{
subfix: () => <IconFluentSearch24Regular
class="text-lg text-gray"
/>
}}
</BaseInput>
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
</div>
) : (

View File

@@ -1,97 +0,0 @@
<script setup lang="ts">
import Close from "@/components/icon/Close.vue";
import {useDisableEventListener, useWindowClick} from "@/hooks/event.ts";
defineProps<{
modelValue: string
placeholder?: string
autofocus?: boolean
prefixIcon?: boolean
}>()
defineEmits(['update:modelValue'])
let focus = $ref(false)
let inputEl = $ref<HTMLDivElement>()
useWindowClick((e: PointerEvent) => {
if (!e) return
focus = inputEl.contains(e.target as any);
})
useDisableEventListener(() => focus)
const vFocus = {
mounted: (el, bind) => {
if (bind.value) {
el.focus()
setTimeout(() => focus = true)
}
}
}
</script>
<template>
<div class="base-input"
:class="{focus}"
ref="inputEl"
>
<IconFluentSearch24Regular
v-if="prefixIcon"
width="20"/>
<input type="text"
:value="modelValue"
v-focus="autofocus"
:placeholder="placeholder"
@input="e=>$emit('update:modelValue',e.target.value)"
>
<transition name="fade">
<Close v-if="modelValue" @click="$emit('update:modelValue','')"/>
</transition>
</div>
</template>
<style scoped lang="scss">
.base-input {
border: 1px solid var(--color-input-border);
border-radius: .4rem;
overflow: hidden;
padding: .2rem .3rem;
transition: all .3s;
display: flex;
align-items: center;
background: var(--color-input-bg);
:deep(svg) {
transition: all .3s;
color: var(--color-input-icon);
}
&.focus {
border: 1px solid var(--color-select-bg);
:deep(svg) {
color: gray;
}
}
input {
font-family: var(--font-family);
font-size: 1.1rem;
outline: none;
min-height: 1.2rem;
flex: 1;
box-sizing: border-box;
outline: none;
border: none;
background: transparent;
&[readonly] {
cursor: not-allowed;
opacity: .7;
}
}
}
</style>

View File

@@ -1,10 +1,13 @@
<script setup lang="ts">
import {ref, useAttrs, watch} from 'vue';
import { ref, useAttrs, watch } from 'vue';
import Close from "@/components/icon/Close.vue";
import { useDisableEventListener } from "@/hooks/event.ts";
const props = defineProps({
modelValue: [String, Number],
placeholder: String,
disabled: Boolean,
autofocus: Boolean,
type: {
type: String,
default: 'text',
@@ -25,6 +28,8 @@ const attrs = useAttrs();
const inputValue = ref(props.modelValue);
const errorMsg = ref('');
let focus = $ref(false)
let inputEl = $ref<HTMLDivElement>()
watch(() => props.modelValue, (val) => {
inputValue.value = val;
@@ -57,24 +62,42 @@ const onChange = (e: Event) => {
};
const onFocus = (e: FocusEvent) => {
focus = true
emit('focus', e);
};
const onBlur = (e: FocusEvent) => {
focus = false
validate(inputValue.value);
emit('blur', e);
};
const clearInput = () => {
inputValue.value = '';
validate('');
emit('update:modelValue', '');
};
//当聚焦时,禁用输入监听
useDisableEventListener(() => focus)
const vFocus = {
mounted: (el, bind) => {
if (bind.value) {
el.focus()
setTimeout(() => focus = true)
}
}
}
</script>
<template>
<div class="custom-input" :class="{ 'is-disabled': disabled, 'has-error': errorMsg }">
<div class="base-input2"
ref="inputEl"
:class="{ 'is-disabled': disabled, 'has-error': errorMsg,focus }">
<slot name="subfix"></slot>
<input
v-bind="attrs"
:type="type"
@@ -85,88 +108,71 @@ const clearInput = () => {
@change="onChange"
@focus="onFocus"
@blur="onBlur"
class="custom-input__inner"
class="inner"
v-focus="autofocus"
:maxlength="maxLength"
/>
<button
<slot name="prefix"></slot>
<Close
v-if="clearable && inputValue && !disabled"
type="button"
class="custom-input__clear"
@click="clearInput"
aria-label="Clear input"
>×
</button>
<div v-if="errorMsg" class="custom-input__error">{{ errorMsg }}</div>
@click="clearInput"/>
<div v-if="errorMsg" class="base-input2__error">{{ errorMsg }}</div>
</div>
</template>
<style scoped lang="scss">
.custom-input {
.base-input2 {
position: relative;
display: inline-block;
display: inline-flex;
box-sizing: border-box;
width: 100%;
background: var(--color-input-bg);
border: 1px solid var(--color-input-border);
border-radius: 4px;
overflow: hidden;
padding: .2rem .3rem;
transition: all .3s;
align-items: center;
background: var(--color-input-bg);
&.is-disabled {
opacity: 0.6;
}
&.has-error {
.custom-input__inner {
.base-input2__inner {
border-color: #f56c6c;
}
.custom-input__error {
.base-input2__error {
color: #f56c6c;
font-size: 0.85rem;
margin-top: 0.25rem;
}
}
&__inner {
width: 100%;
padding: 0.4rem 1.5rem 0.4rem 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
transition: all .3s;
color: var(--color-input-color);
background: var(--color-input-bg);
&:focus {
outline: none;
border-color: #409eff;
box-shadow: 0 0 3px #409eff;
}
&:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
&.focus {
border: 1px solid var(--color-select-bg);
}
&__clear {
position: absolute;
right: 0.4rem;
top: 50%;
transform: translateY(-50%);
border: none;
background: transparent;
font-size: 1.2rem;
line-height: 1;
cursor: pointer;
color: #999;
padding: 0;
user-select: none;
&:hover {
color: #666;
}
&:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
&__error {
padding-left: 0.5rem;
}
.inner {
flex: 1;
font-size: 1rem;
outline: none;
border: none;
box-sizing: border-box;
transition: all .3s;
height: 1.5rem;
color: var(--color-input-color);
}
}
</style>

View File

@@ -104,8 +104,7 @@ textarea {
&:focus {
outline: none;
border-color: #409eff;
box-shadow: 0 0 3px #409eff;
border: 1px solid var(--color-select-bg);
}
}
</style>

View File

@@ -1,10 +1,8 @@
<script setup lang="ts">
import Input from "@/components/Input.vue";
import {Article} from "@/types/types.ts";
import { Article } from "@/types/types.ts";
import BaseList from "@/components/list/BaseList.vue";
import * as sea from "node:sea";
import {watch, watchEffect} from "vue";
import BaseInput from "@/components/base/BaseInput.vue";
const props = withDefaults(defineProps<{
list: Article[],
@@ -34,7 +32,9 @@ let localList = $computed(() => {
let d = Number(t)
//如果是纯数字,把那一条加进去
if (!isNaN(d)) {
res.push(props.list[d])
if (d - 1 < props.list.length) {
res.push(props.list[d - 1])
}
}
} catch (err) {
}
@@ -69,7 +69,14 @@ defineExpose({scrollToBottom, scrollToItem})
<template>
<div class="list">
<div class="search">
<Input prefix-icon v-model="searchKey"/>
<BaseInput
clearable
v-model="searchKey"
>
<template #subfix>
<IconFluentSearch24Regular class="text-lg text-gray"/>
</template>
</BaseInput>
</div>
<BaseList
ref="listRef"

View File

@@ -1,10 +1,10 @@
<script setup lang="ts" generic="T extends {id:string}">
import BaseIcon from "@/components/BaseIcon.vue";
import Input from "@/components/Input.vue";
import {cloneDeep, throttle} from "@/utils";
import {Article} from "@/types/types.ts";
import { cloneDeep, throttle } from "@/utils";
import { Article } from "@/types/types.ts";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import BaseInput from "@/components/base/BaseInput.vue";
interface IProps {
list: T[]
@@ -102,7 +102,14 @@ defineExpose({scrollBottom})
ref="el"
>
<div class="search">
<Input prefix-icon v-model="searchKey"/>
<BaseInput
clearable
v-model="searchKey"
>
<template #subfix>
<IconFluentSearch24Regular class="text-lg text-gray"/>
</template>
</BaseInput>
</div>
<transition-group name="drag" class="list" tag="div">
<div class="item"

View File

@@ -5,7 +5,6 @@ import { DictResource } from "@/types/types.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Empty from "@/components/Empty.vue";
import Input from "@/components/Input.vue";
import BaseButton from "@/components/BaseButton.vue";
import DictList from "@/components/list/DictList.vue";
import BackIcon from "@/components/BackIcon.vue";
@@ -14,6 +13,7 @@ import { computed } from "vue";
import { getDefaultDict } from "@/types/func.ts";
import { useFetch } from "@vueuse/core";
import { DICT_LIST } from "@/config/env.ts";
import BaseInput from "@/components/base/BaseInput.vue";
const {nav} = useNav()
const runtimeStore = useRuntimeStore()
@@ -55,7 +55,7 @@ const searchList = computed<any[]>(() => {
<div class="flex items-center relative gap-2">
<BackIcon class="z-2" @Click='router.back'/>
<div class="flex flex-1 gap-4" v-if="showSearchInput">
<Input prefix-icon placeholder="请输入书籍名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus/>
<BaseInput prefix-icon placeholder="请输入书籍名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus clearable/>
<BaseButton @click="showSearchInput = false, searchKey = ''">取消</BaseButton>
</div>
<div class="py-1 flex flex-1 justify-end" v-else>

View File

@@ -207,16 +207,6 @@ function setArticle(val: Article) {
allWrongWords = new Set()
articleData.list[store.sbook.lastLearnIndex] = val
articleData.article = val
savePracticeData()
clearInterval(timer)
timer = setInterval(() => {
if (isFocus) {
statStore.spend += 1000
savePracticeData(false)
}
}, 1000)
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
articleData.article.sections.map((v, i) => {
v.map((w) => {
@@ -227,6 +217,16 @@ function setArticle(val: Article) {
})
})
})
savePracticeData()
clearInterval(timer)
timer = setInterval(() => {
if (isFocus) {
statStore.spend += 1000
savePracticeData(false)
}
}, 1000)
_nextTick(typingArticleRef?.init)
window.umami?.track('startStudyArticle', {
@@ -314,8 +314,8 @@ function wrong(word: Word) {
if (settingStore.ignoreSimpleWord) {
if (store.simpleWords.includes(temp)) return
}
if (!allWrongWords.has(word.word.toLowerCase())) {
allWrongWords.add(word.word.toLowerCase())
if (!allWrongWords.has(temp)) {
allWrongWords.add(temp)
statStore.wrong++
}

View File

@@ -38,3 +38,6 @@ useWindowClick(() => show = false)
</MiniDialog>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -28,6 +28,7 @@ import { useSettingStore } from "@/stores/setting.ts";
import { MessageBox } from "@/utils/MessageBox.tsx";
import { CAN_REQUEST, Origin } from "@/config/env.ts";
import { detail } from "@/apis";
import { PracticeSaveWordKey } from "@/utils/const.ts";
const runtimeStore = useRuntimeStore()
const base = useBaseStore()
@@ -224,6 +225,7 @@ const {nav} = useNav()
//todo 可以和首页合并
async function startPractice() {
localStorage.removeItem(PracticeSaveWordKey.key)
studyLoading = true
await base.changeDict(runtimeStore.editDict)
studyLoading = false
@@ -366,6 +368,10 @@ async function exportData() {
exportLoading = false
}
function searchWord() {
console.log('wordForm.word',wordForm.word)
}
defineRender(() => {
return (
<BasePage>
@@ -447,7 +453,9 @@ defineRender(() => {
<BaseInput
modelValue={wordForm.word}
onUpdate:modelValue={e => wordForm.word = e}
/>
>
</BaseInput>
</FormItem>
<FormItem label="英音音标">
<BaseInput

View File

@@ -5,7 +5,6 @@ import { DictResource } from "@/types/types.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Empty from "@/components/Empty.vue";
import Input from "@/components/Input.vue";
import BaseButton from "@/components/BaseButton.vue";
import DictList from "@/components/list/DictList.vue";
import BackIcon from "@/components/BackIcon.vue";
@@ -16,6 +15,7 @@ import { computed } from "vue";
import { getDefaultDict } from "@/types/func.ts";
import { useFetch } from "@vueuse/core";
import { DICT_LIST } from "@/config/env.ts";
import BaseInput from "@/components/base/BaseInput.vue";
const {nav} = useNav()
const runtimeStore = useRuntimeStore()
@@ -85,7 +85,7 @@ const searchList = computed<any[]>(() => {
<div class="flex items-center relative gap-2">
<BackIcon class="z-2" @click='router.back'/>
<div class="flex flex-1 gap-4" v-if="showSearchInput">
<Input prefix-icon placeholder="请输入词典名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus/>
<BaseInput clearable placeholder="请输入词典名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus/>
<BaseButton @click="showSearchInput = false, searchKey = ''">取消</BaseButton>
</div>
<div class="py-1 flex flex-1 justify-end" v-else>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import { ShortcutKey, Word } from "@/types/types.ts";
import {ShortcutKey, Word} from "@/types/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio, useTTsPlayAudio } from "@/hooks/sound.ts";
import { emitter, EventKey } from "@/utils/eventBus.ts";
import { nextTick, onMounted, onUnmounted, watch } from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio, useTTsPlayAudio} from "@/hooks/sound.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {nextTick, onMounted, onUnmounted, watch} from "vue";
import Tooltip from "@/components/base/Tooltip.vue";
import SentenceHightLightWord from "@/pages/word/components/SentenceHightLightWord.vue";
import { usePracticeStore } from "@/stores/practice.ts";
import { getDefaultWord } from "@/types/func.ts";
import { _nextTick, sleep } from "@/utils";
import {usePracticeStore} from "@/stores/practice.ts";
import {getDefaultWord} from "@/types/func.ts";
import {_nextTick, sleep} from "@/utils";
interface IProps {
word: Word,
@@ -219,8 +219,6 @@ function play() {
defineExpose({del, showWord, hideWord, play})
let tab = $ref(0)
function mouseleave() {
setTimeout(() => {
showFullWord = false
@@ -299,8 +297,8 @@ function checkCursorPosition() {
}}]
</div>
<VolumeIcon
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</div>
<div class="word my-1"
@@ -318,78 +316,102 @@ function checkCursorPosition() {
<span class="letter" v-else>{{ displayWord }}</span>
</div>
<div class="translate anim"
v-opacity="settingStore.translate"
<div class="translate anim flex flex-col gap-2 my-3"
v-opacity="settingStore.translate || showFullWord"
:style="{
fontSize: settingStore.fontSize.wordTranslateFontSize +'px',
}"
>
<div class="my-2 flex" v-for="(v,i) in word.trans">
<div class="shrink-0" :class="v.pos && 'w-12'">{{ v.pos }}</div>
<div class="flex" v-for="(v,i) in word.trans">
<div class="shrink-0" :class="v.pos ? 'w-12 en-article-family' : '-ml-3'">{{ v.pos }}</div>
<span v-if="settingStore.dictation && !showFullWord" v-html="hideWordInTranslation(v.cn, word.word)"></span>
<span v-else>{{ v.cn }}</span>
</div>
</div>
</div>
<div class="other">
<template v-if="word.sentences && word.sentences.length">
<div class="line-white my-4"></div>
<div class="sentences">
<div class="sentence my-2" v-for="item in word.sentences">
<SentenceHightLightWord class="text-lg" :text="item.c" :word="word.word"
<div class="line-white my-2"></div>
<template v-if="word?.sentences?.length">
<div class="flex flex-col gap-3">
<div class="sentence" v-for="item in word.sentences">
<SentenceHightLightWord class="text-xl" :text="item.c" :word="word.word"
:dictation="(settingStore.dictation && !showFullWord)"/>
<div class="text-md anim" v-opacity="settingStore.translate">{{ item.cn }}</div>
<div class="text-base anim" v-opacity="settingStore.translate || showFullWord">{{ item.cn }}</div>
</div>
</div>
<div class="line-white my-2 mb-5 anim" v-opacity="settingStore.translate || showFullWord"></div>
</template>
<template v-if="word.phrases.length || word.synos.length || word.relWords.root || word.etymology.length">
<div class="line-white my-4"></div>
<div class="tabs">
<div @click="tab = 0" class="tab" :class="tab === 0 && 'active'">短语</div>
<div @click="tab = 1" class="tab" :class="tab === 1 && 'active'">同近义词</div>
<!-- <div @click="tab = 2" class="tab" :class="tab === 2 && 'active'">同根词</div>-->
<div @click="tab = 3" class="tab" :class="tab === 3 && 'active'">词源</div>
</div>
</template>
<template v-if="tab === 0">
<div class="my-2" v-for="item in word.phrases">
<SentenceHightLightWord class="text-lg" :text="item.c" :word="word.word"
:dictation="(settingStore.dictation && !showFullWord)"/>
<div class="text-md anim" v-opacity="settingStore.translate">{{ item.cn }}</div>
</div>
</template>
<template v-if="tab === 1">
<div class="flex my-2" v-for="item in word.synos">
<div class="text-lg w-12">{{ item.pos }}</div>
<div>
<div class="text-md">{{ item.cn }}</div>
<span class="text-md" v-for="(i,j) in item.ws">{{ i }} {{ j !== item.ws.length - 1 ? ' / ' : '' }} </span>
</div>
</div>
</template>
<template v-if="tab === 2">
<div class="mt-2">
<div v-if="word.relWords.root">
词根{{ word.relWords.root }}
</div>
<div class="flex my-2" v-for="item in word.relWords.rels">
<div class="text-lg w-12">{{ item.pos }}</div>
<div>
<div class="flex gap-4" v-for="itemj in item.words">
<div class="text-md">{{ itemj.c }}</div>
<div class="text-md">{{ itemj.cn }}</div>
<div class="anim" v-opacity="settingStore.translate || showFullWord">
<template v-if="word?.phrases?.length">
<div class="flex">
<div class="label">短语</div>
<div class="flex flex-col">
<div class="flex items-center gap-4" v-for="item in word.phrases">
<SentenceHightLightWord class="en" :text="item.c" :word="word.word"
:dictation="(settingStore.dictation && !showFullWord)"/>
<div class="cn anim" v-opacity="settingStore.translate">{{ item.cn }}</div>
</div>
</div>
</div>
</div>
</template>
<template v-if="tab === 3">
<div class="my-2" v-for="item in word.etymology">
<div class="text-lg">{{ item.t }}</div>
<div class="text-md">{{ item.d }}</div>
</div>
</template>
<div class="line-white mt-3 mb-2"></div>
</template>
<template v-if="word?.synos?.length">
<div class="flex">
<div class='label'>同近义词</div>
<div class="flex flex-col gap-3">
<div class="flex" v-for="item in word.synos">
<div class="pos line-height-1.4rem!">{{ item.pos }}</div>
<div>
<div class="cn">{{ item.cn }}</div>
<div>
<span class="en" v-for="(i,j) in item.ws">{{ i }} {{
j !== item.ws.length - 1 ? ' / ' : ''
}} </span>
</div>
</div>
</div>
</div>
</div>
<div class="line-white my-2"></div>
</template>
<template v-if="word?.etymology?.length">
<div class="flex">
<div class="label">词源</div>
<div class="text-base">
<div class="mb-2" v-for="item in word.etymology">
<div class="">{{ item.t }}</div>
<div class="">{{ item.d }}</div>
</div>
</div>
</div>
<!-- <div class="line-white my-2"></div>-->
</template>
<template v-if="word?.relWords?.root && false">
<div class="flex">
<div class="label">同根词</div>
<div class="flex flex-col gap-3">
<div v-if="word.relWords.root" class=" ">
词根<span class="en">{{ word.relWords.root }}</span>
</div>
<div class="flex" v-for="item in word.relWords.rels">
<div class="pos">{{ item.pos }}</div>
<div>
<div class="flex items-center gap-4" v-for="itemj in item.words">
<div class="en">{{ itemj.c }}</div>
<div class="cn">{{ itemj.cn }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
<div class="cursor"
:style="{top:cursor.top+'px',left:cursor.left+'px',height: settingStore.fontSize.wordForeignFontSize +'px'}"></div>
@@ -447,5 +469,24 @@ function checkCursorPosition() {
}
}
.label {
width: 6rem;
padding-top: 0.2rem;
flex-shrink: 0;
}
.cn {
@apply text-base;
}
.en {
@apply text-lg;
}
.pos {
font-family: var(--en-article-family);
@apply text-lg w-12;
}
}
</style>