一、项目概述
本项目是一个小红书文案生成器,用户可以输入写作主题、文章长度和背景信息,系统会生成相应的文案和图片。项目使用Vue 3和Element Plus进行前端开发,通过axios与后端API进行通信,后端API开发以及代码详见前面系列文章。
二、开发环境搭建
1. 安装Node.js和npm
Node.js是一个基于Chrome V8引擎的JavaScript运行环境,npm是Node.js的包管理工具。
- 步骤:
- 访问Node.js官方网站,下载适合你操作系统的安装包。
- 按照安装向导的提示完成安装。安装完成后,打开终端,输入以下命令验证安装是否成功:
node -v
npm -v
2. 创建Vue项目
使用Vue CLI创建一个新的Vue项目。
- 步骤:
- 全局安装Vue CLI:
npm install -g @vue/cli
- 创建新项目:
vue create redbook-content-generator
- 在创建过程中,你可以选择手动配置项目,选择需要的特性,如Babel、ESLint等。
3. 安装项目依赖
进入项目目录,安装Element Plus和axios等依赖。
- 步骤:
cd redbook-content-generator
npm install element-plus axios vue-clipboard3
4. 配置Element Plus
在main.js
中引入并使用Element Plus。
- 代码示例(
src/main.js
):
// 导入Vue核心功能和根组件
import { createApp } from 'vue'
// 导入Element Plus组件库及其样式
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 导入根组件
import App from './App.vue'
// 创建Vue应用实例
const app = createApp(App)
// 注册Element Plus组件库
app.use(ElementPlus)
// 将应用挂载到DOM元素上
app.mount('#app')
三、项目开发
1. 编写组件模板
在App.vue
中编写组件模板,包括表单输入、结果展示等部分。
- 代码示例(
src/App.vue
部分核心代码):
<template>
<div class="container">
<el-card class="input-card">
<template #header>
<div class="card-header">
<h2>小红书文案生成器</h2>
</div>
</template>
<el-form :model="form" label-position="top">
<el-form-item label="写作主题">
<el-input v-model="form.subject" placeholder="请输入写作主题" type="textarea" :rows="2" />
</el-form-item>
<!-- 其他表单输入项 -->
<el-form-item>
<el-button type="primary" :loading="loading" @click="generateContent">生成内容</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-if="workflowResult" class="result-card">
<template #header>
<div class="card-header">
<h3>生成结果</h3>
<div class="status-tag">
<el-tag :type="statusType">{{ statusText }}</el-tag>
</div>
</div>
</template>
<!-- 文章内容 -->
<div v-if="workflowResult.content" class="content-section">
<h4>文章内容 <el-button type="primary" link @click="copyContent(workflowResult.content)">复制</el-button></h4>
<div class="content-text" v-html="formattedContent"></div>
</div>
<!-- 封面文案、图片展示等部分 -->
</el-card>
</div>
</template>
2. 编写脚本逻辑
在<script setup>
标签中编写脚本逻辑,包括数据定义、计算属性和方法。
- 代码示例(
src/App.vue
部分核心代码):
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Picture } from '@element-plus/icons-vue'
import axios from 'axios'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const form = ref({
subject: '',
length: '',
context: ''
})
const loading = ref(false)
const workflowResult = ref(null)
const workflowId = ref(null)
const pollingInterval = ref(null)
const statusType = computed(() => {
if (!workflowResult.value) return ''
const status = workflowResult.value.status
switch (status) {
case 'started':
case 'processing':
return 'warning'
case 'completed':
return 'success'
case 'error':
return 'danger'
default:
return 'info'
}
})
const statusText = computed(() => {
if (!workflowResult.value) return ''
const status = workflowResult.value.status
switch (status) {
case 'started':
return '已开始'
case 'processing':
return '处理中'
case 'completed':
return '已完成'
case 'error':
return '出错'
default:
return status
}
})
const formattedContent = computed(() => {
if (!workflowResult.value?.content) return ''
return workflowResult.value.content.replace(/\n/g, '<br>')
})
// 封装带重试机制的axios请求函数
const axiosWithRetry = async (config, retries = 3) => {
try {
return await axios(config)
} catch (error) {
if (retries > 0 && (error.code === 'ECONNABORTED' || !error.response)) {
await new Promise(resolve => setTimeout(resolve, 1000))
return axiosWithRetry(config, retries - 1)
}
throw error
}
}
// 生成内容的主要函数
const generateContent = async () => {
if (!form.value.subject) {
ElMessage.warning('请输入写作主题')
return
}
try {
loading.value = true
const response = await axiosWithRetry({
method: 'POST',
url: 'http://localhost:8001/workflow/start',
data: form.value,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
workflowResult.value = response.data
workflowId.value = response.data.workflow_id
startPolling()
} catch (error) {
let errorMessage = '启动工作流失败'
if (error.code === 'ECONNABORTED') {
errorMessage = '请求超时,请检查网络连接'
} else if (!error.response) {
errorMessage = '网络连接失败,请检查网络设置'
} else {
errorMessage += ':' + (error.response?.data?.detail || error.message)
}
ElMessage.error(errorMessage)
} finally {
loading.value = false
}
}
// 开始轮询工作流状态的函数
const startPolling = () => {
if (pollingInterval.value) {
clearInterval(pollingInterval.value)
}
let failedAttempts = 0
const MAX_POLLING_FAILURES = 3
pollingInterval.value = setInterval(async () => {
try {
const response = await axiosWithRetry({
method: 'GET',
url: `http://localhost:8001/workflow/${workflowId.value}`,
timeout: 5000,
})
workflowResult.value = response.data
failedAttempts = 0
if (['completed', 'error'].includes(response.data.status)) {
clearInterval(pollingInterval.value)
}
} catch (error) {
failedAttempts++
console.error('轮询工作流状态失败:', error)
if (failedAttempts >= MAX_POLLING_FAILURES) {
clearInterval(pollingInterval.value)
ElMessage.error('网络连接不稳定,请刷新页面重试')
workflowResult.value = {
status: 'error',
error: '网络连接中断,请刷新页面重试'
}
}
}
}, 2000)
}
// 复制文章内容到剪贴板
const copyContent = async (content) => {
try {
await toClipboard(content)
ElMessage.success('复制成功')
} catch (error) {
ElMessage.error('复制失败')
}
}
// 复制封面文案
const copyCoverText = async () => {
if (!workflowResult.value?.cover_text) return
const coverText = [
`主标题:${workflowResult.value.cover_text.main_title}`,
`副标题:${workflowResult.value.cover_text.subtitle}`,
`点缀文案:${workflowResult.value.cover_text.decorative_text}`,
`标签:${workflowResult.value.cover_text.tags.join(' ')}`
].join('\n')
try {
await toClipboard(coverText)
ElMessage.success('复制成功')
} catch (error) {
ElMessage.error('复制失败')
}
}
// 下载图片
const downloadImage = async (url) => {
try {
const response = await axios({
url,
method: 'GET',
responseType: 'blob'
})
const blob = new Blob([response.data])
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = `image_${Date.now()}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
ElMessage.success('下载成功')
} catch (error) {
ElMessage.error('下载失败')
}
}
</script>
四、项目部署
1. 构建项目
使用Vue CLI提供的命令将项目打包构建。
- 步骤:
npm run build
执行该命令后,会在项目根目录下生成一个dist
文件夹,里面包含了打包后的静态文件。
2. 部署到服务器
将dist
文件夹中的文件部署到服务器上。
- 步骤:
- 将
dist
文件夹上传到服务器,可以使用FTP工具或Git等。 - 配置服务器的Web服务器(如Nginx或Apache),将域名或IP指向
dist
文件夹。
- 将
Nginx配置示例
server {
listen 80;
server_name your_domain_or_ip;
root /path/to/your/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Apache配置示例
在Apache的配置文件中添加以下内容:
<VirtualHost *:80>
ServerName your_domain_or_ip
DocumentRoot /path/to/your/dist
<Directory /path/to/your/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
3. 启动服务器
启动Nginx或Apache服务器,使项目可以通过域名或IP访问。
- Nginx启动命令:
sudo systemctl start nginx
- Apache启动命令:
sudo systemctl start apache2
五、总结
通过以上步骤,你可以完成小红书文案生成器项目的开发和部署。在开发过程中,要注意代码的模块化和可维护性;在部署过程中,要确保服务器的配置正确,以保证项目的正常运行。