style: format QuestionVariant.vue

This commit is contained in:
cc 2026-03-26 11:56:38 +08:00
parent 9204349041
commit 22c2a19475
1 changed files with 286 additions and 80 deletions

View File

@ -39,7 +39,7 @@ const originalAnalysis = computed(() => {
const raw = rawContent.value;
const match = raw.match(/###\s*原题分析([\s\S]*?)(?=###\s*变式题|$)/);
if (!match) return null;
const content = match[1].trim();
return {
type: extractField(content, "题型"),
@ -53,15 +53,15 @@ const originalAnalysis = computed(() => {
const variantQuestions = computed(() => {
const raw = rawContent.value;
const result = [];
//
const regex = /###\s*变式题\s*(\d+)([\s\S]*?)(?=###\s*变式题|$)/g;
let match;
while ((match = regex.exec(raw)) !== null) {
const index = parseInt(match[1]);
const content = match[2].trim();
result.push({
index,
type: extractField(content, "题目类型"),
@ -73,7 +73,7 @@ const variantQuestions = computed(() => {
knowledge: extractField(content, "考查知识点"),
});
}
return result;
});
@ -81,12 +81,14 @@ const variantQuestions = computed(() => {
function extractOptions(text) {
const options = [];
// A. xxx B. xxx C. xxx D. xxx
const contentMatch = text.match(/\*\*题目内容\*\*[:]\s*([\s\S]*?)(?=\*\*正确答案\*\*|$)/i);
const contentMatch = text.match(
/\*\*题目内容\*\*[:]\s*([\s\S]*?)(?=\*\*正确答案\*\*|$)/i
);
if (!contentMatch) return options;
const contentBlock = contentMatch[1];
const lines = contentBlock.split('\n');
const lines = contentBlock.split("\n");
for (const line of lines) {
const match = line.match(/^([A-D])[\..。:]\s*(.+)$/i);
if (match) {
@ -96,13 +98,16 @@ function extractOptions(text) {
});
}
}
return options;
}
//
function extractField(text, fieldName) {
const regex = new RegExp(`\\*\\*${fieldName}\\*\\*[:]\\s*([\\s\\S]*?)(?=\\n\\*\\*|$)`, "i");
const regex = new RegExp(
`\\*\\*${fieldName}\\*\\*[:]\\s*([\\s\\S]*?)(?=\\n\\*\\*|$)`,
"i"
);
const match = text.match(regex);
return match ? match[1].trim() : "";
}
@ -113,9 +118,9 @@ const buildRequestBody = () => {
model: VARIANT_MODEL,
messages: [
{ role: "system", content: QUESTION_VARIANT_PROMPT },
{
role: "user",
content: `请分析以下试题,并生成 ${variantCount.value} 道变式题:\n\n${textInput.value}`
{
role: "user",
content: `请分析以下试题,并生成 ${variantCount.value} 道变式题:\n\n${textInput.value}`,
},
],
temperature: VARIANT_TEMPERATURE,
@ -221,11 +226,11 @@ const renderContent = (text) => {
//
const getDifficultyColor = (difficulty) => {
const colors = {
"容易": "#10b981",
"较易": "#34d399",
"适中": "#f59e0b",
"较难": "#f97316",
"困难": "#ef4444",
容易: "#10b981",
较易: "#34d399",
适中: "#f59e0b",
较难: "#f97316",
困难: "#ef4444",
};
return colors[difficulty] || "#6366f1";
};
@ -243,7 +248,16 @@ onUnmounted(() => {
<!-- Nav Bar -->
<div class="nav-bar">
<button class="back-btn" @click="goBack">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="15 18 9 12 15 6" />
</svg>
<span>返回</span>
@ -265,8 +279,19 @@ onUnmounted(() => {
<div class="input-card">
<div class="card-header">
<div class="header-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
@ -274,14 +299,20 @@ onUnmounted(() => {
</div>
<h2 class="header-title">输入原始试题</h2>
</div>
<div class="input-content">
<div class="input-row">
<button class="example-btn" @click="textInput = 'He ______ to school every day.\nA. go B. goes C. going D. went\n\n请选择正确答案。'">
<button
class="example-btn"
@click="
textInput =
'He ______ to school every day.\nA. go B. goes C. going D. went。'
"
>
示例试题
</button>
</div>
<textarea
v-model="textInput"
class="text-area"
@ -296,14 +327,25 @@ onUnmounted(() => {
<div class="config-card">
<div class="config-header">
<div class="header-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
</div>
<h2 class="header-title">生成配置</h2>
</div>
<div class="config-content">
<div class="config-item">
<label class="config-label">变式题数量</label>
@ -331,16 +373,21 @@ onUnmounted(() => {
:disabled="!canGenerate"
@click="startGeneration"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
<span>生成变式题</span>
</button>
<button
v-else
class="cancel-btn"
@click="cancelGeneration"
>
<button v-else class="cancel-btn" @click="cancelGeneration">
<span class="btn-spinner"></span>
<span>生成中点击取消</span>
</button>
@ -359,16 +406,30 @@ onUnmounted(() => {
<!-- Empty State -->
<div v-if="status === 'idle'" class="empty-state">
<div class="empty-icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<p class="empty-title">等待生成</p>
<p class="empty-hint">在左侧输入试题内容选择数量后点击生成变式题</p>
<p class="empty-hint">
在左侧输入试题内容选择数量后点击生成变式题
</p>
</div>
<!-- Generating State -->
<div v-else-if="status === 'generating' && !rawContent" class="empty-state loading-state">
<div
v-else-if="status === 'generating' && !rawContent"
class="empty-state loading-state"
>
<div class="loading-spinner">
<div class="spinner-ring"></div>
</div>
@ -377,18 +438,35 @@ onUnmounted(() => {
</div>
<!-- Result Area -->
<div v-else-if="status === 'generating' || status === 'done'" class="result-area">
<div
v-else-if="status === 'generating' || status === 'done'"
class="result-area"
>
<div class="result-header">
<div class="result-title-wrap">
<div class="result-dot" :class="{ pulse: status === 'generating' }"></div>
<span class="result-title">{{ status === "generating" ? "AI 正在生成..." : "生成完成" }}</span>
<div
class="result-dot"
:class="{ pulse: status === 'generating' }"
></div>
<span class="result-title">{{
status === "generating" ? "AI 正在生成..." : "生成完成"
}}</span>
</div>
</div>
<!-- Original Analysis -->
<div v-if="originalAnalysis" class="analysis-card">
<div class="analysis-header">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
@ -401,17 +479,26 @@ onUnmounted(() => {
</div>
<div class="analysis-item" v-if="originalAnalysis.knowledge">
<span class="analysis-label">考查知识点</span>
<span class="analysis-value">{{ originalAnalysis.knowledge }}</span>
<span class="analysis-value">{{
originalAnalysis.knowledge
}}</span>
</div>
<div class="analysis-item" v-if="originalAnalysis.difficulty">
<span class="analysis-label">难度等级</span>
<span class="analysis-value" :style="{ color: getDifficultyColor(originalAnalysis.difficulty) }">
<span
class="analysis-value"
:style="{
color: getDifficultyColor(originalAnalysis.difficulty),
}"
>
{{ originalAnalysis.difficulty }}
</span>
</div>
<div class="analysis-item" v-if="originalAnalysis.features">
<span class="analysis-label">题目特点</span>
<span class="analysis-value">{{ originalAnalysis.features }}</span>
<span class="analysis-value">{{
originalAnalysis.features
}}</span>
</div>
</div>
</div>
@ -419,14 +506,28 @@ onUnmounted(() => {
<!-- Variant Questions -->
<div class="variants-section">
<div class="variants-header">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
<span>变式题目</span>
<span class="variant-count-badge" v-if="variantQuestions.length > 0">
<span
class="variant-count-badge"
v-if="variantQuestions.length > 0"
>
已生成 {{ variantQuestions.length }}
</span>
</div>
@ -442,77 +543,144 @@ onUnmounted(() => {
<span class="number-badge"> {{ question.index }} </span>
</div>
<div class="question-tags">
<span class="tag type-tag">{{ question.type || "未知题型" }}</span>
<span
class="tag difficulty-tag"
:style="{
backgroundColor: getDifficultyColor(question.difficulty) + '18',
<span class="tag type-tag">{{
question.type || "未知题型"
}}</span>
<span
class="tag difficulty-tag"
:style="{
backgroundColor:
getDifficultyColor(question.difficulty) + '18',
color: getDifficultyColor(question.difficulty),
borderColor: getDifficultyColor(question.difficulty) + '40'
borderColor:
getDifficultyColor(question.difficulty) + '40',
}"
>
{{ question.difficulty || "未知难度" }}
</span>
</div>
</div>
<div class="question-body">
<!-- 题目内容 -->
<div class="question-section content-section" v-if="question.content">
<div
class="question-section content-section"
v-if="question.content"
>
<div class="section-header">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
<span>题目内容</span>
</div>
<div class="section-content content-text" v-html="renderContent(question.content)"></div>
<div
class="section-content content-text"
v-html="renderContent(question.content)"
></div>
</div>
<!-- 选项网格 -->
<div v-if="question.options && question.options.length > 0" class="options-grid">
<div
v-if="question.options && question.options.length > 0"
class="options-grid"
>
<div
v-for="opt in question.options"
:key="opt.label"
class="option-item"
:class="{ 'correct-option': question.answer && question.answer.toUpperCase().includes(opt.label) }"
:class="{
'correct-option':
question.answer &&
question.answer.toUpperCase().includes(opt.label),
}"
>
<span class="option-label">{{ opt.label }}.</span>
<span class="option-text">{{ opt.content }}</span>
</div>
</div>
<!-- 正确答案 -->
<div class="question-section answer-section" v-if="question.answer">
<div
class="question-section answer-section"
v-if="question.answer"
>
<div class="answer-row">
<div class="answer-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
<span class="answer-label">正确答案</span>
<span class="answer-value" v-html="renderContent(question.answer)"></span>
<span
class="answer-value"
v-html="renderContent(question.answer)"
></span>
</div>
</div>
<!-- 详细解析 -->
<div class="question-section explanation-section" v-if="question.explanation">
<div
class="question-section explanation-section"
v-if="question.explanation"
>
<div class="section-header">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
<span>详细解析</span>
</div>
<div class="section-content explanation-text" v-html="renderContent(question.explanation)"></div>
<div
class="section-content explanation-text"
v-html="renderContent(question.explanation)"
></div>
</div>
<!-- 考查知识点 -->
<div class="knowledge-section" v-if="question.knowledge">
<div class="knowledge-icon">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
/>
</svg>
</div>
<span class="knowledge-label">考查知识点</span>
@ -536,7 +704,16 @@ onUnmounted(() => {
<!-- Error State -->
<div v-else-if="status === 'error'" class="empty-state error-state">
<div class="empty-icon error-icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
@ -718,7 +895,9 @@ onUnmounted(() => {
}
@keyframes spin {
to { transform: rotate(360deg); }
to {
transform: rotate(360deg);
}
}
/* Error Banner */
@ -788,7 +967,11 @@ onUnmounted(() => {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(99, 102, 241, 0.2));
background: linear-gradient(
135deg,
rgba(139, 92, 246, 0.2),
rgba(99, 102, 241, 0.2)
);
border-radius: 8px;
color: #a78bfa;
}
@ -930,7 +1113,11 @@ onUnmounted(() => {
}
.count-btn.active {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(99, 102, 241, 0.3));
background: linear-gradient(
135deg,
rgba(139, 92, 246, 0.3),
rgba(99, 102, 241, 0.3)
);
border-color: rgba(139, 92, 246, 0.5);
color: #a78bfa;
}
@ -1051,8 +1238,15 @@ onUnmounted(() => {
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); }
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.2);
}
}
.result-title {
@ -1165,7 +1359,11 @@ onUnmounted(() => {
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
background: linear-gradient(135deg, rgba(139, 92, 246, 0.08), rgba(99, 102, 241, 0.05));
background: linear-gradient(
135deg,
rgba(139, 92, 246, 0.08),
rgba(99, 102, 241, 0.05)
);
border-bottom: 1px solid var(--card-border);
}
@ -1179,7 +1377,11 @@ onUnmounted(() => {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(99, 102, 241, 0.1));
background: linear-gradient(
135deg,
rgba(139, 92, 246, 0.15),
rgba(99, 102, 241, 0.1)
);
padding: 0.35rem 0.85rem;
border-radius: 8px;
border: 1px solid rgba(139, 92, 246, 0.2);
@ -1290,7 +1492,11 @@ onUnmounted(() => {
/* Answer Section */
.answer-section {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.08), rgba(16, 185, 129, 0.04));
background: linear-gradient(
135deg,
rgba(16, 185, 129, 0.08),
rgba(16, 185, 129, 0.04)
);
border-top: 1px solid rgba(16, 185, 129, 0.15);
border-bottom: 1px solid rgba(16, 185, 129, 0.15);
}