diff --git a/.gitignore b/.gitignore index df8964e0..fca0d36b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ Store_vue/node_modules/ Cunkebao/.specstory/ *.cursorindexingignore Server/.specstory/ +Server/thinkphp/ Store_vue/.specstory/ Store_vue/unpackage/ Store_vue/.vscode/ SuperAdmin/.specstory/ Cunkebao/dist Touchkebao/.specstory/ +Serverruntime/ diff --git a/Cunkebao/src/pages/mobile/mine/content/materials/list/api.ts b/Cunkebao/src/pages/mobile/mine/content/materials/list/api.ts index 6037391f..e1d6edea 100644 --- a/Cunkebao/src/pages/mobile/mine/content/materials/list/api.ts +++ b/Cunkebao/src/pages/mobile/mine/content/materials/list/api.ts @@ -3,6 +3,8 @@ import { GetContentItemListParams, CreateContentItemParams, UpdateContentItemParams, + AIRewriteParams, + ReplaceContentParams, } from "./data"; // 获取素材列表 @@ -35,3 +37,13 @@ export function deleteContentItem(id: string) { export function getContentLibraryDetail(id: string) { return request("/v1/content/library/detail", { id }, "GET"); } + +// AI改写内容 +export function aiRewriteContent(params: AIRewriteParams) { + return request("/v1/content/library/aiEditContent", params, "GET"); +} + +// 替换原内容 +export function replaceContent(params: ReplaceContentParams) { + return request("/v1/content/library/aiEditContent", params, "POST"); +} diff --git a/Cunkebao/src/pages/mobile/mine/content/materials/list/data.ts b/Cunkebao/src/pages/mobile/mine/content/materials/list/data.ts index b2bc51ab..3be3872f 100644 --- a/Cunkebao/src/pages/mobile/mine/content/materials/list/data.ts +++ b/Cunkebao/src/pages/mobile/mine/content/materials/list/data.ts @@ -26,6 +26,7 @@ export interface ContentItem { delTime: number; wechatChatroomId?: string | null; senderNickname: string; + senderAvatar?: string | null; createMessageTime?: string | null; comment: string; sendTime: number; @@ -104,3 +105,15 @@ export interface UpdateContentItemParams extends Partial { id: string; } + +// AI改写参数 +export interface AIRewriteParams { + id: string; + aiPrompt: string; +} + +// 替换内容参数 +export interface ReplaceContentParams { + id: string; + content: string; +} diff --git a/Cunkebao/src/pages/mobile/mine/content/materials/list/index.module.scss b/Cunkebao/src/pages/mobile/mine/content/materials/list/index.module.scss index 666b75ad..29c91883 100644 --- a/Cunkebao/src/pages/mobile/mine/content/materials/list/index.module.scss +++ b/Cunkebao/src/pages/mobile/mine/content/materials/list/index.module.scss @@ -131,6 +131,25 @@ display: flex; align-items: center; justify-content: center; + overflow: hidden; + position: relative; +} + +.avatar-img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.avatar-icon-wrapper { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + left: 0; } .avatar-icon { @@ -613,3 +632,147 @@ line-height: 1.6; } } + +// AI改写弹框样式 +.ai-popup-content { + padding: 20px; + max-height: 100vh; + overflow-y: auto; + background: #f9fbfd; + + .ai-popup-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + padding-bottom: 12px; + border-bottom: 1px solid #e8f4ff; + + h3 { + font-size: 18px; + font-weight: 600; + color: #1677ff; + margin: 0; + display: flex; + align-items: center; + + &::before { + content: ''; + display: inline-block; + width: 4px; + height: 18px; + background: #1677ff; + margin-right: 8px; + border-radius: 2px; + } + } + } + + .ai-form { + .ai-form-item { + margin-bottom: 20px; + + .ai-form-label { + font-size: 15px; + font-weight: 500; + color: #333; + margin-bottom: 6px; + display: flex; + align-items: center; + + &::before { + content: ''; + display: inline-block; + width: 3px; + height: 14px; + background: #1677ff; + margin-right: 6px; + border-radius: 2px; + } + } + + .ai-result-description { + font-size: 12px; + color: #999; + margin-bottom: 10px; + } + } + + .ai-submit { + margin: 24px 0; + + button { + height: 44px; + font-size: 16px; + border-radius: 8px; + box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2); + } + } + + .ai-result-box { + background: #ffffff; + border: 1px solid #e0f0ff; + border-radius: 8px; + padding: 16px; + min-height: 100px; + max-height: 200px; + overflow-y: auto; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + + .ai-loading { + display: flex; + justify-content: center; + align-items: center; + height: 120px; + } + + .ai-result-content { + font-size: 15px; + line-height: 1.8; + color: #333; + white-space: pre-wrap; + padding: 4px; + } + + .ai-result-placeholder { + color: #999; + text-align: center; + padding: 30px 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .placeholder-icon { + font-size: 28px; + margin-bottom: 10px; + } + + .placeholder-text { + font-size: 14px; + color: #999; + } + } + } + + .ai-replace-action { + margin-top: 20px; + + button { + height: 44px; + font-size: 16px; + border-radius: 8px; + background: #1677ff; + border-color: #1677ff; + box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2); + color: #ffffff; + + &:hover, &:focus { + background: #4096ff; + border-color: #4096ff; + color: #ffffff; + } + } + } + } +} diff --git a/Cunkebao/src/pages/mobile/mine/content/materials/list/index.tsx b/Cunkebao/src/pages/mobile/mine/content/materials/list/index.tsx index db963f0b..aef8c2a8 100644 --- a/Cunkebao/src/pages/mobile/mine/content/materials/list/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/content/materials/list/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useCallback } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { Toast, SpinLoading, Dialog, Card } from "antd-mobile"; -import { Input, Pagination, Button } from "antd"; +import { Toast, SpinLoading, Dialog, Card, Popup, TextArea } from "antd-mobile"; +import { Input, Pagination, Button, Spin } from "antd"; import { PlusOutlined, SearchOutlined, @@ -18,7 +18,7 @@ import { } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import NavCommon from "@/components/NavCommon"; -import { getContentItemList, deleteContentItem } from "./api"; +import { getContentItemList, deleteContentItem, aiRewriteContent, replaceContent } from "./api"; import { ContentItem } from "./data"; import style from "./index.module.scss"; @@ -42,6 +42,14 @@ const MaterialsList: React.FC = () => { const [total, setTotal] = useState(0); const pageSize = 20; + // AI改写相关状态 + const [showAIRewritePopup, setShowAIRewritePopup] = useState(false); + const [currentMaterial, setCurrentMaterial] = useState(null); + const [aiPrompt, setAiPrompt] = useState(""); + const [aiResult, setAiResult] = useState(""); + const [aiLoading, setAiLoading] = useState(false); + const [replaceLoading, setReplaceLoading] = useState(false); + // 获取素材列表 const fetchMaterials = useCallback(async () => { if (!id) return; @@ -104,9 +112,75 @@ const MaterialsList: React.FC = () => { } }; - const handleView = (materialId: number) => { - // 可以跳转到素材详情页面或显示弹窗 - console.log("查看素材:", materialId); + const handleAIRewrite = (material: ContentItem) => { + setCurrentMaterial(material); + setAiPrompt("重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除 3、适当的换行及加些表情点缀"); + setAiResult(""); + setShowAIRewritePopup(true); + }; + + const handleSubmitAIRewrite = async () => { + if (!currentMaterial) return; + + try { + setAiLoading(true); + const response = await aiRewriteContent({ + id: currentMaterial.id.toString(), + aiPrompt: aiPrompt + }); + + setAiResult(response.contentAfter || "暂无改写结果"); + + // 可以在这里显示原内容和改写后内容的对比 + console.log("原内容:", response.contentFront); + console.log("改写后内容:", response.contentAfter); + } catch (error) { + console.error("AI改写失败:", error); + Toast.show({ + content: "AI改写失败,请重试", + position: "top", + }); + } finally { + setAiLoading(false); + } + }; + + const handleReplaceContent = async () => { + if (!currentMaterial || !aiResult) return; + + try { + setReplaceLoading(true); + await replaceContent({ + id: currentMaterial.id.toString(), + content: aiResult + }); + + Toast.show({ + content: "内容已成功替换", + position: "top", + }); + + // 刷新素材列表 + fetchMaterials(); + + // 关闭弹窗 + closeAIRewritePopup(); + } catch (error) { + console.error("替换内容失败:", error); + Toast.show({ + content: "替换内容失败,请重试", + position: "top", + }); + } finally { + setReplaceLoading(false); + } + }; + + const closeAIRewritePopup = () => { + setShowAIRewritePopup(false); + setCurrentMaterial(null); + setAiPrompt(""); + setAiResult(""); }; const handleRefresh = () => { @@ -348,7 +422,23 @@ const MaterialsList: React.FC = () => {
- + {material.senderAvatar ? ( + 头像 { + e.currentTarget.style.display = 'none'; + const nextElement = e.currentTarget.nextSibling as HTMLElement; + if (nextElement) { + nextElement.style.display = 'flex'; + } + }} + /> + ) : null} +
+ +
@@ -381,7 +471,7 @@ const MaterialsList: React.FC = () => { 编辑
+ {/* AI改写弹框 */} + +
+
+

AI内容改写

+ +
+ +
+ {/* 提示词输入区 */} +
+
提示词
+
+