first commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "词云生成",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
.wordcloud-page {
|
||||
min-height: 100vh;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.page-header {
|
||||
padding: 50px 32px 24px;
|
||||
margin: 20px 0 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 40px;
|
||||
color: var(--text-primary);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 步骤指示器 */
|
||||
.step-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-input);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
border: 4px dashed var(--bg-input);
|
||||
}
|
||||
|
||||
.step-circle.active {
|
||||
background: var(--btn-gradient);
|
||||
border-color: var(--accent-pink);
|
||||
}
|
||||
|
||||
.step-num {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.step-circle.active .step-num {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.step-label.active {
|
||||
color: var(--accent-pink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 60%;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--bg-input);
|
||||
}
|
||||
|
||||
.step-line.active {
|
||||
background: var(--btn-gradient);
|
||||
}
|
||||
|
||||
/* 步骤内容 */
|
||||
.step-content {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
/* 上传区域 */
|
||||
.upload-area {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.upload-desc {
|
||||
font-size: 26px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 22px;
|
||||
color: var(--accent-blue);
|
||||
background: rgba(91, 140, 255, 0.1);
|
||||
padding: 8px 20px;
|
||||
border-radius: 50px;
|
||||
border: var(--line-card);
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.action-btns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-section {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.names-input {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
background: var(--bg-input);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
font-size: 28px;
|
||||
line-height: 1.6;
|
||||
box-sizing: border-box;
|
||||
border: var(--line-card);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.input-stats {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.input-tips {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 生成中 */
|
||||
.generating-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80px 40px;
|
||||
}
|
||||
|
||||
.generating-icon {
|
||||
font-size: 120px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.2); opacity: 0.7; }
|
||||
}
|
||||
|
||||
.generating-title {
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.generating-subtitle {
|
||||
font-size: 28px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 16px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 80%;
|
||||
height: 16px;
|
||||
background: rgba(255, 154, 158, 0.15);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--btn-gradient);
|
||||
border-radius: 8px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-pink);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 结果面板 */
|
||||
.result-panel {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.result-image-wrapper {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.result-image {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.08) 0%, rgba(160, 196, 255, 0.08) 100%);
|
||||
}
|
||||
|
||||
.watermark-badge {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
color: var(--text-primary);
|
||||
padding: 8px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 22px;
|
||||
border: var(--line-card);
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 跳转DIY卡片 */
|
||||
.diy-jump-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.diy-jump-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.diy-jump-desc {
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.diy-jump-arrow {
|
||||
font-size: 40px;
|
||||
color: var(--accent-pink);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { View, Text, Image, Button, Textarea, Input } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import './index.scss'
|
||||
import ThemeToggle from '../../components/ThemeToggle'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
|
||||
export default function WordCloudPage() {
|
||||
const { theme } = useThemeContext()
|
||||
const [step, setStep] = useState(1)
|
||||
const [baseImage, setBaseImage] = useState('')
|
||||
const [namesText, setNamesText] = useState('')
|
||||
const [generatedImage, setGeneratedImage] = useState('')
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [progress, setProgress] = useState(0)
|
||||
|
||||
const steps = [
|
||||
{ num: 1, label: '上传底图' },
|
||||
{ num: 2, label: '输入名单' },
|
||||
{ num: 3, label: '生成预览' }
|
||||
]
|
||||
|
||||
const chooseImage = () => {
|
||||
Taro.chooseImage({
|
||||
count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'],
|
||||
success: (res) => { setBaseImage(res.tempFilePaths[0]); setStep(2) }
|
||||
})
|
||||
}
|
||||
|
||||
const handleGenerate = () => {
|
||||
if (!namesText.trim()) {
|
||||
Taro.showToast({ title: '请先输入名字', icon: 'none' })
|
||||
return
|
||||
}
|
||||
setStep(3); setIsGenerating(true); setProgress(0)
|
||||
const timer = setInterval(() => {
|
||||
setProgress((prev) => {
|
||||
if (prev >= 100) {
|
||||
clearInterval(timer); setIsGenerating(false)
|
||||
setGeneratedImage(baseImage)
|
||||
return 100
|
||||
}
|
||||
return prev + Math.random() * 15
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handleExportImage = () => {
|
||||
if (!generatedImage) return
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
filePath: generatedImage,
|
||||
success: () => Taro.showToast({ title: '已保存到相册', icon: 'success' }),
|
||||
fail: () => Taro.showToast({ title: '保存失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
const goBack = () => { if (step > 1) setStep(step - 1); else Taro.navigateBack() }
|
||||
|
||||
return (
|
||||
<View className={`theme-${theme}`}>
|
||||
<View className='wordcloud-page'>
|
||||
<ThemeToggle />
|
||||
|
||||
<View className='page-header flex-between'>
|
||||
<Text className='back-btn' onClick={goBack}>←</Text>
|
||||
<Text className='page-title'>AI词云生成</Text>
|
||||
<View style={{ width: '60px' }} />
|
||||
</View>
|
||||
|
||||
{/* 步骤指示器 */}
|
||||
<View className='step-indicator dashed-card'>
|
||||
{steps.map((s, idx) => (
|
||||
<View key={s.num} className='step-item'>
|
||||
<View className={`step-circle ${step >= s.num ? 'active' : ''}`}>
|
||||
<Text className='step-num'>{s.num}</Text>
|
||||
</View>
|
||||
<Text className={`step-label ${step >= s.num ? 'active' : ''}`}>{s.label}</Text>
|
||||
{idx < steps.length - 1 && <View className={`step-line ${step > s.num ? 'active' : ''}`} />}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 步骤1: 上传底图 */}
|
||||
{step === 1 && (
|
||||
<View className='step-content'>
|
||||
<View className='upload-area dashed-card' onClick={chooseImage}>
|
||||
{baseImage ? (
|
||||
<Image className='preview-image' src={baseImage} mode='aspectFit' />
|
||||
) : (
|
||||
<View className='upload-placeholder'>
|
||||
<Text className='upload-icon'>📷</Text>
|
||||
<Text className='upload-title'>点击上传底图</Text>
|
||||
<Text className='upload-desc'>支持照片、Logo、图案等</Text>
|
||||
<Text className='upload-tip'>系统将用名字填充到深色区域</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{baseImage && (
|
||||
<View className='action-btns'>
|
||||
<View className='btn-gradient' onClick={() => setStep(2)}>
|
||||
<Text>下一步:输入名单</Text>
|
||||
</View>
|
||||
<View className='btn-outline' onClick={() => setBaseImage('')}>
|
||||
<Text>重新选择</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 步骤2: 输入名单 */}
|
||||
{step === 2 && (
|
||||
<View className='step-content'>
|
||||
<View className='input-section dashed-card'>
|
||||
<Text className='section-title'>输入名字名单</Text>
|
||||
<Textarea
|
||||
className='names-input'
|
||||
placeholder={`例如:\n张三\n李四\n王五`}
|
||||
value={namesText}
|
||||
onInput={(e) => setNamesText(e.detail.value)}
|
||||
maxlength={5000}
|
||||
/>
|
||||
<Text className='input-stats'>
|
||||
已输入 {namesText.split(/\n|,|,/).filter(n => n.trim()).length} 个名字
|
||||
</Text>
|
||||
<View className='input-tips'>
|
||||
<Text className='tip-item'>名字会随机分布在底图的深色区域</Text>
|
||||
<Text className='tip-item'>建议输入10-200个名字效果最佳</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='action-btns'>
|
||||
<View className='btn-gradient' onClick={handleGenerate}>
|
||||
<Text>开始生成词云</Text>
|
||||
</View>
|
||||
<View className='btn-outline' onClick={() => setStep(1)}>
|
||||
<Text>上一步</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 步骤3: 生成中/预览 */}
|
||||
{step === 3 && (
|
||||
<View className='step-content'>
|
||||
{isGenerating ? (
|
||||
<View className='generating-panel'>
|
||||
<Text className='generating-icon'>✨</Text>
|
||||
<Text className='generating-title'>正在生成词云...</Text>
|
||||
<Text className='generating-subtitle'>AI正在将名字融入图案</Text>
|
||||
<View className='progress-bar'>
|
||||
<View className='progress-fill' style={{ width: `${Math.min(progress, 100)}%` }} />
|
||||
</View>
|
||||
<Text className='progress-text'>{Math.min(Math.round(progress), 100)}%</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='result-panel'>
|
||||
<View className='result-image-wrapper dashed-card'>
|
||||
<Image className='result-image' src={generatedImage || ''} mode='aspectFit' />
|
||||
<View className='watermark-badge'>
|
||||
<Text>智绘微刻预览</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='result-actions'>
|
||||
<View className='action-btn' onClick={handleExportImage}>
|
||||
<Text className='action-icon'>💾</Text>
|
||||
<Text className='action-label'>保存图片</Text>
|
||||
</View>
|
||||
<View className='action-btn' onClick={handleGenerate}>
|
||||
<Text className='action-icon'>🔄</Text>
|
||||
<Text className='action-label'>重新生成</Text>
|
||||
</View>
|
||||
<View className='action-btn' onClick={() => setStep(2)}>
|
||||
<Text className='action-icon'>✏️</Text>
|
||||
<Text className='action-label'>修改名单</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user