前端页面历史记录列表实现

hot-fix
chantouRichard 2 months ago
parent 16dccb1f00
commit da3f1638c6

@ -337,14 +337,7 @@ Authorization: 登录接口返回的token
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"conversation": {
"id": 1,
"type": "debate",
"title": "对话标题",
"createTime": "2023-01-01 12:00:00"
},
"records": [
"data":
{
"id": 1,
"userMessage": "用户消息",
@ -352,8 +345,6 @@ Authorization: 登录接口返回的token
"sequence": 1,
"createTime": "2023-01-01 12:00:00"
}
]
}
}
```
- 调用时机:用户点击辩论历史记录列表中的某一项时调用
@ -374,7 +365,7 @@ Authorization: 登录接口返回的token
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"data":
[
{
"id": 1,
@ -384,7 +375,7 @@ Authorization: 登录接口返回的token
"createTime": "2023-01-01 12:00:00"
}
]
}
}
```
- 调用时机:用户点击立论历史记录列表中的某一项时调用

@ -50,7 +50,7 @@ public class WxAIController {
if (argument == null) {
return Result.error("立论获取失败");
}
log.info("test",argument);
long relatedConversationId = wxArgumentService.UpdateArgument(conversationId, topic, stance, argument, topic + stance, token);
Map<String, Object> data = new HashMap<>();

@ -41,6 +41,7 @@ public class WxConversationController {
@GetMapping("/list")
public Result<List<ConversationDTO>> getConversationList(@RequestHeader("Authorization") String token){
log.info("test111:",token);
try{
int userId = jwtUtil.getUserIdFromToken(token);

@ -6,6 +6,7 @@ import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@ -17,6 +18,7 @@ import java.util.Map;
/**
* JWT
*/
@Slf4j
@Component
public class JwtUtil {
@ -45,6 +47,7 @@ public class JwtUtil {
* tokenopenid
*/
public int getUserIdFromToken(String bearerToken) {
log.info("test:",bearerToken);
int userId;
try {
String token = extractToken(bearerToken);
@ -73,6 +76,7 @@ public class JwtUtil {
* tokenJWT
*/
private Claims getClaimsFromToken(String token) {
log.info("111111:",token);
Claims claims = null;
try {
// 使用正确的方法: parseClaimsJws而不是parseClaimsJwt

@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/wx_miniapp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 1234
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml

@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/wx_miniapp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 1234
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml

@ -24,6 +24,11 @@
>AI辅助构建辩论框架智能生成论点与论据</view
>
</view>
<view class="card-icon-wrapper">
<view class="card-icon" @click.stop="showHistory = true">
<image src="/static/icons/list.png" mode="aspectFit"></image>
</view>
</view>
<view class="collapse-icon">
<image
:src="
@ -104,18 +109,38 @@
@close="showSheet = false"
@click="handleSheetClick"
/>
<ConversationHistory
:visible="showHistory"
:history-list="chatHistory"
@update:visible="showHistory = $event"
@select="handleSelect"
@createNew="createNew"
/>
</view>
</template>
<script>
import { useArgumentStore } from "../stores/ArgumentStore";
import Popup from "./Popup.vue";
import ConversationHistory from "./ConversationHistory.vue";
import { useTokenStore } from "../stores/tokenStore";
export default {
components: { Popup },
components: { Popup, ConversationHistory },
mounted() {
this.getHistoryList();
this.conversationId = useArgumentStore().conversationId;
console.log("conversationId:", this.conversationId);
this.messages = useArgumentStore().conversation;
},
data() {
return {
conversationId: -1,
showSheet: false,
showHistory: false,
storeArg: "",
input: "",
@ -130,9 +155,107 @@ export default {
position: "",
question: "",
isCardCollapsed: false, //
chatHistory: ["test1", "test2"],
};
},
methods: {
async getHistoryList() {
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: "http://localhost:8080/api/conversation/list", // URL
method: "GET", // GET
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
// type === "argument"
const argumentList = res.data.data.filter(
(item) => item.type === "argument"
);
// chatHistory
this.chatHistory = argumentList;
// resolve
if (argumentList.length > 0) {
resolve(argumentList); // resolve(argumentList[0])
} else {
resolve("没有找到类型为 argument 的对话");
}
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
createNew() {
this.messages = [
{
role: "ai",
content:
"哈喽~ 我是辩论助手,很高兴为你服务!请告诉我你想立论的立场和题目。",
},
];
console.log("createNew");
this.conversationId = -1;
useArgumentStore().setConversationId(-1);
useArgumentStore().setConversation(this.messages);
},
handleSelect(conversationId) {
console.log("选中了历史记录 id:", conversationId);
this.showHistory = false;
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: `http://localhost:8080/api/conversation/argument/${conversationId}`, // 使 conversationId
method: "GET",
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
this.messages = [];
for (let item of res.data.data) {
this.messages.push({
role: "user",
content: item.userMessage,
});
this.messages.push({
role: "ai",
content: item.content,
});
}
useArgumentStore().setConversation(this.messages);
this.conversationId = conversationId;
useArgumentStore().setConversationId(conversationId);
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
handleSheetClick() {
const pinia = this.$pinia;
const argumentStore = useArgumentStore(pinia);
@ -190,28 +313,49 @@ export default {
return new Promise((resolve, reject) => {
console.log("callAI:", topic, stance);
// token
const token = useTokenStore().token.content;
if (!token) {
reject("未登录,请先登录");
return;
}
uni.request({
url: "http://localhost:8080/api/ai/argument",
method: "POST",
header: {
"Content-Type": "application/json",
Authorization: "Bearer " + token, //
},
data: {
conversationId: this.conversationId,
topic: topic,
stance: stance,
},
success: (res) => {
console.log("res:", res);
let reviewJson = JSON.parse(res.data.data.argument);
console.log("API响应:", res);
if (res.statusCode === 200 && res.data.code === 200) {
resolve(
reviewJson.choices[0].message.content || "AI 没有返回有效内容"
);
const responseData = res.data.data;
console.log("responseData:", responseData);
// ID
if (this.conversationId == -1) {
this.conversationId = responseData.conversationId;
console.log("更新会话ID为", responseData.id);
this.getHistoryList();
useArgumentStore().setConversationId(this.conversationId);
}
resolve(responseData.content);
} else {
reject("请求失败:" + (res.data.msg || "未知错误"));
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
console.error("请求失败:", err);
reject("请求失败:" + err.errMsg);
},
});
@ -229,18 +373,6 @@ export default {
this.storeArg = msg;
this.showSheet = true;
// uni.showActionSheet({
// itemList: [""],
// success: (res) => {
// if (res.tapIndex === 0) {
// const pinia = this.$pinia;
// const argumentStore = useArgumentStore(pinia);
// argumentStore.setArgument(msg);
// this.$emit("start-debate", msg); //
// }
// },
// });
},
},
};

@ -0,0 +1,359 @@
<template>
<div v-if="visible">
<div class="overlay" @click="close"></div>
<transition name="slide">
<div class="history-sidebar">
<div class="history-header">
<div class="title">💬对话历史</div>
</div>
<div class="create" v-if="!isHome" @click="createNew()"></div>
<!-- 判断是否有历史记录 -->
<div v-if="historyList.length > 0" class="history-list-container">
<ul class="history-list">
<li
v-for="(item, index) in historyList"
:key="item.id || index"
class="history-item"
@click="onItemClick(item)"
>
<view v-if="isHome" class="activity-icon" :class="item.type">
<image :src="getActivityIcon(item.type)" mode="aspectFit"></image>
</view>
<view v-if="isHome" class="activity-content">
<view class="activity-title">{{ getWords(item.title) }}</view>
<view class="activity-time">{{ item.updateTime }}</view>
</view>
<p v-if="!isHome" class="message-text">
{{ getWords(item.title) }}
</p>
</li>
</ul>
</div>
<!-- 没有数据时显示提示信息 -->
<div v-else class="no-data">
<image src="/static/icons/no-data.png" mode="aspectFit" class="no-data-icon" />
<p class="no-data-text">暂无数据</p>
</div>
</div>
</transition>
<view class="animated-bg">
<view
v-for="i in 6"
:key="i"
class="bg-circle"
:style="getRandomCircleStyle()"
></view>
</view>
</div>
</template>
<script>
export default {
name: "ConversationHistory",
props: {
visible: { type: Boolean, required: true },
historyList: { type: Array, default: () => [] },
isHome: { type: Boolean, default: false },
},
mounted() {
this.startCircleUpdate();
},
data() {
return {
circleStyle: this.getRandomCircleStyle(),
};
},
methods: {
close() {
this.$emit("update:visible", false);
},
onItemClick(item) {
this.$emit("select", item.id);
},
createNew() {
console.log("Creating new conversation...");
this.$emit("createNew");
this.close();
},
//
getRandomCircleStyle() {
const size = Math.random() * 300 + 100;
const x = Math.random() * 100;
const y = Math.random() * 100;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;
return {
width: `${size}rpx`,
height: `${size}rpx`,
left: `${x}%`,
top: `${y}%`,
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
};
},
startCircleUpdate() {
setInterval(() => {
this.circleStyle = this.getRandomCircleStyle();
}, 5000); // 5
},
getWords(text) {
return text.length > 18 ? text.substring(0, 18) + "..." : text;
// return text;
},
//
getActivityIcon(type) {
const icons = {
debate: "/static/icons/chat-1-line.png",
argument: "/static/icons/lightbulb-line.png",
review: "/static/icons/file-chart-line.png",
};
return icons[type] || icons.debate;
},
},
unmounted() {
clearInterval();
},
};
</script>
<style scoped>
.no-data {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 50px;
}
.no-data-icon {
width: 80px;
height: 80px;
opacity: 0.6;
}
.no-data-text {
margin-top: 15px;
font-size: 16px;
color: #999;
}
.slide-enter-active,
.slide-leave-active {
transition: all 0.4s ease;
}
.slide-enter-from,
.slide-leave-to {
transform: translateX(-100%);
opacity: 0;
}
.slide-enter-to,
.slide-leave-from {
transform: translateX(0);
opacity: 1;
}
.title {
font-size: 24px;
font-weight: bold;
justify-content: center;
align-content: center;
color: #ffffff;
margin-bottom: 10px;
}
.activity-icon {
width: 60rpx;
height: 60rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
}
.activity-content {
flex: 1;
}
.activity-icon.debate {
background: rgba(245, 158, 11, 0.3);
}
.activity-icon.argument {
background: rgba(16, 185, 129, 0.3);
}
.activity-icon.review {
background: rgba(79, 70, 229, 0.3);
}
.activity-icon image {
width: 36rpx;
height: 36rpx;
filter: brightness(0) invert(1);
}
.activity-content {
flex: 1;
}
.activity-title {
font-size: 28rpx;
color: #ffffff;
margin-bottom: 6rpx;
}
.activity-time {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.6);
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.4);
z-index: 500;
backdrop-filter: blur(4px);
}
.history-sidebar {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 100vh;
background-color: #7951dd;
box-shadow: -6px 0 12px rgba(0, 0, 0, 0.2);
padding: 24px 20px;
overflow-y: auto;
z-index: 800;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
.history-header {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
font-weight: 600;
letter-spacing: 1px;
}
.create {
font-size: 18px;
font-weight: bold;
color: #ffffff;
background-color: #9269f9;
width: 120px;
height: 40px;
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
cursor: pointer;
transition: all 0.2s ease; /* 平滑过渡 */
z-index: 999;
}
/* 添加点击时的动画效果 */
.create:active {
animation: clickEffect 0.5s ease forwards;
}
/* 定义动画:点击时缩小一点并加深背景颜色 */
@keyframes clickEffect {
0% {
transform: scale(1);
background-color: #9269f9;
}
50% {
transform: scale(0.95);
background-color: #7a54d8;
}
100% {
transform: scale(1);
background-color: #9269f9;
}
}
.history-list {
list-style: none;
padding: 0;
margin: 0;
}
.history-item {
display: flex;
margin-bottom: 15px;
padding: 12px 16px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
z-index: 1001;
will-change: background-color, transform;
}
.history-item:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: scale(1.02);
}
.message-text {
color: #ffffff;
margin: 0;
font-size: 15px;
word-break: break-word;
}
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.4s ease;
}
.slide-fade-enter {
transform: translateX(100%);
opacity: 0;
}
.slide-fade-leave-to {
transform: translateX(100%);
opacity: 0;
}
/* 动态背景 */
.animated-bg {
position: absolute;
top: 0;
left: 0;
width: 340px;
height: 100%;
overflow: hidden;
z-index: 600;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.02)
);
animation: float 20s infinite ease-in-out;
opacity: 0.4;
/* 添加过渡效果 */
transition: all 1.5s ease-in-out;
}
</style>

@ -27,6 +27,11 @@
>与AI进行实时辩论对练提升应变能力</view
>
</view>
<view class="card-icon-wrapper">
<view class="card-icon" @click.stop="showHistory = true">
<image src="/static/icons/list.png" mode="aspectFit"></image>
</view>
</view>
<view class="collapse-icon">
<image
:src="
@ -117,6 +122,14 @@
@close="showSheet = false"
@click="handleSheetClick"
/>
<ConversationHistory
:visible="showHistory"
:history-list="chatHistory"
@update:visible="showHistory = $event"
@select="handleSelect"
@createNew="createNew"
/>
</view>
</template>
@ -124,9 +137,11 @@
import { useArgumentStore } from "../stores/ArgumentStore";
import { useDebateStore } from "../stores/DebateStore";
import Popup from "./Popup.vue";
import ConversationHistory from "./ConversationHistory.vue";
import { useTokenStore } from "../stores/tokenStore";
export default {
components: { Popup },
components: { Popup,ConversationHistory },
props: {
argument: {
type: Object,
@ -136,11 +151,21 @@ export default {
mounted() {
const pinia = this.$pinia;
const store = useArgumentStore(pinia);
this.content = store.selectedArgument.content;
if(store.selectedArgument.content)this.content = store.selectedArgument.content;
this.getHistoryList();
this.conversationId = useDebateStore().conversationId;
console.log("conversationId:", this.conversationId);
this.messages = useDebateStore().conversation;
},
data() {
return {
conversationId: -1,
showHistory: false,
chatHistory: ["test1", "test2"],
showSheet:false,
StoreHistory:"",
input: "",
@ -166,6 +191,104 @@ export default {
};
},
methods: {
async getHistoryList() {
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: "http://localhost:8080/api/conversation/list", // URL
method: "GET", // GET
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
// type === "argument"
const debateList = res.data.data.filter(
(item) => item.type === "debate"
);
// chatHistory
this.chatHistory = debateList;
// resolve
if (debateList.length > 0) {
resolve(debateList); // resolve(argumentList[0])
} else {
resolve("没有找到类型为 argument 的对话");
}
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
createNew() {
this.messages = [
{
role: "ai",
content:
"哈喽~ 我是辩论助手,很高兴为你服务!请告诉我你想立论的立场和题目。",
},
];
console.log("createNew");
this.conversationId = -1;
useDebateStore().setConversationId(-1);
useDebateStore().setConversation(this.messages);
},
handleSelect(conversationId) {
console.log("选中了历史记录 id:", conversationId);
this.showHistory = false;
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: `http://localhost:8080/api/conversation/debate/${conversationId}`, // 使 conversationId
method: "GET",
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
this.messages=[];
for(let item of res.data.data){
this.messages.push({
role:"user",
content:item.userMessage
});
this.messages.push({
role:"ai",
content:item.content
});
}
useDebateStore().setConversation(this.messages);
this.conversationId = conversationId;
useDebateStore().setConversationId(conversationId);
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
handleSheetClick() {
const pinia = this.$pinia;
const store = useDebateStore(pinia);
@ -188,21 +311,6 @@ export default {
this.StoreHistory = history;
this.showSheet = true;
// console.log(history);
// uni.showActionSheet({
// itemList: [""],
// success: (res) => {
// if (res.tapIndex === 0) {
// const pinia = this.$pinia;
// const store = useDebateStore(pinia);
// store.setDebate(history);
// this.$emit("start-review", "debate"); //
// // console.log(":",store.selectedDebate.content);
// }
// },
// });
},
// /
toggleCard() {
@ -285,27 +393,44 @@ export default {
this.content = "";
return new Promise((resolve, reject) => {
console.log("callAI:", history, content);
const token = useTokenStore().token.content;
if (!token) {
reject("未登录,请先登录");
return;
}
uni.request({
url: "http://localhost:8080/api/ai/debate",
method: "POST",
header: {
"Content-Type": "application/json",
Authorization: "Bearer " + token, //
},
data: {
history: history,
content: content,
conversationId: this.conversationId,
// history: history,
userMessage: content,
},
success: (res) => {
console.log("res:", res);
let reviewJson = JSON.parse(res.data.data.debate);
this.scrollToBottom();
console.log("API响应:", res);
if (res.statusCode === 200 && res.data.code === 200) {
resolve(
reviewJson.choices[0].message.content || "AI 没有返回有效内容"
);
const responseData = res.data.data;
console.log("responseData:", responseData);
// ID
if (this.conversationId == -1) {
this.conversationId = responseData.conversationId;
console.log("更新会话ID为", responseData.id);
this.getHistoryList();
useDebateStore().setConversationId(this.conversationId);
}
resolve(responseData.content);
} else {
reject("请求失败:" + (res.data.msg || "未知错误"));
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {

@ -2,7 +2,12 @@
<view class="home-component">
<!-- 动态背景元素 -->
<view class="animated-bg">
<view v-for="i in 6" :key="i" class="bg-circle" :style="getRandomCircleStyle()"></view>
<view
v-for="i in 6"
:key="i"
class="bg-circle"
:style="getRandomCircleStyle()"
></view>
</view>
<!-- 顶部欢迎卡片 -->
@ -24,33 +29,50 @@
<view class="feature-desc">与AI进行实时辩论对练提升应变能力</view>
</view>
<view class="feature-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
<image
src="/static/icons/arrow-right-s-line.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="feature-card" @click="switchTab(2)">
<view class="feature-icon">
<image src="/static/icons/lightbulb-line.png" mode="aspectFit"></image>
<image
src="/static/icons/lightbulb-line.png"
mode="aspectFit"
></image>
</view>
<view class="feature-text">
<view class="feature-title">立论助手</view>
<view class="feature-desc">AI辅助构建辩论框架智能生成论点与论据</view>
<view class="feature-desc"
>AI辅助构建辩论框架智能生成论点与论据</view
>
</view>
<view class="feature-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
<image
src="/static/icons/arrow-right-s-line.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="feature-card" @click="switchTab(3)">
<view class="feature-icon">
<image src="/static/icons/file-chart-line.png" mode="aspectFit"></image>
<image
src="/static/icons/file-chart-line.png"
mode="aspectFit"
></image>
</view>
<view class="feature-text">
<view class="feature-title">复盘分析</view>
<view class="feature-desc">分析辩论过程提供改进建议与优化方向</view>
</view>
<view class="feature-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
<image
src="/static/icons/arrow-right-s-line.png"
mode="aspectFit"
></image>
</view>
</view>
</view>
@ -61,21 +83,34 @@
<view class="section-title">最近活动</view>
<view class="section-more" @click="showMoreActivities">
<text>查看更多</text>
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
<image
src="/static/icons/arrow-right-s-line.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="activity-list">
<view class="activity-item" v-for="(activity, index) in recentActivities" :key="index">
<view
class="activity-item"
v-for="(activity, index) in recentActivities.slice(0, 3)"
:key="index"
>
<view class="activity-icon" :class="activity.type">
<image :src="getActivityIcon(activity.type)" mode="aspectFit"></image>
<image
:src="getActivityIcon(activity.type)"
mode="aspectFit"
></image>
</view>
<view class="activity-content">
<view class="activity-title">{{ activity.title }}</view>
<view class="activity-time">{{ activity.time }}</view>
<view class="activity-time">{{ activity.updateTime }}</view>
</view>
<view class="activity-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
<image
src="/static/icons/arrow-right-s-line.png"
mode="aspectFit"
></image>
</view>
</view>
</view>
@ -83,33 +118,106 @@
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
<transition name="slide">
<ConversationHistory
:visible="showHistory"
:history-list="recentActivities"
:isHome="true"
@update:visible="showHistory = $event"
/>
</transition>
</view>
</template>
<script>
import ConversationHistory from "./ConversationHistory.vue";
import { useTokenStore } from "../stores/tokenStore";
export default {
components: {
ConversationHistory,
},
mounted() {
this.getHistoryList();
},
data() {
return {
showHistory: false,
chatHistory: ["test1", "test2"],
recentActivities: [
{
type: 'debate',
type: "debate",
title: '关于"教育改革"的辩论',
time: '今天 14:30'
time: "今天 14:30",
},
{
type: 'argument',
type: "argument",
title: '关于"环境保护"的立论',
time: '昨天 09:15'
time: "昨天 09:15",
},
{
type: 'review',
type: "review",
title: '关于"人工智能"的辩论复盘',
time: '3天前'
}
]
time: "3天前",
},
],
};
},
methods: {
async getHistoryList() {
// this.recentActivities = [
// {
// type: 'debate',
// title: '""',
// time: ' 14:30'
// },
// {
// type: 'argument',
// title: '""',
// time: ' 09:15'
// },
// {
// type: 'review',
// title: '""',
// time: '3'
// }
// ]
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: "http://localhost:8080/api/conversation/list", // URL
method: "GET", // GET
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
//
const conversation = res.data.data[0];
this.recentActivities = res.data.data;
resolve(conversation ? conversation : "没有可用的对话");
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
createNew() {
console.log("createNew");
},
handleSelect(id) {
console.log("选中了历史记录 id:", id);
},
//
getRandomCircleStyle() {
const size = Math.random() * 300 + 100;
@ -117,51 +225,69 @@ export default {
const y = Math.random() * 100;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;
return {
width: `${size}rpx`,
height: `${size}rpx`,
left: `${x}%`,
top: `${y}%`,
animationDelay: `${delay}s`,
animationDuration: `${duration}s`
animationDuration: `${duration}s`,
};
},
//
getActivityIcon(type) {
const icons = {
'debate': '/static/icons/chat-1-line.png',
'argument': '/static/icons/lightbulb-line.png',
'review': '/static/icons/file-chart-line.png'
debate: "/static/icons/chat-1-line.png",
argument: "/static/icons/lightbulb-line.png",
review: "/static/icons/file-chart-line.png",
};
return icons[type] || icons.debate;
},
//
switchTab(index) {
this.$emit('tab-change', index);
this.$emit("tab-change", index);
},
//
showMoreActivities() {
uni.showToast({
title: '功能开发中',
icon: 'none'
});
}
}
this.showHistory = true;
console.log("showMoreActivities:", this.showHistory);
// uni.showToast({
// title: '',
// icon: 'none'
// });
},
},
};
</script>
<style scoped>
.slide-enter-active,
.slide-leave-active {
transition: all 0.3s ease-in-out;
position: fixed;
width: 100%;
z-index: 9999;
}
.slide-enter,
.slide-leave-to {
transform: translateX(-100%);
opacity: 1;
}
.home-component {
width: 100%;
min-height: 100vh;
padding: 30rpx;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #4338CA 0%, #7C3AED 100%);
background: linear-gradient(135deg, #4338ca 0%, #7c3aed 100%);
overflow-x: hidden;
box-sizing: border-box;
padding-bottom: 180rpx; /* 为底部TabBar留出空间 */
@ -182,14 +308,19 @@ export default {
.bg-circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.02)
);
animation: float 20s infinite ease-in-out;
opacity: 0.4;
transition: all 1.5s ease-in-out;
}
@keyframes float {
0%, 100% {
0%,
100% {
transform: translate(0, 0) scale(1);
}
25% {
@ -238,7 +369,7 @@ export default {
.welcome-title {
font-size: 48rpx;
color: #FFFFFF;
color: #ffffff;
font-weight: 700;
margin-bottom: 16rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
@ -250,7 +381,6 @@ export default {
line-height: 1.5;
}
/* 功能卡片区域 */
.feature-grid {
display: flex;
@ -324,7 +454,7 @@ export default {
.feature-title {
font-size: 32rpx;
color: #FFFFFF;
color: #ffffff;
font-weight: 600;
margin-bottom: 8rpx;
}
@ -365,7 +495,7 @@ export default {
.section-title {
font-size: 36rpx;
color: #FFFFFF;
color: #ffffff;
font-weight: 600;
}
@ -438,7 +568,7 @@ export default {
.activity-title {
font-size: 28rpx;
color: #FFFFFF;
color: #ffffff;
margin-bottom: 6rpx;
}
@ -468,4 +598,4 @@ export default {
height: calc(20rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
}
</style>
</style>

@ -27,6 +27,11 @@
>分析辩论过程提供改进建议与优化方向</view
>
</view>
<view class="card-icon-wrapper">
<view class="card-icon" @click.stop="showHistory = true">
<image src="/static/icons/list.png" mode="aspectFit"></image>
</view>
</view>
<view class="collapse-icon">
<image
:src="
@ -103,15 +108,26 @@
@close="showSheet = false"
@click="handleSheetClick"
/>
<ConversationHistory
:visible="showHistory"
:history-list="chatHistory"
@update:visible="showHistory = $event"
@select="handleSelect"
@createNew="createNew"
/>
</view>
</template>
<script>
import { useDebateStore } from "../stores/DebateStore";
import Popup from "./Popup.vue";
import ConversationHistory from "./ConversationHistory.vue";
import { useTokenStore } from "../stores/tokenStore";
import { useReviewStore } from "../stores/reviewStore";
export default {
components: { Popup },
components: { Popup,ConversationHistory },
props: {
debate: {
type: Object,
@ -121,10 +137,19 @@ export default {
mounted() {
const pinia = this.$pinia;
const store = useDebateStore(pinia);
this.content = store.selectedDebate.content;
if(store.selectedDebate.content)this.content = store.selectedDebate.content;
this.getHistoryList();
this.conversationId = useReviewStore().conversationId;
this.messages = useReviewStore().conversation;
},
data() {
return {
conversationId: -1,
showHistory: false,
chatHistory: ["test1", "test2"],
showSheet: false,
StoreReview: "",
input: "",
@ -141,6 +166,103 @@ export default {
};
},
methods: {
async getHistoryList() {
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: "http://localhost:8080/api/conversation/list", // URL
method: "GET", // GET
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
// type === "argument"
const reviewList = res.data.data.filter(
(item) => item.type === "review"
);
// chatHistory
this.chatHistory = reviewList;
// resolve
if (reviewList.length > 0) {
resolve(reviewList); // resolve(argumentList[0])
} else {
resolve("没有找到类型为 argument 的对话");
}
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
createNew() {
this.messages = [
{
role: "ai",
content:
"哈喽~ 我是辩论助手,很高兴为你服务!请告诉我你想立论的立场和题目。",
},
];
console.log("createNew");
this.conversationId = -1;
useReviewStore().setConversationId(-1);
useReviewStore().setConversation(this.messages);
},
handleSelect(conversationId) {
console.log("选中了历史记录 id:", conversationId);
this.showHistory = false;
return new Promise((resolve, reject) => {
const token = useTokenStore().token.content;
console.log("token:", token);
uni.request({
url: `http://localhost:8080/api/conversation/review/${conversationId}`, // 使 conversationId
method: "GET",
header: {
Authorization: "Bearer " + token,
},
success: (res) => {
console.log("res:", res);
if (res.statusCode === 200 && res.data.code === 200) {
this.messages=[];
for(let item of res.data.data){
this.messages.push({
role:"user",
content:item.userMessage
});
this.messages.push({
role:"ai",
content:item.content
});
}
useReviewStore().setConversation(this.messages);
this.conversationId = conversationId;
useReviewStore().setConversationId(conversationId);
} else {
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {
reject("请求失败:" + err.errMsg);
},
});
});
},
handleSheetClick() {
const content = this.StoreReview;
uni.setClipboardData({
@ -212,25 +334,42 @@ export default {
return new Promise((resolve, reject) => {
console.log("callAI:", content);
const token = useTokenStore().token.content;
if (!token) {
reject("未登录,请先登录");
return;
}
uni.request({
url: "http://localhost:8080/api/ai/review",
method: "POST",
header: {
"Content-Type": "application/json",
Authorization: "Bearer " + token
},
data: {
conversationId: this.conversationId,
content: content,
},
success: (res) => {
console.log("res:", res);
let reviewJson = JSON.parse(res.data.data.review);
this.scrollToBottom();
console.log("API响应:", res);
if (res.statusCode === 200 && res.data.code === 200) {
resolve(
reviewJson.choices[0].message.content || "AI 没有返回有效内容"
);
const responseData = res.data.data;
console.log("responseData:", responseData);
// ID
if (this.conversationId == -1) {
this.conversationId = responseData.conversationId;
console.log("更新会话ID为", responseData.id);
this.getHistoryList();
useReviewStore().setConversationId(this.conversationId);
}
resolve(responseData.content);
} else {
reject("请求失败:" + (res.data.msg || "未知错误"));
reject("请求失败:" + (res.data.message || "未知错误"));
}
},
fail: (err) => {

@ -25,6 +25,7 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useTokenStore } from '../../stores/tokenStore';
const fullText = ref('微信一键登录');
const displayText = ref('');
@ -81,6 +82,11 @@ const requestLogin = (code) =>{
success(res) {
if(res.data.code === 200){
wx.setStorageSync('token', res.data.data.token);
const tokenStore = useTokenStore();
tokenStore.setToken(res.data.data.token);
console.log("token:",res.data.data.token);
console.log("登录成功");
uni.redirectTo({
url: '/pages/index/index'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -1,13 +1,27 @@
// stores/argumentStore.ts
import { defineStore } from 'pinia'
import { defineStore } from "pinia";
export const useArgumentStore = defineStore('argument', {
export const useArgumentStore = defineStore("argument", {
state: () => ({
selectedArgument: null
conversationId: -1,
selectedArgument: "",
conversation: [
{
role: "ai",
content:
"哈喽~ 我是辩论助手,很高兴为你服务!请告诉我你想立论的立场和题目。",
},
],
}),
actions: {
setArgument(arg) {
this.selectedArgument = arg
}
}
})
this.selectedArgument = arg;
},
setConversationId(id) {
this.conversationId = id;
},
setConversation(conversation) {
this.conversation = conversation;
},
},
});

@ -3,13 +3,25 @@ import { defineStore } from 'pinia'
export const useDebateStore = defineStore('debate', {
state: () => ({
selectedDebate: {
content: '',
},
conversationId: -1,
selectedDebate: "",
conversation: [
{
role: "ai",
content:
"哈喽~ 我是辩论助手,很高兴为你服务!请告诉我你想立论的立场和题目。",
},
],
}),
actions: {
setDebate(content) {
this.selectedDebate.content = content;
}
},
setConversationId(id) {
this.conversationId = id;
},
setConversation(conversation) {
this.conversation = conversation;
},
}
});

@ -0,0 +1,23 @@
// stores/argumentStore.ts
import { defineStore } from "pinia";
export const useReviewStore = defineStore("review", {
state: () => ({
conversationId: -1,
conversation: [
{
role: "ai",
content:
"哈喽~ 我是辩论助手,很高兴为你服务!请告诉我你想立论的立场和题目。",
},
],
}),
actions: {
setConversationId(id) {
this.conversationId = id;
},
setConversation(conversation) {
this.conversation = conversation;
},
},
});

@ -0,0 +1,15 @@
// stores/DebateStore.ts
import { defineStore } from 'pinia'
export const useTokenStore = defineStore('token', {
state: () => ({
token: {
content: '',
},
}),
actions: {
setToken(content) {
this.token.content = content;
}
}
});
Loading…
Cancel
Save