前端页面开发,数据库操作

main
xiangcongshuai 1 day ago
parent 7184f4c2ae
commit 025aa15d57

@ -0,0 +1,162 @@
const sqlite3 = require("sqlite3").verbose();
const path = require("path");
// 获取数据库路径
function getDatabasePath() {
const dbPath = path.join(__dirname, "..", "..", "live_chat.db");
return dbPath;
}
// 更新配置值
function updateConfigValue(key, value) {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(getDatabasePath(), err => {
if (err) {
console.error("数据库连接错误:", err.message);
return reject(err);
}
});
// 先尝试更新如果影响行数为0则插入新记录
db.run(
`UPDATE config SET value = ? WHERE key = ?`,
[value, key],
function (err) {
if (err) {
db.close();
return reject(err);
}
// 如果没有匹配的记录则插入
if (this.changes === 0) {
db.run(
`INSERT INTO config (key, value) VALUES (?, ?)`,
[key, value],
function (err) {
db.close();
if (err) return reject(err);
resolve({
success: true,
action: "insert",
changes: this.changes
});
}
);
} else {
db.close();
resolve({ success: true, action: "update", changes: this.changes });
}
}
);
});
}
// 获取配置值
function getConfigValue(key) {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(getDatabasePath(), err => {
if (err) {
return reject(err);
}
});
db.get(`SELECT value FROM config WHERE key = ?`, [key], (err, row) => {
db.close();
if (err) return reject(err);
resolve(row ? row.value : null);
});
});
}
// 批量插入系统消息
const bulkInsertSystemMessages = messages => {
return new Promise((resolve, reject) => {
if (!Array.isArray(messages) || messages.length === 0) {
reject(new Error("请提供有效的消息数组"));
return;
}
try {
const db = new sqlite3.Database(getDatabasePath(), err => {
if (err) {
reject(err);
return;
}
// 清空表
db.run("DELETE FROM system_message", function (err) {
if (err) {
db.close();
reject(err);
return;
}
// 重置自增计数器SQLite特定
db.run(
"DELETE FROM sqlite_sequence WHERE name = 'system_message'",
err => {
if (err) {
db.close();
reject(err);
return;
} else {
// 使用事务进行批量插入,提高性能
db.run("BEGIN TRANSACTION", err => {
if (err) {
reject(err);
db.close();
return;
}
const stmt = db.prepare(
"INSERT INTO system_message (message) VALUES (?)"
);
let completed = 0;
let errorOccurred = null;
messages.forEach(message => {
stmt.run(message, function (err) {
if (err && !errorOccurred) {
errorOccurred = err;
}
completed++;
// 所有消息都处理完毕
if (completed === messages.length) {
stmt.finalize();
if (errorOccurred) {
db.run("ROLLBACK", () => {
reject(errorOccurred);
db.close();
});
} else {
db.run("COMMIT", err => {
if (err) {
reject(err);
} else {
resolve({
success: true,
count: messages.length
});
}
db.close();
});
}
}
});
});
});
}
}
);
});
});
} catch (error) {
reject(error);
}
});
};
module.exports = {
updateConfigValue,
getConfigValue,
bulkInsertSystemMessages
};

@ -1,16 +1,21 @@
const { app, BrowserWindow } = require("electron");
const { ipcMain } = require("electron");
const path = require("path");
const http = require("http");
const httpProxy = require("http-proxy"); // 需要安装npm install http-proxy
const {
updateConfigValue,
getConfigValue,
bulkInsertSystemMessages
} = require("./dbHandler");
// 创建代理服务器
const proxyServer = httpProxy.createProxyServer({});
// 定义目标服务
const proxyTargets = {
"/live-digital-avatar-manage": "http://192.168.10.25:8080/live-digital-avatar-manage",
"/asr": "http://192.168.10.138:8090",
"/tts": "http://192.168.10.113:9880"
"/live-digital-avatar-manage":
"http://192.168.10.25:9909/live-digital-avatar-manage"
};
// 创建本地 HTTP 服务器
@ -29,7 +34,35 @@ const server = http.createServer((req, res) => {
res.statusCode = 404;
res.end("Not Found");
});
// 注册IPC通信处理
function registerIpcHandlers() {
// 更新backend_token
ipcMain.handle("update-config", async (event, key, token) => {
try {
return await updateConfigValue(key, token);
} catch (error) {
console.error("更新backend_token失败:", error);
throw error;
}
});
ipcMain.handle("get-config", async (event, key) => {
try {
return await getConfigValue(key);
} catch (error) {
console.error("获取失败:", error);
throw error;
}
});
ipcMain.handle("bulk-insert-system-messages", async (event, messages) => {
try {
return await bulkInsertSystemMessages(messages);
} catch (error) {
console.error("更新失败:", error);
throw error;
}
});
}
// 启动服务器
server.listen(3000, () => {
console.log("Proxy server running on port 3000");
@ -37,17 +70,40 @@ server.listen(3000, () => {
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
width: 1920,
height: 1080,
webPreferences: {
preload: path.join(__dirname, "preload.js"), // 使用计算后的 __dirname
nodeIntegration: true,
contextIsolation: false
nodeIntegration: false,
contextIsolation: true
}
});
// 捕获 preload 加载错误
win.webContents.on("did-fail-load", (event, errorCode, errorDescription) => {
console.error("Preload 加载失败:", errorDescription);
});
win.webContents.openDevTools();
// 加载应用
win.loadFile("dist/index.html");
if (process.env.NODE_ENV === "development") {
win.loadURL("http://localhost:8848/#/login");
win.webContents.openDevTools();
} else {
win.loadFile("dist/index.html");
}
// win.loadURL("http://localhost:8848");
// win.webContents.openDevTools();
}
app.whenReady().then(createWindow);
app.whenReady().then(() => {
createWindow();
// 注册IPC处理
registerIpcHandlers();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});

@ -1,6 +1,15 @@
import { contextBridge } from "electron";
const { contextBridge, ipcRenderer } = require("electron");
// const path = require("path");
// 安全地暴露 API 给渲染进程
// 安全暴露数据库操作方法
contextBridge.exposeInMainWorld("electronAPI", {
// 可以在这里添加需要暴露的 API
updateConfig: async (key, value) => {
return await ipcRenderer.invoke("update-config", key, value);
},
getConfig: async (key, value) => {
return await ipcRenderer.invoke("get-config", key, value);
},
bulkInsertSystemMessages: async messages => {
return await ipcRenderer.invoke("bulk-insert-system-messages", messages);
}
});

3
package-lock.json generated

@ -88,7 +88,7 @@
"pretty-quick": "^3.1.3",
"rimraf": "^5.0.1",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"sass": "^1.89.2",
"sass-loader": "^13.3.2",
"stylelint": "^15.9.0",
"stylelint-config-html": "^1.1.0",
@ -21124,6 +21124,7 @@
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.89.2.tgz",
"integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",

@ -33,6 +33,14 @@
"directories": {
"output": "dist-electron"
},
"asar": true,
"extraResources": [
{
"from": "resources/",
"to": "resources/",
"filter": ["!*.asar"]
}
],
"files": [
"dist/**/*",
"electron/**/*",
@ -92,6 +100,7 @@
"qs": "^6.11.2",
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.0",
"sqlite3": "^5.1.7",
"uuid": "^9.0.1",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
@ -142,7 +151,7 @@
"pretty-quick": "^3.1.3",
"rimraf": "^5.0.1",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"sass": "^1.89.2",
"sass-loader": "^13.3.2",
"stylelint": "^15.9.0",
"stylelint-config-html": "^1.1.0",

File diff suppressed because it is too large Load Diff

@ -1,9 +1,9 @@
import { http } from "@/utils/http";
/** ,ID
*/
export const chatKnowledgeQA = (data?: object) => {
return http.request("get", "/chat/knowledgeQA", {
params: data
});
export const getSalespitch = (data?: object) => {
return http.request<any>(
"post",
"/live-digital-avatar-manage/liveDigital/generate/salespitch",
{ data }
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame" clip-path="url(#clip0_469_5371)">
<path id="Vector" d="M7.99999 14.6663C9.84093 14.6663 11.5076 13.9201 12.714 12.7137C13.9205 11.5073 14.6667 9.84061 14.6667 7.99967C14.6667 6.15874 13.9205 4.49207 12.714 3.28563C11.5076 2.0792 9.84093 1.33301 7.99999 1.33301C6.15906 1.33301 4.49239 2.0792 3.28595 3.28563C2.07952 4.49207 1.33333 6.15874 1.33333 7.99967C1.33333 9.84061 2.07952 11.5073 3.28595 12.7137C4.49239 13.9201 6.15906 14.6663 7.99999 14.6663Z" stroke="#00B42A" stroke-width="1.2" stroke-linejoin="round"/>
<path id="Vector_2" d="M5.33333 8L7.33333 10L11.3333 6" stroke="#00B42A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_469_5371">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 881 B

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame" clip-path="url(#clip0_437_2419)">
<path id="Vector" d="M8.02013 3.33301L8.00793 12.6663" stroke="#2E80FA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M3.33337 8H12.6667" stroke="#2E80FA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_437_2419">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 526 B

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame 427319817">
<path id="Star 1" d="M9.32617 13.2871C9.45102 13.5599 9.67054 13.7794 9.94336 13.9043L12.5049 15.0762L9.94336 16.249C9.70482 16.3582 9.50718 16.5395 9.37793 16.7656L9.32617 16.8662L8.15332 19.4277L6.98145 16.8662L6.92969 16.7656C6.80043 16.5395 6.60278 16.3582 6.36426 16.249L3.80176 15.0762L6.36426 13.9043C6.63707 13.7794 6.8566 13.5599 6.98145 13.2871L8.15332 10.7246L9.32617 13.2871Z" fill="white" stroke="#2E80FA" stroke-width="1.5"/>
<path id="Star 2" d="M11.5288 4.24707C11.62 4.49338 11.8147 4.68717 12.0611 4.77832L12.8667 5.07617L12.0611 5.375C11.8454 5.45479 11.6695 5.61381 11.5679 5.81738L11.5288 5.90723L11.23 6.71289L10.9322 5.90723L10.8931 5.81738C10.7915 5.61394 10.6164 5.45478 10.4009 5.375L9.59427 5.07617L10.4009 4.77832C10.6471 4.68716 10.841 4.49321 10.9322 4.24707L11.23 3.44043L11.5288 4.24707Z" fill="white" stroke="#2E80FA"/>
<path id="Star 3" d="M18.0046 9.25C18.1058 9.52368 18.3217 9.73951 18.5954 9.84082L20.2712 10.4609L18.5954 11.0811C18.3217 11.1823 18.1059 11.3982 18.0046 11.6719L17.3845 13.3477L16.7643 11.6719C16.663 11.3982 16.4472 11.1823 16.1735 11.0811L14.4968 10.4609L16.1735 9.84082C16.4472 9.73954 16.6631 9.52372 16.7643 9.25L17.3845 7.57324L18.0046 9.25Z" fill="white" stroke="#0CE9E5" stroke-width="1.2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,17 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon-center-16px" clip-path="url(#clip0_519_1330)">
<g id="icon-center-16px_2" clip-path="url(#clip1_519_1330)">
<g id="tips/exclamation-circle-fill">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M1.33337 7.99967C1.33337 4.31778 4.31814 1.33301 8.00004 1.33301C11.6819 1.33301 14.6667 4.31778 14.6667 7.99967C14.6667 11.6816 11.6819 14.6663 8.00004 14.6663C4.31814 14.6663 1.33337 11.6816 1.33337 7.99967ZM7.33337 9.99967V11.333H8.66671V9.99967H7.33337ZM8.66671 9.33301L8.66671 4.66634H7.33337L7.33337 9.33301H8.66671Z" fill="#FF7D00"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_519_1330">
<rect width="16" height="16" fill="white"/>
</clipPath>
<clipPath id="clip1_519_1330">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 867 B

@ -4,7 +4,7 @@ import userImg from "@/assets/home/user.png";
import actUserImg from "@/assets/home/act_user.png";
import liveImg from "@/assets/home/live.png";
import actLiveImg from "@/assets/home/act_live.png";
import settingmg from "@/assets/home/setting.png";
import settingImg from "@/assets/home/setting.png";
import actSettingImg from "@/assets/home/act_setting.png";
import { useRouter } from "vue-router";
const activedName = ref("user");
@ -38,11 +38,11 @@ onMounted(() => {});
</div>
<div
class="sidebar-item setting"
:class="[{ active: activedName === 'settingmg' }]"
@click="activedName = 'settingmg'"
:class="[{ active: activedName === 'setting' }]"
@click="handleSidebarClick('setting', '/setting')"
>
<img v-if="activedName === 'settingmg'" :src="actSettingImg" alt="" />
<img v-else :src="settingmg" alt="" />
<img v-if="activedName === 'setting'" :src="actSettingImg" alt="" />
<img v-else :src="settingImg" alt="" />
<span>设置</span>
</div>
</div>

@ -31,6 +31,16 @@ export default [
rank: 103
}
},
{
path: "/setting",
name: "Setting",
component: () => import("@/views/Setting/index.vue"),
meta: {
title: "设置",
showLink: false,
rank: 105
}
},
{
path: "/digitalHuman",
redirect: "/digitalHuman/list",

@ -220,7 +220,7 @@ video {
}
label {
font-weight: 600;
// font-weight: 600;
}
*,

@ -0,0 +1,8 @@
// src/electron-api.d.ts
interface Window {
electronAPI: {
updateConfig: (key: string, value: string) => void;
getConfig: (key: string) => Promise<string>;
bulkInsertSystemMessages: (messages: any) => void;
};
}

@ -0,0 +1,342 @@
<script setup lang="ts">
import { FormInstance } from "element-plus/es/components";
import { reactive, ref } from "vue";
import headPicImg from "@/assets/live/head_pic.png";
import rotateImg from "@/assets/live/rotate.png";
import tipsImg from "@/assets/svg/live/tips.svg";
const ruleFormRef = ref<FormInstance>();
const formData = reactive({
name: "",
liveTime: [],
userReply: "1",
intervalTime: "",
isEnter: true,
isGlobal: true,
randomTriggerProbability: "",
replyMethod: "1"
});
const rules = {
liveTime: [{ required: true, message: "请选择直播时间", trigger: "change" }]
};
</script>
<template>
<div class="BasicSettings">
<div class="live">1111</div>
<div class="main">
<div class="title">基础设置</div>
<div class="main-content">
<el-form
ref="ruleFormRef"
:model="formData"
:rules="rules"
label-position="top"
>
<el-form-item label="直播数字人" prop="name">
<div class="card">
<img :src="headPicImg" alt="" />
<div class="card-content">
<div class="top">
<span class="name">慧文</span>
<span class="live-num">{{ `直播100场` }}</span>
</div>
<div class="desc">营销推广-美妆护肤</div>
</div>
<img class="rotate" :src="rotateImg" alt="" />
</div>
</el-form-item>
<el-row>
<el-col :span="12" style="padding-right: 16px">
<el-form-item label="直播时间" prop="liveTime">
<el-date-picker
v-model="formData.liveTime"
value-format="yyyy-MM-dd HH:MM:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" style="padding-right: 16px">
<el-form-item label="计划选品数量" prop="expectedDiagnosisResult">
<el-input v-model="formData.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="选择尺寸" prop="expectedDiagnosisResult">
<el-input v-model="formData.name" />
</el-form-item>
</el-col>
</el-row>
<div class="bullet-list-title">弹幕回复</div>
<div class="bullet-item">
<div class="bullet-item-title">
<el-switch v-model="formData.isGlobal" />
<span>全局弹幕回复</span>
</div>
<div class="bullet-item-content" v-if="formData.isGlobal">
<div class="bullet-list-content-title">单用户回复</div>
<el-radio-group v-model="formData.userReply">
<el-radio value="1" size="large">单用户互动限制</el-radio>
<el-radio value="2" size="large">全局互动限制</el-radio>
</el-radio-group>
<div class="bullet-list-item">
<span>单用户回复间隔: </span>
<el-input-number
:min="1"
:max="10"
style="width: 100px; margin-left: 16px; margin-right: 16px"
v-model="formData.intervalTime"
/>
<span>分钟</span>
</div>
<div class="bullet-tip">
<tipsImg />
<span>限制单用户的互动回复频率的时间间隔</span>
</div>
</div>
</div>
<div class="bullet-item">
<div class="bullet-item-title">
<el-switch v-model="formData.isEnter" />
<span>进入直播间</span>
</div>
<div class="bullet-item-content" v-if="formData.isEnter">
<el-col :span="12" style="padding-right: 16px">
<el-form-item
label="随机触发概率"
prop="randomTriggerProbability"
>
<el-input v-model="formData.randomTriggerProbability">
<template #append> %</template>
</el-input>
</el-form-item>
</el-col>
<div class="bullet-desc">
1%-100% 之间的数值(不可为0)若需关闭该互动功能
</div>
<div class="bullet-list-content-title">回复方式</div>
<el-radio-group v-model="formData.replyMethod">
<el-radio value="1" size="large">文本回复</el-radio>
<el-radio value="2" size="large">文本转语音回复</el-radio>
</el-radio-group>
<div class="bullet-list-item" />
</div>
</div>
<div class="bullet-item">
<div class="bullet-item-title">
<el-switch v-model="formData.isEnter" />
<span>关注</span>
</div>
<div class="bullet-item-content" v-if="formData.isEnter">
<el-col :span="12" style="padding-right: 16px">
<el-form-item
label="随机触发概率"
prop="randomTriggerProbability"
>
<el-input v-model="formData.randomTriggerProbability">
<template #append> %</template>
</el-input>
</el-form-item>
</el-col>
<div class="bullet-list-content-title">回复方式</div>
<el-radio-group v-model="formData.replyMethod">
<el-radio value="1" size="large">文本回复</el-radio>
<el-radio value="2" size="large">文本转语音回复</el-radio>
</el-radio-group>
<div class="bullet-list-item" />
</div>
</div>
<div class="bullet-item">
<div class="bullet-item-title">
<el-switch v-model="formData.isEnter" />
<span>送礼物</span>
</div>
<div class="bullet-item-content" v-if="formData.isEnter">
<el-col :span="12" style="padding-right: 16px">
<el-form-item
label="随机触发概率"
prop="randomTriggerProbability"
>
<el-input v-model="formData.randomTriggerProbability">
<template #append> %</template>
</el-input>
</el-form-item>
</el-col>
<div class="bullet-list-content-title">回复方式</div>
<el-radio-group v-model="formData.replyMethod">
<el-radio value="1" size="large">文本回复</el-radio>
<el-radio value="2" size="large">文本转语音回复</el-radio>
</el-radio-group>
<div class="bullet-list-item" />
</div>
</div>
<div class="bullet-item">
<div class="bullet-item-title">
<el-switch v-model="formData.isEnter" />
<span>点赞</span>
</div>
<div class="bullet-item-content" v-if="formData.isEnter">
<el-col :span="12" style="padding-right: 16px">
<el-form-item
label="随机触发概率"
prop="randomTriggerProbability"
>
<el-input v-model="formData.randomTriggerProbability">
<template #append> %</template>
</el-input>
</el-form-item>
</el-col>
<div class="bullet-list-content-title">回复方式</div>
<el-radio-group v-model="formData.replyMethod">
<el-radio value="1" size="large">文本回复</el-radio>
<el-radio value="2" size="large">文本转语音回复</el-radio>
</el-radio-group>
<div class="bullet-list-item" />
</div>
</div>
</el-form>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.BasicSettings {
margin-left: 16px;
flex: 1;
display: flex;
flex-direction: row-reverse;
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
.live {
width: 453px;
border-left: 1px solid rgba(46, 128, 250, 0.1);
}
.main {
flex: 1;
height: calc(100vh - 160px);
overflow: auto;
.title {
height: 54px;
border-bottom: 1px solid rgba(46, 128, 250, 0.1);
padding-left: 16px;
line-height: 54px;
font-weight: bold;
font-size: 16px;
color: #1d2129;
}
.main-content {
padding: 16px;
.card {
width: 480px;
height: 104px;
background: rgba(46, 128, 250, 0.1);
border-radius: 8px 8px 8px 8px;
position: relative;
display: flex;
align-items: center;
padding-left: 16px;
img {
width: 72px;
height: 72px;
margin-right: 16px;
}
.rotate {
position: absolute;
width: 24px;
height: 24px;
right: 16px;
top: 16px;
margin-right: 0;
}
.card-content {
.top {
display: flex;
align-items: center;
.name {
font-weight: bold;
font-size: 14px;
color: #1d2129;
}
.live-num {
font-weight: 400;
font-size: 12px;
color: #2e80fa;
height: 20px;
line-height: 20px;
text-align: center;
background: #ffffff;
border-radius: 2px 2px 2px 2px;
border: 1px solid #2e80fa;
padding: 0 4px;
margin-left: 14px;
}
}
.desc {
font-weight: 500;
font-size: 14px;
color: #666666;
margin-top: 8px;
}
}
}
.bullet-list-title {
margin-top: 16px;
font-weight: bold;
font-size: 14px;
color: #000000;
margin-bottom: 8px;
}
.bullet-item {
margin-bottom: 16px;
.bullet-item-title {
display: flex;
align-items: center;
span {
margin-left: 12px;
font-weight: 500;
font-size: 14px;
color: #1d2129;
}
}
.bullet-list-content-title {
font-weight: bold;
font-size: 14px;
color: #1d2129;
}
.bullet-list-item {
display: flex;
align-items: center;
font-weight: 500;
font-size: 14px;
color: #333333;
margin-bottom: 16px;
}
.bullet-tip {
width: 342px;
height: 32px;
background: #fff7e8;
border-radius: 4px 4px 4px 4px;
display: flex;
font-size: 14px;
color: #1d2129;
align-items: center;
padding-left: 12px;
margin-bottom: 16px;
span {
margin-left: 8px;
}
}
.bullet-desc {
font-size: 14px;
color: #4e5969;
margin-bottom: 16px;
}
}
}
}
}
</style>

@ -0,0 +1,305 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import { reactive, ref } from "vue";
import cheackImg from "@/assets/svg/live/check.svg";
import starImg from "@/assets/svg/live/star.svg";
import plusImg from "@/assets/svg/live/plus.svg";
import emptyImg from "@/assets/live/empty.png";
import tipsImg from "@/assets/svg/live/tips.svg";
import { FormInstance } from "element-plus/es/components";
import { getSalespitch } from "@/api/chat";
import { Delete } from "@element-plus/icons-vue";
const ruleFormRef = ref<FormInstance>();
const salespitchList = ref(["2222"]);
import { message } from "@/utils/message";
const router = useRouter();
const formData = reactive({
productName: "",
detail: "",
script: "",
specifications: [
{
size: "",
//
price: ""
}
]
});
const loading = ref(false);
const rules = {
script: [{ required: true, message: "请输入", trigger: "change" }]
};
const handlePriceInput = item => {
item.price = item.price
.replace(/[^\d.]/g, "")
.replace(/^\./g, "")
.replace(/(\.\d*)\./g, "$1")
.replace(/(\d+\.\d{2}).*/g, "$1");
};
const buttonText = ref("生成AI话术");
const addNorms = () => {
formData.specifications.push({
size: "",
price: ""
});
};
const generateScript = async () => {
loading.value = true;
buttonText.value = "正在生成";
const res = await getSalespitch({
productName: formData.productName,
specifications: formData.specifications,
detail: formData.detail
});
loading.value = false;
buttonText.value = "生成AI话术";
if (res.code === 200) {
salespitchList.value = res.data;
// await window.electronAPI.clearSystemMessages();
const result = await window.electronAPI.bulkInsertSystemMessages(res.data);
if (result.success) {
message("生成成功!", { type: "success", showClose: true });
}
}
};
</script>
<template>
<div class="GoodsManage">
<div class="ai-script">
<div class="script-title">
<starImg />
<span class="name">AI直播讲品话术</span>
<cheackImg />
<span class="desc">生成/修改内容将自动保存</span>
</div>
<div v-if="salespitchList.length === 0" class="script-empty">
<img :src="emptyImg" alt="" />
<span>暂无AI讲品话术~</span>
</div>
<div v-else class="script-content">
<div class="tip">
<tipsImg />
<span>以下内容由AI自动生成</span>
</div>
<div
v-for="(item, index) in salespitchList"
:key="index"
class="speech-item"
>
{{ item }}
</div>
</div>
</div>
<div class="main-content">
<el-form
ref="ruleFormRef"
:model="formData"
:rules="rules"
label-position="top"
>
<el-form-item label="商品名称" prop="productName">
<el-input size="large" v-model="formData.productName" />
</el-form-item>
<el-form-item label="商品规格" prop="specifications">
<div
class="norms-list"
v-for="(item, index) in formData.specifications"
:key="index"
>
<el-input
class="norms-item"
size="large"
v-model="item.size"
placeholder="请输入商品规格"
/>
<el-input
class="norms-item"
size="large"
v-model="item.price"
placeholder="请输入商品价格"
@input="handlePriceInput(item)"
/>
<el-icon
v-show="index === formData.specifications.length - 1"
@click="formData.specifications.splice(index, 1)"
role="img"
style="
font-size: 18px;
color: rgba(234, 42, 42, 1);
cursor: pointer;
"
><Delete
/></el-icon>
</div>
</el-form-item>
<div class="add-norms-btn" @click="addNorms">
<plusImg />
<span>添加规格</span>
</div>
<el-form-item label="商品详情" prop="detail">
<el-input
:rows="8"
type="textarea"
placeholder="请输入"
v-model="formData.detail"
/>
</el-form-item>
<!-- <el-form-item label="AI生成直播讲品话术" prop="script">
<el-input
:rows="8"
type="textarea"
placeholder="请输入根据商品信息需要AI主要主要讲解的内容如重点突出商品的特殊工艺和无污染适应各种场景"
v-model="formData.script"
/>
</el-form-item> -->
<div class="tips">
<tipsImg />
<span
>AI商品讲解话术由大模型基于上述内容自动生成还能结合直播时长和商品特点生成适配的A!数字人口播内容</span
>
</div>
<div class="footer-btns">
<el-button class="reset" @click="ruleFormRef.resetFields()"
>重置</el-button
>
<el-button :loading="loading" type="primary" @click="generateScript">
{{ buttonText }}</el-button
>
</div>
</el-form>
</div>
</div>
</template>
<style lang="scss" scoped>
.GoodsManage {
margin-left: 16px;
flex: 1;
display: flex;
flex-direction: row-reverse;
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
.ai-script {
width: 453px;
display: flex;
flex-direction: column;
border-left: 1px solid rgba(46, 128, 250, 0.1);
.script-title {
height: 54px;
border-bottom: 1px solid rgba(46, 128, 250, 0.1);
display: flex;
align-items: center;
padding-left: 16px;
.name {
font-weight: bold;
font-size: 16px;
color: #1d2129;
margin-left: 8px;
margin-right: 12px;
}
.desc {
font-weight: 500;
font-size: 14px;
color: #86909c;
margin-left: 4px;
}
}
.script-content {
padding: 16px;
overflow: auto;
height: calc(100vh - 210px);
.tip {
display: flex;
align-items: center;
margin-bottom: 8px;
span {
font-weight: 500;
font-size: 14px;
color: #4e5969;
margin-left: 8px;
}
}
.speech-item {
font-weight: 500;
font-size: 14px;
color: #1d2129;
background: #f3f4fc;
border-radius: 4px 4px 4px 4px;
padding: 8px 12px;
margin-bottom: 20px;
}
}
.script-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
font-weight: 500;
font-size: 16px;
color: #86909c;
img {
width: 222px;
height: 138px;
}
}
}
.main-content {
flex: 1;
padding: 16px;
position: relative;
.norms-list {
display: flex;
width: 100%;
align-items: center;
margin-bottom: 12px;
.norms-item {
width: calc(50% - 24px);
margin-right: 16px;
}
}
.add-norms-btn {
margin: 12px 0 16px 0;
display: flex;
width: 96px;
height: 32px;
border-radius: 4px 4px 4px 4px;
border: 1px solid #2e80fa;
cursor: pointer;
align-items: center;
font-size: 14px;
color: #2e80fa;
justify-content: center;
}
.tips {
display: flex;
height: 32px;
line-height: 32px;
background: #fff7e8;
font-weight: 400;
font-size: 14px;
color: #1d2129;
padding-left: 12px;
align-items: center;
width: 739px;
span {
margin-left: 8px;
}
}
.footer-btns {
position: absolute;
bottom: 32px;
right: 16px;
.reset {
width: 94px;
height: 32px;
border-radius: 4px 4px 4px 4px;
border: 1px solid #2e80fa;
}
}
}
}
</style>

@ -0,0 +1,155 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import emptyImg from "@/assets/live/empty.png";
import addImg from "@/assets/live/add_script.png";
const scriptList = ref([
{
script:
"欢迎新进来的朋友{{nickname)}呀!刚开播没多久,大家有想聊的都可以在弹幕里说。"
},
{ script: "欢迎新面孔{{nickname}}" },
{ script: "欢迎{{nickname}} 加入直播间!" },
{
script:
"欢迎{inickname}} (自动识别昵称)~刚进来可能还不熟悉,有任何疑问都能问,我看到都会回"
}
]);
const followList = ref([
{ script: "看到有新朋友关注啦,特别感谢!" },
{
script:
"谢谢关注直播间的朋友!关注之后,之前聊过的话题有后续,你也能及时看到,"
},
{ script: "感谢 {{nickname}}宝贝 的关注!" },
{
script: "感谢关注!关注了就是自己人啦,以后开播会第一时间通知你,常来互动呀"
}
]);
const likeList = ref([
{ script: "看到大家在点赞啦,谢谢大家的支持" },
{ script: "谢谢大家的点赞支持!" },
{ script: "谢谢朋友们的点赞!虽然没什么福利,但能被大家认可,心里还挺开心的" },
{ script: "看到点赞数慢慢往上走,谢谢大家!有喜欢的内容,用点赞告诉我就好啦" }
]);
</script>
<template>
<div class="InteractionScript">
<!-- <div class="empty">
<img :src="emptyImg" alt="" />
<img class="add" :src="addImg" alt="" />
</div> -->
<div class="main-content">
<div class="card-item">
<div class="title">
<span>进入直播间</span>
<span class="desc">直播开启后将随机播放以下话术</span>
</div>
<div class="script-list">
<div
v-for="(item, index) in scriptList"
:key="index"
class="script-item"
>
<span>{{ item.script }}</span>
</div>
</div>
</div>
<div class="card-item">
<div class="title">
<span>关注</span>
</div>
<div class="script-list">
<div
v-for="(item, index) in followList"
:key="index"
class="script-item"
>
<span>{{ item.script }}</span>
</div>
</div>
</div>
<div class="card-item">
<div class="title">
<span>点赞</span>
</div>
<div class="script-list">
<div
v-for="(item, index) in likeList"
:key="index"
class="script-item"
>
<span>{{ item.script }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.InteractionScript {
margin-left: 16px;
flex: 1;
display: flex;
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
position: relative;
padding: 24px;
.empty {
margin: auto;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.add {
width: 120px;
height: 32px;
cursor: pointer;
}
}
.main-content {
margin-bottom: 12px;
width: 100%;
.card-item {
border-radius: 8px 8px 8px 8px;
border: 1px solid rgba(70, 142, 250, 0.15);
margin-bottom: 16px;
.title {
height: 48px;
background: rgba(46, 128, 250, 0.05);
display: flex;
align-items: center;
padding: 0 12px;
font-weight: bold;
font-size: 16px;
color: #000000;
.desc {
font-weight: 500;
font-size: 14px;
color: #86909c;
margin-left: 12px;
}
}
.script-list {
padding: 12px;
display: flex;
flex-wrap: wrap;
.script-item {
width: calc(50% - 24px);
height: 32px;
background: #ffffff;
border-radius: 4px 4px 4px 4px;
border: 1px solid #dcdcdc;
line-height: 32px;
padding-left: 8px;
margin-bottom: 12px;
font-size: 14px;
color: #1d2129;
margin-right: 24px;
}
}
}
}
}
</style>

@ -2,15 +2,23 @@
import { useRouter } from "vue-router";
import { ref } from "vue";
import editImg from "@/assets/live/edit.png";
import GoodsManage from "./components/GoodsManage.vue";
import InteractionScript from "./components/InteractionScript.vue";
import BasicSettings from "./components/BasicSettings.vue";
const router = useRouter();
const liveName = ref("慧文的直播间");
const activedKey = ref("1");
const sidebarList = ref([
{ name: "直播商品" },
{ name: "互动脚本" },
{ name: "基础设置" }
{ name: "直播商品", key: "1" },
{ name: "互动脚本", key: "2" },
{ name: "基础设置", key: "3" }
]);
const GoodsManageRef = ref(undefined);
const InteractionScriptRef = ref(undefined);
const BasicSettingsRef = ref(undefined);
const chanageActived = (key: string) => {
activedKey.value = key;
};
</script>
<template>
@ -31,13 +39,22 @@ const sidebarList = ref([
<div class="ai-content">
<div class="sidebar">
<div
@click="chanageActived(item.key)"
v-for="(item, index) in sidebarList"
:key="index"
class="sidebar-item"
:class="{ active: activedKey === item.key }"
>
<span>{{ item.name }}</span>
<span v-if="activedKey === item.key" class="line" />
</div>
</div>
<GoodsManage v-show="activedKey === '1'" ref="GoodsManageRef" />
<InteractionScript
v-show="activedKey === '2'"
ref="InteractionScriptRef"
/>
<BasicSettings ref="BasicSettingsRef" v-show="activedKey === '3'" />
</div>
</div>
</template>
@ -46,6 +63,8 @@ const sidebarList = ref([
.AiLive {
background: #f4f8ff;
padding: 24px;
display: flex;
flex-direction: column;
.top {
display: flex;
justify-content: space-between;
@ -72,5 +91,39 @@ const sidebarList = ref([
}
}
}
.ai-content {
display: flex;
flex: 1;
.sidebar {
width: 208px;
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
padding-top: 16px;
.sidebar-item {
height: 40px;
line-height: 40px;
padding-left: 16px;
font-size: 14px;
color: #4e5969;
cursor: pointer;
position: relative;
.line {
width: 2px;
height: 24px;
background: #2e80fa;
border-radius: 0px 2px 2px 0px;
position: absolute;
left: 0;
top: 8px;
}
}
.active {
background: rgba(46, 128, 250, 0.1);
font-weight: bold;
font-size: 14px;
color: #2e80fa;
}
}
}
}
</style>

@ -0,0 +1,124 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import tipsImg from "@/assets/svg/live/tips.svg";
const modelType = ref("");
const tableData = reactive([
{
name: "通义千问",
apiKey: "xxxxxxxxxxxxxxxxx",
authorizationExpiry: "2022-03-28 15:14:07",
serviceStatus: "正常",
desc: "提供实时数字及npl大模型视觉模型服务能力"
}
]);
</script>
<template>
<div class="Setting">
<div class="set-top">
<div class="title">设置</div>
<div class="top-right">
<el-button>取消</el-button>
<el-button type="primary">保存设置</el-button>
</div>
</div>
<div class="main-content">
<div class="main-title">大模型服务授权</div>
<div class="model-content">
<div class="select-model">选择大模型接入模式</div>
<el-radio-group v-model="modelType">
<el-radio label="1">私有算力模式(本地)</el-radio>
<el-radio label="2">弹性算力模式(云端)</el-radio>
</el-radio-group>
<div class="tips">
<tipsImg />
<div class="tips-content">
<span>数据本地化部署需自备硬件(显卡>8G显存等) </span>
<span>云端弹性调度算力免硬件投入点击了解套餐差异</span>
</div>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="name" label="服务名称" />
<el-table-column prop="apiKey" label="API Key" />
<el-table-column prop="authorizationExpiry" label="授权有效期" />
<el-table-column prop="serviceStatus" label="服务状态" />
<el-table-column prop="desc" label="说明" />
<el-table-column prop="address" label="操作">
<template #default="scope">
<div style="display: flex; align-items: center">
<span class="text-btn">编辑</span>
<span class="text-btn">测试连接</span>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.Setting {
background: #f4f8ff;
padding: 24px;
display: flex;
flex-direction: column;
flex: 1;
.set-top {
display: flex;
justify-content: space-between;
align-items: center;
.title {
font-weight: 800;
font-size: 20px;
color: #1d2129;
}
}
.main-content {
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
border-radius: 8px 8px 8px 8px;
flex: 1;
margin-top: 16px;
.main-title {
height: 54px;
border-bottom: 1px solid rgba(46, 128, 250, 0.1);
padding-left: 16px;
line-height: 54px;
font-weight: bold;
font-size: 16px;
color: #1d2129;
}
.model-content {
padding: 16px;
.main-title {
font-weight: bold;
font-size: 14px;
color: #1d2129;
margin-bottom: 8px;
}
.tips {
display: flex;
align-items: center;
background: #fff7e8;
border-radius: 4px 4px 4px 4px;
padding: 5px 12px;
margin-top: 12px;
margin-bottom: 16px;
.tips-content {
display: flex;
flex-direction: column;
font-size: 14px;
color: #1d2129;
margin-left: 8px;
}
}
.text-btn {
font-size: 14px;
color: #2e80fa;
cursor: pointer;
margin-right: 12px;
}
}
}
}
</style>

@ -11,6 +11,7 @@ import PreviewOpen from "@iconify-icons/icon-park-outline/preview-open";
import { useRouter } from "vue-router";
import { removeToken, setToken } from "@/utils/auth";
import { storageLocal, storageSession } from "@pureadmin/utils";
import { message } from "@/utils/message";
defineOptions({
name: "Login"
});
@ -20,8 +21,8 @@ const loading = ref(false);
const passwordType = ref("password");
const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive({
username: "",
password: ""
username: "admin",
password: "sst123456#"
});
function showPass() {
if (passwordType.value === "password") {
@ -34,15 +35,27 @@ function showPass() {
});
}
const onLogin = async (formEl: FormInstance | undefined) => {
console.log("login", ruleForm);
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
getLogin(ruleForm)
.then((res: any) => {
.then(async (res: any) => {
if (res.code === 200) {
loading.value = false;
setToken(res.data);
router.push("/digitalHuman");
const result = await window.electronAPI.updateConfig(
"backend_token",
res.data
);
console.log("result: ", result);
if (result.changes > 0) {
router.push("/digitalHuman");
} else {
message("没有记录被更新,请检查 config 表是否存在", {
type: "error"
});
}
}
})
.catch(() => {
@ -54,10 +67,12 @@ const onLogin = async (formEl: FormInstance | undefined) => {
}
});
};
onMounted(() => {
onMounted(async () => {
removeToken();
storageLocal().clear();
storageSession().clear();
// const sss = await window.electronAPI.getConfig("reply_prob_follow");
// console.log("sss: ", sss);
});
</script>

Loading…
Cancel
Save