前端
This commit is contained in:
1370
frontend/app/web-gold/src/views/content-style/Benchmark.vue
Normal file
1370
frontend/app/web-gold/src/views/content-style/Benchmark.vue
Normal file
File diff suppressed because it is too large
Load Diff
905
frontend/app/web-gold/src/views/content-style/Copywriting.vue
Normal file
905
frontend/app/web-gold/src/views/content-style/Copywriting.vue
Normal file
@@ -0,0 +1,905 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { usePromptStore } from '@/stores/prompt'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { CommonService } from '@/api/common'
|
||||
import useVoiceText from '@gold/hooks/web/useVoiceText'
|
||||
import GmIcon from '@/components/icons/Icon.vue'
|
||||
|
||||
const promptStore = usePromptStore()
|
||||
const md = new MarkdownIt()
|
||||
|
||||
// 表单数据(合并为单一输入)
|
||||
const form = ref({
|
||||
prompt: '',
|
||||
userInput: '', // 用户输入的文本或视频链接
|
||||
amplitude: 50 // 幅度,默认50%
|
||||
})
|
||||
|
||||
// 生成的文案内容
|
||||
const generatedContent = ref('')
|
||||
|
||||
// 编辑模式相关
|
||||
const isEditMode = ref(false)
|
||||
const editableContent = ref('')
|
||||
const originalContent = ref('')
|
||||
|
||||
// 加载状态
|
||||
const isLoading = ref(false)
|
||||
const { getVoiceText } = useVoiceText()
|
||||
|
||||
// 页面加载时,如果有提示词则自动填充
|
||||
onMounted(() => {
|
||||
if (promptStore.currentPrompt) {
|
||||
form.value.prompt = promptStore.currentPrompt
|
||||
}
|
||||
})
|
||||
|
||||
// 生成文案(流式)
|
||||
async function generateCopywriting() {
|
||||
const inputContent = form.value.userInput || ''
|
||||
|
||||
if (!inputContent.trim()) {
|
||||
message.warning('请输入内容')
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
generatedContent.value = '' // 清空之前的内容
|
||||
|
||||
try {
|
||||
// 如果看起来是视频/音频链接,先尝试转写;否则直接作为文本
|
||||
let userText = inputContent
|
||||
if (isLikelyUrl(inputContent)) {
|
||||
try {
|
||||
message.info('正在获取视频转写...')
|
||||
const transcriptions = await getVoiceText([{ audio_url: inputContent }])
|
||||
const transcript = Array.isArray(transcriptions) && transcriptions[0] ? (transcriptions[0].value || '') : ''
|
||||
if (transcript.trim()) {
|
||||
userText = transcript
|
||||
} else {
|
||||
message.warning('未从链接获取到可用的语音文本,将直接使用原始输入')
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取转写失败,使用原始输入:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 callWorkflow 流式 API
|
||||
const requestData = {
|
||||
audio_prompt: form.value.prompt || '', // 音频提示词
|
||||
user_text: userText, // 用户输入内容或由链接转写得到的文本
|
||||
amplitude: form.value.amplitude // 幅度,范围 0-100
|
||||
}
|
||||
|
||||
const ctrl = new AbortController()
|
||||
let fullText = ''
|
||||
let errorOccurred = false
|
||||
let isResolved = false
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
// 设置超时
|
||||
const timeout = setTimeout(() => {
|
||||
if (!isResolved) {
|
||||
ctrl.abort()
|
||||
reject(new Error('请求超时,请稍后重试'))
|
||||
}
|
||||
}, 180000) // 3分钟超时
|
||||
|
||||
CommonService.callWorkflowStream({
|
||||
data: requestData,
|
||||
ctrl,
|
||||
onMessage: (event) => {
|
||||
try {
|
||||
if (errorOccurred) return
|
||||
|
||||
const dataStr = event?.data || ''
|
||||
if (!dataStr) return
|
||||
|
||||
try {
|
||||
const obj = JSON.parse(dataStr)
|
||||
// 根据实际返回格式解析
|
||||
const piece = obj?.text || obj?.content || obj?.data || ''
|
||||
if (piece) {
|
||||
fullText += piece
|
||||
generatedContent.value = fullText
|
||||
}
|
||||
} catch (parseErr) {
|
||||
console.warn('解析流数据异常:', parseErr)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('解析流数据异常:', e)
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
clearTimeout(timeout)
|
||||
if (!isResolved) {
|
||||
errorOccurred = true
|
||||
ctrl.abort()
|
||||
const errorMsg = err?.message || '网络请求失败'
|
||||
console.error('SSE请求错误:', err)
|
||||
message.error(errorMsg)
|
||||
reject(new Error(errorMsg))
|
||||
}
|
||||
},
|
||||
onClose: () => {
|
||||
clearTimeout(timeout)
|
||||
if (!isResolved) {
|
||||
isResolved = true
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
generatedContent.value = fullText.trim()
|
||||
message.success('文案生成成功')
|
||||
} catch (error) {
|
||||
console.error('生成文案失败:', error)
|
||||
message.error('生成文案失败,请重试')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 获取当前输入值
|
||||
function getCurrentInputValue() {
|
||||
return (form.value.userInput || '').trim()
|
||||
}
|
||||
|
||||
// 粗略判断是否为 URL(含常见平台域名或 http(s) 开头)
|
||||
function isLikelyUrl(value) {
|
||||
if (!value) return false
|
||||
const v = String(value).trim()
|
||||
if (/^https?:\/\//i.test(v)) return true
|
||||
return /(douyin\.com|bilibili\.com|youtube\.com|youtu\.be|tiktok\.com|ixigua\.com|v\.qq\.com)/i.test(v)
|
||||
}
|
||||
|
||||
// 切换编辑模式
|
||||
function toggleEditMode() {
|
||||
if (!isEditMode.value) {
|
||||
// 进入编辑模式
|
||||
originalContent.value = generatedContent.value
|
||||
editableContent.value = generatedContent.value
|
||||
}
|
||||
isEditMode.value = !isEditMode.value
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
function saveEdit() {
|
||||
generatedContent.value = editableContent.value
|
||||
isEditMode.value = false
|
||||
message.success('文案已保存')
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
function cancelEdit() {
|
||||
editableContent.value = originalContent.value
|
||||
isEditMode.value = false
|
||||
message.info('已取消编辑')
|
||||
}
|
||||
|
||||
// 复制内容(编辑模式复制编辑区,否则复制生成内容),带降级方案
|
||||
function copyContent() {
|
||||
const text = isEditMode.value ? (editableContent.value || '') : (generatedContent.value || '')
|
||||
if (!text.trim()) {
|
||||
message.warning('没有可复制的内容')
|
||||
return
|
||||
}
|
||||
|
||||
// 优先使用异步 Clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('文案已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
// 降级到选中复制
|
||||
fallbackCopy(text)
|
||||
})
|
||||
return
|
||||
}
|
||||
// 直接降级
|
||||
fallbackCopy(text)
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
try {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
textarea.style.left = '-9999px'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
const ok = document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
if (ok) {
|
||||
message.success('文案已复制到剪贴板')
|
||||
} else {
|
||||
message.error('复制失败,请手动复制')
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('fallback copy failed:', e)
|
||||
message.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
|
||||
defineOptions({ name: 'ContentStyleCopywriting' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="copywriting-page">
|
||||
<!-- 页面标题区域 -->
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="10" :md="24">
|
||||
<div class="form-section">
|
||||
<a-card class="form-card" :bordered="false" title="创作设置">
|
||||
<a-form :model="form" layout="vertical" class="form-container">
|
||||
<a-form-item class="form-item">
|
||||
<template #label>
|
||||
基础提示词
|
||||
<span class="form-tip-inline">从视频分析提取的提示词,将作为文案生成的基础参考</span>
|
||||
</template>
|
||||
<a-textarea
|
||||
v-model:value="form.prompt"
|
||||
placeholder="这里显示从视频分析中提取的创作提示词,将作为文案生成的基础参考"
|
||||
:auto-size="{ minRows: 6, maxRows: 12 }"
|
||||
class="custom-textarea"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 统一输入:文本或视频链接 -->
|
||||
<a-form-item class="form-item">
|
||||
<template #label>
|
||||
输入内容/视频链接
|
||||
<span class="form-tip-inline">输入要生成的主题/段落,或粘贴视频链接以自动转写</span>
|
||||
</template>
|
||||
<a-textarea
|
||||
v-model:value="form.userInput"
|
||||
placeholder="直接输入文字,或粘贴视频链接(抖音、B站、YouTube等)"
|
||||
:auto-size="{ minRows: 6, maxRows: 12 }"
|
||||
class="custom-textarea"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 幅度设置 -->
|
||||
<a-form-item class="form-item">
|
||||
<template #label>
|
||||
创作幅度
|
||||
<span class="form-tip-inline">调整创作幅度,数值越大创意性越强</span>
|
||||
</template>
|
||||
<div class="amplitude-row">
|
||||
<a-slider
|
||||
v-model:value="form.amplitude"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:tooltip-formatter="(value) => `${value}%`"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<a-input-number
|
||||
v-model:value="form.amplitude"
|
||||
:min="0"
|
||||
:max="100"
|
||||
style="width: 96px; margin-left: 12px;"
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="form-item">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="generateCopywriting"
|
||||
:disabled="!getCurrentInputValue() || isLoading"
|
||||
:loading="isLoading"
|
||||
block
|
||||
size="large"
|
||||
class="generate-btn"
|
||||
>
|
||||
<template v-if="!isLoading">
|
||||
<span class="btn-icon"><GmIcon name="icon-sparkle" :size="16" /></span>
|
||||
生成文案
|
||||
</template>
|
||||
<template v-else>
|
||||
生成中...
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="14" :md="24">
|
||||
<div class="result-section">
|
||||
|
||||
|
||||
<a-card class="result-card" :bordered="false" title="生成结果">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button v-if="generatedContent" @click="toggleEditMode" size="small" class="action-btn">
|
||||
<span class="btn-icon"><GmIcon :name="isEditMode ? 'icon-eye' : 'icon-edit'" :size="14" /></span>
|
||||
{{ isEditMode ? '预览' : '编辑' }}
|
||||
</a-button>
|
||||
<a-button v-if="generatedContent" @click="copyContent" size="small" class="action-btn copy-btn">
|
||||
<span class="btn-icon"><GmIcon name="icon-copy" :size="14" /></span>
|
||||
复制
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<div v-if="generatedContent" class="result-content">
|
||||
<!-- 编辑模式 -->
|
||||
<div v-if="isEditMode" class="edit-mode">
|
||||
<a-textarea
|
||||
v-model:value="editableContent"
|
||||
:auto-size="{ minRows: 15, maxRows: 30 }"
|
||||
placeholder="在这里编辑生成的文案内容..."
|
||||
class="edit-textarea"
|
||||
/>
|
||||
<div class="edit-actions">
|
||||
<a-space>
|
||||
<a-button @click="saveEdit" type="primary" size="small" class="save-btn">
|
||||
<span class="btn-icon"><GmIcon name="icon-save" :size="14" /></span>
|
||||
保存
|
||||
</a-button>
|
||||
<a-button @click="cancelEdit" size="small" class="cancel-btn">
|
||||
<span class="btn-icon"><GmIcon name="icon-cancel" :size="14" /></span>
|
||||
取消
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览模式 -->
|
||||
<div v-else class="generated-content" v-html="md.render(generatedContent)"></div>
|
||||
</div>
|
||||
|
||||
<a-empty v-else class="custom-empty">
|
||||
<template #description>
|
||||
<div class="empty-description">
|
||||
<div class="empty-icon"><GmIcon name="icon-empty-doc" :size="36" /></div>
|
||||
<div class="empty-title">等待生成文案</div>
|
||||
<div class="empty-desc">请选择内容类型并填写相关信息,然后点击"生成文案"按钮</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-empty>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 页面整体样式 */
|
||||
.copywriting-page {
|
||||
background: var(--color-bg);
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 页面标题区域 */
|
||||
.page-header {
|
||||
padding: 20px 0 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin: 0 0 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 1rem;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 区域标题 */
|
||||
.section-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
font-size: 0.9rem;
|
||||
color: #9ca3af;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.form-card,
|
||||
.result-card {
|
||||
background: var(--color-surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-inset-card);
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-card:hover,
|
||||
.result-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-card :deep(.ant-card-head),
|
||||
.result-card :deep(.ant-card-head) {
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.form-card :deep(.ant-card-head-title),
|
||||
.result-card :deep(.ant-card-head-title) {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.form-card :deep(.ant-card-body),
|
||||
.result-card :deep(.ant-card-body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-item :deep(.ant-form-item-label) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-item :deep(.ant-form-item-label > label) {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表单标签后的内联提示(不使用 emoji) */
|
||||
.form-tip-inline {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 自定义输入框样式 */
|
||||
.custom-input,
|
||||
.custom-textarea {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.3s ease;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.custom-input:focus,
|
||||
.custom-textarea:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px var(--color-primary-glow);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.custom-input:hover,
|
||||
.custom-textarea:hover {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.custom-textarea::placeholder {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 已合并输入:移除单选组相关样式 */
|
||||
|
||||
/* 输入区域动画 */
|
||||
.input-section {
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 旧的块级提示兼容(如他处已有复用时可沿用) */
|
||||
.form-tip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 生成按钮样式 */
|
||||
.generate-btn {
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
background: var(--color-primary);
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
background: var(--color-primary);
|
||||
box-shadow: var(--glow-primary);
|
||||
}
|
||||
|
||||
.generate-btn:active {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 按钮禁用态保持主色,不换颜色(仅本页) */
|
||||
:deep(.ant-btn-primary[disabled]),
|
||||
:deep(.ant-btn-primary[disabled]:hover),
|
||||
:deep(.ant-btn-primary[disabled]:active),
|
||||
:deep(.ant-btn-primary.ant-btn-disabled),
|
||||
:deep(.ant-btn-primary.ant-btn-disabled:hover),
|
||||
:deep(.ant-btn-primary.ant-btn-disabled:active) {
|
||||
background: var(--color-primary) !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
color: #fff !important;
|
||||
opacity: 0.6; /* 仅降低不透明度,不改变颜色 */
|
||||
cursor: not-allowed;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-btn {
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: rgba(59, 130, 246, 0.12);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 幅度滑块样式 */
|
||||
.amplitude-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 暗色下滑块可见性增强 */
|
||||
:deep(.ant-slider) {
|
||||
padding: 10px 0; /* 增加垂直留白,减少拥挤感 */
|
||||
}
|
||||
|
||||
:deep(.ant-slider-rail) {
|
||||
background-color: #252525; /* 未选中轨道更深,增强对比 */
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-slider-track) {
|
||||
background-color: var(--color-primary);
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-slider:hover .ant-slider-track) {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
:deep(.ant-slider-handle::after) {
|
||||
box-shadow: 0 0 0 2px var(--color-primary);
|
||||
}
|
||||
|
||||
:deep(.ant-slider-handle:focus-visible::after),
|
||||
:deep(.ant-slider-handle:hover::after),
|
||||
:deep(.ant-slider-handle:active::after) {
|
||||
box-shadow: 0 0 0 3px var(--color-primary-glow);
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.custom-empty {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-description {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 编辑模式样式 */
|
||||
.edit-mode {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.edit-textarea {
|
||||
margin-bottom: 12px;
|
||||
border-radius: 6px;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-border);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.edit-textarea:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px var(--color-primary-glow);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.edit-textarea:hover {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.edit-textarea::placeholder {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.edit-actions {
|
||||
text-align: right;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
background: var(--color-primary);
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.save-btn:hover {
|
||||
background: var(--color-primary);
|
||||
filter: brightness(1.04);
|
||||
box-shadow: var(--glow-primary);
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 生成内容样式 */
|
||||
.result-content {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.generated-content {
|
||||
padding: 24px;
|
||||
background: #111111;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
line-height: 1.9;
|
||||
color: #f5f5f5;
|
||||
min-height: 400px;
|
||||
font-size: 15.5px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.generated-content :deep(h1) {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: #ffffff;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.generated-content :deep(h2) {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
margin: 22px 0 12px 0;
|
||||
color: #fff;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.generated-content :deep(h3) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 18px 0 10px 0;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.generated-content :deep(p) {
|
||||
margin: 12px 0 14px 0;
|
||||
color: #e3e6ea;
|
||||
line-height: 1.9;
|
||||
font-size: 15.5px;
|
||||
}
|
||||
|
||||
.generated-content :deep(ul),
|
||||
.generated-content :deep(ol) {
|
||||
margin: 12px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.generated-content :deep(li) {
|
||||
margin: 6px 0;
|
||||
color: #e3e6ea;
|
||||
line-height: 1.9;
|
||||
font-size: 15.5px;
|
||||
}
|
||||
|
||||
.generated-content :deep(strong) {
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.generated-content :deep(code) {
|
||||
background: #0b0b0b;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13.5px;
|
||||
color: #ffb86c;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.generated-content :deep(pre) {
|
||||
background: #0b0b0b;
|
||||
padding: 16px 18px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 12px 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.generated-content :deep(pre code) {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: none;
|
||||
color: #ffb86c;
|
||||
}
|
||||
|
||||
.generated-content :deep(blockquote) {
|
||||
margin: 16px 0;
|
||||
padding: 12px 16px;
|
||||
background: rgba(22, 119, 255, 0.12);
|
||||
border-left: 4px solid var(--color-primary);
|
||||
border-radius: 0 6px 6px 0;
|
||||
font-style: italic;
|
||||
color: #e3e6ea;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
padding: 16px 0 24px 0;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.5rem;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 0 16px 24px 16px;
|
||||
}
|
||||
|
||||
.form-card,
|
||||
.result-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-card :deep(.ant-card-body),
|
||||
.result-card :deep(.ant-card-body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.radio-icon {
|
||||
font-size: 1rem;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
height: 36px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.generated-content {
|
||||
padding: 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user