306 lines
7.0 KiB
Vue
306 lines
7.0 KiB
Vue
<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>
|