Files
joke/web/src/views/generate/index.vue
T

355 lines
8.2 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>
<div class="generate-page">
<h1 class="page-title">
<span class="emoji">&#x2728;</span>
智能笑话生成器
</h1>
<!-- 场景选择 -->
<div class="section">
<div class="section-label">选择场景可多选</div>
<div class="scenario-chips">
<el-check-tag
v-for="s in predefinedScenarios"
:key="s"
:checked="selectedScenarios.includes(s)"
@change="toggleScenario(s)"
class="scenario-chip"
>
{{ s }}
</el-check-tag>
</div>
</div>
<!-- 关键词输入 -->
<div class="section">
<div class="section-label">添加关键词可选</div>
<el-input
v-model="keywordInput"
placeholder="输入关键词,如:加班、相亲、熊孩子..."
@keyup.enter="addKeyword"
clearable
>
<template #append>
<el-button @click="addKeyword">添加</el-button>
</template>
</el-input>
<div v-if="keywords.length" class="keyword-tags">
<el-tag
v-for="(kw, i) in keywords"
:key="i"
closable
@close="removeKeyword(i)"
class="keyword-tag"
>
{{ kw }}
</el-tag>
</div>
</div>
<!-- 生成按钮 -->
<div class="generate-actions">
<el-button
type="primary"
size="large"
:loading="loading"
:disabled="loading"
@click="handleGenerate"
class="generate-btn"
>
<span v-if="!loading">&#x1F3B2; 开始生成</span>
<span v-else>生成中...</span>
</el-button>
<el-button v-if="generatedJoke" size="large" @click="resetForm">
清空
</el-button>
</div>
<!-- 生成结果 -->
<div v-if="generatedJoke" class="result-card">
<h2 class="result-title">{{ generatedJoke.title }}</h2>
<pre class="result-content">{{ generatedJoke.content }}</pre>
<div class="result-actions">
<el-button type="primary" @click="handleGenerate">
&#x1F504; 重新生成
</el-button>
<el-button @click="handleFavorite" :type="isFavorited ? 'danger' : 'default'">
<span v-if="!isFavorited">&#x2764; 收藏</span>
<span v-else>&#x2714; 已收藏</span>
</el-button>
</div>
</div>
<!-- 收藏列表 -->
<div v-if="favorites.length" class="favorites-section">
<h3>&#x1F4EC; 我的收藏 ({{ favorites.length }})</h3>
<div class="favorite-list">
<div
v-for="(fav, i) in favorites"
:key="fav.id"
class="favorite-item"
>
<div class="fav-title">{{ fav.title }}</div>
<div class="fav-content">{{ fav.content.substring(0, 100) }}...</div>
<div class="fav-meta">
<span class="fav-date">{{ formatDate(fav.created_at) }}</span>
<el-button type="danger" size="small" text @click="removeFavorite(i)">
删除
</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { generateJoke } from '@/api/generate'
const predefinedScenarios = ['职场', '校园', '社交', '家庭', '情感', '搞笑日常']
const selectedScenarios = ref([])
const keywordInput = ref('')
const keywords = ref([])
const loading = ref(false)
const generatedJoke = ref(null)
const isFavorited = ref(false)
// 收藏
const favorites = ref(JSON.parse(localStorage.getItem('joke_favorites') || '[]'))
const STORAGE_KEY = 'joke_favorites'
function toggleScenario(s) {
const idx = selectedScenarios.value.indexOf(s)
if (idx >= 0) {
selectedScenarios.value.splice(idx, 1)
} else {
selectedScenarios.value.push(s)
}
}
function addKeyword() {
const kw = keywordInput.value.trim()
if (kw && !keywords.value.includes(kw)) {
keywords.value.push(kw)
keywordInput.value = ''
}
}
function removeKeyword(i) {
keywords.value.splice(i, 1)
}
async function handleGenerate() {
loading.value = true
isFavorited.value = false
try {
const res = await generateJoke(keywords.value, selectedScenarios.value)
generatedJoke.value = res
// 检查是否已收藏
const exists = favorites.value.find(f => f.content === res.content)
isFavorited.value = !!exists
} catch (e) {
ElMessage.error(e.detail || '生成失败,请重试')
generatedJoke.value = null
} finally {
loading.value = false
}
}
function handleFavorite() {
if (!generatedJoke.value) return
// 检查是否已存在
const exists = favorites.value.findIndex(f => f.content === generatedJoke.value.content)
if (exists >= 0) {
ElMessage.info('已经收藏过了')
return
}
favorites.value.unshift({
id: Date.now().toString(),
title: generatedJoke.value.title,
content: generatedJoke.value.content,
created_at: new Date().toISOString()
})
localStorage.setItem(STORAGE_KEY, JSON.stringify(favorites.value))
isFavorited.value = true
ElMessage.success('收藏成功')
}
function removeFavorite(i) {
favorites.value.splice(i, 1)
localStorage.setItem(STORAGE_KEY, JSON.stringify(favorites.value))
// 检查当前生成的是否也从收藏中被删除了
if (generatedJoke.value) {
const stillExists = favorites.value.find(f => f.content === generatedJoke.value.content)
isFavorited.value = !!stillExists
}
}
function resetForm() {
generatedJoke.value = null
isFavorited.value = false
}
function formatDate(dateStr) {
if (!dateStr) return ''
return new Date(dateStr).toLocaleDateString('zh-CN')
}
</script>
<style scoped>
.generate-page {
max-width: 720px;
margin: 0 auto;
padding: 40px 24px;
}
.page-title {
font-size: 28px;
font-weight: 800;
color: var(--text-primary);
text-align: center;
margin-bottom: 40px;
}
.emoji {
display: inline-block;
margin-right: 10px;
}
.section {
margin-bottom: 28px;
}
.section-label {
font-size: 15px;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 12px;
}
.scenario-chips {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
:deep(.scenario-chip) {
padding: 8px 18px;
border-radius: 20px;
background: var(--bg-card);
border: 1.5px solid var(--border);
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
:deep(.scenario-chip:hover) {
border-color: #9333ea;
}
:deep(.scenario-chip.is-checked) {
background: #9333ea;
border-color: #9333ea;
color: white;
}
.keyword-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.keyword-tag {
background: #f3e8ff;
border-color: #9333ea;
color: #9333ea;
}
.generate-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-bottom: 32px;
}
:deep(.generate-btn) {
padding: 12px 40px;
font-size: 16px;
background: linear-gradient(135deg, #9333ea, #7c3aed);
border: none;
border-radius: 24px;
}
:deep(.generate-btn:hover) {
box-shadow: 0 4px 20px rgba(147, 51, 234, 0.4);
}
.result-card {
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: 32px;
margin-bottom: 32px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.result-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 16px;
text-align: center;
}
.result-content {
font-size: 16px;
line-height: 1.9;
color: var(--text-secondary);
white-space: pre-wrap;
margin-bottom: 24px;
}
.result-actions {
display: flex;
justify-content: center;
gap: 12px;
}
.favorites-section {
margin-top: 40px;
}
.favorites-section h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.favorite-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.favorite-item {
background: var(--bg-card);
border-radius: var(--radius);
padding: 16px 20px;
border: 1px solid var(--border);
}
.fav-title {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 6px;
}
.fav-content {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 8px;
line-height: 1.5;
}
.fav-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.fav-date {
font-size: 12px;
color: var(--text-muted);
}
</style>