From a59704ae42ab4c99ed2c627e1bf8d8e8ef00dd38 Mon Sep 17 00:00:00 2001
From: xiongxiaoyang <1179705413@qq.com>
Date: Wed, 19 Feb 2025 23:46:53 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0AI=E5=86=99=E4=BD=9C?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/author.js | 13 ++
src/main.js | 5 +-
src/views/author/ChapterAdd.vue | 259 ++++++++++++++++++++++++++++++--
3 files changed, 266 insertions(+), 11 deletions(-)
diff --git a/src/api/author.js b/src/api/author.js
index 0a248ae..9953560 100644
--- a/src/api/author.js
+++ b/src/api/author.js
@@ -24,6 +24,19 @@ export function publishChapter(bookId,params) {
return request.post(`/author/book/chapter/${bookId}`, params);
}
+export function aiGenerate(action,params) {
+ const formData = new FormData();
+ Object.keys(params).forEach(key => {
+ formData.append(key, params[key]);
+ });
+ return request.post(`/author/ai/${action}`, formData, {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ timeout: 60000
+ });
+}
+
export function deleteChapter(id) {
return request.delete(`/author/book/chapter/${id}`);
}
diff --git a/src/main.js b/src/main.js
index e528c7c..02075be 100644
--- a/src/main.js
+++ b/src/main.js
@@ -5,6 +5,7 @@ import App from './App.vue'
import router from '@/router'
import '@/assets/styles/base.css'
import '@/assets/styles/main.css'
+import { Loading } from '@element-plus/icons-vue'
const app = createApp(App)
@@ -12,4 +13,6 @@ app.use(ElementPlus)
app.use(router)
-app.mount('#app')
\ No newline at end of file
+app.mount('#app')
+
+app.component('Loading', Loading)
diff --git a/src/views/author/ChapterAdd.vue b/src/views/author/ChapterAdd.vue
index 641f052..0bb3691 100644
--- a/src/views/author/ChapterAdd.vue
+++ b/src/views/author/ChapterAdd.vue
@@ -25,7 +25,7 @@
章节名:
章节内容:
+
是否收费:
- 免费
- 收费
+ 免费
+ 收费
import "@/assets/styles/book.css";
-import { reactive, toRefs, onMounted, ref } from "vue";
+import { reactive, toRefs, computed, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
-import { ElMessage } from "element-plus";
-import { publishChapter } from "@/api/author";
+import { ElMessage} from "element-plus";
+import { publishChapter, aiGenerate } from "@/api/author";
import AuthorHeader from "@/components/author/Header.vue";
-import picUpload from "@/assets/images/pic_upload.png";
export default {
name: "authorChapterAdd",
components: {
@@ -118,12 +193,145 @@ export default {
setup() {
const route = useRoute();
const router = useRouter();
+ const editor = ref(null);
const state = reactive({
bookId: route.query.id,
chapter: { chapterName: "", chapterContent: "", isVip: 0 },
+ hasSelection: false,
+ generating: false,
+ selectedText: "",
+ aiButtons: [
+ { label: "AI扩写", action: "expand", type: "primary" },
+ { label: "AI缩写", action: "condense", type: "success" },
+ { label: "AI续写", action: "continue", type: "warning" },
+ { label: "AI润色", action: "polish", type: "danger" },
+ ],
+ dialogVisible: false,
+ currentAction: '',
+ ratio: 30, // 默认扩写/缩写比例
+ length: 200, // 默认续写长度
});
+ const dialogTitle = computed(() => {
+ const map = {
+ expand: '扩写设置',
+ condense: '缩写设置',
+ continue: '续写设置',
+ polish: '润色设置'
+ }
+ return map[state.currentAction]
+ })
+
+ const openDialog = (action) => {
+ state.currentAction = action
+ // 润色不需要参数
+ if (action === 'polish') {
+ handleAI(action)
+ } else {
+ state.dialogVisible = true
+ }
+ }
+
+ const validateParams = () => {
+ if (state.currentAction === 'expand') {
+ if (!state.ratio || state.ratio < 110 || state.ratio > 200) {
+ ElMessage.error('请输入110-200%之间的比例')
+ return false
+ }
+ }
+ if (state.currentAction === 'condense') {
+ if (!state.ratio || state.ratio < 1 || state.ratio > 99) {
+ ElMessage.error('请输入1-99%之间的比例')
+ return false
+ }
+ }
+ if (state.currentAction === 'continue') {
+ if (!state.length || state.length < 50 || state.length > 1000) {
+ ElMessage.error('请输入50-1000字之间的长度')
+ return false
+ }
+ }
+ return true
+ }
+
+ const confirmParams = async () => {
+ if (!validateParams()) return
+
+ state.dialogVisible = false
+ await handleAI(state.currentAction)
+ }
+
+ const getActionName = (action) => {
+ return {
+ expand: `扩写(${state.ratio}%)`,
+ condense: `缩写(${state.ratio}%)`,
+ continue: `续写(${state.length}字)`,
+ polish: '润色'
+ }[action]
+ }
+
+
+ const checkSelection = () => {
+ const textarea = editor.value;
+ if (textarea) {
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+ state.hasSelection = start !== end;
+ if (state.hasSelection) {
+ state.selectedText = textarea.value.substring(start, end);
+ }
+ }
+ };
+
+ const typewriterEffect = (text) => {
+ return new Promise((resolve) => {
+ let index = 0;
+ const typing = setInterval(() => {
+ if (index < text.length) {
+ state.chapter.chapterContent += text.charAt(index);
+ index++;
+ // 自动滚动到底部
+ editor.scrollTop = editor.scrollHeight;
+ } else {
+ clearInterval(typing);
+ resolve();
+ }
+ }, 20);
+ });
+ };
+
+ const handleAI = async (action) => {
+
+ try {
+ state.generating = true
+
+ const params = {
+ text: state.selectedText
+ }
+
+ // 添加参数
+ if (action === 'expand' || action === 'condense') {
+ params.ratio = state.ratio
+ }
+ if (action === 'continue') {
+ params.length = state.length
+ }
+
+ const response = await aiGenerate(action,params)
+
+ // 在原有内容后追加生成内容,并实现打字效果
+ const newContent = `\n\n【AI生成内容】${response.data}`;
+ state.hasSelection = false;
+ state.selectedText = '';
+ await typewriterEffect(newContent);
+ } catch (error) {
+ ElMessage.error("AI生成失败:" + error.message);
+ } finally {
+ state.generating = false;
+ }
+ };
+
const saveChapter = async () => {
console.log("sate=========", state.chapter);
if (!state.chapter.chapterName) {
@@ -141,12 +349,19 @@ export default {
}
await publishChapter(state.bookId, state.chapter);
- router.push({ name: "authorChapterList", query:{'id':state.bookId} });
+ router.push({ name: "authorChapterList", query: { id: state.bookId } });
};
return {
...toRefs(state),
+ editor,
+ checkSelection,
+ handleAI,
saveChapter,
+ dialogTitle,
+ openDialog,
+ confirmParams,
+ getActionName
};
},
};
@@ -744,4 +959,28 @@ a.redBtn:hover {
padding: 10px;
line-height: 1.8;
}
+
+/* 新增AI工具栏样式 */
+.ai-toolbar {
+ margin-bottom: 10px;
+ width: 500px;
+}
+.ai-toolbar .el-button {
+ margin-right: 10px;
+}
+
+.textarea {
+ position: relative;
+ font-family: "Microsoft YaHei", sans-serif;
+ line-height: 1.6;
+ padding: 10px;
+}
+
+.ai-toolbar .el-input {
+ margin-bottom: 15px;
+}
+
+:deep(.el-dialog__body) {
+ padding: 20px;
+}