|
|
@@ -0,0 +1,352 @@
|
|
|
+<template>
|
|
|
+ <div v-if="visible" class="modal-mask" @click.self="emit('close')">
|
|
|
+ <div class="modal hint-modal scale-in">
|
|
|
+ <div class="modal-head">
|
|
|
+ <h3 class="modal-title">
|
|
|
+ <svg
|
|
|
+ width="14"
|
|
|
+ height="14"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ fill="none"
|
|
|
+ stroke="#f97316"
|
|
|
+ stroke-width="2"
|
|
|
+ stroke-linecap="round"
|
|
|
+ stroke-linejoin="round"
|
|
|
+ >
|
|
|
+ <path d="M9 18h6" />
|
|
|
+ <path d="M10 22h4" />
|
|
|
+ <path
|
|
|
+ d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ {{ aiName ? `${aiName} 的任务提示` : '任务提示' }}
|
|
|
+ </h3>
|
|
|
+ <button class="close-btn" type="button" aria-label="关闭" @click="emit('close')">
|
|
|
+ <svg
|
|
|
+ width="14"
|
|
|
+ height="14"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ fill="none"
|
|
|
+ stroke="currentColor"
|
|
|
+ stroke-width="2"
|
|
|
+ stroke-linecap="round"
|
|
|
+ stroke-linejoin="round"
|
|
|
+ >
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18" />
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="loading" class="state-panel">
|
|
|
+ <p class="state-text">正在生成任务提示...</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else-if="error" class="state-panel error-panel">
|
|
|
+ <p class="error-text">{{ error }}</p>
|
|
|
+ <button class="retry-btn" type="button" @click="emit('retry')">
|
|
|
+ 重试
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-else-if="hint">
|
|
|
+ <div class="hint-context">
|
|
|
+ <p class="context-label">当前问题</p>
|
|
|
+ <p class="context-body">{{ hint.current_question }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="hint-section">
|
|
|
+ <p class="section-label">参考句子</p>
|
|
|
+ <div class="sentences">
|
|
|
+ <div
|
|
|
+ v-for="(sentence, index) in hint.example_sentences"
|
|
|
+ :key="`${sentence.english}-${index}`"
|
|
|
+ class="sentence-card"
|
|
|
+ >
|
|
|
+ <p class="sentence-en">{{ sentence.english }}</p>
|
|
|
+ <p class="sentence-zh">{{ sentence.chinese }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="hint-section">
|
|
|
+ <p class="section-label">关键词汇</p>
|
|
|
+ <div class="vocab-grid">
|
|
|
+ <div
|
|
|
+ v-for="(vocab, index) in hint.key_vocabulary"
|
|
|
+ :key="`${vocab.word}-${index}`"
|
|
|
+ class="vocab-item"
|
|
|
+ >
|
|
|
+ <p class="vocab-word">{{ vocab.word }}</p>
|
|
|
+ <p class="vocab-meaning">{{ vocab.meaning }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="hint-footer">用自己的话表达更棒哦</p>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import type { TaskHint } from '@/types/englishSpeaking'
|
|
|
+
|
|
|
+defineProps<{
|
|
|
+ visible: boolean
|
|
|
+ loading: boolean
|
|
|
+ error?: string | null
|
|
|
+ hint?: TaskHint | null
|
|
|
+ aiName?: string
|
|
|
+}>()
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ close: []
|
|
|
+ retry: []
|
|
|
+}>()
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.modal-mask {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ z-index: 50;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 16px;
|
|
|
+ background: rgba(0, 0, 0, 0.3);
|
|
|
+ backdrop-filter: blur(2px);
|
|
|
+}
|
|
|
+
|
|
|
+.modal {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 80vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.hint-modal {
|
|
|
+ max-width: 440px;
|
|
|
+ min-width: 280px;
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-title {
|
|
|
+ display: flex;
|
|
|
+ min-width: 0;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ margin: 0;
|
|
|
+ color: #111827;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-title svg {
|
|
|
+ flex: 0 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn {
|
|
|
+ display: flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #f3f4f6;
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.state-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 128px;
|
|
|
+ padding: 16px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.state-text,
|
|
|
+.error-text {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.state-text {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.error-panel {
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.error-text {
|
|
|
+ color: #ef4444;
|
|
|
+ overflow-wrap: anywhere;
|
|
|
+}
|
|
|
+
|
|
|
+.retry-btn {
|
|
|
+ min-width: 72px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 14px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #f97316;
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #ea580c;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.hint-context {
|
|
|
+ padding: 12px 14px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background: #fff7ed;
|
|
|
+ border: 1px solid #fed7aa;
|
|
|
+ border-radius: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.context-label,
|
|
|
+.section-label {
|
|
|
+ margin: 0 0 8px;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1.4;
|
|
|
+ letter-spacing: 0.03em;
|
|
|
+ text-transform: uppercase;
|
|
|
+}
|
|
|
+
|
|
|
+.context-label {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ color: #f97316;
|
|
|
+}
|
|
|
+
|
|
|
+.context-body {
|
|
|
+ margin: 0;
|
|
|
+ color: #374151;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ overflow-wrap: anywhere;
|
|
|
+}
|
|
|
+
|
|
|
+.hint-section {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-label {
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.sentences {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-card {
|
|
|
+ min-width: 0;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: #f9fafb;
|
|
|
+ border: 1px solid #f3f4f6;
|
|
|
+ border-radius: 12px;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #fed7aa;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-en,
|
|
|
+.sentence-zh,
|
|
|
+.vocab-word,
|
|
|
+.vocab-meaning {
|
|
|
+ overflow-wrap: anywhere;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-en {
|
|
|
+ margin: 0;
|
|
|
+ color: #1f2937;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-zh {
|
|
|
+ margin: 2px 0 0;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.vocab-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.vocab-item {
|
|
|
+ min-width: 0;
|
|
|
+ padding: 8px 10px;
|
|
|
+ background: #f9fafb;
|
|
|
+ border: 1px solid #f3f4f6;
|
|
|
+ border-radius: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.vocab-word {
|
|
|
+ margin: 0;
|
|
|
+ color: #1f2937;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.vocab-meaning {
|
|
|
+ margin: 2px 0 0;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-size: 10px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.hint-footer {
|
|
|
+ margin: 16px 0 0;
|
|
|
+ color: #d1d5db;
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 1.5;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 420px) {
|
|
|
+ .hint-modal {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vocab-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|