2026-05-06 15:56:56 +08:00

306 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
:model-value="modelValue"
class="word-detail-dialog"
width="1000px"
:show-close="false"
:close-on-click-modal="true"
modal-class="word-detail-overlay"
align-center
@update:model-value="$emit('update:modelValue', $event)"
>
<div v-if="word" class="detail-panel">
<div class="detail-header">
<div class="word-main">
<h2 class="detail-word">{{ word.word }}</h2>
<button class="sound-row" type="button" aria-label="播放发音">
<svg class="sound-icon" viewBox="0 0 24 24" fill="none">
<path d="M4 9v6h4l5 4V5L8 9H4Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
<path d="M16 9c1.1 1.6 1.1 4.4 0 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
<span>/ˈklevə(r)/</span>
</button>
<p class="definition">int. 用于否定的回答或陈述没有不是</p>
</div>
<button class="book-link" type="button">
<svg viewBox="0 0 28 28" fill="none">
<path d="M7 4h11a3 3 0 0 1 3 3v17H9a2 2 0 0 1-2-2V4Z" stroke="currentColor" stroke-width="2" />
<path d="M7 20h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="M13 9h5M13 14h5" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
<span>生词本</span>
</button>
</div>
<div class="tab-row">
<button
v-for="tab in tabs"
:key="tab.key"
class="detail-tab"
:class="{ active: activeTab === tab.key }"
type="button"
@click="activeTab = tab.key"
>
{{ tab.label }}
</button>
</div>
<div class="tab-content">
<div v-if="activeTab === 'example'" class="example-block">
<button class="sentence-audio" type="button" aria-label="播放例句">
<svg class="sound-icon" viewBox="0 0 24 24" fill="none">
<path d="M4 9v6h4l5 4V5L8 9H4Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
<path d="M16 9c1.1 1.6 1.1 4.4 0 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
<span>Just say yes or no.</span>
</button>
<p class="translation">只要说不是</p>
</div>
<div v-else-if="activeTab === 'mnemonic'" class="mnemonic-block">
<h3>形象联想</h3>
<p>
将字母 n 想象成一个摇头的动作o 想象成张大的嘴巴合起来就像在说时摇头和张嘴的样子
</p>
<h3>谐音记忆</h3>
<p>
no 的发音类似于中文的可以联想成不要再闹了来表示拒绝短小有力的发音也适合在回答问题时快速表达否定
</p>
<h3>用法提示</h3>
<p>作为否定回答时可单独使用也可以放在名词前表达没有</p>
</div>
<div v-else class="level-block">
<h3>1-第2关</h3>
<div class="record-grid">
<div class="record-card">
<span>选择题</span>
<p>正确 <strong class="success">5</strong> </p>
<p>错误 <strong class="danger">5</strong> </p>
</div>
<div class="record-card">
<span>拼写题</span>
<p>正确 <strong class="success">50</strong> </p>
<p>错误 <strong class="danger">999</strong> </p>
</div>
<div class="record-card">
<span>复习</span>
<p>成功 <strong class="success">555</strong> </p>
<p>失败 <strong class="danger">5</strong> </p>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import type { WordCardItem } from "./WordCard.vue";
const props = defineProps<{
modelValue: boolean;
word: WordCardItem | null;
}>();
defineEmits<{
"update:modelValue": [value: boolean];
}>();
const tabs = [
{ key: "example", label: "例句" },
{ key: "mnemonic", label: "助记" },
{ key: "level", label: "关卡" },
] as const;
type DetailTab = (typeof tabs)[number]["key"];
const activeTab = ref<DetailTab>("example");
watch(
() => props.modelValue,
(visible: boolean) => {
if (visible) activeTab.value = "example";
},
);
</script>
<style scoped lang="scss">
:global(.word-detail-dialog) {
--el-dialog-padding-primary: 0;
border-radius: 24px;
box-shadow: none;
}
:global(.word-detail-dialog .el-dialog__header) {
display: none;
}
:global(.word-detail-dialog .el-dialog__body) {
padding: 0;
}
:global(.word-detail-overlay) {
background-color: rgba(0, 0, 0, 0.58);
}
.detail-panel {
width: 1000px;
height: 680px;
padding: 64px 56px 58px;
overflow-y: auto;
border-radius: 24px;
background: #fff;
scrollbar-color: #d6dced transparent;
scrollbar-width: thin;
}
.detail-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 32px;
padding-bottom: 34px;
border-bottom: 1px solid #edf0f5;
}
.detail-word {
color: #121212;
font-size: 38px;
font-weight: 700;
line-height: 1;
}
.sound-row,
.sentence-audio,
.book-link {
display: inline-flex;
align-items: center;
border: none;
background: transparent;
color: #333;
cursor: pointer;
font: inherit;
}
.sound-row {
gap: 10px;
margin-top: 28px;
font-size: 24px;
}
.sound-icon {
width: 24px;
height: 24px;
}
.definition {
margin-top: 28px;
color: #333;
font-size: 24px;
}
.book-link {
gap: 8px;
color: #333;
font-size: 24px;
white-space: nowrap;
svg {
width: 28px;
height: 28px;
}
}
.tab-row {
display: flex;
align-items: center;
gap: 22px;
margin-top: 34px;
}
.detail-tab {
border: none;
border-radius: 999px;
background: transparent;
color: #333;
cursor: pointer;
font-size: 24px;
padding: 8px 18px;
&.active {
background: #eaf4ff;
color: #2d6fe0;
}
}
.tab-content {
margin-top: 32px;
color: #333;
font-size: 24px;
}
.sentence-audio {
gap: 10px;
font-size: 24px;
}
.translation {
margin-top: 12px;
padding-left: 34px;
color: #555;
font-size: 22px;
}
.mnemonic-block {
line-height: 1.7;
h3 {
margin: 0 0 16px;
color: #121212;
font-size: 24px;
font-weight: 700;
}
p + h3 {
margin-top: 28px;
}
}
.level-block h3 {
margin-bottom: 22px;
color: #121212;
font-size: 24px;
}
.record-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.record-card {
display: flex;
align-items: center;
gap: 30px;
min-height: 70px;
border: 1px solid #edf0f5;
border-radius: 10px;
padding: 0 22px;
color: #333;
font-size: 22px;
}
.success {
color: #12b886;
font-size: 28px;
}
.danger {
color: #e84040;
font-size: 28px;
}
</style>